diff --git a/.changeset/dark-sidebar-contrast.md b/.changeset/dark-sidebar-contrast.md new file mode 100644 index 00000000..648216b3 --- /dev/null +++ b/.changeset/dark-sidebar-contrast.md @@ -0,0 +1,6 @@ +--- +"@pymodel/pythinker-code": patch +"@pymodel/pythinker-desktop": patch +--- + +Make the workspace header, session timestamps, and the settings row legible in dark mode on the translucent desktop sidebar. diff --git a/.changeset/desktop-dedicated-update-channel.md b/.changeset/desktop-dedicated-update-channel.md new file mode 100644 index 00000000..92f79f0f --- /dev/null +++ b/.changeset/desktop-dedicated-update-channel.md @@ -0,0 +1,5 @@ +--- +"@pymodel/pythinker-desktop": patch +--- + +Publish desktop releases to a dedicated update channel so update checks resolve a desktop build instead of an unrelated release, and fail the release when a packaged build carries no update feed. diff --git a/.changeset/desktop-pin-host-port.md b/.changeset/desktop-pin-host-port.md new file mode 100644 index 00000000..58497f40 --- /dev/null +++ b/.changeset/desktop-pin-host-port.md @@ -0,0 +1,5 @@ +--- +"@pymodel/pythinker-desktop": patch +--- + +Pin the Host port so the desktop app reconnects to its own Host, and stop reporting builds that cannot self-update as update errors. diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index adea503d..6710701f 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -195,28 +195,17 @@ async function createMainWindow(): Promise { autoHideMenuBar: true, frame: process.platform === 'win32', titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'hidden', - ...(process.platform === 'darwin' ? {} : { - titleBarOverlay: { - color: '#00000000', - symbolColor: '#7f858f', - height: 44, - }, - }), - ...(process.platform === 'darwin' ? { - trafficLightPosition: { x: 16, y: 18 }, - vibrancy: 'sidebar' as const, - visualEffectState: 'followWindow' as const, - } : {}), - // Windows uses an opaque window so theme colors do not blend with desktop wallpaper. - ...(process.platform === 'win32' ? { - backgroundColor: '#0d1117', - hasShadow: true, - roundedCorners: true, - thickFrame: true, - } : { - transparent: true, - backgroundColor: '#00000000', - }), + titleBarOverlay: process.platform === 'darwin' ? undefined : { + color: '#00000000', + symbolColor: '#7f858f', + height: 44, + }, + trafficLightPosition: process.platform === 'darwin' ? { x: 16, y: 18 } : undefined, + // Use an opaque window so theme colors do not blend with desktop wallpaper. + backgroundColor: '#0d1117', + hasShadow: process.platform === 'win32' ? true : undefined, + roundedCorners: process.platform === 'win32' ? true : undefined, + thickFrame: process.platform === 'win32' ? true : undefined, title: APP_NAME, webPreferences: { contextIsolation: true, diff --git a/apps/desktop/tests/packaging-config.spec.ts b/apps/desktop/tests/packaging-config.spec.ts index 62c764ce..0e02b922 100644 --- a/apps/desktop/tests/packaging-config.spec.ts +++ b/apps/desktop/tests/packaging-config.spec.ts @@ -86,9 +86,10 @@ describe('desktop packaging configuration', () => { expect(siteSource).toContain('releases/download/v${DESKTOP_VERSION}') expect(siteSource).not.toContain('releases/download/v0.1.0') - const desktopShowcaseMatch = siteSource.match(/
/) - expect(desktopShowcaseMatch).not.toBeNull() - expect(desktopShowcaseMatch![0]).toContain('/brand/windows11.svg') + expect(siteSource).toContain('id="desktop"') + expect(siteSource).toMatch( + /\{[^}]*icon: '\/brand\/windows11\.svg', href: desktopDownloads\.windows[^}]*\}/, + ) }) it('maps the staged Host node_modules directory as the copy root', () => { diff --git a/apps/desktop/tests/window-appearance.spec.ts b/apps/desktop/tests/window-appearance.spec.ts index 0a3b5fc9..dade733c 100644 --- a/apps/desktop/tests/window-appearance.spec.ts +++ b/apps/desktop/tests/window-appearance.spec.ts @@ -8,36 +8,48 @@ const desktopRoot = resolve(import.meta.dirname, '..') const mainSource = readFileSync(resolve(desktopRoot, 'src', 'main.ts'), 'utf8') describe('desktop window appearance configuration', () => { - it('keeps Windows opaque and non-Windows windows transparent', () => { + it('keeps the main window opaque on every platform', () => { const backgroundMaterialMatches = [...mainSource.matchAll(/backgroundMaterial/gu)] - const win32BranchMatches = [...mainSource.matchAll( - /\.\.\.\(process\.platform === 'win32' \? \{([\s\S]*?)\} : \{\s*transparent: true,/gu, - )] - const nonWin32BranchMatches = [...mainSource.matchAll( - /\} : \{\s*transparent: true,[\s\S]*?\}\),\s*title:/gu, + const mainWindowOptionMatches = [...mainSource.matchAll( + /const window = new BrowserWindow\(\{([\s\S]*?)\n \}\)/gu, )] expect(backgroundMaterialMatches).toHaveLength(0) - expect(win32BranchMatches).toHaveLength(1) - expect(nonWin32BranchMatches).toHaveLength(1) + expect(mainWindowOptionMatches).toHaveLength(1) - const win32Branch = win32BranchMatches[0]![1]! - const opaqueColorMatches = [...win32Branch.matchAll(/backgroundColor:\s*'#[0-9a-fA-F]{6}'/gu)] - const alphaColorMatches = [...win32Branch.matchAll(/#[0-9a-fA-F]{8}/gu)] - const hasShadowMatches = [...win32Branch.matchAll(/hasShadow:\s*true/gu)] - const roundedCornersMatches = [...win32Branch.matchAll(/roundedCorners:\s*true/gu)] - const thickFrameMatches = [...win32Branch.matchAll(/thickFrame:\s*true/gu)] + const mainWindowOptions = mainWindowOptionMatches[0]![1]! + const vibrancyMatches = [...mainWindowOptions.matchAll(/vibrancy\s*:/gu)] + const visualEffectStateMatches = [...mainWindowOptions.matchAll(/visualEffectState\s*:/gu)] + const transparentMatches = [...mainWindowOptions.matchAll(/transparent:\s*true/gu)] + const backgroundColorMatches = [...mainWindowOptions.matchAll( + /backgroundColor:\s*'(#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?)'/gu, + )] + const transparentBackgroundMatches = backgroundColorMatches.filter( + (match) => match[1]!.length === 9 && match[1]!.endsWith('00'), + ) + const hasShadowMatches = [...mainWindowOptions.matchAll( + /hasShadow:\s*process\.platform === 'win32' \? true : undefined/gu, + )] + const roundedCornersMatches = [...mainWindowOptions.matchAll( + /roundedCorners:\s*process\.platform === 'win32' \? true : undefined/gu, + )] + const thickFrameMatches = [...mainWindowOptions.matchAll( + /thickFrame:\s*process\.platform === 'win32' \? true : undefined/gu, + )] - expect(opaqueColorMatches).toHaveLength(1) - expect(alphaColorMatches).toHaveLength(0) + expect(vibrancyMatches).toHaveLength(0) + expect(visualEffectStateMatches).toHaveLength(0) + expect(transparentMatches).toHaveLength(0) + expect(backgroundColorMatches).toHaveLength(1) + expect(backgroundColorMatches[0]![1]).not.toBe('#00000000') + expect(transparentBackgroundMatches).toHaveLength(0) expect(hasShadowMatches).toHaveLength(1) expect(roundedCornersMatches).toHaveLength(1) expect(thickFrameMatches).toHaveLength(1) - expect(win32Branch).toContain('backgroundColor') - expect(nonWin32BranchMatches[0]![0]).toContain('transparent: true') - expect(win32Branch).toContain('hasShadow') - expect(win32Branch).toContain('roundedCorners') - expect(win32Branch).toContain('thickFrame') + expect(mainWindowOptions).toContain('backgroundColor') + expect(mainWindowOptions).toContain('hasShadow') + expect(mainWindowOptions).toContain('roundedCorners') + expect(mainWindowOptions).toContain('thickFrame') }) }) diff --git a/apps/pythinker-web/src/components/Composer.vue b/apps/pythinker-web/src/components/Composer.vue index d7092026..35f1aef7 100644 --- a/apps/pythinker-web/src/components/Composer.vue +++ b/apps/pythinker-web/src/components/Composer.vue @@ -735,12 +735,18 @@ const hasUpload = computed(() => !!props.uploadImage); // --------------------------------------------------------------------------- const dropdownOpen = ref(false); +const modelPillRef = ref(null); +const modelDropdownStyle = ref>({}); const permDropdownOpen = ref(false); const toolbarRef = ref(null); function toggleDropdown(): void { dropdownOpen.value = !dropdownOpen.value; if (dropdownOpen.value) { + const rect = modelPillRef.value?.getBoundingClientRect(); + modelDropdownStyle.value = rect + ? { maxHeight: `${Math.max(160, rect.top - 4 - 12)}px` } + : {}; permDropdownOpen.value = false; document.addEventListener('click', onDocClick, true); } else { @@ -1179,6 +1185,7 @@ function selectModel(modelId: string): void { -