feat(desktop): macOS vibrancy for the left sidebar with a toggle

Add native macOS vibrancy behind the left sidebar (the only translucent
surface; header/chat/right sidebar stay opaque), plus a setting to turn it off.

- Window created with vibrancy applied after first show (avoids the cold-launch
  no-composite quirk); minimize/restore suppress the frost during the genie
  animation. Renderer frosts the sidebar via --sidebar-vibrancy-overlay once
  data-oc-vibrancy[-ready] are set; project-actions pill matches when open.
- data-oc-vibrancy-ready defaults are set in cssGenerator (DOM guaranteed),
  not the preload (document-start race left the sidebar un-frosted on launch).
- prefers-reduced-transparency falls back to solid surfaces.
- Appearance settings (macOS desktop only): a checkbox to enable/disable
  vibrancy, persisted to settings.json and applied via a Save & restart button
  (vibrancy is a window-creation option, so it needs a relaunch).
This commit is contained in:
Bohdan Triapitsyn
2026-06-09 18:38:01 +03:00
parent f9acf68c8f
commit 3541bc63f9
19 changed files with 288 additions and 12 deletions
+2
View File
@@ -201,6 +201,8 @@ type DesktopBridgeGlobal = {
type ElectronRuntimeGlobal = {
runtime?: string;
macVibrancy?: boolean;
macVibrancySupported?: boolean;
};
const getElectronRuntime = (): ElectronRuntimeGlobal | null => {
@@ -1573,6 +1573,10 @@ export const settingsDict = {
'settings.openchamber.visual.field.selectWeekStartAria': 'Select week start',
'settings.openchamber.visual.actions.reloadThemes': 'Reload themes',
'settings.openchamber.visual.actions.reloadingThemes': 'Reloading themes...',
'settings.openchamber.visual.field.macVibrancy': 'Window transparency',
'settings.openchamber.visual.field.macVibrancyHint': 'Use the native macOS blur (vibrancy) behind the sidebar. Turn off for fully solid, opaque surfaces.',
'settings.openchamber.visual.actions.saveAndRestart': 'Save & restart',
'settings.openchamber.visual.actions.restarting': 'Restarting…',
'settings.openchamber.visual.field.themeImportInfoAria': 'Theme import info',
'settings.openchamber.visual.field.themeImportInfoTooltip': 'Import custom themes from ~/.config/openchamber/themes/',
'settings.openchamber.visual.field.installAppName': 'Install App Name',
@@ -1539,6 +1539,10 @@ export const settingsDict = {
"settings.openchamber.visual.field.selectTimeFormatAria": "Seleccionar formato de hora",
"settings.openchamber.visual.field.selectWeekStartAria": "Seleccionar inicio de la semana",
"settings.openchamber.visual.actions.reloadThemes": "Recargar temas",
"settings.openchamber.visual.field.macVibrancy": "Transparencia de la ventana",
"settings.openchamber.visual.field.macVibrancyHint": "Usa el desenfoque nativo de macOS (vibrancy) detrás de la barra lateral. Desactívalo para superficies totalmente sólidas y opacas.",
"settings.openchamber.visual.actions.saveAndRestart": "Guardar y reiniciar",
"settings.openchamber.visual.actions.restarting": "Reiniciando…",
"settings.openchamber.visual.actions.reloadingThemes": "Recargando temas...",
"settings.openchamber.visual.field.themeImportInfoAria": "Información de importación de temas",
"settings.openchamber.visual.field.themeImportInfoTooltip": "Importar temas personalizados desde ~/.config/openchamber/themes/",
@@ -1539,6 +1539,10 @@ export const settingsDict = {
'settings.openchamber.visual.field.selectTimeFormatAria': '시간 형식 선택',
'settings.openchamber.visual.field.selectWeekStartAria': '주 시작 요일 선택',
'settings.openchamber.visual.actions.reloadThemes': '테마 다시 로드',
'settings.openchamber.visual.field.macVibrancy': '창 투명도',
'settings.openchamber.visual.field.macVibrancyHint': '사이드바 뒤에 macOS 기본 블러(vibrancy)를 사용합니다. 완전히 불투명한 표면을 원하면 끄세요.',
'settings.openchamber.visual.actions.saveAndRestart': '저장 후 재시작',
'settings.openchamber.visual.actions.restarting': '재시작 중…',
'settings.openchamber.visual.actions.reloadingThemes': '테마 다시 로드 중...',
'settings.openchamber.visual.field.themeImportInfoAria': '테마 가져오기 정보',
'settings.openchamber.visual.field.themeImportInfoTooltip': '~/.config/openchamber/themes/에서 사용자 정의 테마를 가져옵니다',
@@ -878,6 +878,10 @@ export const settingsDict = {
'settings.openchamber.tunnel.warning.quickModeReliability': 'Aby uzyskać bardziej niezawodny długotrwały dostęp, przełącz się na tryb Managed Remote lub Managed Local.',
'settings.openchamber.tunnel.warning.replacesActiveTunnel': 'Uruchomienie tego tunelu zastępuje aktywny tunel i unieważnia istniejące linki połączenia oraz zdalne sesje.',
'settings.openchamber.visual.actions.reloadThemes': 'Przeładuj motywy',
'settings.openchamber.visual.field.macVibrancy': 'Przezroczystość okna',
'settings.openchamber.visual.field.macVibrancyHint': 'Używaj natywnego rozmycia macOS (vibrancy) za panelem bocznym. Wyłącz, aby uzyskać w pełni nieprzezroczyste powierzchnie.',
'settings.openchamber.visual.actions.saveAndRestart': 'Zapisz i uruchom ponownie',
'settings.openchamber.visual.actions.restarting': 'Ponowne uruchamianie…',
'settings.openchamber.visual.actions.reloadingThemes': 'Przeładowywanie motywów...',
'settings.openchamber.visual.actions.resetCodeFontAria': 'Zresetuj czcionkę kodu',
'settings.openchamber.visual.actions.resetFontSizeAria': 'Zresetuj rozmiar czcionki',
@@ -1539,6 +1539,10 @@ export const settingsDict = {
"settings.openchamber.visual.field.selectTimeFormatAria": "Selecionar formato de hora",
"settings.openchamber.visual.field.selectWeekStartAria": "Selecionar início da semana",
"settings.openchamber.visual.actions.reloadThemes": "Recarregar temas",
"settings.openchamber.visual.field.macVibrancy": "Transparência da janela",
"settings.openchamber.visual.field.macVibrancyHint": "Usa o desfoque nativo do macOS (vibrancy) atrás da barra lateral. Desative para superfícies totalmente sólidas e opacas.",
"settings.openchamber.visual.actions.saveAndRestart": "Salvar e reiniciar",
"settings.openchamber.visual.actions.restarting": "Reiniciando…",
"settings.openchamber.visual.actions.reloadingThemes": "Recarregando temas...",
"settings.openchamber.visual.field.themeImportInfoAria": "Informações de importação de temas",
"settings.openchamber.visual.field.themeImportInfoTooltip": "Importar temas personalizados de ~/.config/openchamber/themês/",
@@ -1539,6 +1539,10 @@ export const settingsDict = {
"settings.openchamber.visual.field.selectTimeFormatAria": "Вибрати формат часу",
"settings.openchamber.visual.field.selectWeekStartAria": "Вибрати початок тижня",
"settings.openchamber.visual.actions.reloadThemes": "Перезавантажити теми",
"settings.openchamber.visual.field.macVibrancy": "Прозорість вікна",
"settings.openchamber.visual.field.macVibrancyHint": "Використовувати нативне розмиття macOS (vibrancy) під сайдбаром. Вимкніть для повністю непрозорих поверхонь.",
"settings.openchamber.visual.actions.saveAndRestart": "Зберегти та перезапустити",
"settings.openchamber.visual.actions.restarting": "Перезапуск…",
"settings.openchamber.visual.actions.reloadingThemes": "Перезавантаження тем...",
"settings.openchamber.visual.field.themeImportInfoAria": "Інформація про імпорт теми",
"settings.openchamber.visual.field.themeImportInfoTooltip": "Імпорт спеціальних тем із ~/.config/openchamber/themes/",
@@ -1539,6 +1539,10 @@ export const settingsDict = {
'settings.openchamber.visual.field.selectTimeFormatAria': '选择时间格式',
'settings.openchamber.visual.field.selectWeekStartAria': '选择一周起始日',
'settings.openchamber.visual.actions.reloadThemes': '重新加载主题',
'settings.openchamber.visual.field.macVibrancy': '窗口透明度',
'settings.openchamber.visual.field.macVibrancyHint': '在侧边栏后使用 macOS 原生模糊(vibrancy)。关闭以获得完全不透明的界面。',
'settings.openchamber.visual.actions.saveAndRestart': '保存并重启',
'settings.openchamber.visual.actions.restarting': '正在重启…',
'settings.openchamber.visual.actions.reloadingThemes': '正在重新加载主题...',
'settings.openchamber.visual.field.themeImportInfoAria': '主题导入说明',
'settings.openchamber.visual.field.themeImportInfoTooltip': '从 ~/.config/openchamber/themes/ 导入自定义主题',
@@ -1458,6 +1458,10 @@
'settings.openchamber.visual.field.selectTimeFormatAria': '選擇時間格式',
'settings.openchamber.visual.field.selectWeekStartAria': '選擇一週起始日',
'settings.openchamber.visual.actions.reloadThemes': '重新載入主題',
'settings.openchamber.visual.field.macVibrancy': '視窗透明度',
'settings.openchamber.visual.field.macVibrancyHint': '在側邊欄後使用 macOS 原生模糊(vibrancy)。關閉以獲得完全不透明的介面。',
'settings.openchamber.visual.actions.saveAndRestart': '儲存並重新啟動',
'settings.openchamber.visual.actions.restarting': '正在重新啟動…',
'settings.openchamber.visual.actions.reloadingThemes': '正在重新載入主題...',
'settings.openchamber.visual.field.themeImportInfoAria': '主題匯入說明',
'settings.openchamber.visual.field.themeImportInfoTooltip': '從 ~/.config/openchamber/themes/ 匯入自訂主題',
+22
View File
@@ -123,6 +123,9 @@ const sidebarBaseRgb = hexToRgb(theme.colors.surface.muted);
const isDark = theme.metadata.variant === 'dark';
const strongAlpha = isDark ? 0.15 : 0.5;
const softAlpha = isDark ? 0.1 : 0.3;
// Translucent fill painted over the native macOS vibrancy layer for the
// left sidebar — high enough alpha to stay legible, low enough to frost.
const vibrancyAlpha = isDark ? 0.66 : 0.76;
if (sidebarBaseRgb) {
vars.push(
@@ -131,6 +134,9 @@ const sidebarBaseRgb = hexToRgb(theme.colors.surface.muted);
vars.push(
` --sidebar-overlay-soft: rgb(${sidebarBaseRgb} / ${softAlpha}) !important;`,
);
vars.push(
` --sidebar-vibrancy-overlay: rgb(${sidebarBaseRgb} / ${vibrancyAlpha}) !important;`,
);
} else {
const base = theme.colors.surface.muted;
vars.push(
@@ -139,6 +145,9 @@ const sidebarBaseRgb = hexToRgb(theme.colors.surface.muted);
vars.push(
` --sidebar-overlay-soft: ${this.opacity(base, softAlpha)} !important;`,
);
vars.push(
` --sidebar-vibrancy-overlay: ${this.opacity(base, vibrancyAlpha)} !important;`,
);
}
if (theme.colors.charts?.series && Array.isArray(theme.colors.charts.series)) {
@@ -186,6 +195,19 @@ const sidebarBaseRgb = hexToRgb(theme.colors.surface.muted);
document.head.appendChild(style);
document.documentElement.setAttribute('data-theme', theme.metadata.variant);
const hasMacVibrancy = typeof window !== 'undefined'
&& window.__OPENCHAMBER_ELECTRON__?.runtime === 'electron'
&& window.__OPENCHAMBER_ELECTRON__?.macVibrancy === true;
document.documentElement.toggleAttribute('data-oc-vibrancy', hasMacVibrancy);
// Default the "ready" flag here (DOM is guaranteed to exist) rather than
// relying on the preload, which sets it at document-start when
// documentElement may not exist yet — that race left the sidebar stuck
// un-frosted on cold launch until a minimize/restore re-sent ready=true.
// The minimize/restore IPC continues to toggle this afterwards.
if (hasMacVibrancy) {
document.documentElement.toggleAttribute('data-oc-vibrancy-ready', true);
}
}
private generatePrimaryColors(primary: Theme['colors']['primary']): string[] {