Skip to content

Make the default node font color follow the theme - #380

Merged
dovvnloading merged 1 commit into
mainfrom
fix/theme-aware-node-font-color
Sep 1, 2026
Merged

dovvnloading merged 1 commit into
mainfrom
fix/theme-aware-node-font-color

Conversation

@dovvnloading

Copy link
Copy Markdown
Owner

Problem

The canvas node font color is scene state (font_color, applied as --gl-node-font-color on the canvas wrapper by useCanvasFontVars). Its default was the literal "#F0F0F0" - a white chosen for the dark theme - so under the light palette every chat, thinking and markdown body rendered near-white text on white cards. A stored hex cannot adapt to a theme.

Reproduce on main: load any graph with the theme set to Match System and emulate prefers-color-scheme: light.

Change

The default is now the empty string, meaning "follow the theme". An explicitly chosen color still stores its hex and applies as-is in both themes.

  • backend/domain/graph.py: font_color defaults to ""; setFontColor("") round-trips as the reset-to-theme value. font_color is in-memory session state with no persistence path, so there is no migration.
  • useCanvasFontVars.ts: "" removes --gl-node-font-color rather than writing an empty value, so the existing CSS fallback - var(--gl-node-font-color, var(--gl-surface-text-primary)) - resolves to the active palette's body-text token, which the token contrast test already holds to WCAG AA in both themes. Writing "" would suppress the fallback: an empty custom property is still a set property.
  • ViewPopover: the font-color row gains a leading "follow the theme" swatch, active when unset; the readout shows Auto instead of an empty hex; the font preview falls back to the theme token; Reset to Defaults returns to "" rather than to the dark-theme white; and the native color input is fed a valid hex when the stored value is empty (a color input coerces "" to #000000 with a console warning).

Test plan

  • test_canvas.py: new round-trip - default is "", an explicit hex stores, "" resets. 326 backend canvas/migration tests pass.
  • useCanvasFontVars.test.tsx, new: an explicit color is written as-is; "" removes the property (asserted as absence, since an empty value would reintroduce the bug in a new shape); family and size still apply when the color is theme-following.
  • ViewPopover.test.tsx: the Auto swatch renders and reads active when unset; picking it commits "" through the debounce; an explicit color shows its hex with Auto unpressed; Reset commits "". Writing the Reset test exposed that the store double never mocked clearFilters - Reset threw an unhandled TypeError in jsdom - fixed in the double.
  • npm run check against a clean checkout of this branch: 2135 pass. pytest for the touched backend suites and ruff clean.
  • Verified live against the built bundle in both themes: dark renders #E0E0E0 node text with no --gl-node-font-color set; flipping to light renders #1F1F1F on light cards; the View panel shows the active Auto swatch and Auto readout.

The canvas node font color is scene state (font_color, applied as
--gl-node-font-color on the canvas wrapper). Its default was the literal
"#F0F0F0" - a white chosen for the dark theme - so switching to the light
palette rendered near-white node text on white cards: chat, thinking and
markdown bodies were unreadable. A stored hex cannot adapt to a theme.

The default is now the empty string, meaning "follow the theme":

- backend/domain/graph.py: font_color defaults to ""; setFontColor("")
  round-trips as the reset-to-theme value. font_color is in-memory session
  state with no persistence path, so no migration is needed.
- useCanvasFontVars.ts: "" REMOVES --gl-node-font-color rather than writing
  an empty value, so the existing CSS fallback -
  var(--gl-node-font-color, var(--gl-surface-text-primary)) - resolves to
  the active palette's body-text token, which the token contrast test
  already holds to WCAG AA in both themes. Writing "" would suppress the
  fallback: an empty custom property is still a set property.
- ViewPopover: the font-color row gains a leading "follow the theme" swatch
  (active when unset), the readout shows "Auto" instead of an empty hex,
  the preview falls back to the theme token, Reset returns to "" rather
  than to the dark-theme white, and the native color input is fed a valid
  hex when the stored value is empty (a color input coerces "" to #000000
  with a console warning).

An explicitly chosen color still stores its hex and applies as-is in both
themes - "I chose this color" keeps meaning exactly that.

Tests: a backend round-trip for the new default and the "" reset; a hook
suite pinning remove-vs-write (an empty value would reintroduce the bug in
a new shape); panel tests for the Auto swatch, the readout, commit-through-
debounce, and Reset. Writing the Reset test exposed that the test double
never mocked clearFilters - Reset threw an unhandled TypeError in jsdom the
moment a test actually clicked it - fixed in the double.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dovvnloading
dovvnloading merged commit 56a51a8 into main Sep 1, 2026
4 checks passed
@dovvnloading
dovvnloading deleted the fix/theme-aware-node-font-color branch September 1, 2026 02:21
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