Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
aced5c0
feat(theme): add useAppearance hook with design-language root attributes
afonsojramos Jul 30, 2026
60b7633
feat(theme): add design-language chrome token layer via @theme inline
afonsojramos Jul 30, 2026
15f3245
refactor(theme): route renderer through app-owned ui/ layer with impo…
afonsojramos Jul 30, 2026
bc29091
feat(theme): add design-language settings axis with Classic/Glass sel…
afonsojramos Jul 30, 2026
d87fd88
refactor(theme): trim non-essential comments
afonsojramos Jul 30, 2026
6289fa8
feat(theme): add Glass shell surfaces with per-platform material branch
afonsojramos Jul 30, 2026
98ea376
feat(theme): wire macOS window vibrancy via IPC for Glass
afonsojramos Jul 30, 2026
74f6c25
feat(theme): degrade Glass to solid under reduced transparency / cont…
afonsojramos Jul 30, 2026
06dcfde
feat(theme): desaturate Glass status palette toward a native tone
afonsojramos Jul 30, 2026
760e4e9
fix(theme): construct a transparent vibrant window on macOS so Glass …
afonsojramos Jul 30, 2026
ee83c29
feat(theme): dissolve sidebar into unified Glass and mute the primary…
afonsojramos Jul 30, 2026
2321fc0
feat(theme): soften notification grouping bands and count pills under…
afonsojramos Jul 30, 2026
3c95c11
feat(theme): make Glass translucency always-on and add a visible side…
afonsojramos Jul 30, 2026
bb071c4
feat(theme): make the sidebar logo follow the icon colour under Glass
afonsojramos Jul 30, 2026
8b9e6ce
feat(theme): lighten Glass tints, soften blur, and use a translucent …
afonsojramos Jul 30, 2026
2344322
chore(dev): quiet codegen and Chromium dev logs, skip auto-launch in dev
afonsojramos Jul 30, 2026
2321176
fix(theme): make Glass vibrancy show the real desktop on macOS
afonsojramos Jul 30, 2026
8969c46
feat(theme): refine Glass surfaces
afonsojramos Jul 30, 2026
b699d97
fix(theme): keep Glass translucent after a runtime Classic to Glass s…
afonsojramos Jul 30, 2026
66b21a5
fix(theme): harden the native-theme sync
afonsojramos Jul 30, 2026
576624f
feat(theme): apply Glass to controls, list rows, and overlays
afonsojramos Jul 31, 2026
af53366
refactor(components): import Primer directly instead of the ui barrel
afonsojramos Jul 31, 2026
9d03ba5
revert(dev): restore codegen dotenv warning behavior
afonsojramos Jul 31, 2026
51408e0
feat(theme): float the Glass account header directly on the glass
afonsojramos Jul 31, 2026
01615ab
feat(theme): bare the Glass account-profile button inside account cards
afonsojramos Jul 31, 2026
bba2ca8
feat(theme): re-add high contrast for Classic, driven by the setting …
afonsojramos Aug 2, 2026
49ee263
refactor(settings): drop the redundant "value display" comments
afonsojramos Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/main/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { pathToFileURL } from 'node:url';
import type { BrowserWindowConstructorOptions } from 'electron';

import { APPLICATION } from '../shared/constants';
import { isMacOS } from '../shared/platform';

import { isDevMode } from './utils';

Expand Down Expand Up @@ -39,6 +40,23 @@ export const WindowConfig: BrowserWindowConstructorOptions = {
minWidth: 500,
minHeight: 400,
resizable: false,
/**
* macOS Glass uses a native vibrancy material as the window background, which
* blurs the real desktop behind it. Deliberately NOT `transparent: true`: a
* transparent window makes behind-window vibrancy sample almost nothing (it
* renders near-opaque over the desktop), so the material must own the
* background instead. `popover` is a bright, frosted menu-style material (a
* touch more see-through than `menu`); `under-window` looks solid over the
* desktop. `active` keeps it translucent even though the popup shows without
* activating the app.
* Not applied on Windows/Linux, where the CSS `backdrop-filter` path handles Glass.
*/
...(isMacOS()
? {
vibrancy: 'popover' as const,
visualEffectState: 'active' as const,
}
: {}),
/** Hide the app from the Windows taskbar */
skipTaskbar: true,
webPreferences: {
Expand Down
17 changes: 17 additions & 0 deletions src/main/handlers/system.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { nativeTheme } from 'electron';
import type { Menubar } from 'electron-menubar';

import { EVENTS } from '../../shared/events';
Expand Down Expand Up @@ -30,6 +31,9 @@ vi.mock('electron', () => ({
powerMonitor: {
on: vi.fn(),
} satisfies Pick<Electron.PowerMonitor, 'on'>,
nativeTheme: {
themeSource: 'system',
} satisfies Pick<Electron.NativeTheme, 'themeSource'>,
}));

describe('main/handlers/system.ts', () => {
Expand Down Expand Up @@ -130,6 +134,19 @@ describe('main/handlers/system.ts', () => {
});
});

describe('SET_NATIVE_THEME', () => {
it('syncs nativeTheme.themeSource with the requested source', () => {
registerSystemHandlers(menubar);

const handler = handleMock.mock.calls.find(
(call: unknown[]) => call[0] === EVENTS.SET_NATIVE_THEME,
)?.[1];
handler?.({}, 'dark');

expect(nativeTheme.themeSource).toBe('dark');
});
});

describe('UPDATE_KEEP_WINDOW_ON_BLUR', () => {
it('forwards the value to applyKeepWindowOnBlur', () => {
registerSystemHandlers(menubar);
Expand Down
31 changes: 29 additions & 2 deletions src/main/handlers/system.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { app, powerMonitor, shell } from 'electron';
import { app, nativeTheme, powerMonitor, shell } from 'electron';
import type { Menubar } from 'electron-menubar';

import { EVENTS } from '../../shared/events';
import { logInfo } from '../../shared/logger';

import { handleMainEvent, onMainEvent, sendRendererEvent } from '../events';
import { applyKeepWindowOnBlur } from '../lifecycle/window';
import { applyKeepWindowOnBlur, applyWindowVibrancy } from '../lifecycle/window';
import { isDevMode } from '../utils';

/**
* Register IPC handlers for OS-level system operations.
Expand Down Expand Up @@ -73,8 +74,14 @@ export function registerSystemHandlers(mb: Menubar): void {

/**
* Update the application's auto-launch setting based on the provided configuration.
*
* Skipped in development: the unsigned dev Electron binary cannot register as a
* macOS login item, so calling this would only emit a Chromium error log.
*/
onMainEvent(EVENTS.UPDATE_AUTO_LAUNCH, (_, settings) => {
if (isDevMode()) {
return;
}
app.setLoginItemSettings(settings);
});

Expand All @@ -84,4 +91,24 @@ export function registerSystemHandlers(mb: Menubar): void {
onMainEvent(EVENTS.UPDATE_KEEP_WINDOW_ON_BLUR, (_, value: boolean) => {
applyKeepWindowOnBlur(mb, value);
});

/**
* Toggle the macOS window vibrancy material for the Glass design language.
* Request/response so the renderer can await the material before clearing the
* window's own background (avoids a black frame during the switch).
*/
handleMainEvent(EVENTS.SET_WINDOW_VIBRANCY, (_, enabled: boolean) => {
applyWindowVibrancy(mb, enabled);
return undefined;
});

/**
* Sync the native appearance with the app's color mode so the macOS vibrancy

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wasn't able to find where in macOS System Settings the vibrancy setting is. Can you point me in the right direction :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn’t a β€œvibrancy” toggle as such. Vibrancy is the native translucency material (NSVisualEffectView) that macOS uses for menus, sidebars, the menu bar and so on. We just ask the OS for it via the window’s vibrancy option, which is what gives Glass its frosted look. The one user-facing control that affects it is System Settings β†’ Accessibility β†’ Display β†’ β€œReduce transparency”: switch that on and macOS renders these materials solid, which we honor (the window falls back to a solid surface via prefers-reduced-transparency).

* material renders light/dark to match; without this, dark Glass gets a light
* material and its light text becomes illegible.
*/
handleMainEvent(EVENTS.SET_NATIVE_THEME, (_, source) => {
nativeTheme.themeSource = source;
return undefined;
});
}
69 changes: 68 additions & 1 deletion src/main/lifecycle/window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import type { Menubar } from 'electron-menubar';
import type MenuBuilder from '../menu';
import {
__resetWindowLifecycleForTests,
configureWindowEvents,
applyKeepWindowOnBlur,
applyWindowVibrancy,
configureWindowEvents,
} from './window';

const appOnMock = vi.fn();
Expand Down Expand Up @@ -73,6 +74,8 @@ describe('main/lifecycle/window.ts', () => {
setSize: vi.fn(),
center: vi.fn(),
setAlwaysOnTop: vi.fn(),
setVibrancy: vi.fn(),
setBackgroundColor: vi.fn(),
hide: vi.fn(),
isDestroyed: vi.fn().mockReturnValue(false),
on: vi.fn(),
Expand Down Expand Up @@ -184,6 +187,70 @@ describe('main/lifecycle/window.ts', () => {
});
});

describe('applyWindowVibrancy', () => {
it('applies the material on macOS when enabled', () => {
setPlatform('darwin');

applyWindowVibrancy(menubar, true);

expect(menubar.window?.setVibrancy).toHaveBeenCalledWith('popover');
});

it('removes the material on macOS when disabled', () => {
setPlatform('darwin');

applyWindowVibrancy(menubar, false);

expect(menubar.window?.setVibrancy).toHaveBeenCalledWith(null);
});

it('is a no-op off macOS', () => {
applyWindowVibrancy(menubar, true);

expect(menubar.window?.setVibrancy).not.toHaveBeenCalled();
});

it('skips the call when the window is destroyed', () => {
setPlatform('darwin');
// oxlint-disable-next-line no-unsafe-optional-chaining -- window is guaranteed defined in this test
(menubar.window?.isDestroyed as ReturnType<typeof vi.fn>).mockReturnValue(true);

applyWindowVibrancy(menubar, true);

expect(menubar.window?.setVibrancy).not.toHaveBeenCalled();
});

it('re-applies the remembered material when the window is shown', () => {
setPlatform('darwin');
configureWindowEvents(menubar, menuBuilder);
applyWindowVibrancy(menubar, true);
// oxlint-disable-next-line no-unsafe-optional-chaining -- window is guaranteed defined in this test
(menubar.window?.setVibrancy as ReturnType<typeof vi.fn>).mockClear();

findWindowHandler(menubar, 'show')?.({ preventDefault: vi.fn() });

expect(menubar.window?.setVibrancy).toHaveBeenCalledWith('popover');
});

it('clears the window background when enabled so the material can show', () => {
setPlatform('darwin');

applyWindowVibrancy(menubar, true);

// Regression: a runtime Classic -> Glass switch must clear the opaque
// backdrop left by Classic, otherwise vibrancy cannot sample the desktop.
expect(menubar.window?.setBackgroundColor).toHaveBeenCalledWith('#00000000');
});

it('restores an opaque backdrop when disabled', () => {
setPlatform('darwin');

applyWindowVibrancy(menubar, false);

expect(menubar.window?.setBackgroundColor).toHaveBeenCalledWith('#ffffff');
});
});

describe('devtools-closed handler', () => {
it('delegates re-centering to mb.recenterOnTray()', () => {
configureWindowEvents(menubar, menuBuilder);
Expand Down
23 changes: 23 additions & 0 deletions src/main/lifecycle/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type MenuBuilder from '../menu';

let isQuitting = false;
let keepWindowOnBlur = false;
let windowVibrancyEnabled = false;

/**
* Reset module-level lifecycle flags. Module-level state is unavoidable
Expand All @@ -19,6 +20,26 @@ let keepWindowOnBlur = false;
export function __resetWindowLifecycleForTests(): void {
isQuitting = false;
keepWindowOnBlur = false;
windowVibrancyEnabled = false;
}

/**
* Enable or disable the macOS window vibrancy material (the Glass design
* language). No-op off macOS. The desired state is remembered so it can be
* re-applied if `electron-menubar` rebuilds the window (see `configureWindowEvents`).
*/
export function applyWindowVibrancy(mb: Menubar, enabled: boolean): void {
windowVibrancyEnabled = enabled;
if (!isMacOS() || !mb.window || mb.window.isDestroyed()) {
return;
}
mb.window.setVibrancy(enabled ? 'popover' : null);
// Clear the window's own background so the vibrancy material can sample the
// desktop; a prior Classic disable leaves it opaque, which would otherwise
// block Glass on a runtime Classic β†’ Glass switch. `#00000000` works without a
// `transparent` window because the vibrancy view provides the translucency.
// Restore an opaque backdrop for Classic once the material is removed.
mb.window.setBackgroundColor(enabled ? '#00000000' : '#ffffff');
}

/**
Expand Down Expand Up @@ -55,6 +76,8 @@ export function configureWindowEvents(mb: Menubar, menuBuilder: MenuBuilder): vo

win.on('show', () => {
menuBuilder.setWindowVisibility(true);
// Re-apply vibrancy in case the window was rebuilt since it was last set.
applyWindowVibrancy(mb, windowVibrancyEnabled);
});

win.on('hide', () => {
Expand Down
15 changes: 14 additions & 1 deletion src/preload/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { contextBridge, webFrame } from 'electron';

import type { IKeyboardShortcut } from '../shared/events';
import type { IKeyboardShortcut, NativeThemeSource } from '../shared/events';
import { EVENTS } from '../shared/events';
import { isLinux, isMacOS, isWindows } from '../shared/platform';

Expand Down Expand Up @@ -63,6 +63,19 @@ export const api = {
*/
setKeepWindowOnBlur: (value: boolean) => sendMainEvent(EVENTS.UPDATE_KEEP_WINDOW_ON_BLUR, value),

/**
* Enable or disable the macOS window vibrancy material for Glass. Resolves once
* the material has been applied so the renderer can order the visual switch.
*/
setWindowVibrancy: (enabled: boolean) => invokeMainEvent(EVENTS.SET_WINDOW_VIBRANCY, enabled),

/**
* Sync the native window appearance (`nativeTheme.themeSource`) with the app's
* color mode, so the macOS vibrancy material renders light/dark to match β€”
* otherwise dark Glass gets a light material and its light text washes out.
*/
setNativeTheme: (source: NativeThemeSource) => invokeMainEvent(EVENTS.SET_NATIVE_THEME, source),

/**
* Apply the global keyboard shortcut for toggling the app window visibility.
*
Expand Down
Loading