Add 15 themes: neon, editorial, retro, atmosphere, and accessibility families - #459
Merged
Conversation
Three vivid dark themes with full token coverage (parity with terracotta plus terminal selection), high-contrast blocks, effects/spacing tokens, shell selectors, Monaco vars, and picker entries with descriptions. Every text-bearing pair clears WCAG AA; neon primaries carry dark text. Screenshots under screenshots/themes/.
…gibility Three new themes designed around specific human needs: - colorblind-safe (dark): every semantic color — status, diff add/remove, terminal ANSI — comes from the Okabe-Ito palette so it stays distinguishable under protanopia, deuteranopia and tritanopia. ANSI green/blue/cyan are same-hue lightness shifts chosen by search to maximise the worst-case simulated ΔE across the six-colour set. - low-fatigue (warm sepia-dark): mid-dark backgrounds, parchment text, honey-gold accent, desaturated low-blue palette, no pure white anywhere. - high-legibility (light): 7:1+ body text, 3:1+ borders/focus rings, color only for state (one blue for links/focus/selection, dark AAA status tones). Each theme overrides the full token set (chrome, shells, buttons, inputs, navigation, scrollbars, terminal palette, Monaco, git-diff-view tints) and gets a high-contrast block so the additive toggle still works. Supporting changes: - scripts/check-theme-contrast.mjs (pnpm theme:contrast): parses colors.css, gates WCAG contrast for text/UI/terminal pairs per theme, and simulates CVD (Machado 2009) for status/diff/ANSI sets. Only the new themes are gated; other themes are report-only. - tests/theme-screenshots.spec.ts (pnpm theme:screenshots): writes screenshots/themes/*.png including CVD-simulated captures. - isLightTheme() helper replaces four hardcoded light-id comparisons (DiffViewer, FileEditor, LogsView, usePaneLogo) so a third light theme keeps editors on their light palettes. - ThemeProvider derives VALID_THEMES / class list from THEME_CLASSES; index.html bootstrap, config types, pickers and the a11y spec regex learn the new ids. SelectItem gains an optional description line.
Three print-and-paper themes: Folio (cream paper & ink, light), Newsprint (cool grey stock, graphite, ink-blue accent, light) and Walnut (warm brown-black, parchment, brass accent, dark). Full token coverage incl. high-contrast blocks, terminal palettes and Monaco vars. - Export THEME_CLASSES and derive isLightTheme from it; DiffViewer, FileEditor and usePaneLogo no longer hardcode the two light ids - LogsView reads the theme's terminal palette instead of a fixed one - ThemeProvider stamps classes in a layout effect so descendants' effects read fresh tokens - SelectItem gains an optional description line; pickers describe the three new themes - Add vitest contrast gate (themeContrast.test.ts), index.html sync test and a Playwright screenshot spec; commit screenshots/themes/*.png
Three retro-computing themes with full token coverage: amber-crt (dark, amber-phosphor on black-brown glass), teletype (light, brown ink on warm paper with burnt-orange/olive accents) and dot-matrix (dark, 4-step LCD green with one hot accent for errors). Each gets colors/effects/spacing tokens, shell chrome, monaco and terminal palettes, high-contrast blocks, and picker entries with one-line descriptions. Light/dark detection for Monaco, the diff view, log rendering and the logo now derives from the theme's base class (isLightTheme) instead of comparing against the two original light ids, so teletype renders as a light theme. Adds scripts/check-theme-contrast.mjs (pnpm theme:contrast) as a WCAG gate over the token pairs, an opt-in Playwright spec (pnpm theme:screenshots) that regenerates screenshots/themes/*.png, and the resulting evidence captures.
- Fail the gate on unresolved tokens instead of silently skipping them, and composite navigation pairs over the navigation surface rather than bg-primary. - Drop the parity edits to monaco-overrides.css (nothing imports it; the live overrides are in index.css). - Use a real hunk from this change in the screenshot fixture, fix the teletype muted-text annotation, and refresh the stale THEME_CLASSES pointer.
Three low-saturation themes drawn from natural light: Haar (pre-dawn sea fog, light base), Abyss (deep ocean, dark), Understory (moss and bark, dark). Full token coverage in colors.css plus effects/spacing/index.css shells and Monaco overrides, high-contrast blocks, and registration in every theme list (provider, bootstrap scripts, config types). Also: a shared THEME_OPTIONS list with picker descriptions feeding both theme pickers, an isLightTheme helper replacing hard-coded light checks, scripts/check-theme-contrast.mjs (pnpm check:theme-contrast), an opt-in Playwright screenshot generator, and screenshots/themes/ evidence.
…evidence spec - colorblind-safe diff row tints raised to 26%/40%/52% mixes so add vs remove keeps ΔE ≥ 15 under protanopia (was 8.5 at 14%); diff tints now live in colors.css as --color-diff-* tokens for all three themes and index.css references them, so pnpm theme:contrast measures the values users see. - --color-focus-ring-subtle (the ring components actually paint) raised to 0.85 alpha / opaque in the three themes and added to the UI 3:1 checks. - low-fatigue high-contrast muted → #bbab93 (7.0:1); the gate now requires the muted family at 7:1 whenever high-contrast is applied. - SelectItem keeps its row layout for description-less items and exposes the description via aria-describedby; description wraps at 16rem. - THEME_CLASSES moved to themeContextValue.ts; new vitest asserts both index.html bootstrap maps match it. - theme-screenshots spec: only writes into screenshots/themes when run via playwright.themes.config.ts (pnpm theme:screenshots); asserts the high-contrast block changes the muted token; terminal fixture no longer imitates a test run; ESC written as \u001b. - check-theme-contrast: --all is report-only, unknown theme ids exit 2 with a message, disabled row excluded from pass counts, comments corrected.
- One contrast gate (scripts/check-theme-contrast.mjs, pnpm theme:contrast) covering all 15 batch themes with per-family profiles; the original twelve stay report-only. - One screenshot spec (tests/theme-screenshots.spec.ts, pnpm theme:screenshots) capturing the same view for every theme into screenshots/themes/batch/, plus high-contrast, CVD-simulated and grouped-picker captures; per-branch PNGs removed. - THEME_OPTIONS orders the picker: original themes, then the batch by family, with family group labels in the Appearance Select. - Drop the duplicate index.html bootstrap test (themeClasses.test.ts covers it).
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
React Doctor found 2 new issues in 1 file · 2 warnings · score 70 / 100 (Needs work) · 9 fixed · vs 2 warnings
Reviewed by React Doctor for commit |
This was referenced Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
One PR that folds the five parallel theme branches into
main: 15 new themes in five families, every palette exactly as its source branch shipped it. Supersedes #451, #453, #454, #456 and thethemes-a11ybranch (no PR) — I have not closed them; that's your call.Try it: check out the branch,
pnpm dev, Settings → Appearance → Theme (or the Theme dropdown on the Home page).Lane: light: consolidate → prepare-pr → pr-test-automation
Status:
pnpm typecheck✓ ·pnpm lint✓ · frontend vitest 245/245 ✓ ·pnpm theme:contrast15/15 ✓ · Playwright 61 pass, 1 pre-existing failure also onmain(details in Verification). Existing themes' tokens are untouched. No new dependencies.What changes for people already using Pane
The five branches each carried a copy of the same scaffolding; one copy of each was kept (details in Consolidation decisions at the end). Net effect on the app, most visible first:
frontend/src/utils/themeOptions.ts): original twelve first, then the 15 grouped by family. The Appearance Select gets family group labels; the Home dropdown is flat in the same order (it used to list Light (sharp)/Dark/OLED last). Every theme, including the original twelve, now has a one-line description.isLightTheme()replaces the hard-codedlight/light-roundedchecks in the diff viewer, Monaco, logs and logo.pnpm theme:contrast(gate, all 15) andpnpm theme:screenshots(regeneratesscreenshots/themes/batch/).Calls I made that you may want to overrule
status-neutralat 4.3:1: inside Add Haar, Abyss and Understory atmosphere themes #456's 3:1 UI gate, below the 4.5 the other dark/light families chose. Not repainted (see Contrast).screenshots/themes/*.pngremoved;screenshots/themes/batch/replaces them.pnpm theme:contrast/pnpm check:theme-contrast) in favour ofthemes-a11y's script, extended to all 15 — that is whatpnpm theme:contrastruns now.themes-a11y'sthemeBootstrap.test.tsdropped in favour of Add Folio, Newsprint and Walnut editorial themes #453'sthemeClasses.test.ts(same coverage, one extra assertion).lg(Add Haar, Abyss and Understory atmosphere themes #456) overmd(themes-a11y) /sm(main), to fit descriptions.The 15 themes
Swatches are
--color-bg-primary·--color-surface-primary·--color-text-primary·--color-interactive-primary·--color-terminal-bg, resolved fromcolors.css. Screenshots for each are in the Gallery below.synthwave#120b1f#1a1029#f1eafc#ff5fd7#0b0714screenshots/themes/batch/synthwave.pngacid#111311#171a17#eaf2ea#b4ff3a#0a0b0ascreenshots/themes/batch/acid.pngtokyo-rain#0d1120#131a2c#e6ebf4#ff4f9a#080b16screenshots/themes/batch/tokyo-rain.pngfolio#f6f0e4#fbf7ee#1f1b17#b3321c#fbf7eescreenshots/themes/batch/folio.pngnewsprint#f1f2f0#f8f8f6#24272b#1f4e8c#f8f8f6screenshots/themes/batch/newsprint.pngwalnut#1a1512#221c18#ede1cd#c9a24d#120e0bscreenshots/themes/batch/walnut.pngamber-crt#140f08#1b140a#f0d9a8#ffb000#0c0905screenshots/themes/batch/amber-crt.pngteletype#f5efe2#fbf8f1#2b2116#b5451b#f3ecdcscreenshots/themes/batch/teletype.pngdot-matrix#10150c#161d10#dcecb0#8bac0f#0a0d08screenshots/themes/batch/dot-matrix.pnghaar#eceff3#f5f7f9#252d36#4f6d8a#e6ebefscreenshots/themes/batch/haar.pngabyss#07111c#0b1826#dbeaf1#4db8ab#04090fscreenshots/themes/batch/abyss.pngunderstory#131911#192117#e4e9d9#a6b56d#0d110cscreenshots/themes/batch/understory.pngcolorblind-safethemes-a11y#12161b#181d23#e7ebf0#0072b2#0b0e12screenshots/themes/batch/colorblind-safe.pnglow-fatiguethemes-a11y#27221d#2e2923#e6dcc7#d1a54a#1f1b17screenshots/themes/batch/low-fatigue.pnghigh-legibilitythemes-a11y#ffffff#ffffff#111111#1e40af#ffffffscreenshots/themes/batch/high-legibility.pngNo id collisions across the five branches, so no theme was renamed. All 27 ids (12 existing + 15 new) are registered identically in the
Themeunion,THEME_CLASSES, bothindex.htmlbootstrap scripts,AppConfig.themein bothconfig.tsfiles,THEME_OPTIONS,colors.css(theme block +high-contrastblock each) and the contrast script — checked by script during consolidation, and pinned bythemeClasses.test.ts(bootstrap ↔THEME_CLASSES) and theRecord<Theme, …>type onTHEME_OPTIONS.Gallery
Same view for every theme: sidebar, a session with the Review (diff) panel open, terminal dock printing the 16 ANSI colours (1440×900, mocked session via the Playwright harness; images pinned to commit
2b97115).Neon —
synthwave·acid·tokyo-rainEditorial —
folio·newsprint·walnutRetro —
amber-crt·teletype·dot-matrixAtmosphere —
haar·abyss·understoryAccessibility —
colorblind-safe·low-fatigue·high-legibilityHigh contrast on top of Folio (light) and Synthwave (dark) — the spec also asserts, for all 15, that
--color-text-mutedchanges when.high-contrastis applied:Colorblind Safe under simulated protanopia · deuteranopia · tritanopia (Machado 2009 matrices, same as the contrast script):
Appearance picker — left: as the user sees it (the popover scrolls;
SelectContentcaps at 24rem). Right: the height cap lifted for the capture only so all six groups are in one frame.All files live in
screenshots/themes/batch/; regenerate withPLAYWRIGHT_PORT=<free port> pnpm theme:screenshots.Contrast
pnpm theme:contrast— 15/15 gated themes pass (exit 0). Each family is gated at the bar its source PR promised, so no palette needed a fix:¹ 1px input border, scrollbar thumb, the subtle focus ring, and "on-dark" link text on the page background. Only the accessibility family designed to these; every existing Pane theme fails them too (
pnpm theme:contrast -- --allshows the report-only rows). Under the strict bar Haar's six bright ANSI colours sit at 3.4–4.1:1 andstatus-neutralat 4.3:1 — inside #456's 3:1 UI gate, below the 4.5 the other dark/light families chose; left as designed rather than repainting the theme. Both+ high-contrastvariants pass everywhere; the muted family reaches ≥ 7:1 under the toggle for all 15.Picker internals
THEME_OPTIONS(from #456, plus afamilyfield) drives both pickers: original twelve in their existing order, then Neon, Editorial, Retro, Atmosphere, Accessibility. The Appearance Select renders one Radix group per family with aStandard/Neon/ … label (SelectGroup+ the already-definedSelectLabel, now exported); the Home dropdown has no group concept and stays flat in the same order. Descriptions come from #456 for the original twelve (which closes the "two calls for you" in #453) and from each family's PR for the 15; they wrap inside the popover and are exposed viaaria-describedby(fromthemes-a11y).Consolidation decisions
Merged in order neon → editorial → retro → nature → a11y (
git merge --no-ff), then one commit for the shared tooling. Every conflict resolved as the union; per-theme CSS blocks (colors.css,effects.css,spacing.css,index.css) were verified verbatim against their source branch. Where the five branches each carried their own copy of the same shared change, one was kept:THEME_CLASSES+isLightTheme()inthemeContextValue.ts;ThemeProviderderivesVALID_THEMES/ class removal from itthemes-a11y)ThemeProviderstamps classes inuseLayoutEffectisLightTheme()replacing the hard-codedlight/light-roundedchecks inDiffViewer,FileEditor,LogsView,usePaneLogothemes-a11y(same change)LogsViewANSI palette--color-terminal-*viagetTerminalTheme())isLightThemevariant; log colours match the terminal in every theme (existing themes' logs change to their own terminal palettes too)SelectItemdescriptionpropthemes-a11y'saria-describedbyand wrappingTHEME_OPTIONS(+family)scripts/check-theme-contrast.mjs(pnpm theme:contrast)themes-a11y's script (CSS cascade resolver, alpha compositing, CVD simulation, markdown output), extended to gate all 15pnpm theme:contrast/pnpm check:theme-contrast) droppedtests/theme-screenshots.spec.ts(pnpm theme:screenshots,playwright.themes.config.ts)themes-a11y's, generalised to all 15pnpm testwith PNGs redirected to the test output dir; only the themes config writes intoscreenshots/themes/batch/frontend/src/styles/themeContrast.test.ts(vitest)index.html↔THEME_CLASSESsync testthemeClasses.test.ts;themes-a11y'sthemeBootstrap.test.tsdroppedscreenshots/themes/*.pngscreenshots/themes/batch/replaces themlg(#456) overmd(themes-a11y) /sm(main)AGENTS.md,scripts/README.mdNot touched: existing themes' tokens, no new dependencies.
monaco-overrides.cssstill isn't imported anywhere (as #454 / #456 noted); the live Monaco overrides are the copies inindex.css— the file was updated in step by the branches that touched it and left as-is here.Verification
pnpm typecheck✓ ·pnpm lint✓ (oxlint, eslint, advisory, knip) ·pnpm --filter frontend test245/245 ✓ ·mainvitest 527/531 — the 4 failures arebetter-sqlite3-multiple-cipherscompiled for Electron rather than the local Node (npm rebuild better-sqlite3-multiple-ciphersfixes it; this PR's onlymain/change is thethemeunion inconfig.ts).pnpm theme:contrast— 15/15 gated themes pass.tests/theme-screenshots.spec.ts31/31 (main view + high-contrast composition per theme, CVD, grouped picker),tests/settings.spec.ts,tests/accessibility.spec.ts,tests/dropdown-keyboard-nav.spec.ts— 61 pass; the one failure,settings.spec.ts:282 discards remote subview drafts and rebaselines a completed host setup, fails identically on a cleanmaincheckout (Remote Access host-setup flow, unrelated — the same failure every source PR reported).PLAYWRIGHT_PORT=<free port> pnpm theme:screenshots.Manual testing
Follow-ups (not in this PR)
SelectContent), so the 27-entry list scrolls; a taller cap or a per-picker override is a one-line UI decision.pnpm theme:contrast -- --all.QA summary (automated first pass)
Status: automated pass complete at head
2b97115; no product defects found. Evidence is committed to the branch (no release assets used).tests/electronApiMock.ts) — the same harness the five source PRs used; not the packaged Electron shell. Fixture sessionthemes-batch-15(mocked repopane, two sessions, Review diff onthemeContextValue.ts, terminal scrollback with the 16-colour ANSI swatch).tests/theme-screenshots.spec.ts, 31/31): for each of the 15 themes — boot on the theme, open the session, open Review, expand the diff, expand the terminal, capture (screenshots/themes/batch/<id>.png); boot each theme with High contrast on and assert--color-text-muteddiffers from the theme's own value (captured forfolio,synthwave); Colorblind Safe under three CVD filters; Settings → Appearance → Theme with all 15 options and 6 group labels asserted visible, captured as shipped and unrolled, then a selection round-trip (High Legibility→<html class="light high-legibility">).tests/settings.spec.ts,tests/accessibility.spec.ts(theme trigger regex covers all 27 names),tests/dropdown-keyboard-nav.spec.ts(Home dropdown order: Light (rounded) → Light (sharp) → Forge; Escape doesn't commit) — 61 pass, 1 pre-existing failure identical onmain.pnpm theme:contrast15/15; verbatim check of every theme'scolors.css/effects.css/spacing.css/index.cssblocks against its source branch — identical.SelectContent's 24rem cap and pins the popover for the frame only; the diff/terminal content is fixture data, not a live agent.Still needs a human (see Manual testing): real Claude/Codex CLI output under the new terminal palettes (dim SGR 2 text), Monaco editor tab under a light batch theme, relaunch flash check in the packaged app, and a look at the Home dropdown on a real window size.