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 ? (
-