diff --git a/apps/desktop/src/main/rpc.ts b/apps/desktop/src/main/rpc.ts index 5c888103..d40685ac 100644 --- a/apps/desktop/src/main/rpc.ts +++ b/apps/desktop/src/main/rpc.ts @@ -1167,9 +1167,7 @@ export function registerRpc(contents: WebContents, options: RegisterRpcOptions = return { done: true } }) - // Live theme change: repaint native chrome (Windows overlay symbols + the OS - // light/dark hint). Window material is fixed at creation, so a glass <-> opaque - // switch only takes full effect on the next launch. + // Live theme change: repaint Windows overlay symbols and the OS light/dark hint. r.register('theme.apply', (params) => { const win = BrowserWindow.fromWebContents(contents) if (!win) return { applied: false } diff --git a/apps/desktop/src/main/window.test.ts b/apps/desktop/src/main/window.test.ts index e905c254..3e8d2ee1 100644 --- a/apps/desktop/src/main/window.test.ts +++ b/apps/desktop/src/main/window.test.ts @@ -18,30 +18,15 @@ describe('theme registries agree across packages', () => { }) describe('themeWindowChrome', () => { - it('requests platform translucency for glass themes', () => { - expect(themeWindowChrome(themes.obsidian, 'win32')).toMatchObject({ - backgroundColor: '#00000000', - backgroundMaterial: 'acrylic', - }) - expect(themeWindowChrome(themes.obsidian, 'darwin')).toMatchObject({ - vibrancy: 'under-window', - visualEffectState: 'active', - }) - expect(themeWindowChrome(themes.obsidian, 'linux')).toMatchObject({ transparent: true }) - }) - - it('gives opaque themes a solid background and no translucency', () => { - for (const platform of ['win32', 'darwin', 'linux'] as const) { - const chrome = themeWindowChrome(themes.graphite, platform) - expect(chrome.backgroundMaterial).toBeUndefined() - expect(chrome.vibrancy).toBeUndefined() - expect(chrome.transparent).toBeUndefined() + it('gives every theme a solid background', () => { + for (const theme of Object.values(themes)) { + const chrome = themeWindowChrome(theme) expect(chrome.backgroundColor).toMatch(/^#[0-9a-f]{6}$/) } }) it('never flashes black for light themes', () => { - const chrome = themeWindowChrome(themes.porcelain, 'win32') + const chrome = themeWindowChrome(themes.porcelain) const red = parseInt(chrome.backgroundColor.slice(1, 3), 16) expect(red).toBeGreaterThan(0xd0) // Overlay symbols must be dark on a light backdrop. @@ -49,17 +34,15 @@ describe('themeWindowChrome', () => { }) it('derives the overlay symbol color from the theme foreground', () => { - expect(themeWindowChrome(themes.obsidian, 'win32').symbolColor).toMatch(/^#[0-9a-f]{6}$/) - expect(themeWindowChrome(themes.obsidian, 'win32').symbolColor).not.toBe( - themeWindowChrome(themes.porcelain, 'win32').symbolColor, + expect(themeWindowChrome(themes.obsidian).symbolColor).toMatch(/^#[0-9a-f]{6}$/) + expect(themeWindowChrome(themes.obsidian).symbolColor).not.toBe( + themeWindowChrome(themes.porcelain).symbolColor, ) }) - it('differs between a glass theme and an opaque theme', () => { - const glass = themeWindowChrome(themes.nocturne, 'win32') - const opaque = themeWindowChrome(themes.verdant, 'win32') - expect(glass.backgroundColor).not.toBe(opaque.backgroundColor) - expect(glass.backgroundMaterial).toBe('acrylic') - expect(opaque.backgroundMaterial).toBeUndefined() + it('uses each theme background rather than a shared transparent value', () => { + expect(themeWindowChrome(themes.nocturne).backgroundColor).not.toBe( + themeWindowChrome(themes.verdant).backgroundColor, + ) }) }) diff --git a/apps/desktop/src/main/window.ts b/apps/desktop/src/main/window.ts index a7bf4f17..bac945f4 100644 --- a/apps/desktop/src/main/window.ts +++ b/apps/desktop/src/main/window.ts @@ -7,15 +7,9 @@ import { appDisplayName } from './dev-instance' import { getSettingsStore } from './store' /** - * Platform chrome strategy (PLAN §8): - * - Windows: hidden frame + native titleBarOverlay (snap/max/min preserved), - * backgroundMaterial 'acrylic' when the active theme opts into glass - * - macOS: hiddenInset traffic lights + vibrancy 'under-window' (glass only) - * - Linux: hidden frame; custom controls ship in the renderer titlebar; - * transparent window for glass themes (compositor blur not guaranteed) - * - * Opaque themes get a solid `backgroundColor` from their own `bg` token, so a - * light theme never flashes black before the renderer paints. + * The native window always uses a solid `backgroundColor` from the active + * theme's `bg` token, so no desktop content bleeds through and a light theme + * never flashes black before the renderer paints. * * Window bounds persist across launches via the settings store. */ @@ -31,32 +25,12 @@ export const PACKAGED_CONTENT_SECURITY_POLICY = export interface ThemeWindowChrome { backgroundColor: string symbolColor: string - transparent?: true - backgroundMaterial?: 'acrylic' - vibrancy?: 'under-window' - visualEffectState?: 'active' } -/** - * Native window options derived from a theme. Glass themes request the - * platform's translucency and a fully transparent backdrop; opaque themes get - * the theme's own background so the first frame matches the UI. - */ -export function themeWindowChrome( - theme: Theme, - platform: NodeJS.Platform = process.platform, -): ThemeWindowChrome { +/** Native window colors derived from a theme. */ +export function themeWindowChrome(theme: Theme): ThemeWindowChrome { const symbolColor = oklchToHex(theme.colors.fg) ?? FALLBACK_SYMBOL - if (!theme.glass) { - return { backgroundColor: oklchToHex(theme.colors.bg) ?? FALLBACK_BG, symbolColor } - } - const translucent = - platform === 'win32' - ? { backgroundMaterial: 'acrylic' as const } - : platform === 'darwin' - ? { vibrancy: 'under-window' as const, visualEffectState: 'active' as const } - : { transparent: true as const } - return { backgroundColor: '#00000000', symbolColor, ...translucent } + return { backgroundColor: oklchToHex(theme.colors.bg) ?? FALLBACK_BG, symbolColor } } /** The theme the window should paint, per persisted settings. */ @@ -66,8 +40,7 @@ export function persistedTheme(): Theme { /** * Repaints native chrome for a live theme change: the Windows overlay symbol - * color and the OS-level light/dark hint. Window material cannot change after - * creation, so glass transitions land on the next launch. + * color and the OS-level light/dark hint. */ export function applyThemeToWindow(win: BrowserWindow, theme: Theme): void { const chrome = themeWindowChrome(theme) @@ -97,15 +70,6 @@ export function createMainWindow(): BrowserWindow { process.platform === 'win32' ? { color: '#00000000', symbolColor: chrome.symbolColor, height: 38 } : false, - // Glass: the desktop shows through the shell chrome. Windows gets DWM - // acrylic; macOS gets native vibrancy; Linux composites its own blur via - // CSS backdrop-filter inside a transparent window. Opaque themes skip all - // three and render on the solid backgroundColor above. - ...(chrome.backgroundMaterial ? { backgroundMaterial: chrome.backgroundMaterial } : {}), - ...(chrome.vibrancy - ? { vibrancy: chrome.vibrancy, visualEffectState: chrome.visualEffectState } - : {}), - ...(chrome.transparent ? { transparent: true } : {}), webPreferences: { preload: join(import.meta.dirname, '../preload/index.cjs'), sandbox: true, diff --git a/apps/desktop/src/renderer/src/App.tsx b/apps/desktop/src/renderer/src/App.tsx index 3c8385da..800c2030 100644 --- a/apps/desktop/src/renderer/src/App.tsx +++ b/apps/desktop/src/renderer/src/App.tsx @@ -742,7 +742,7 @@ function Shell() { if (settingsOpen || hubOpen) { return ( -
+
+
ARI / @@ -800,7 +800,7 @@ function Shell() { } return ( -
+
{sidebarOpen ? ( -