Skip to content

Add 15 themes: neon, editorial, retro, atmosphere, and accessibility families - #459

Merged
parsakhaz merged 16 commits into
mainfrom
themes-batch-15
Aug 18, 2026
Merged

Add 15 themes: neon, editorial, retro, atmosphere, and accessibility families#459
parsakhaz merged 16 commits into
mainfrom
themes-batch-15

Conversation

@parsakhaz

@parsakhaz parsakhaz commented Aug 18, 2026

Copy link
Copy Markdown
Member

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 the themes-a11y branch (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:contrast 15/15 ✓ · Playwright 61 pass, 1 pre-existing failure also on main (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:

  • Home dropdown order changes. Both theme pickers now read one list (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.
  • Existing themes' Logs panels look different. Logs use the theme's terminal palette instead of a hard-coded GitHub/Tailwind ANSI set, so every theme's logs now match its own terminal.
  • Light-based themes get light code surfaces: isLightTheme() replaces the hard-coded light/light-rounded checks in the diff viewer, Monaco, logs and logo.
  • Theme classes are stamped in a layout effect, so terminal and logs no longer read the previous theme's tokens on a switch.
  • Tooling: pnpm theme:contrast (gate, all 15) and pnpm theme:screenshots (regenerates screenshots/themes/batch/).

Calls I made that you may want to overrule

The 15 themes

Swatches are --color-bg-primary · --color-surface-primary · --color-text-primary · --color-interactive-primary · --color-terminal-bg, resolved from colors.css. Screenshots for each are in the Gallery below.

id name base family source bg surface text accent terminal bg screenshot
synthwave Synthwave dark Neon #451 #120b1f #1a1029 #f1eafc #ff5fd7 #0b0714 screenshots/themes/batch/synthwave.png
acid Acid Terminal dark Neon #451 #111311 #171a17 #eaf2ea #b4ff3a #0a0b0a screenshots/themes/batch/acid.png
tokyo-rain Tokyo Rain dark Neon #451 #0d1120 #131a2c #e6ebf4 #ff4f9a #080b16 screenshots/themes/batch/tokyo-rain.png
folio Folio light Editorial #453 #f6f0e4 #fbf7ee #1f1b17 #b3321c #fbf7ee screenshots/themes/batch/folio.png
newsprint Newsprint light Editorial #453 #f1f2f0 #f8f8f6 #24272b #1f4e8c #f8f8f6 screenshots/themes/batch/newsprint.png
walnut Walnut dark Editorial #453 #1a1512 #221c18 #ede1cd #c9a24d #120e0b screenshots/themes/batch/walnut.png
amber-crt Amber CRT dark Retro #454 #140f08 #1b140a #f0d9a8 #ffb000 #0c0905 screenshots/themes/batch/amber-crt.png
teletype Teletype light Retro #454 #f5efe2 #fbf8f1 #2b2116 #b5451b #f3ecdc screenshots/themes/batch/teletype.png
dot-matrix Dot Matrix dark Retro #454 #10150c #161d10 #dcecb0 #8bac0f #0a0d08 screenshots/themes/batch/dot-matrix.png
haar Haar light Atmosphere #456 #eceff3 #f5f7f9 #252d36 #4f6d8a #e6ebef screenshots/themes/batch/haar.png
abyss Abyss dark Atmosphere #456 #07111c #0b1826 #dbeaf1 #4db8ab #04090f screenshots/themes/batch/abyss.png
understory Understory dark Atmosphere #456 #131911 #192117 #e4e9d9 #a6b56d #0d110c screenshots/themes/batch/understory.png
colorblind-safe Colorblind Safe dark Accessibility themes-a11y #12161b #181d23 #e7ebf0 #0072b2 #0b0e12 screenshots/themes/batch/colorblind-safe.png
low-fatigue Low Fatigue dark Accessibility themes-a11y #27221d #2e2923 #e6dcc7 #d1a54a #1f1b17 screenshots/themes/batch/low-fatigue.png
high-legibility High Legibility light Accessibility themes-a11y #ffffff #ffffff #111111 #1e40af #ffffff screenshots/themes/batch/high-legibility.png

No id collisions across the five branches, so no theme was renamed. All 27 ids (12 existing + 15 new) are registered identically in the Theme union, THEME_CLASSES, both index.html bootstrap scripts, AppConfig.theme in both config.ts files, THEME_OPTIONS, colors.css (theme block + high-contrast block each) and the contrast script — checked by script during consolidation, and pinned by themeClasses.test.ts (bootstrap ↔ THEME_CLASSES) and the Record<Theme, …> type on THEME_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).

Neonsynthwave · acid · tokyo-rain

synthwave acid tokyo-rain

Editorialfolio · newsprint · walnut

folio newsprint walnut

Retroamber-crt · teletype · dot-matrix

amber-crt teletype dot-matrix

Atmospherehaar · abyss · understory

haar abyss understory

Accessibilitycolorblind-safe · low-fatigue · high-legibility

colorblind-safe low-fatigue high-legibility

High contrast on top of Folio (light) and Synthwave (dark) — the spec also asserts, for all 15, that --color-text-muted changes when .high-contrast is applied:

folio + high-contrast synthwave + high-contrast
folio high contrast synthwave high contrast

Colorblind Safe under simulated protanopia · deuteranopia · tritanopia (Machado 2009 matrices, same as the contrast script):

protanopia deuteranopia tritanopia

Appearance picker — left: as the user sees it (the popover scrolls; SelectContent caps at 24rem). Right: the height cap lifted for the capture only so all six groups are in one frame.

as shipped full list (capture-only override)
appearance picker appearance picker, full list

All files live in screenshots/themes/batch/; regenerate with PLAYWRIGHT_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:

profile themes text painted UI terminal ANSI status on bg hairline pairs¹ CVD
standard neon, editorial, retro, abyss, understory ≥ 4.5 ≥ 3 ≥ 4.5 ≥ 4.5 report-only
standard, atmosphere bar haar ≥ 4.5 ≥ 3 ≥ 3 ≥ 3 report-only
strict colorblind-safe, low-fatigue ≥ 4.5 ≥ 3 ≥ 4.5 ≥ 4.5 gated colorblind-safe
strict, AAA high-legibility ≥ 7 ≥ 3 ≥ 4.5 ≥ 7 gated

¹ 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 -- --all shows the report-only rows). Under the strict bar Haar's six bright ANSI colours sit at 3.4–4.1:1 and status-neutral at 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-contrast variants pass everywhere; the muted family reaches ≥ 7:1 under the toggle for all 15.

Picker internals

THEME_OPTIONS (from #456, plus a family field) 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 a Standard / Neon / … label (SelectGroup + the already-defined SelectLabel, 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 via aria-describedby (from themes-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:

shared change kept why
THEME_CLASSES + isLightTheme() in themeContextValue.ts; ThemeProvider derives VALID_THEMES / class removal from it #453's shape (identical to #454, themes-a11y) one source of truth; index 0 is the light/dark base
ThemeProvider stamps classes in useLayoutEffect #453 terminal / logs no longer see the previous theme's classes on switch
isLightTheme() replacing the hard-coded light/light-rounded checks in DiffViewer, FileEditor, LogsView, usePaneLogo #453 / #454 / #456 / themes-a11y (same change) needed by every light-based theme here (Folio, Newsprint, Teletype, Haar, High Legibility)
LogsView ANSI palette #453 (reads the theme's --color-terminal-* via getTerminalTheme()) superset of the isLightTheme variant; log colours match the terminal in every theme (existing themes' logs change to their own terminal palettes too)
SelectItem description prop #453's props interface + column layout, plus themes-a11y's aria-describedby and wrapping one implementation, best of each
Picker source #456's THEME_OPTIONS (+ family) replaces four hand-maintained lists; missing id = typecheck error
Contrast gate 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 15 see Contrast above; #454's and #456's scripts (pnpm theme:contrast / pnpm check:theme-contrast) dropped
Screenshot spec tests/theme-screenshots.spec.ts (pnpm theme:screenshots, playwright.themes.config.ts) themes-a11y's, generalised to all 15 runs under plain pnpm test with PNGs redirected to the test output dir; only the themes config writes into screenshots/themes/batch/
frontend/src/styles/themeContrast.test.ts (vitest) #453 still runs; now covers 27 themes' muted text (AA, and AAA under high contrast)
index.htmlTHEME_CLASSES sync test #453's themeClasses.test.ts; themes-a11y's themeBootstrap.test.ts dropped same coverage, one extra assertion
Per-branch screenshots/themes/*.png removed; screenshots/themes/batch/ replaces them one harness, one view, regenerable
Home dropdown width lg (#456) over md (themes-a11y) / sm (main) descriptions
AGENTS.md, scripts/README.md one Themes line / one section reflect the kept commands

Not touched: existing themes' tokens, no new dependencies. monaco-overrides.css still isn't imported anywhere (as #454 / #456 noted); the live Monaco overrides are the copies in index.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 test 245/245 ✓ · main vitest 527/531 — the 4 failures are better-sqlite3-multiple-ciphers compiled for Electron rather than the local Node (npm rebuild better-sqlite3-multiple-ciphers fixes it; this PR's only main/ change is the theme union in config.ts).
  • pnpm theme:contrast — 15/15 gated themes pass.
  • Playwright (Chromium against the Vite dev server, mocked electron API): tests/theme-screenshots.spec.ts 31/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 clean main checkout (Remote Access host-setup flow, unrelated — the same failure every source PR reported).
  • Regenerate captures: PLAYWRIGHT_PORT=<free port> pnpm theme:screenshots.

Manual testing

  • Settings → Appearance: the six groups render with labels; picking any of the 15 recolours sidebar, tab bar, editor, diff, terminal, logs and modals (no dark panels inside Folio / Newsprint / Teletype / Haar / High Legibility).
  • Home page theme dropdown lists the same 27 in the same order and switches themes.
  • Toggle High contrast on a light and a dark batch theme — muted text steps up without a hue shift.
  • Relaunch on a batch theme — no light-rounded flash (bootstrap lists are in sync).
  • Switch theme with a Logs panel open — log ANSI colours change immediately.
  • Run a Claude/Codex session under Colorblind Safe and Amber CRT and read dim (SGR 2) output.

Follow-ups (not in this PR)

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).

  • Environment: Chromium via Playwright against the Vite dev renderer with the mocked Electron API (tests/electronApiMock.ts) — the same harness the five source PRs used; not the packaged Electron shell. Fixture session themes-batch-15 (mocked repo pane, two sessions, Review diff on themeContextValue.ts, terminal scrollback with the 16-colour ANSI swatch).
  • Journeys driven (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-muted differs from the theme's own value (captured for folio, 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">).
  • Also run: 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 on main.
  • Gate: pnpm theme:contrast 15/15; verbatim check of every theme's colors.css / effects.css / spacing.css / index.css blocks against its source branch — identical.
  • Harness artefacts to know about: the "full list" picker capture lifts 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.

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).
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@github-actions

Copy link
Copy Markdown

React Doctor found 2 new issues in 1 file · 2 warnings · score 70 / 100 (Needs work) · 9 fixed · vs main

2 warnings

src/components/panels/editor/FileEditor.tsx

  • ⚠️ L64 Many related useState calls prefer-useReducer
  • ⚠️ L1194 Many related useState calls prefer-useReducer

Reviewed by React Doctor for commit 2b97115. See inline comments for fixes.

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.

1 participant