From ddedc026878f1bc3462754700cddca1c436a63a5 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 1 Feb 2026 18:29:34 +0200 Subject: [PATCH] feat: introduced a token-based theming system across the UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .opencode/{command => commands}/changelog.md | 0 .opencode/skills/theme-system/SKILL.md | 205 +++++++ .../theme-system/references/adding-themes.md | 51 ++ AGENTS.md | 11 + README.md | 6 + docs/CUSTOM_THEMES.md | 218 +++++++ packages/desktop/index.html | 49 +- .../src-tauri/src/commands/settings.rs | 66 +++ packages/desktop/src-tauri/src/main.rs | 240 ++++++++ .../chat/AgentMentionAutocomplete.tsx | 10 +- .../ui/src/components/chat/ChatContainer.tsx | 14 +- .../ui/src/components/chat/ChatEmptyState.tsx | 15 +- .../src/components/chat/ChatErrorBoundary.tsx | 4 +- packages/ui/src/components/chat/ChatInput.tsx | 145 +---- .../ui/src/components/chat/ChatMessage.tsx | 4 +- .../components/chat/CommandAutocomplete.tsx | 4 +- .../ui/src/components/chat/DiffPreview.tsx | 4 +- .../chat/FileMentionAutocomplete.tsx | 18 +- .../src/components/chat/MarkdownRenderer.tsx | 100 ++-- .../ui/src/components/chat/ModelControls.tsx | 57 +- .../ui/src/components/chat/PermissionCard.tsx | 2 +- .../ui/src/components/chat/QuestionCard.tsx | 27 +- .../src/components/chat/ServerFilePicker.tsx | 12 +- .../src/components/chat/SkillAutocomplete.tsx | 10 +- .../ui/src/components/chat/StatusChip.tsx | 2 +- .../ui/src/components/chat/TimelineDialog.tsx | 2 +- .../components/chat/UnifiedControlsDrawer.tsx | 4 +- .../chat/message/ToolOutputDialog.tsx | 18 +- .../chat/message/parts/ProgressiveGroup.tsx | 13 +- .../chat/message/parts/ToolPart.tsx | 47 +- packages/ui/src/components/icons/DiffIcon.tsx | 27 + packages/ui/src/components/layout/Header.tsx | 96 ++- packages/ui/src/components/layout/Sidebar.tsx | 6 +- .../ui/src/components/mcp/McpDropdown.tsx | 6 +- .../components/multirun/ModelMultiSelect.tsx | 20 +- .../components/multirun/MultiRunLauncher.tsx | 4 +- .../components/providers/ThemeProvider.tsx | 24 +- .../components/sections/agents/AgentsPage.tsx | 8 +- .../sections/agents/AgentsSidebar.tsx | 4 +- .../sections/agents/ModelSelector.tsx | 34 +- .../sections/commands/AgentSelector.tsx | 2 +- .../sections/commands/CommandsPage.tsx | 24 +- .../sections/commands/CommandsSidebar.tsx | 4 +- .../git-identities/GitIdentitiesSidebar.tsx | 4 +- .../sections/openchamber/DefaultsSettings.tsx | 9 +- .../sections/openchamber/GitSettings.tsx | 13 +- .../openchamber/OpenChamberSidebar.tsx | 2 +- .../openchamber/OpenChamberVisualSettings.tsx | 134 ++++- .../openchamber/SessionRetentionSettings.tsx | 7 +- .../sections/providers/ProvidersPage.tsx | 2 +- .../sections/providers/ProvidersSidebar.tsx | 2 +- .../sections/shared/SettingsSidebarItem.tsx | 4 +- .../components/sections/skills/SkillsPage.tsx | 4 +- .../sections/skills/SkillsSidebar.tsx | 4 +- .../skills/catalog/InstallFromRepoDialog.tsx | 17 +- .../components/session/BranchPickerDialog.tsx | 10 +- .../session/DirectoryAutocomplete.tsx | 2 +- .../session/DirectoryExplorerDialog.tsx | 2 +- .../src/components/session/DirectoryTree.tsx | 34 +- .../session/GitHubIssuePickerDialog.tsx | 8 +- .../session/GitHubPullRequestPickerDialog.tsx | 8 +- .../src/components/session/SessionDialogs.tsx | 2 +- .../src/components/session/SessionSidebar.tsx | 16 +- .../ui/src/components/ui/CodeMirrorEditor.tsx | 1 + .../src/components/ui/ContextUsageDisplay.tsx | 4 +- .../ui/src/components/ui/ErrorBoundary.tsx | 4 +- .../ui/src/components/ui/MemoryDebugPanel.tsx | 4 +- .../src/components/ui/MobileOverlayPanel.tsx | 2 +- .../ui/src/components/ui/OpenChamberLogo.tsx | 35 +- .../ui/src/components/ui/UpdateDialog.tsx | 4 +- .../ui/src/components/ui/animated-tabs.tsx | 4 +- packages/ui/src/components/ui/button.tsx | 6 +- packages/ui/src/components/ui/checkbox.tsx | 68 +++ packages/ui/src/components/ui/collapsible.tsx | 2 +- packages/ui/src/components/ui/command.tsx | 8 +- packages/ui/src/components/ui/dialog.tsx | 2 +- .../ui/src/components/ui/dropdown-menu.tsx | 20 +- packages/ui/src/components/ui/input.tsx | 7 +- .../ui/src/components/ui/number-input.tsx | 4 +- packages/ui/src/components/ui/select.tsx | 10 +- packages/ui/src/components/ui/switch.tsx | 2 +- packages/ui/src/components/ui/textarea.tsx | 10 +- packages/ui/src/components/ui/toggle.tsx | 4 +- packages/ui/src/components/views/DiffView.tsx | 12 +- .../ui/src/components/views/FilesView.tsx | 26 +- .../src/components/views/PierreDiffViewer.tsx | 308 ++++++---- .../ui/src/components/views/SettingsView.tsx | 10 +- .../agent-manager/AgentManagerEmptyState.tsx | 13 +- .../agent-manager/AgentManagerSidebar.tsx | 4 +- .../components/views/git/HistoryCommitRow.tsx | 8 +- .../ui/src/contexts/DiffWorkerProvider.tsx | 69 ++- .../ui/src/contexts/ThemeSystemContext.tsx | 224 +++++-- .../ui/src/contexts/theme-system-context.ts | 2 + packages/ui/src/index.css | 66 ++- packages/ui/src/lib/shiki/appThemeRegistry.ts | 95 +++ packages/ui/src/lib/shiki/flexokiThemes.ts | 96 --- .../ui/src/lib/shiki/registerFlexokiThemes.ts | 15 - .../lib/shiki/textMateThemeFromAppTheme.ts | 482 +++++++++++++++ .../ui/src/lib/shiki/themes/flexoki-dark.json | 547 ------------------ .../src/lib/shiki/themes/flexoki-light.json | 547 ------------------ .../ui/src/lib/shiki/vscodeTextMateTheme.ts | 14 + .../ui/src/lib/theme/themes/aura-dark.json | 176 ++++++ .../ui/src/lib/theme/themes/aura-light.json | 176 ++++++ .../ui/src/lib/theme/themes/ayu-dark.json | 176 ++++++ .../ui/src/lib/theme/themes/ayu-light.json | 176 ++++++ .../src/lib/theme/themes/carbonfox-dark.json | 176 ++++++ .../src/lib/theme/themes/carbonfox-light.json | 176 ++++++ .../src/lib/theme/themes/catppuccin-dark.json | 176 ++++++ .../lib/theme/themes/catppuccin-light.json | 176 ++++++ .../ui/src/lib/theme/themes/dracula-dark.json | 176 ++++++ .../src/lib/theme/themes/dracula-light.json | 176 ++++++ .../ui/src/lib/theme/themes/flexoki-dark.json | 176 ++++++ .../ui/src/lib/theme/themes/flexoki-dark.ts | 197 ------- .../src/lib/theme/themes/flexoki-light.json | 176 ++++++ .../ui/src/lib/theme/themes/flexoki-light.ts | 197 ------- .../ui/src/lib/theme/themes/gruvbox-dark.json | 176 ++++++ .../src/lib/theme/themes/gruvbox-light.json | 176 ++++++ packages/ui/src/lib/theme/themes/index.ts | 37 +- .../src/lib/theme/themes/kanagawa-dark.json | 176 ++++++ .../src/lib/theme/themes/kanagawa-light.json | 176 ++++++ .../ui/src/lib/theme/themes/mono-dark.json | 176 ++++++ .../ui/src/lib/theme/themes/mono-light.json | 176 ++++++ .../src/lib/theme/themes/mono-plus-dark.json | 155 +++++ .../src/lib/theme/themes/mono-plus-light.json | 155 +++++ .../ui/src/lib/theme/themes/monokai-dark.json | 176 ++++++ .../src/lib/theme/themes/monokai-light.json | 176 ++++++ .../src/lib/theme/themes/nightowl-dark.json | 176 ++++++ .../src/lib/theme/themes/nightowl-light.json | 176 ++++++ .../ui/src/lib/theme/themes/nord-dark.json | 176 ++++++ .../ui/src/lib/theme/themes/nord-light.json | 176 ++++++ .../src/lib/theme/themes/onedarkpro-dark.json | 176 ++++++ .../lib/theme/themes/onedarkpro-light.json | 176 ++++++ packages/ui/src/lib/theme/themes/presets.ts | 69 +++ .../src/lib/theme/themes/solarized-dark.json | 176 ++++++ .../src/lib/theme/themes/solarized-light.json | 176 ++++++ .../src/lib/theme/themes/tokyonight-dark.json | 176 ++++++ .../lib/theme/themes/tokyonight-light.json | 176 ++++++ .../ui/src/lib/theme/themes/vesper-dark.json | 176 ++++++ .../ui/src/lib/theme/themes/vesper-light.json | 176 ++++++ packages/ui/src/lib/theme/vscode/adapter.ts | 371 +++++++++--- packages/ui/src/stores/useUIStore.ts | 49 +- packages/ui/src/styles/design-system.css | 19 +- packages/ui/src/styles/typography.css | 4 +- packages/ui/src/types/theme.ts | 2 +- packages/web/index.html | 44 +- packages/web/server/index.js | 220 ++++++- scripts/convert-vscode-theme.cjs | 497 ++++++++++++++++ 147 files changed, 9853 insertions(+), 2435 deletions(-) rename .opencode/{command => commands}/changelog.md (100%) create mode 100644 .opencode/skills/theme-system/SKILL.md create mode 100644 .opencode/skills/theme-system/references/adding-themes.md create mode 100644 docs/CUSTOM_THEMES.md create mode 100644 packages/ui/src/components/icons/DiffIcon.tsx create mode 100644 packages/ui/src/components/ui/checkbox.tsx create mode 100644 packages/ui/src/lib/shiki/appThemeRegistry.ts delete mode 100644 packages/ui/src/lib/shiki/flexokiThemes.ts delete mode 100644 packages/ui/src/lib/shiki/registerFlexokiThemes.ts create mode 100644 packages/ui/src/lib/shiki/textMateThemeFromAppTheme.ts delete mode 100644 packages/ui/src/lib/shiki/themes/flexoki-dark.json delete mode 100644 packages/ui/src/lib/shiki/themes/flexoki-light.json create mode 100644 packages/ui/src/lib/shiki/vscodeTextMateTheme.ts create mode 100644 packages/ui/src/lib/theme/themes/aura-dark.json create mode 100644 packages/ui/src/lib/theme/themes/aura-light.json create mode 100644 packages/ui/src/lib/theme/themes/ayu-dark.json create mode 100644 packages/ui/src/lib/theme/themes/ayu-light.json create mode 100644 packages/ui/src/lib/theme/themes/carbonfox-dark.json create mode 100644 packages/ui/src/lib/theme/themes/carbonfox-light.json create mode 100644 packages/ui/src/lib/theme/themes/catppuccin-dark.json create mode 100644 packages/ui/src/lib/theme/themes/catppuccin-light.json create mode 100644 packages/ui/src/lib/theme/themes/dracula-dark.json create mode 100644 packages/ui/src/lib/theme/themes/dracula-light.json create mode 100644 packages/ui/src/lib/theme/themes/flexoki-dark.json delete mode 100644 packages/ui/src/lib/theme/themes/flexoki-dark.ts create mode 100644 packages/ui/src/lib/theme/themes/flexoki-light.json delete mode 100644 packages/ui/src/lib/theme/themes/flexoki-light.ts create mode 100644 packages/ui/src/lib/theme/themes/gruvbox-dark.json create mode 100644 packages/ui/src/lib/theme/themes/gruvbox-light.json create mode 100644 packages/ui/src/lib/theme/themes/kanagawa-dark.json create mode 100644 packages/ui/src/lib/theme/themes/kanagawa-light.json create mode 100644 packages/ui/src/lib/theme/themes/mono-dark.json create mode 100644 packages/ui/src/lib/theme/themes/mono-light.json create mode 100644 packages/ui/src/lib/theme/themes/mono-plus-dark.json create mode 100644 packages/ui/src/lib/theme/themes/mono-plus-light.json create mode 100644 packages/ui/src/lib/theme/themes/monokai-dark.json create mode 100644 packages/ui/src/lib/theme/themes/monokai-light.json create mode 100644 packages/ui/src/lib/theme/themes/nightowl-dark.json create mode 100644 packages/ui/src/lib/theme/themes/nightowl-light.json create mode 100644 packages/ui/src/lib/theme/themes/nord-dark.json create mode 100644 packages/ui/src/lib/theme/themes/nord-light.json create mode 100644 packages/ui/src/lib/theme/themes/onedarkpro-dark.json create mode 100644 packages/ui/src/lib/theme/themes/onedarkpro-light.json create mode 100644 packages/ui/src/lib/theme/themes/presets.ts create mode 100644 packages/ui/src/lib/theme/themes/solarized-dark.json create mode 100644 packages/ui/src/lib/theme/themes/solarized-light.json create mode 100644 packages/ui/src/lib/theme/themes/tokyonight-dark.json create mode 100644 packages/ui/src/lib/theme/themes/tokyonight-light.json create mode 100644 packages/ui/src/lib/theme/themes/vesper-dark.json create mode 100644 packages/ui/src/lib/theme/themes/vesper-light.json create mode 100644 scripts/convert-vscode-theme.cjs diff --git a/.opencode/command/changelog.md b/.opencode/commands/changelog.md similarity index 100% rename from .opencode/command/changelog.md rename to .opencode/commands/changelog.md diff --git a/.opencode/skills/theme-system/SKILL.md b/.opencode/skills/theme-system/SKILL.md new file mode 100644 index 00000000..9304a6ec --- /dev/null +++ b/.opencode/skills/theme-system/SKILL.md @@ -0,0 +1,205 @@ +--- +name: theme-system +description: Use when creating or modifying UI components, styling, or visual elements in OpenChamber. All UI colors must use theme tokens - never hardcoded values or Tailwind color classes. +license: MIT +compatibility: opencode +--- + +## Overview + +OpenChamber uses a JSON-based theme system. Themes are defined in `packages/ui/src/lib/theme/themes/`. Users can also add custom themes via `~/.config/openchamber/themes/`. + +**Core principle:** UI colors must use theme tokens - never hardcoded hex colors or Tailwind color classes. + +## When to Use + +- Creating or modifying UI components +- Working with colors, backgrounds, borders, or text + +## Quick Decision Tree + +1. **Code display?** → `syntax.*` +2. **Feedback/status?** → `status.*` +3. **Primary CTA?** → `primary.*` +4. **Interactive/clickable?** → `interactive.*` +5. **Background layer?** → `surface.*` +6. **Text?** → `surface.foreground` or `surface.mutedForeground` + +## Critical Rules + +- `surface.elevated` = inputs, cards, panels +- `interactive.hover` = **ONLY on clickable elements** +- `interactive.selection` = active/selected states (not primary!) +- Status colors = **ONLY for actual feedback** (errors, warnings, success) +- Input footers = `bg-transparent` on elevated background + +### Never Use + +- Hardcoded hex colors (`#FF0000`) +- Tailwind colors (`bg-white`, `text-blue-500`, `bg-gray-*`) +- Deprecated: `bg-secondary`, `bg-muted` + +## Usage + +### Via Hook +```tsx +import { useThemeSystem } from '@/contexts/useThemeSystem'; +const { currentTheme } = useThemeSystem(); + +
+``` + +### Via CSS Variables +```tsx +
+``` + +## Color Tokens + +### Surface Colors + +| Token | Usage | +|-------|-------| +| `surface.background` | Main app background | +| `surface.elevated` | Inputs, cards, panels, popovers | +| `surface.muted` | Secondary backgrounds, sidebars | +| `surface.foreground` | Primary text | +| `surface.mutedForeground` | Secondary text, hints | +| `surface.subtle` | Subtle dividers | + +### Interactive Colors + +| Token | Usage | +|-------|-------| +| `interactive.border` | Default borders | +| `interactive.hover` | Hover on **clickable elements only** | +| `interactive.selection` | Active/selected items | +| `interactive.selectionForeground` | Text on selection | +| `interactive.focusRing` | Focus indicators | + +### Status Colors + +| Token | Usage | +|-------|-------| +| `status.error` | Errors, validation failures | +| `status.warning` | Warnings, cautions | +| `status.success` | Success messages | +| `status.info` | Informational messages | + +Each has variants: `*`, `*Foreground`, `*Background`, `*Border`. + +### Primary Colors + +| Token | Usage | +|-------|-------| +| `primary.base` | Primary CTA buttons | +| `primary.hover` | Hover on primary elements | +| `primary.foreground` | Text on primary background | + +**Primary vs Selection:** Primary = "click me" (CTA), Selection = "currently active" (state). + +### Syntax Colors + +For code display only. Never use for UI elements. + +| Token | Usage | +|-------|-------| +| `syntax.base.background` | Code block background | +| `syntax.base.foreground` | Default code text | +| `syntax.base.keyword` | Keywords | +| `syntax.base.string` | Strings | +| `syntax.highlights.diffAdded` | Added lines | +| `syntax.highlights.diffRemoved` | Removed lines | + +## Examples + +### Input Area + +```tsx +const { currentTheme } = useThemeSystem(); + +
+