feat(web): add Shiki/Mermaid theme preference#653
Conversation
Two new appearance settings (light + dark) drive Shiki theme selection across the highlighter composable, Monaco file viewer, and markstream-vue markdown renderers, defaulting to github-light / github-dark. The appearance code preview no longer hard-codes its theme.
New Appearance > Diagrams row picks one of mermaid's bundled themes (auto /
default / dark / forest / neutral) and persists to mermaid-theme. A themed
wrapper around MarkdownRender's mermaid slot injects an inline init directive
(skipped when the source already opens with %%{) so the choice carries through
both worker parsing and rendering. Auto keeps following the interface isDark.
Appearance reorganization: - Mermaid Theme row now embeds a small flowchart preview so the chosen theme is visible immediately. A reactive key remounts the SVG when the theme (or interface dark) changes. - Shiki Light/Dark theme pickers move into Typography next to the code font controls, and the syntax-highlighting code preview drops to the bottom of the section so all code-styling rows precede their preview.
The diagram already renders with its own outline; the surrounding card chrome (border + bg-surface-editor) was redundant and made the row read like a mocked tile rather than a live preview.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a3fa0d53a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const activeThemes = () => ({ | ||
| light: settings.shikiThemeLight as BundledTheme, | ||
| dark: settings.shikiThemeDark as BundledTheme, | ||
| }) |
There was a problem hiding this comment.
Re-highlight mounted code blocks after theme changes
These settings are only read when highlight()/highlightLang() is called, but existing consumers such as pages/home/components/code-block.vue watch only the code, language, and filename. If a user changes Settings → Appearance while a chat code fence or tool diff is already mounted, the HTML keeps the old Shiki colors until the message remounts or the code changes, so the new theme preference is not applied to the main code-block surfaces.
Useful? React with 👍 / 👎.
| auto: 'Auto', | ||
| default: 'Default', | ||
| dark: 'Dark', | ||
| forest: 'Forest', | ||
| neutral: 'Neutral', |
There was a problem hiding this comment.
Use i18n for Mermaid theme labels
These labels are rendered directly in the Mermaid Theme dropdown, so zh/ja users see the new setting values in English despite locale files being updated for the rest of this section. apps/web/AGENTS.md requires all user-facing strings to use i18n keys, so move these labels into the locale files before exposing the setting.
Useful? React with 👍 / 👎.
| const content = props.node?.content ?? '' | ||
| const next = applyMermaidThemeToSource(content, settings.mermaidTheme) | ||
| if (next === content) return props.node | ||
| return { ...props.node, content: next } |
There was a problem hiding this comment.
Write the Mermaid theme into node.code
The Mermaid renderer receives the same CodeBlockNode shape as other fenced blocks, whose source is carried on node.code/node.raw (the existing chat code block reads those fields), but this wrapper reads and writes only a new content field. When a user selects forest or neutral, the injected init directive is ignored by MermaidBlockNode, so the new Mermaid theme preference does not actually change those diagrams.
Useful? React with 👍 / 👎.
| :typewriter="false" | ||
| :fade="false" | ||
| :code-block-monaco-options="codeBlockMonacoOptions" | ||
| :theme="codeBlockTheme" |
There was a problem hiding this comment.
Forward Shiki themes through code-block props
For MarkdownRender's built-in code block renderer, the unified theme option is forwarded through code-block-props; passing it as a top-level theme prop here is ignored. In file previews and release notes that use the default Markstream code block, selecting a custom Shiki theme therefore leaves code blocks on the library default theme, so wrap this as :code-block-props="{ theme: codeBlockTheme }" or use the dedicated code-block theme props.
Useful? React with 👍 / 👎.
Hide every toolbar control on the preview's mermaid block (header, mode toggle, copy/export/fullscreen/collapse, zoom, interactions) and flatten the container chrome via scoped overrides on .mermaid-block-container and .mermaid-preview-area so only the bare diagram remains.
Two adjustments to the appearance page: - Move the Diagrams (mermaid) section below Typography, since it's a child concern of code styling. - Replace the stacked Light/Dark Shiki theme rows + single shared preview with a two-column grid: each picker sits directly above a preview rendered in its own theme. Each preview keeps the theme's background and reads line numbers from the inline pre color (currentColor / 0.45) so they stay legible on any theme, independent of the interface mode. The isDark watcher on the code preview is dropped — both previews are now bound to their own picked theme, not the interface mode.
The 2-column preview block previously used the UI's --color-border for the wrapper outline, so flipping the interface theme nudged both preview chrome even though the picked Shiki themes hadn't changed. Replace the mode-aware wrapper border with an inset box-shadow on the <pre> derived from currentColor (mix at 18%) so the outline rides on the theme's own text color and stays identical across light/dark interface flips. Also surface a small "Code highlight" heading + description above the grid so the block aligns with how the other settings rows announce their purpose.
packages/ui ships a layered \`.dark .shiki, .dark .shiki span { color:
var(--shiki-dark) !important }\` rule. With single-theme rendering the
override blasted past every inline token color in dark interface mode,
making the previews unreadable.
Render each preview in dual-theme mode with BOTH halves set to the same
picked theme. Shiki then writes \`--shiki-dark\` equal to the picked
colors, so the !important rule resolves back to the inline values and the
override is a visual no-op. Each preview now stays true to its picked
theme regardless of interface mode.
Also drop the chatty interface-mode caveat from the Code-highlight
description — it's our boundary to handle, not something to push onto
the user.
Summary
在 appearance settings: