feat(ui): gate dark mode at build time - #110
Merged
Merged
Conversation
…t by component
Dark mode is applied by toggling `<html data-theme="dark">` and letting
`variables.css` override the token block. Any style that bypasses a token and
hardcodes a colour looks fine in light and becomes a bright patch in dark —
and nobody switches themes twice before pushing, so every new feature adds
another one. Fixing them one at a time does not converge; there is no feedback
loop.
`scripts/check-dark-mode.mjs` runs before the build (next to check-i18n) and
scans five blind spots:
css-literal-color a colour literal in a CSS declaration
css-var-literal-color a literal hidden inside a custom property's value
(bulk rename scripts have never caught these)
js-inline-color literals in TSX inline styles / SVG attributes
undefined-var-fallback var(--undefined, #fff) — the fallback always wins,
so it is a hardcode
prefers-color-scheme the media query fights the manual light/dark/system
toggle
It is a **ratchet**, not a wall: `dark-mode-baseline.json` freezes the current
debt per file per rule, exceeding it fails the build, and dropping below it
prints a hint to tighten with `--update`. Existing debt can only shrink.
Deliberate hardcodes are exempted with `dark-ok: reason` on the line or the
comment above it, and long templates with `dark-ok-begin` / `dark-ok-end` — a
reason is mandatory.
Three precision details keep it from crying wolf: `white` inside
`var(--color-text-white)` is a name and not a colour; a fallback on an
*already defined* token is dead code rather than an accident; and multi-line
declarations report their starting line so the `dark-ok` comment has somewhere
natural to sit.
The gate also scans the **desktop shell** (`desktop/src-tauri/src/**.rs`),
which embeds whole HTML pages as Rust string literals. It is the same UI to a
user but is neither `.css` nor `.tsx`, so it had no feedback at all. CSS is
lifted out of `<style>` blocks by blanking everything else rather than slicing,
so reported line numbers are the real ones in the `.rs` file.
Also adds the paired `--color-tint-{purple,cyan,teal,amber,green,red,blue}-bg`
tokens plus matching foregrounds. Only semantic colours (success/warning/error/
primary) had pairs before, so badges that exist purely to tell categories apart
had nothing to reach for and each feature picked its own light hex — the slash
panel, skill/plugin chips, loop status badges and channel connection status all fell into it.
Baseline starts at 630 findings across 58 files, i.e. this repo's current debt
frozen as-is; the build passes today and can only improve from here.
Note: three files (ProjectCard.tsx, ProjectDetailPanel.tsx, chat.css) keep
their existing literals — their upstream cleanup did not transfer to this tree
— so those stay in the baseline for a later pass.
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.
Dark mode is applied by toggling
<html data-theme="dark">and lettingvariables.cssoverride the token block. Any style that bypasses a token and hardcodes a colour looks fine in light and becomes a bright patch in dark — and nobody switches themes twice before pushing, so every new feature adds another one. Fixing them one at a time does not converge; there is no feedback loop.The gate
scripts/check-dark-mode.mjsruns before the build (next tocheck-i18n) and scans five blind spots:css-literal-colorcss-var-literal-colorjs-inline-colorundefined-var-fallbackvar(--undefined, #fff)— the fallback always wins, so it is a hardcodeprefers-color-schemeIt is a ratchet, not a wall:
dark-mode-baseline.jsonfreezes current debt per file per rule, exceeding it fails the build, dropping below it prints a hint to tighten via--update. Existing debt can only shrink.Deliberate hardcodes are exempted with
dark-ok: reasonon the line or the comment above it, and long templates withdark-ok-begin/dark-ok-end. A reason is mandatory.Three precision details keep it from crying wolf:
whiteinsidevar(--color-text-white)is a name, not a colour; a fallback on an already defined token is dead code rather than an accident; and multi-line declarations report their starting line so thedark-okcomment has somewhere natural to sit.It also covers the desktop shell
desktop/src-tauri/src/**.rsembeds whole HTML pages as Rust string literals. To a user that is the same UI, but it is neither.cssnor.tsx, so it had no feedback at all — which is exactly how #109's bugs survived. CSS is lifted out of<style>blocks by blanking everything else rather than slicing, so reported line numbers are the real ones in the.rsfile.Paired category tokens
Adds
--color-tint-{purple,cyan,teal,amber,green,red,blue}-bgplus matching foregrounds. Only semantic colours (success/warning/error/primary) had pairs before, so badges that exist purely to tell categories apart had nothing to reach for and each feature picked its own light hex — the slash panel, skill/plugin chips, loop status badges and channel connection status all fell into it.State
npm run buildpasses today (check-i18n → check-dark-mode → tsc → vite).ProjectCard.tsx,ProjectDetailPanel.tsx,chat.css) keep their existing literals — their upstream cleanup did not transfer to this tree — so they stay in the baseline for a later pass.