feat(ui): expose Shiki syntax themes#450
Conversation
Greptile SummaryThis PR adds a
Confidence Score: 4/5Safe to merge. The change is well-scoped and thoroughly tested; the main behavior gap is the catch block dropping the custom syntax theme when a language grammar fails. The plumbing from CLI/config through to the Shiki highlighter and both highlight caches is consistent and the Catppuccin default wiring is verified end-to-end by the new pierre.test.ts assertions. The two notes are a fallback path that silently loses the configured syntax theme on language-grammar failures, and a redundant hook dependency that could cause harmless extra effect runs — neither affects correctness in the common case. src/ui/diff/pierre.ts (catch block fallback behavior) and src/ui/diff/useHighlightedDiff.ts / src/ui/diff/useHighlightedSource.ts (redundant theme dependency). Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["CLI --syntax-theme / config syntax_theme"] --> B["resolveConfiguredCliInput\n(syntaxTheme in CommonOptions)"]
B --> C["App.tsx: withSyntaxTheme(baseTheme, syntaxTheme)\n→ activeTheme.syntaxTheme"]
C --> D{syntaxTheme set?}
D -- yes --> E["highlighterThemeName → theme.syntaxTheme\ne.g. 'catppuccin-mocha' / 'dracula'"]
D -- no --> F["highlighterThemeName → pierreThemeName(appearance)\n'pierre-dark' / 'pierre-light'"]
E --> G["prepareHighlighter(language, theme)\ncacheKey: syntaxThemeName:language"]
F --> G
G --> H{language found?}
H -- yes --> I["renderDiffWithHighlighter\nwith Shiki theme colors"]
H -- no --> J["catch: fallbackTheme = appearance string\nprepareHighlighter('text', 'dark'/'light')\n⚠ syntaxTheme dropped"]
I --> K["SHARED_HIGHLIGHTED_DIFF_CACHE\nkey: theme.id:syntaxTheme:file_id:fp"]
J --> K
C --> L["Catppuccin themes\nsyntaxTheme = 'catppuccin-{flavor}'"]
L --> D
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["CLI --syntax-theme / config syntax_theme"] --> B["resolveConfiguredCliInput\n(syntaxTheme in CommonOptions)"]
B --> C["App.tsx: withSyntaxTheme(baseTheme, syntaxTheme)\n→ activeTheme.syntaxTheme"]
C --> D{syntaxTheme set?}
D -- yes --> E["highlighterThemeName → theme.syntaxTheme\ne.g. 'catppuccin-mocha' / 'dracula'"]
D -- no --> F["highlighterThemeName → pierreThemeName(appearance)\n'pierre-dark' / 'pierre-light'"]
E --> G["prepareHighlighter(language, theme)\ncacheKey: syntaxThemeName:language"]
F --> G
G --> H{language found?}
H -- yes --> I["renderDiffWithHighlighter\nwith Shiki theme colors"]
H -- no --> J["catch: fallbackTheme = appearance string\nprepareHighlighter('text', 'dark'/'light')\n⚠ syntaxTheme dropped"]
I --> K["SHARED_HIGHLIGHTED_DIFF_CACHE\nkey: theme.id:syntaxTheme:file_id:fp"]
J --> K
C --> L["Catppuccin themes\nsyntaxTheme = 'catppuccin-{flavor}'"]
L --> D
|
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| }, [appearance, appearanceCacheKey, file, highlightedCacheKey, shouldLoadHighlight]); | ||
| }, [appearanceCacheKey, file, highlightedCacheKey, shouldLoadHighlight, theme]); |
There was a problem hiding this comment.
Redundant
theme dependency in useLayoutEffect
appearanceCacheKey is derived from buildCacheKey(theme, file), which already encodes theme.id, theme.syntaxTheme, and theme.appearance — every property of the theme that drives a different highlight result. Listing theme separately means the effect also re-runs whenever non-highlight fields change (UI colors, border colors, etc.) without affecting the cache key, causing a redundant cache lookup and no-op. The same applies to useHighlightedSource.ts line 71 where theme is listed alongside cacheKey. Removing theme from both dependency arrays would rely solely on the cache key, which is the intended discriminator.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/diff/useHighlightedDiff.ts
Line: 214-217
Comment:
**Redundant `theme` dependency in `useLayoutEffect`**
`appearanceCacheKey` is derived from `buildCacheKey(theme, file)`, which already encodes `theme.id`, `theme.syntaxTheme`, and `theme.appearance` — every property of the theme that drives a different highlight result. Listing `theme` separately means the effect also re-runs whenever non-highlight fields change (UI colors, border colors, etc.) without affecting the cache key, causing a redundant cache lookup and no-op. The same applies to `useHighlightedSource.ts` line 71 where `theme` is listed alongside `cacheKey`. Removing `theme` from both dependency arrays would rely solely on the cache key, which is the intended discriminator.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
ee3a22e to
0fad3b1
Compare
Summary
syntax_themeconfig and--syntax-themeCLI support so UI themes and Shiki syntax themes can be chosen independently.ttheme-cycle shortcut with an opencode-style selector modal for UI themes and bundled Shiki syntax themes (↑/↓, Enter, Esc).Refs #415.
Testing
bun run formatbun run typecheckbun run lintbun test src/ui/AppHost.interactions.test.tsx src/ui/components/ui-components.test.tsx src/ui/lib/ui-lib.test.ts src/ui/diff/pierre.test.ts src/core/config.test.ts src/core/cli.test.ts src/ui/themes.test.tsbun test— 1058 pass, 14 skip, 0 failThis PR description was generated by Pi using OpenAI GPT-5 Codex (2026-06-16)