Skip to content

Commit d33b8c2

Browse files
committed
merge: reconcile sidebar changes with main
2 parents 17639fb + 7040eed commit d33b8c2

110 files changed

Lines changed: 578 additions & 374 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/pin-subagent-model.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Add a setting to pin every subagent to the selected model, use the dark banner in every sidebar, and show Pythinker desktop updates as one continuous download.

apps/desktop/src/updater.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ function hasUpdateConfig(): boolean {
264264
function configureExplicitConsent(): void {
265265
autoUpdater.autoDownload = false
266266
autoUpdater.autoInstallOnAppQuit = false
267+
autoUpdater.disableDifferentialDownload = true
267268
autoUpdater.channel = settings.channel === 'stable' ? STABLE_UPDATER_CHANNEL : settings.channel
268269
autoUpdater.allowPrerelease = settings.channel !== 'stable'
269270
autoUpdater.allowDowngrade = false
@@ -346,6 +347,7 @@ function wireUpdaterEvents(): void {
346347
})
347348
})
348349
autoUpdater.on('download-progress', (progress: ProgressInfo) => {
350+
if (state.status !== 'downloading') return
349351
updateState({
350352
status: 'downloading',
351353
percent: progress.percent,
@@ -444,7 +446,7 @@ export function setAutoUpdate(enabled: boolean): UpdateState {
444446
}
445447
if (enabled) {
446448
scheduleChecks()
447-
if (!wasEnabled) void checkForUpdatesNow()
449+
if (!wasEnabled && state.availableVersion === undefined) void checkForUpdatesNow()
448450
} else {
449451
clearTimers()
450452
}

apps/desktop/tests/updater.spec.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ vi.mock('electron-updater', () => ({
3030
checkForUpdates: vi.fn(),
3131
downloadUpdate: vi.fn(() => Promise.resolve([])),
3232
quitAndInstall: vi.fn(),
33+
disableDifferentialDownload: false,
3334
},
3435
},
3536
}))
@@ -69,6 +70,7 @@ afterEach(() => {
6970
autoUpdater.autoInstallOnAppQuit = undefined as unknown as boolean
7071
autoUpdater.allowPrerelease = undefined as unknown as boolean
7172
autoUpdater.allowDowngrade = undefined as unknown as boolean
73+
autoUpdater.disableDifferentialDownload = false
7274
;(autoUpdater as unknown as { _channel: string | null })._channel = null
7375
})
7476

@@ -241,6 +243,7 @@ describe('strict update consent', () => {
241243
expect(localAutoUpdater.allowDowngrade).toBe(false)
242244
expect(localAutoUpdater.autoDownload).toBe(false)
243245
expect(localAutoUpdater.autoInstallOnAppQuit).toBe(false)
246+
expect(localAutoUpdater.disableDifferentialDownload).toBe(true)
244247
expect(localAutoUpdater.checkForUpdates).not.toHaveBeenCalled()
245248
expect(localAutoUpdater.downloadUpdate).not.toHaveBeenCalled()
246249
expect(localAutoUpdater.quitAndInstall).not.toHaveBeenCalled()
@@ -339,6 +342,7 @@ describe('strict update consent', () => {
339342
const {
340343
getUpdateState: getLocalUpdateState,
341344
initUpdater: initLocalUpdater,
345+
startUpdateDownload: startLocalUpdateDownload,
342346
} = await import('../src/updater')
343347
vi.mocked(localApp.getPath).mockReturnValue(directory)
344348
Object.defineProperty(localApp, 'isPackaged', { configurable: true, value: true })
@@ -366,6 +370,7 @@ describe('strict update consent', () => {
366370
releaseDate: '2026-08-22T12:00:00.000Z',
367371
releaseNotes: [{ note: 'First change' }, { note: null }, { note: 'Second change' }],
368372
})
373+
startLocalUpdateDownload()
369374
progress?.({ percent: 42.5, transferred: 425, total: 1_000, bytesPerSecond: 85 })
370375

371376
expect(getLocalUpdateState()).toMatchObject({
@@ -381,6 +386,38 @@ describe('strict update consent', () => {
381386
})
382387
})
383388

389+
it('keeps an available update downloadable when automatic checks are enabled', async () => {
390+
vi.resetModules()
391+
const directory = temporaryDirectory()
392+
writeFileSync(join(directory, 'app-update.yml'), '', 'utf8')
393+
writeFileSync(join(directory, 'update-settings.json'), '{"autoUpdate":false}\n', 'utf8')
394+
const { app: localApp } = await import('electron')
395+
const { default: localElectronUpdater } = await import('electron-updater')
396+
const {
397+
getUpdateState: getLocalUpdateState,
398+
initUpdater: initLocalUpdater,
399+
setAutoUpdate: setLocalAutoUpdate,
400+
startUpdateDownload: startLocalUpdateDownload,
401+
} = await import('../src/updater')
402+
const localAutoUpdater = localElectronUpdater.autoUpdater
403+
vi.mocked(localApp.getPath).mockReturnValue(directory)
404+
Object.defineProperty(localApp, 'isPackaged', { configurable: true, value: true })
405+
Object.defineProperty(process, 'resourcesPath', { configurable: true, value: directory })
406+
407+
initLocalUpdater(() => undefined)
408+
const available = vi.mocked(localAutoUpdater.on).mock.calls.find(
409+
([event]) => event === 'update-available',
410+
)?.[1] as ((info: { version: string }) => void) | undefined
411+
available?.({ version: '1.2.3' })
412+
413+
expect(setLocalAutoUpdate(true)).toMatchObject({ status: 'available', availableVersion: '1.2.3' })
414+
expect(localAutoUpdater.checkForUpdates).not.toHaveBeenCalled()
415+
expect(startLocalUpdateDownload()).toMatchObject({ status: 'downloading' })
416+
expect(localAutoUpdater.downloadUpdate).toHaveBeenCalledOnce()
417+
setLocalAutoUpdate(false)
418+
expect(getLocalUpdateState()).toMatchObject({ status: 'downloading' })
419+
})
420+
384421
it('downloads and installs only through separate explicit actions', async () => {
385422
vi.resetModules()
386423
const directory = temporaryDirectory()
@@ -420,6 +457,55 @@ describe('strict update consent', () => {
420457
expect(readUpdateSettings(directory)).toMatchObject({ pendingInstallVersion: '1.2.3' })
421458
})
422459

460+
it('does not return to downloading after the update is ready', async () => {
461+
vi.resetModules()
462+
const directory = temporaryDirectory()
463+
writeFileSync(join(directory, 'app-update.yml'), '', 'utf8')
464+
writeFileSync(join(directory, 'update-settings.json'), '{"autoUpdate":false}\n', 'utf8')
465+
const { app: localApp } = await import('electron')
466+
const { default: localElectronUpdater } = await import('electron-updater')
467+
const {
468+
getUpdateState: getLocalUpdateState,
469+
initUpdater: initLocalUpdater,
470+
startUpdateDownload: startLocalUpdateDownload,
471+
} = await import('../src/updater')
472+
const localAutoUpdater = localElectronUpdater.autoUpdater
473+
vi.mocked(localApp.getPath).mockReturnValue(directory)
474+
Object.defineProperty(localApp, 'isPackaged', { configurable: true, value: true })
475+
Object.defineProperty(process, 'resourcesPath', { configurable: true, value: directory })
476+
477+
initLocalUpdater(() => undefined)
478+
const available = vi.mocked(localAutoUpdater.on).mock.calls.find(
479+
([event]) => event === 'update-available',
480+
)?.[1] as ((info: { version: string }) => void) | undefined
481+
const progress = vi.mocked(localAutoUpdater.on).mock.calls.find(
482+
([event]) => event === 'download-progress',
483+
)?.[1] as ((info: {
484+
percent: number
485+
transferred: number
486+
total: number
487+
bytesPerSecond: number
488+
}) => void) | undefined
489+
const downloaded = vi.mocked(localAutoUpdater.on).mock.calls.find(
490+
([event]) => event === 'update-downloaded',
491+
)?.[1] as ((info: { version: string }) => void) | undefined
492+
493+
available?.({ version: '1.2.3' })
494+
startLocalUpdateDownload()
495+
progress?.({ percent: 100, transferred: 1_000, total: 1_000, bytesPerSecond: 80 })
496+
downloaded?.({ version: '1.2.3' })
497+
progress?.({ percent: 1, transferred: 10, total: 1_000, bytesPerSecond: 20 })
498+
499+
expect(getLocalUpdateState()).toMatchObject({
500+
status: 'downloaded',
501+
availableVersion: '1.2.3',
502+
percent: 100,
503+
transferred: 1_000,
504+
total: 1_000,
505+
})
506+
expect(localAutoUpdater.downloadUpdate).toHaveBeenCalledOnce()
507+
})
508+
423509
it('does not let a scheduled check overwrite a downloaded update', async () => {
424510
vi.useFakeTimers()
425511
try {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"sourceHash": "03705439bd693600af327df8f8c9d0f18b34f6017be16fc949363c197d6499cc",
2+
"sourceHash": "1ad130e2bf05745b2175ec94f82d942fcfb9e4d9ba9a2d9f6853f973132cd348",
33
"sourceFileCount": 404
44
}

0 commit comments

Comments
 (0)