feat: introduced a token-based theming system across the UI

* feat: added themes system

* feat: smart sidebar auto-hide for files/diff tabs + lower files sidebar threshold

* feat: added Checkbox component and update theming

- Add reusable Checkbox component for toggles across UI
- Replace several inputs with Checkbox in settings and commands panels
- Add DiffIcon and apply surface/border theming to key UI areas

* feat: Add convert-vscode-theme.cjs to convert VS Code themes to OpenChamber format

* refactor: remove unused permission logic from ChatInput

- Remove unused permission rules parsing logic from ChatInput
- Memoize renderTheme in DiffWorkerProvider to avoid unnecessary recalculations
- Remove forceOpaque helper in vscode theme adapter

* fix: guard VSCode theme loading in MarkdownRenderer

* feat: add custom user themes loading and reload

- Load user themes from ~/.config/openchamber/themes at runtime
- Expose /api/config/themes to fetch custom themes
- Allow theme reloading from Settings → Theme → Reload themes in the UI
This commit is contained in:
Bohdan Triapitsyn
2026-02-01 18:29:34 +02:00
committed by GitHub
parent 5bb2c56bc0
commit ddedc02687
147 changed files with 9853 additions and 2435 deletions
+25 -12
View File
@@ -1,21 +1,34 @@
import type { Theme } from '@/types/theme';
import { flexokiLightTheme } from './flexoki-light';
import { flexokiDarkTheme } from './flexoki-dark';
import { presetThemes } from './presets';
import flexokiLightRaw from './flexoki-light.json';
import flexokiDarkRaw from './flexoki-dark.json';
export const themes: Theme[] = [
flexokiLightTheme,
flexokiDarkTheme,
];
export const flexokiLightTheme = flexokiLightRaw as Theme;
export const flexokiDarkTheme = flexokiDarkRaw as Theme;
export {
flexokiLightTheme,
flexokiDarkTheme,
};
export const DEFAULT_LIGHT_THEME_ID = 'flexoki-light' as const;
export const DEFAULT_DARK_THEME_ID = 'flexoki-dark' as const;
export const themes: Theme[] = [flexokiLightTheme, flexokiDarkTheme, ...presetThemes];
export function getThemeById(id: string): Theme | undefined {
return themes.find(theme => theme.metadata.id === id);
// Back-compat for a short-lived rename.
const resolvedId =
id === 'app-light' ? 'flexoki-light' :
id === 'app-dark' ? 'flexoki-dark' :
id;
return themes.find(theme => theme.metadata.id === resolvedId);
}
export function getDefaultTheme(prefersDark: boolean): Theme {
return prefersDark ? flexokiDarkTheme : flexokiLightTheme;
const variant: Theme['metadata']['variant'] = prefersDark ? 'dark' : 'light';
const defaultId = prefersDark ? DEFAULT_DARK_THEME_ID : DEFAULT_LIGHT_THEME_ID;
const defaultTheme = getThemeById(defaultId);
if (defaultTheme && defaultTheme.metadata.variant === variant) {
return defaultTheme;
}
return themes.find((theme) => theme.metadata.variant === variant) ?? themes[0] ?? flexokiLightTheme;
}