Make the default node font color follow the theme - #380
Merged
Merged
Conversation
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>
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.
Problem
The canvas node font color is scene state (
font_color, applied as--gl-node-font-coloron the canvas wrapper byuseCanvasFontVars). 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_colordefaults to"";setFontColor("")round-trips as the reset-to-theme value.font_coloris in-memory session state with no persistence path, so there is no migration.useCanvasFontVars.ts:""removes--gl-node-font-colorrather 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 showsAutoinstead 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#000000with 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 mockedclearFilters- Reset threw an unhandled TypeError in jsdom - fixed in the double.npm run checkagainst a clean checkout of this branch: 2135 pass.pytestfor the touched backend suites andruffclean.#E0E0E0node text with no--gl-node-font-colorset; flipping to light renders#1F1F1Fon light cards; the View panel shows the active Auto swatch andAutoreadout.