Skip to content

The interface has a light side - #599

Merged
WaylandYang merged 9 commits into
devfrom
feat/the-interface-has-a-light-side
Sep 11, 2026
Merged

WaylandYang merged 9 commits into
devfrom
feat/the-interface-has-a-light-side

Conversation

@WaylandYang

Copy link
Copy Markdown
Contributor

A light theme, and the sweep that makes one possible: every colour in the web app is now a token, and the canvas reads the tokens at runtime.

What a person sees

The user menu gains a Theme section under Language: Dark (the default and the brand), Light, Follow system. The choice lives in the browser (utopia.theme), like the language — it never reaches the backend. A small inline script in index.html applies it before first paint, so a refresh does not flash dark and then turn light.

Light is paper, not white: ground #fafafa, ink #171717 / #5f5f5f, lines and surfaces become black-at-alpha with the same alpha values as dark (they describe hierarchy, not colour), and the five meaning colours get a deeper version of the same hue (pale rose and pale gold do not read on paper).

The sweep

Rule 4 of DESIGN.md says colour is a token, never a value. It was enforced for Tailwind classes only; strings in TS and rules in styles.css had 56 raw values in pages and 55 more in the stylesheet — every one of them "white on dark", which is exactly what breaks when the ground flips.

  • styles.css: 41 new tokens; the two RGB triplets --u-ink-rgb / --u-ground-rgb carry every rgba(255,255,255,α) and rgba(10,10,10,α) with the alpha left where it was; canvas tokens (--u-node-*, --u-edge-*, --u-halo, --u-scrub-*), surfaces for scrim / pop / modal / card, a code-highlight palette. Outside the three token blocks there is no colour value left in the stylesheet.
  • graphVisuals.ts is now the reader: its exports are live let bindings filled by refreshPalette() from getComputedStyle (canvas cannot read var()); Graph and the schema graph call it before they build (node shells and edge colours are baked into graph attributes) and rebuild on onThemeChange. The two pages' own colour constants are gone.
  • The 16-colour entity palette moves to src/palette.ts — it is data colour (rule 4 allows it) and must stay byte-identical to crates/utopia-store/src/palette.rs. A paper-tuned variant would have to change both sides; not in this cut.
  • style-guard gains raw-colour: #rrggbb or rgba( anywhere in src/**/*.ts{,x} fails the build, except the two reader files, test fixtures and rgba(0,0,0,0). It now also scans .ts (the canvas and the login scene live there) and ignores block comments. 72 files, 0 violations.

Checked in the browser

Both themes on Graph (16 entities, edges, scrubber), Ontology (schema graph), Chat, Library, Settings, and the 500 page. Switching through the real menu re-bakes the canvas without a reload:

light: nodeShell rgb(252,240,250)  edge rgba(90,90,90,0.12)
dark:  nodeShell rgb(48,37,46)     edge rgba(163,163,163,0.1)

pnpm build (guard + tsc + vite) and 19 web tests green; theme.ts has a unit test for the three-way resolution.

Not in this cut

A paper-tuned entity palette (server and client together); the canvas edge label work is unrelated. The default stays dark.

🤖 Generated with Claude Code

WaylandYang and others added 4 commits September 10, 2026 23:58
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
@WaylandYang

Copy link
Copy Markdown
Contributor Author

Review before merge (code only; the running app still has to be looked at). Not mergeable yet: backend and migrations are red because of this PR, and the default theme regresses.

Blocking

  1. crates/utopia-store/src/palette.rs:141 and :164include_str!("../../../web/src/ui/index.tsx") still reads the file ENTITY_PALETTE / colorForKey moved out of. Both palette::tests panic, which is the backend and migrations failure. Point the two include_str! (and the comment at :30) at web/src/palette.ts; the 16 colours are byte-identical on both sides.
  2. web/src/pages/graphVisuals.ts:64, graphCanvas.ts:190,223, OntologySchemaGraph.tsx:828mix() only understands 6-digit hex; refreshPalette() now sets INK = "rgba(255,255,255,1)", so mix(ownColor, INK, 0.7) mixes toward #808080. In dark mode, hovering or selecting a node draws a dull grey ring instead of the near-white one it had before this PR. Give INK a hex token or route mix through parseRgba.
  3. web/src/ui/index.tsx:46 and :323text-white on the wordmark link and on the Select's selected option; ui/ is exempt from the colour guard so it did not catch them. In light mode the "Utopia" wordmark is invisible on #fafafa and the selected row's label vanishes in every Select. text-ink.
  4. web/src/styles.css:733.u-btn-danger { color: var(--u-text) } is #171717 on #c9353a, about 3.5:1. --u-on-danger exists for exactly this and is only used in KbSettings.tsx:472; every confirm dialog uses Button variant="danger".
  5. web/src/styles.css:700–704.u-btn-primary:hover background and border became var(--u-text), the rest value; the hover state is gone in both themes.

Not blocking

  • Graph.tsx:1139–1236 — a theme switch bumps themeTick and rebuilds the whole canvas (new Sigma, relayout, camera reset). OntologySchemaGraph.tsx:726 syncs the live instance instead; Graph could do the same.
  • LoginScene.tsx:67refreshPalette() runs in the render body (about 50 getComputedStyle calls, doubled under StrictMode). Move it into the effect.
  • theme.ts setTheme notifies listeners even when the resolved theme did not change (dark → system on a dark OS rebuilds the canvas for nothing).
  • The raw-colour guard regex misses #rgb, #rrggbbaa, hsl() and named colours; nothing slips through today.

Checked and fine: all 61 light overrides exist on :root and every var(--u-*) resolves; no flash on load (index.html inline script, initTheme() before render); utopia.theme persisted with try/catch; theme / themeNames present in both en.ts and zh.ts; sigma accepts the spaced rgba(...) computed values.

WaylandYang and others added 5 commits September 11, 2026 07:58
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
@WaylandYang
WaylandYang merged commit 2767877 into dev Sep 11, 2026
4 checks passed
@WaylandYang
WaylandYang deleted the feat/the-interface-has-a-light-side branch September 11, 2026 00:30
IFS4thewin pushed a commit to IFS4thewin/InfoTopia that referenced this pull request Sep 12, 2026
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