automatic OS-driven dark mode on every web frontend (authd, buchinese, dashcam #17

Closed
opened 2026-05-13 19:02:10 +00:00 by brendan · 0 comments
Owner

Originally filed: 2026-05-11 in ~/features.md, block #11.
Cross-project companion issues: brendan/authd, brendan/buchinese, brendan/dashcam, brendan/inventory, brendan/movement

2026-05-11 — cross-project: automatic OS-driven dark mode on every web frontend (authd, buchinese, dashcam, inventory, movement, nanodrop) — no in-app toggle, no preference persistence, purely prefers-color-scheme media-query driven

User wants every bchen.dev web app to follow the device's system theme automatically. When the OS (iOS, macOS, Android, Windows, Linux desktop) is in dark mode, the app renders in dark mode; when light, light. No toggle switch anywhere in the UI. No setting, no cookie, no localStorage, no per-account preference — the only input is @media (prefers-color-scheme: dark). If the user changes their system theme, the app updates live (browsers re-evaluate the media query on theme change with no reload needed).

Source request (verbatim from user): "file automatic dark mode as a feature request for every app. no need for a toggle switch anywhere, just have it be automatic based on device theming"

Apps in scope (one PR per app — these ship independently and don't share CSS):

  1. ~/authd/public/style.css + login/account/oauth-consent HTML pages
  2. ~/buchinese/public/style.css + articles list/new/view HTML pages
  3. ~/dashcam/public/style.css (single shared stylesheet, server-rendered via src/views/layout.ts)
  4. ~/inventory/public/style.css + all HTML pages under public/ (login, index, item, items-new, oauth-clients, oauth-consent, api-tokens). Note: inventory already has /icon-color-scheme.js for the PWA home-screen icon swap — that's icon-only and unrelated; the CSS work is still needed.
  5. ~/movement/src/client/styles/main.css + popover.css (and any other stylesheets under src/client/styles/)
  6. ~/nanodrop/public/style.css

Out of scope this round (no web frontend, or N/A): portman, tradebot, fleet (CLI/supervisor), the roles/ docs.

Implementation approach (apply to every app; details may vary by stylesheet):

  1. Declare both schemes to the browser. Add <meta name="color-scheme" content="light dark"> to every served HTML page's <head>. This tells the browser the page supports both modes and lets it pick sensible defaults for form controls, scrollbars, and the initial paint background before CSS loads (prevents the "white flash on dark OS" problem). In server-rendered layouts (dashcam src/views/layout.ts, movement client) add it there once; in static HTML files (authd/buchinese/inventory/nanodrop public/*.html) add to each file. Verify there is no existing <meta name="color-scheme" content="light"> that would block this — if so, change it to light dark.
  2. Refactor each stylesheet to CSS custom properties. Replace hardcoded colors with var(--name) references. Define the light-mode palette under :root { ... } and the dark-mode palette under @media (prefers-color-scheme: dark) { :root { ... } }. Minimum variable set per app (extend as the design needs):
    • --bg (page background)
    • --bg-elevated (cards, panels, modals)
    • --fg (primary text)
    • --fg-muted (secondary text, labels, hints)
    • --border (dividers, input borders)
    • --accent (primary buttons, links)
    • --accent-fg (text on accent backgrounds)
    • --danger (destructive buttons, error text) + --danger-fg
    • --success (where used) + --success-fg
    • Form control background/text/border (--input-bg, --input-fg, --input-border)
  3. Pick the dark palette carefully — don't just invert. True-black (#000) on OLED looks dramatic but causes haloing around text; aim for a soft near-black like #0f1115 or #111827 for --bg and #1a1d23 / #1f2937 for --bg-elevated. Text should be off-white (#e5e7eb / #f3f4f6), not pure white. Accent color often needs a saturation/brightness bump in dark mode to keep contrast — e.g. an accent that's #2563eb on light might become #3b82f6 on dark. Verify WCAG AA contrast (4.5:1 for body text, 3:1 for large text) for every color pair using a tool like https://webaim.org/resources/contrastchecker/ or npx wcag-contrast-check.
  4. Form controls — the input/textarea/select { font-size: 16px } rule from the global CLAUDE.md still applies and is orthogonal to dark mode; do NOT regress it. For dark mode specifically, set --input-bg, --input-fg, --input-border and apply them. Browser-native controls (<input type="date">, <select>) will mostly take care of themselves once color-scheme: light dark is in <meta>, but verify their popups look right on both iOS Safari and desktop Chrome.
  5. Images, icons, illustrations. Where an image has a transparent background and dark foreground (logos, line-art icons), it may disappear on dark mode. Options:
    • Use SVG with currentColor and let CSS color it via color: var(--fg).
    • Provide a <picture><source media="(prefers-color-scheme: dark)" srcset="logo-dark.svg"><img src="logo-light.svg" /></picture> swap.
    • For PWA app icons, inventory's /icon-color-scheme.js already handles the home-screen icon swap on install; mirror that pattern in any other PWA that ships maskable icons (currently just inventory and nanodrop have manifests — check public/manifest.webmanifest).
  6. Code blocks, syntax highlighting, charts. buchinese has prose articles with Chinese text and English translations — make sure both the article body and the inline word-popover are themed. dashcam has clip thumbnails and analyzer output — make sure the analyzer's color-coded labels still have AA contrast on dark. If any app uses a syntax-highlighting library (e.g. highlight.js, shiki) check it ships dual themes or load a dark theme inside the media query.
  7. Charts / data visualization. Currently none of the apps in scope have charts; if one is added later, the chart library should respect the theme variables.
  8. Email templates (authd password-reset, etc.) — out of scope. Email dark-mode support is a separate beast (@media (prefers-color-scheme: dark) inside HTML emails works on Apple Mail and Outlook.com but is unreliable elsewhere). Skip for this round; file as a follow-up if the user wants it.
  9. PDFs, printed output — out of scope; print stylesheets should remain light-on-white.

Anti-patterns to avoid:

  • No <html class="dark"> JS toggle. That's the Tailwind/Next.js default, but the user explicitly said no toggle and no persisted preference. Pure media query only.
  • No localStorage / cookie that overrides the OS preference. The OS is the single source of truth.
  • No "auto / light / dark" three-way switch. Just auto.
  • Do not gate behind a feature flag or user account preference — the rollout is "ship it on the next deploy and it's live for everyone".
  • Do not break the global iOS font-size rule (input,textarea,select { font-size: 16px }) when refactoring CSS — it must still compute to ≥16px on mobile for affected controls.
  • Do not use <meta name="viewport" ... user-scalable=no> to suppress any iOS quirk — same accessibility reasoning as the font-size rule.

Test plan per app:

  • Open every page on macOS / iOS Safari with system theme set to Dark, then to Light. Verify legibility, no white flashes on navigation, no invisible elements.
  • DevTools "Emulate CSS prefers-color-scheme" — toggle both modes on every route.
  • Run an axe-core or Lighthouse accessibility audit; fail if any text drops below AA contrast.
  • For PWAs (inventory, nanodrop): add-to-home-screen on iOS in both themes, verify the splash + icon look right (inventory's /icon-color-scheme.js already covers the icon; verify it still works after the CSS refactor).
  • Movement: the popover overlays (popover.css) need verification — overlays on top of arbitrary page content are a common dark-mode breakage point.
  • buchinese: verify the Chinese-character word-popover (which floats on top of article text) and the per-sentence English translation toggles both render legibly in dark mode.

Acceptance per app PR:

  • npm run build / tsc --noEmit clean.
  • All existing tests still pass; no new tests are required (this is presentation-only) but a Playwright smoke test that loads the index page in both prefers-color-scheme values and asserts getComputedStyle(document.body).backgroundColor differs is a nice-to-have.
  • Manual visual inspection by user on the deployed site in both modes before the next app's PR opens (or implementer ships all six in parallel and the user reviews together — implementer's call based on whether they bundle).
  • No regression in the iOS focus-zoom rule (font-size ≥16px on text inputs).

Sequencing / coordination:

  • Six independent PRs, one per app. No cross-app coordination needed — each app's CSS is self-contained.
  • Suggested order (lowest risk first): nanodrop → dashcam → authd → inventory → buchinese → movement. nanodrop and dashcam have the smallest stylesheets; movement has the most complex client (multiple stylesheets, popovers, client-rendered DOM); buchinese has the most content-rich pages where bad contrast would be most visible.
  • The implementer can pick any one app per tick; this is a long-tail polish feature and doesn't need to ship all six at once.
  • Standard implementer pipeline applies (plan → execute → review → push to a feature branch → auto-merge if all checks pass and tests are green).

Out of scope (file as follow-ups if wanted):

  • A user-facing theme toggle (the user explicitly rejected this).
  • Per-account theme preference persisted in authd or per-app DB.
  • Email-template dark mode.
  • A new "high-contrast" mode beyond standard dark.
  • Themed PWA splash screens beyond what the current manifest.webmanifest declares.
  • Going back and adding dark mode to any historical screenshots / docs (the README screenshots can stay light-mode).

Going-forward expectation: like the iOS focus-zoom rule in ~/.claude/CLAUDE.md, this is a one-off retrofit. After all six apps ship, any new web frontend the user builds should bake in prefers-color-scheme support from day one. If this pattern becomes load-bearing, the reporter or user can promote it to a global rule in ~/.claude/CLAUDE.md or ~/.claude/rules/common/coding-style.md (filing note: same writability caveat as the focus-zoom rule — the rules tree is owned by nobody:nogroup from sandbox sessions, so the rule may have to live in ~/.claude/CLAUDE.md instead).

Source: user via spawn-host chat (2026-05-11). Filed by reporter; spawn-host sessions do not edit project code directly per ~/CLAUDE.md policy.

> **Originally filed:** 2026-05-11 in ~/features.md, block #11. > **Cross-project companion issues:** brendan/authd, brendan/buchinese, brendan/dashcam, brendan/inventory, brendan/movement <!-- TIER-RESOLVED 2026-05-11T22:Z inventory — implementer merged https://gitea.bchen.dev/brendan/inventory/pulls/20 at merge_commit de8647fdb8b501980a8b6224ce6e69c9c1211b3c (REVIEWED_SHA 2aae2da0333d23246917693a74f608ffb0f59b2a = feat 4de8a29 + fix 2026-cycle-2 c2a4556 + refactor 2aae2da). Added `<meta name="color-scheme" content="light dark">` to all 4 served HTML shells (index.html, item.html, items-new.html, login.html); refactored public/style.css to semantic tokens (--bg/--bg-elevated/--fg/--fg-muted/--border-strong/--accent/--accent-fg/--accent-hover/--danger/--danger-fg/--danger-bg/--input-bg/--input-fg/--input-border/--input-bg-focus) with light values byte-equivalent to pre-refactor; appended @media (prefers-color-scheme: dark) { :root { ... } } block using family-pinned dashcam/authd palette (#0f1115 bg, #1a1d23 elevated, #e5e7eb fg, #9ca3af muted, #6b7280 border-strong/input-border, #374151 border-color, #f87171 danger). Extended iOS 16px form-input rule to include `select` per global ~/.claude/CLAUDE.md. Cycle 1 review flagged two regression points where light-only var(--gray-50) leaked into dark mode (.item-list a:hover and .flash[role="status"] would render near-white #fafafa on near-black #0f1115); cycle 2 fix c2a4556 repointed both to var(--bg-elevated) + added regression test scanning CSS minus :root for any var(--gray-*) leak; refactor 2aae2da simplified the guard test to use expect(...).not.toMatch(...) idiom and broadened to --gray-600. 202/202 vitest specs green (was 191; +11 new across style.test.ts and pages.test.ts: meta tag in every shell, dark @media block, dark token values, --gray-* leak guard). npm run build (esbuild client + sw regen + tsc --noEmit) clean. WCAG AA verified for all key dark-mode pairs: --fg #e5e7eb on --bg #0f1115 ≈16:1 (AAA), --fg-muted on --bg ≈6.3:1 (AA), --border-strong/--input-border #6b7280 on --bg ≈3.6:1 (AA UI), --danger #f87171 on --bg ≈5.6:1, --accent-fg on --accent ≈16:1. public/icon-color-scheme.js + public/manifest.webmanifest + theme-color meta deliberately untouched (separate concerns). Auto-merge via Gitea API POST /pulls (PR #20 opened) + POST /merge with SHA-pinned head_commit_id=2aae2da (token from ~/.config/tea/config.yml since ~/.git-credentials missing in sandbox); merge_commit=de8647fdb8b501980a8b6224ce6e69c9c1211b3c. Feature branch deleted post-merge. ENV_VAR_GATE clean (zero new/renamed/removed env vars). SECURITY None — pure presentation; static meta literal, CSS-only refactor, no new routes/handlers/auth surface/env vars/deps/lockfile changes; no JS reads/writes color preference. Manual visual smoke (DevTools "Emulate CSS prefers-color-scheme: dark") deferred to user — no headed browser in autonomous loop. Remaining tiers: buchinese, movement (2 of 6 apps still pending). --> <!-- TIER-RESOLVED 2026-05-11T20:Z authd — implementer merged https://gitea.bchen.dev/brendan/authd/pulls/13 at merge_commit 5c7ef41703429219e8246510a079f6809529efb4 (REVIEWED_SHA 00735a7c — feat + refactor:drop --gray-400 + fix:WCAG-border-bump). Added `<meta name="color-scheme" content="light dark">` to all 5 HTML pages in public/ (login.html, account.html, admin.html, consent.html, mfa-challenge.html); refactored public/style.css to semantic tokens with `:root` defining light palette and `@media (prefers-color-scheme: dark) { :root { ... } }` block (soft near-black bg, off-white fg). 5 new semantic tokens added (--bg-elevated, --surface-hover, --accent-hover, --danger-bg, --focus-ring); --gray-400 primitive dropped post-refactor. Final hardening commit bumped dark-mode --border to #6b7280 to clear WCAG 1.4.11 3:1 UI contrast. 16px iOS form-input rule preserved (from PR #11). 8 new vitest assertions in style.test.ts (25→33); 209/209 suite green. Auto-merge via Gitea API POST /pulls (PR #13) + POST /merge SHA-pinned head_commit_id=00735a7c. Feature branch deleted post-merge. ENV_VAR_GATE clean. SECURITY None — pure presentation; static meta literal, CSS-only refactor, zero new routes/handlers/env vars/deps/lockfile changes. Manual visual smoke (DevTools "Emulate prefers-color-scheme: dark") deferred to user — no headed browser in autonomous loop. Remaining tiers: buchinese, inventory, movement (3 of 6 apps still pending). --> <!-- TIER-RESOLVED 2026-05-11T19:Z dashcam — implementer merged https://gitea.bchen.dev/brendan/dashcam/pulls/3 at merge_commit 69ef0fa7776ea6214e519b9b2c158558a1807ab4 (REVIEWED_SHA 7d12049b1f2dec8921d7343c22c78352a26a5002 = feat 131644f + refactor 7d12049). Added `<meta name="color-scheme" content="light dark">` to src/views/layout.ts head; refactored public/style.css (~113 LOC changed) from raw color literals/`--gray-*` aliases to semantic tokens (--bg, --bg-elevated, --fg, --fg-muted, --fg-subtle, --border, --border-strong) with light values byte-identical to pre-refactor; appended `@media (prefers-color-scheme: dark) { :root { ... } }` block (bg #0f1115, fg #e5e7eb soft-near-black palette). Added defensive `input,textarea,select { font-size: 16px }` rule per global iOS focus-zoom invariant (dashcam has no current styled form controls, but rule is additive and future-proofs). Added tests/views/layout.test.ts (40 LOC, asserts head meta + dark @media block + token presence + 16px rule). Refactor commit 7d12049 dropped 7 unused semantic tokens the executor speculatively defined (verified zero references). #clip-video letterbox stays literal `#000` (correct video UX in both modes). WCAG AA clears for all key pairs; tightest dark pair --fg-subtle #7b8492 on --bg #0f1115 ≈ 4.96:1. 82/82 vitest specs green (was ~70 pre-branch + new layout.test.ts); npm run build (esbuild client + tsc --noEmit) clean. Auto-merge via Gitea API POST /pulls (PR #3 opened) + POST /merge SHA-pinned head_commit_id=7d12049b1f2dec8921d7343c22c78352a26a5002 (token from ~/.config/tea/config.yml since ~/.git-credentials missing in this sandbox); merge_commit=69ef0fa7776ea6214e519b9b2c158558a1807ab4. Feature branch deleted post-merge. ENV_VAR_GATE clean. SECURITY None — pure presentation; static meta literal, CSS-only refactor, no new routes/handlers/env vars/dependencies, dropped tokens verified unreferenced. Manual visual smoke (DevTools "Emulate prefers-color-scheme: dark") deferred to user — no headed browser in autonomous loop. Remaining tiers: authd, buchinese, inventory, movement (4 of 6 apps still pending). --> <!-- TIER-RESOLVED 2026-05-11T16:57Z nanodrop — implementer merged https://gitea.bchen.dev/brendan/nanodrop/pulls/8 at merge_commit 3f8da8c12cc16b9eb3eacbab6060e7f6f7e8eb00. Added `<meta name="color-scheme" content="light dark">` to layout.ts; refactored public/style.css (425→502 LOC, +166/-67 net via 17-token semantic palette + dark @media block); +4 vitest assertions in style.test.ts + pages.test.ts (130/130 green, was 126). WCAG AA clears for all key pairs (tightest `--danger` on `--danger-bg` ≈4.85:1). 16px iOS font-size rule preserved. Refactor commit 38e7c0c byte-equivalent shorthand restore. No toggle/storage/cookie; pure prefers-color-scheme. Remaining tiers: authd, buchinese, dashcam, inventory, movement (5 of 6 apps still pending). --> **2026-05-11 — cross-project: automatic OS-driven dark mode on every web frontend (authd, buchinese, dashcam, inventory, movement, nanodrop) — no in-app toggle, no preference persistence, purely `prefers-color-scheme` media-query driven** User wants every bchen.dev web app to follow the device's system theme automatically. When the OS (iOS, macOS, Android, Windows, Linux desktop) is in dark mode, the app renders in dark mode; when light, light. **No toggle switch anywhere in the UI.** No setting, no cookie, no localStorage, no per-account preference — the only input is `@media (prefers-color-scheme: dark)`. If the user changes their system theme, the app updates live (browsers re-evaluate the media query on theme change with no reload needed). **Source request (verbatim from user):** "file automatic dark mode as a feature request for every app. no need for a toggle switch anywhere, just have it be automatic based on device theming" **Apps in scope** (one PR per app — these ship independently and don't share CSS): 1. `~/authd/public/style.css` + login/account/oauth-consent HTML pages 2. `~/buchinese/public/style.css` + articles list/new/view HTML pages 3. `~/dashcam/public/style.css` (single shared stylesheet, server-rendered via `src/views/layout.ts`) 4. `~/inventory/public/style.css` + all HTML pages under `public/` (login, index, item, items-new, oauth-clients, oauth-consent, api-tokens). **Note:** inventory already has `/icon-color-scheme.js` for the PWA home-screen icon swap — that's icon-only and unrelated; the CSS work is still needed. 5. `~/movement/src/client/styles/main.css` + `popover.css` (and any other stylesheets under `src/client/styles/`) 6. `~/nanodrop/public/style.css` **Out of scope this round** (no web frontend, or N/A): `portman`, `tradebot`, `fleet` (CLI/supervisor), the `roles/` docs. **Implementation approach** (apply to every app; details may vary by stylesheet): 1. **Declare both schemes to the browser.** Add `<meta name="color-scheme" content="light dark">` to every served HTML page's `<head>`. This tells the browser the page supports both modes and lets it pick sensible defaults for form controls, scrollbars, and the initial paint background before CSS loads (prevents the "white flash on dark OS" problem). In server-rendered layouts (dashcam `src/views/layout.ts`, movement client) add it there once; in static HTML files (authd/buchinese/inventory/nanodrop public/*.html) add to each file. **Verify there is no existing `<meta name="color-scheme" content="light">` that would block this** — if so, change it to `light dark`. 2. **Refactor each stylesheet to CSS custom properties.** Replace hardcoded colors with `var(--name)` references. Define the light-mode palette under `:root { ... }` and the dark-mode palette under `@media (prefers-color-scheme: dark) { :root { ... } }`. Minimum variable set per app (extend as the design needs): - `--bg` (page background) - `--bg-elevated` (cards, panels, modals) - `--fg` (primary text) - `--fg-muted` (secondary text, labels, hints) - `--border` (dividers, input borders) - `--accent` (primary buttons, links) - `--accent-fg` (text on accent backgrounds) - `--danger` (destructive buttons, error text) + `--danger-fg` - `--success` (where used) + `--success-fg` - Form control background/text/border (`--input-bg`, `--input-fg`, `--input-border`) 3. **Pick the dark palette carefully — don't just invert.** True-black (`#000`) on OLED looks dramatic but causes haloing around text; aim for a soft near-black like `#0f1115` or `#111827` for `--bg` and `#1a1d23` / `#1f2937` for `--bg-elevated`. Text should be off-white (`#e5e7eb` / `#f3f4f6`), not pure white. Accent color often needs a saturation/brightness bump in dark mode to keep contrast — e.g. an accent that's `#2563eb` on light might become `#3b82f6` on dark. Verify WCAG AA contrast (4.5:1 for body text, 3:1 for large text) for every color pair using a tool like `https://webaim.org/resources/contrastchecker/` or `npx wcag-contrast-check`. 4. **Form controls** — the `input/textarea/select { font-size: 16px }` rule from the global CLAUDE.md still applies and is orthogonal to dark mode; do NOT regress it. For dark mode specifically, set `--input-bg`, `--input-fg`, `--input-border` and apply them. Browser-native controls (`<input type="date">`, `<select>`) will mostly take care of themselves once `color-scheme: light dark` is in `<meta>`, but verify their popups look right on both iOS Safari and desktop Chrome. 5. **Images, icons, illustrations.** Where an image has a transparent background and dark foreground (logos, line-art icons), it may disappear on dark mode. Options: - Use SVG with `currentColor` and let CSS color it via `color: var(--fg)`. - Provide a `<picture><source media="(prefers-color-scheme: dark)" srcset="logo-dark.svg"><img src="logo-light.svg" /></picture>` swap. - For PWA app icons, inventory's `/icon-color-scheme.js` already handles the home-screen icon swap on install; mirror that pattern in any other PWA that ships maskable icons (currently just inventory and nanodrop have manifests — check `public/manifest.webmanifest`). 6. **Code blocks, syntax highlighting, charts.** buchinese has prose articles with Chinese text and English translations — make sure both the article body and the inline word-popover are themed. dashcam has clip thumbnails and analyzer output — make sure the analyzer's color-coded labels still have AA contrast on dark. If any app uses a syntax-highlighting library (e.g. highlight.js, shiki) check it ships dual themes or load a dark theme inside the media query. 7. **Charts / data visualization.** Currently none of the apps in scope have charts; if one is added later, the chart library should respect the theme variables. 8. **Email templates** (authd password-reset, etc.) — **out of scope.** Email dark-mode support is a separate beast (`@media (prefers-color-scheme: dark)` inside HTML emails works on Apple Mail and Outlook.com but is unreliable elsewhere). Skip for this round; file as a follow-up if the user wants it. 9. **PDFs, printed output** — out of scope; print stylesheets should remain light-on-white. **Anti-patterns to avoid:** - **No `<html class="dark">` JS toggle.** That's the Tailwind/Next.js default, but the user explicitly said no toggle and no persisted preference. Pure media query only. - **No localStorage / cookie that overrides the OS preference.** The OS is the single source of truth. - **No "auto / light / dark" three-way switch.** Just auto. - **Do not gate behind a feature flag or user account preference** — the rollout is "ship it on the next deploy and it's live for everyone". - **Do not break the global iOS font-size rule** (`input,textarea,select { font-size: 16px }`) when refactoring CSS — it must still compute to ≥16px on mobile for affected controls. - **Do not use `<meta name="viewport" ... user-scalable=no>` to suppress any iOS quirk** — same accessibility reasoning as the font-size rule. **Test plan per app:** - Open every page on macOS / iOS Safari with system theme set to Dark, then to Light. Verify legibility, no white flashes on navigation, no invisible elements. - DevTools "Emulate CSS prefers-color-scheme" — toggle both modes on every route. - Run an axe-core or Lighthouse accessibility audit; fail if any text drops below AA contrast. - For PWAs (inventory, nanodrop): add-to-home-screen on iOS in both themes, verify the splash + icon look right (inventory's `/icon-color-scheme.js` already covers the icon; verify it still works after the CSS refactor). - Movement: the popover overlays (`popover.css`) need verification — overlays on top of arbitrary page content are a common dark-mode breakage point. - buchinese: verify the Chinese-character word-popover (which floats on top of article text) and the per-sentence English translation toggles both render legibly in dark mode. **Acceptance per app PR:** - `npm run build` / `tsc --noEmit` clean. - All existing tests still pass; no new tests are required (this is presentation-only) but a Playwright smoke test that loads the index page in both prefers-color-scheme values and asserts `getComputedStyle(document.body).backgroundColor` differs is a nice-to-have. - Manual visual inspection by user on the deployed site in both modes before the next app's PR opens (or implementer ships all six in parallel and the user reviews together — implementer's call based on whether they bundle). - No regression in the iOS focus-zoom rule (font-size ≥16px on text inputs). **Sequencing / coordination:** - Six independent PRs, one per app. No cross-app coordination needed — each app's CSS is self-contained. - **Suggested order** (lowest risk first): nanodrop → dashcam → authd → inventory → buchinese → movement. nanodrop and dashcam have the smallest stylesheets; movement has the most complex client (multiple stylesheets, popovers, client-rendered DOM); buchinese has the most content-rich pages where bad contrast would be most visible. - The implementer can pick any one app per tick; this is a long-tail polish feature and doesn't need to ship all six at once. - Standard implementer pipeline applies (plan → execute → review → push to a feature branch → auto-merge if all checks pass and tests are green). **Out of scope (file as follow-ups if wanted):** - A user-facing theme toggle (the user explicitly rejected this). - Per-account theme preference persisted in authd or per-app DB. - Email-template dark mode. - A new "high-contrast" mode beyond standard dark. - Themed PWA splash screens beyond what the current `manifest.webmanifest` declares. - Going back and adding dark mode to any historical screenshots / docs (the README screenshots can stay light-mode). **Going-forward expectation:** like the iOS focus-zoom rule in `~/.claude/CLAUDE.md`, this is a one-off retrofit. After all six apps ship, any *new* web frontend the user builds should bake in `prefers-color-scheme` support from day one. If this pattern becomes load-bearing, the reporter or user can promote it to a global rule in `~/.claude/CLAUDE.md` or `~/.claude/rules/common/coding-style.md` (filing note: same writability caveat as the focus-zoom rule — the rules tree is owned by `nobody:nogroup` from sandbox sessions, so the rule may have to live in `~/.claude/CLAUDE.md` instead). Source: user via spawn-host chat (2026-05-11). Filed by reporter; spawn-host sessions do not edit project code directly per `~/CLAUDE.md` policy.
brendan added the feature label 2026-05-13 19:02:10 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: brendan/nanodrop#17