Skip to content

feat(code-block): user-configurable Shiki theme (light/dark independent) - #142

Merged
lehhair merged 2 commits into
lehhair:mainfrom
SsparKluo:feat/code-block-theme-settings
Jul 19, 2026
Merged

lehhair merged 2 commits into
lehhair:mainfrom
SsparKluo:feat/code-block-theme-settings

Conversation

@SsparKluo

Copy link
Copy Markdown
Contributor

Problem

Code block syntax highlighting is hardcoded to GitHub Dark/Light Default regardless of the active UI theme. getShikiTheme(isDark) in src/lib/shikiTheme.ts ignores the preset entirely, so every Shiki-rendered surface — fenced markdown code, bash tool commands, diffs, file previews — looks like GitHub even when the user has picked a different UI theme.

Solution

Make the Shiki theme user-configurable in Appearance settings, with light and dark picked independently (each switches with the resolved color mode). Defaults preserve current behavior (GitHub Light/Dark Default), so existing users see no change.

Highlights

  • All 65 Shiki bundled themes available. Uses bundledThemesInfo from shiki/themes directly as the canonical catalog — no maintenance burden when Shiki adds new themes.
  • Per-theme lazy loading. Worker uses each entry's static import('@shikijs/themes/<id>') literal, so Vite generates one ~14–46 KB chunk per theme and only fetches on first use. Init preloads just the user's current 2 choices; total init cost unchanged for default users.
  • Single point of resolution. All Shiki surfaces (markdown fenced code, BashRenderer, DiffViewer, CodeMirrorReadonly, CodeBlock, CodePreview, AttachmentDetailModal) go through useSyntaxHighlight, so changing the setting updates every code-bearing surface automatically.
  • Minimal re-render footprint. New useCodeBlockThemes() hook subscribes to a derived string key, so code blocks only re-render when the theme IDs actually change — not on every appearance tweak.

Implementation

  • themeStore: new codeBlockThemeLight / codeBlockThemeDark fields with localStorage persistence + export/import backup.
  • src/lib/codeBlockThemes.ts: catalog wrapping bundledThemesInfo, with normalizeCodeBlockTheme fallback.
  • src/workers/shikiWorker.ts: theme map from bundledThemesInfo, new ensureTheme(instance, themeId) for lazy loading.
  • src/lib/shikiWorkerClient.ts: init reads localStorage to preload user's choices (was hardcoded GitHub).
  • src/lib/shikiTheme.ts: new getShikiTheme(isDark, light, dark) signature, falls back to GitHub on invalid id.
  • src/hooks/useSyntaxHighlight.ts: 3 call sites pass user's configured themes; new useCodeBlockThemes() subscription.
  • src/features/settings/components/CodeBlockThemeSettings.tsx: new Appearance section with two grouped <select> dropdowns (themes grouped by light/dark, but cross-picking allowed) + live Shiki-rendered preview.

Backward compatibility

  • Default themes: github-light-default / github-dark-default → identical to current behavior
  • No migration needed — missing localStorage keys fall back to defaults
  • Backup import normalizes unknown/missing theme IDs to defaults

Verification

  • tsc --noEmit: clean
  • vitest: 525/525 passing (added 2 cases for getShikiTheme)
  • npm run build: success — per-theme chunks emitted as separate files (e.g. one-dark-pro-*.js 33KB, dracula-*.js 21KB, github-dark-default-*.js 14KB)

@lehhair

lehhair commented Jul 19, 2026

Copy link
Copy Markdown
Owner

我之前改了用户信息,导致后面的 commit hash 都变了,所以你当前的 PR 分支需要基于新的历史重新整理一下。麻烦先备份原分支,然后从最新的 main(如果 PR 目标是 dev 就用 dev)新建分支,把本次 PR 的提交按顺序 cherry-pick 过去,最后强推回原来的 PR 分支:

git fetch upstream --prune --force
git switch <PR分支名>
git branch backup/<PR分支名>-before-rewrite

git switch -c <PR分支名>-rebuilt upstream/main
git cherry-pick <本次PR的commit1> <commit2> ...

git branch -f <PR分支名> HEAD
git switch <PR分支名>
git push --force-with-lease origin <PR分支名>

强推原分支后,现有 PR 会自动更新,不需要关闭或重新提交。不要直接执行普通 git pull 或把新旧历史合并到一起,否则会把旧历史重新带回来。

感谢,麻烦了,之前用的是别的设备写的代码,导致用户信息填错了

Code block syntax highlighting was previously hardcoded to GitHub
Dark/Light Default regardless of the active UI theme (src/lib/shikiTheme.ts
returned github-*-default for any isDark). This made theme switching feel
half-done — UI chrome changed but every code block still looked like GitHub.

Now the user can pick any of Shiki's 65 bundled themes for each side
(light + dark) in Appearance settings. Defaults preserve existing behavior
(GitHub Light/Dark Default).

Implementation:
- themeStore: new codeBlockThemeLight / codeBlockThemeDark fields with
  localStorage persistence + export/import backup.
- src/lib/codeBlockThemes.ts: wraps bundledThemesInfo from shiki/themes
  for the dropdown, plus normalizeCodeBlockTheme fallback.
- shikiWorker: theme map comes from bundledThemesInfo (each entry's
  import field is a literal '@shikijs/themes/<id>' static import, so Vite
  generates one lazy chunk per theme). ensureTheme() lazy-loads on
  first highlight request for an unknown theme; init preloads the user's
  current 2 choices.
- shikiWorkerClient.ensureShikiWorkerReady: reads localStorage to preload
  user's configured themes instead of hardcoded GitHub.
- shikiTheme.getShikiTheme: new signature (isDark, light, dark) — picks
  by resolved mode, falls back to GitHub on invalid id.
- useSyntaxHighlight: 3 call sites pass user's configured themes via a
  new useCodeBlockThemes() subscription (only re-renders when the IDs
  actually change — other appearance tweaks don't trigger code-block
  re-render).
- CodeBlockThemeSettings.tsx: new Appearance section with two grouped
  <select> dropdowns + live Shiki-rendered preview.

Per-theme chunks (one-dark-pro 33KB, dracula 21KB, github-dark-default
14KB, etc.) lazy-load on first use. Total init cost unchanged when user
keeps GitHub defaults.
@SsparKluo
SsparKluo force-pushed the feat/code-block-theme-settings branch from 04f96c5 to 40bb61a Compare July 19, 2026 12:42
@SsparKluo

Copy link
Copy Markdown
Contributor Author

已基于最新的 upstream/main (00a7ccc4) 重建分支并 force-push,PR 已自动更新。

操作记录:

  • 备份:backup/feat-code-block-theme-settings-before-rewrite
  • upstream/main 新建临时分支,cherry-pick 了原 PR 的两个 commit(feat + 清理 TODO.md)
  • --force-with-lease 推回 feat/code-block-theme-settings

GitHub 这边显示 mergeable: MERGEABLE,typecheck / 525 tests / build 都过。感谢指出。

@lehhair
lehhair merged commit ca76dfa into lehhair:main Jul 19, 2026
1 check passed
SsparKluo added a commit to SsparKluo/OpenCodeUI that referenced this pull request Jul 20, 2026
Upstream (v0.6.33 → v0.6.34) merged in:
- PR lehhair#142 (feat/code-block): user-configurable Shiki theme (originally our PR)
- Settings dialog search + navigation + config editor drilldown
- Sidebar UI tweaks (control interactions, folder drag-sort fix)
- Settings: align CSS override toolbar
- Markdown: 思考折叠时渲染单行预览
- Settings: default chat-related values
- Various settings dialog component refactors

Conflict resolution:
- CodeBlockThemeSettings.tsx, themeStore.ts, locales, code-block-related
  code: take upstream — the merged PR lehhair#142 is the canonical implementation
- ServersSettings.tsx: upstream rewrite + re-apply fork 'canDeleteDefault'
  (allow deleting default server on non-Tauri when others exist)
- AppearanceSettings.tsx: take upstream import additions
  (DropdownMenu, MenuItem, SettingField, etc.)
- SidePanel.tsx: drop upstream project selector dropdown + search input
  (fork customization: single 'add workspace' button next to New Chat)
  — also drop now-unused refs/state (projectsExpanded, searchInputRef, ...)
- SidebarFooter.tsx: keep fork's slim cog+dot design — context stats stay
  in InputToolbar via ContextUsageButton (fork customization)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants