Skip to content

bento/dash: the theme keys off data-theme, like the other three apps - #449

Open
nyblnet wants to merge 1 commit into
mainfrom
dash-theme
Open

nyblnet wants to merge 1 commit into
mainfrom
dash-theme

Conversation

@nyblnet

@nyblnet nyblnet commented Sep 12, 2026

Copy link
Copy Markdown
Owner

The maintainer asked dash to fix the divergence. The divergence was a mechanism, not a palette. Measured with one gesture per app: slides, spaces and type theme off :root[data-theme="dark"] and none respond to color-scheme alone; dash alone themed off light-dark(), which reads color-scheme. Setting data-theme="dark" on dash did nothing — so kernel's coming shared stylesheet, which can only key off the attribute because slides pins color-scheme: only light, could not have themed dash at all.

The palette is unchanged, value for value — proven, not assumed

All 37 light-dark() pairs across four files were split by script into :root plus a :root[data-theme="dark"] block. Then every old pair was re-read from git and compared against both new blocks: zero mismatches, plus --bar-opacity carried in from its media query. The dark block keeps :root's order and section markers so the two still read side by side — that was the real advantage of the pair-per-token form, and it is the honest cost of this change.

Dash's own theme store is gone

settings.ts had its own applyTheme, injecting a transient <style> that pinned color-scheme, at module load. Replaced by kernel/src/theme.tsimported, not modified, so no kernel lock — with startTheme() called from main.ts after capturePristine() and before the first paint.

That ordering is the entire guarantee, and it answers the measured bug that justified the old design: with the attribute set at module load, bento.serialize() had returned <html data-theme="light">. Re-measured after this change, with the live root at data-theme="dark": the serialized tag is <html lang="en">. The same localStorage key was already in use, so an existing preference carries over.

--radius takes the shared 10px — and I had the reason wrong

I told the lead it was "drift, no reasoning anywhere". It was in the rig's DIVERGENCES list as a deliberate three-step scale with 7px as the control step. It changes anyway — a shared token should mean one thing across four apps and kernel's sheet is about to own it — but as a documented choice overridden for a stated reason, recorded in DECISIONS.md, not undone by mistake. --radius-lg / --radius-xl are dash's own and keep the scale.

Three rigs asserted the old mechanism; they assert properties now

  • test-dash-theme: no light-dark() declaration; no token inside a prefers-color-scheme query; every dark token has a light twin; no dark value merely repeats its light one; seventeen named tokens that would be worst to lose are present in the dark block. Its :root parser now strips comments before matching — my rewritten header mentioned data-theme="dark", the selector capture swept the comment up, and the light block vanished as if it were dark: eight "dash declares --ink" failures against a palette that was fully there, the exact costume the rig's own header warns about.
  • Its DIVERGENCES loop now fails on an exemption for a token that has come back into line. It used to check only that the reason was long enough — the same rot as bento/dash: the release blockers, two kinds of sheet, and an Excel bounce test #323's NOT_RUN list.
  • test-dash-surface's tokens() merged every :root block into one map, so the dark block overwrote the light values and every "light:" contrast check was measuring dark numbers. It takes a theme now.
  • test-dash-surfaces asserted the transient <style>; it asserts the order of capturePristine() and startTheme() in source instead, since that is the invariant and the DOM stub cannot serialize.

Both new guards negative-controlled with the sabotage confirmed applied: a stale --radius exemption fails the loop; --line deleted from the dark block fails the named-token check.

Deliberately not in this PR

--bg is left exactly as it was — themed, commented "the page, and the sheet itself". Whether a grid's paper inverts is the maintainer's question; leaving it untouched keeps it visible until they rule. And index.html's splash keeps its light-dark() colours: it paints before any script runs, so the attribute cannot reach it and following the OS is all it can do; color-scheme: light dark stays declared on :root for it and for the browser furniture in that window.

Verified

For whoever merges

This conflicts with #426 and #441 on dash/CHANGELOG.md, and with #426 on docs/DECISIONS.md — all append-point, all keep-both, all mine. I've verified that resolution works above. Happy to stack them in whatever order you'd rather merge; say which and I'll rebase rather than have you resolve four of mine by hand.

Unblocks kernel's tier 4: dash is now themeable by a shared sheet keyed off data-theme.

The maintainer asked dash to fix the divergence. The divergence was a
MECHANISM: measured with one gesture per app, slides, spaces and type theme off
`:root[data-theme="dark"]` and dash alone themed off light-dark(), which reads
color-scheme instead. Setting data-theme="dark" on dash did nothing — so
kernel's coming shared stylesheet, which can only key off the attribute because
slides pins `color-scheme: only light`, could not have themed dash at all.

THE PALETTE IS UNCHANGED, value for value, and that is proven rather than
assumed: every one of the 37 light-dark() pairs across four files was split by
script, then every old pair was re-read from git and compared against the new
:root and the new dark block — zero mismatches, plus --bar-opacity carried in
from its media query. The dark block keeps :root's order and section markers so
the two can still be read side by side, which was the real advantage of the
pair-per-token form and is the honest cost of this change.

DASH'S OWN THEME STORE IS GONE. settings.ts had its own applyTheme injecting a
transient <style> that pinned color-scheme, at module load. It is replaced by
kernel/src/theme.ts — imported, not modified, so no kernel lock — with
startTheme() called from main.ts AFTER capturePristine() and before the first
paint. That ordering is the whole guarantee, and it answers the measured bug
that justified the old design: with the attribute set at module load,
bento.serialize() had returned `<html data-theme="light">`. Re-measured after
this change with the live root at data-theme="dark": the serialized shell's
tag is `<html lang="en">`. The same localStorage key was already in use, so an
existing preference carries over.

--radius TAKES THE SHARED 10px, AND I HAD THE REASON WRONG. I told the lead it
was drift with no reasoning anywhere. It was in the rig's DIVERGENCES list as a
deliberate three-step scale with 7px as the control step. It changes anyway,
because a shared token should mean one thing across four apps and kernel's
sheet is about to own it — a documented choice overridden for a stated reason,
not undone by mistake. --radius-lg and --radius-xl are dash's and keep the scale.

THREE RIGS ASSERTED THE OLD MECHANISM and are rewritten to assert properties:
  - test-dash-theme: no light-dark() declaration, no token inside a
    prefers-color-scheme query, every dark token has a light twin, no dark
    value merely repeats its light one, and seventeen named tokens that would
    be worst to lose are present in the dark block. Its :root parser also now
    strips comments BEFORE matching — my rewritten header mentioned
    `data-theme="dark"`, the selector capture swept the comment up, and the
    light block vanished as if it were dark: eight "dash declares --ink"
    failures against a palette that was fully there, which is the exact
    costume the rig's own header warns about.
  - test-dash-theme's DIVERGENCES loop now fails on an exemption for a token
    that has come back into line. It used to check only that the reason was
    long enough, so --radius's entry would have outlived its reason forever.
  - test-dash-surface's tokens() read every :root block into one map, so the
    dark block overwrote the light values and every "light:" contrast check
    was measuring dark numbers. It takes a theme now.
  - test-dash-surfaces asserted the transient <style>; it asserts the ORDER of
    capturePristine() and startTheme() in main.ts source instead, since that
    is the invariant, and the DOM stub cannot serialize.
Both new guards negative-controlled: a stale --radius exemption fails the loop;
--line deleted from the dark block fails the named-token check.

--bg IS LEFT EXACTLY AS IT WAS, themed and commented "the page, and the sheet
itself". Whether a grid's paper inverts is the maintainer's question and it is
deliberately not folded in here. index.html's splash keeps its light-dark()
colours: it paints before any script runs, so the attribute cannot reach it and
following the OS is all it can do; color-scheme stays declared on :root for it.

Verified: 62/62 dash rigs, tsc clean, build:single, splice gate. In the built
shell: data-theme="dark" themes the sheet, ink and bar opacity; color-scheme
alone no longer does; the Settings picker round-trips dark/auto through the
kernel store; author-coloured cells stay readable in dark via the unchanged
--ink-on-fill.
@github-actions

Copy link
Copy Markdown

Build size

main (401cade) → dash-theme (6a6d0e7)

app base PR change
bento/slides 678.4 KiB 678.4 KiB 0.0 KiB (0.00%)
bento/spaces 271.3 KiB 271.3 KiB 0.0 KiB (0.00%)
bento/dash 424.1 KiB 424.0 KiB -0.0 KiB (-0.01%)

Updated: 2026-09-12T00:54:47Z

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