Skip to content

Give the board a palette of its own, so a theme can move it - #77

Merged
aenawi merged 1 commit into
developfrom
hud-theme-store
Sep 12, 2026
Merged

aenawi merged 1 commit into
developfrom
hud-theme-store

Conversation

@aenawi

@aenawi aenawi commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Target

  • This is going into develop: the normal case for a fix, a chapter, a
    lab change, a typo
  • This is a release: develop → main, shipping to the published guide
  • This is a hotfix: straight into main, because published advice is
    currently locking readers out or leaving them exposed

What this changes

The board's nine colours move from the page's own tokens to nine --board-*
properties that default to them, which gives a stylesheet somewhere to stand
between the page and the drawing. A theme is then a folder under
control/ui/hud/themes/ holding theme.json and theme.css; themes.go
walks the embedded UI at startup and serves what it found at /api/themes, so
adding one is a folder and a rebuild with no list edited anywhere.

No issue — this was asked for directly. It is the first of a planned series;
what is deliberately not in it is listed at the bottom.

Kind

  • Prose: clarity, typo, dead link
  • The advice was wrong, unsafe, or out of date
  • Lab or sandbox: code, checks, a new scenario
  • A divergence between the lab and the sandbox, closed or newly documented
  • Tests
  • Something else

If this changes advice or a command

Not advice, but it changes behaviour, so it was measured rather than assumed.

OS: macOS 15 (Darwin 25.6.0), Docker Desktop
Browser: Chromium via CDP, headless
Go: as pinned in lab/control/Dockerfile
Measured on: 2026-09-12, against the lab running on this machine

What I ran, and what it printed:

$ docker compose up -d --build --no-deps control
 Container control Started

$ curl -s http://127.0.0.1:8099/api/themes
[
  {"id":"chapter","name":"Chapter","note":"The guide's own palette. …",
   "ground":"any","author":"the guide"},
  {"id":"warroom","name":"Warroom","note":"An operations wall: amber on …",
   "ground":"dark","author":"the guide"}
]

$ curl -s -o /dev/null -w '%{http_code}\n' .../hud/themes/chapter/theme.css
200
$ curl -s -o /dev/null -w '%{http_code}\n' .../hud/themes/warroom/theme.css
200

In the page, driving the picker:

  selecting warroom   --board-accent  #0d7a6f -> #e8a33d
                      --accent        #3ddbc7 (the page's dark token, untouched)
                      data-theme      -> dark          (the theme's declared ground)
                      data-hud-theme  -> warroom

  then the theme button
                      data-theme      -> light         (the button still wins)
                      --board-accent  #e8a33d          (the palette stays)

  selecting chapter   --board-accent  #0d7a6f
                      --accent        #0d7a6f
                      identical       true             (it overrides nothing)

  console errors      none
  • I checked whether any other chapter makes the same claim — the nine
    colours were only ever read in one place, readTokens() in hud/scene.js
  • If this step can lock someone out of a machine, the warning sits before the step — n/a
  • The mechanism is explained, not just the command — lab/README.md gains a
    Themes for the board section with the nine properties and what each paints

Checks

  • make -C lab check passes — "Everything held."
  • gofmt is clean (make check fails on any output at all)
  • New source files carry an SPDX-License-Identifier header

If this touches the guide's pages

It does not. Everything here is under lab/, which is the control server and
its embedded UI. The guide's own pages, assets/ and nav.js are untouched.

If this touches the lab or sandbox engine

The engine is untouched: no probe, no attack, no rung and no Result changed.
The only edit inside hud/ is the nine property names readTokens() reads.
Ticked because they were checked, not because they were assumed:

  • Nothing is drawn that was not measured
  • A failed attack does not render as a green tick
  • danger still outranks ok
  • Time is not faked
  • The engine still returns the rung that decided the outcome, not a bare pass/fail
  • If a score moved, the comparison table in lab/README.md moved with it — no
    score moved; the 27 board-rule tests pass unchanged

Licence

  • I have the right to submit this, and I am fine with it going out under
    CC BY-SA 4.0 (guide) or GPL-3.0-or-later (code), per the file I touched

Not in this change

Kept out on purpose, one idea per branch:

  • The feed. The readout cards want typed events — flows, rates, drop
    counters — rather than four separate SSE streams and a parser per consumer.
    That is its own branch and its own conversation.
  • Widgets and slots. Cards docked around the board, declared by a theme's
    manifest rather than by index.html. Needs the feed first.
  • The other skins. warroom is here as proof the seam holds, not as a
    finished look; it moves nine colours and nothing else, because nine colours
    is all a theme can reach today.
  • Runtime-loaded themes. The store is //go:embed-ed, so a theme needs a
    rebuild. A mounted directory would remove that, and would also mean the
    control server — which holds the Docker socket — loading JavaScript from
    disk. Worth doing deliberately, later, if at all.

The board has never carried colours. readTokens() in hud/scene.js reads nine
custom properties off the page and paints from what it read, which is how the
light/dark button moves the drawing with it. That was already the whole
mechanism a theme needs; what was missing was somewhere for one to stand.

So the nine become --board-*, declared once in style.css and each defaulting to
the page token it used to name. Nothing about the light/dark path changed: the
defaults follow the page into both grounds, because a var() resolves where it
is used rather than where it is declared. What is new is that a stylesheet can
now redefine them without reaching past the drawing and repainting the panels
around it.

A theme is a folder under ui/hud/themes/ with theme.json and theme.css in it.
themes.go walks the embedded UI at startup and serves what it found at
/api/themes, so the picker is populated from the server rather than from a list
in the markup — adding a theme is a folder and a rebuild, and nothing else is
edited. That is the same shape panels.go already has.

Two themes ship. `chapter` overrides nothing and is the board exactly as it
was; a test fails if it ever starts to, because a second copy of those nine
colours is the copy that goes stale. `warroom` is amber on a dark ground, and
exists so the seam is demonstrated rather than asserted.

A theme that does not parse, or whose id disagrees with its folder, is skipped
with a line at the console and the rest of the store still loads — the choice
missing() already makes for set-pieces, for the same reason.

theme.json's `ground` is advisory: selecting a theme drawn for dark moves the
page there, and the light/dark button still wins afterwards.

Measured against the running lab on 2026-09-12: /api/themes serves both, both
stylesheets are served, selecting warroom moves --board-accent from #0d7a6f to
#e8a33d while --accent is untouched, pressing the theme button afterwards
changes the ground and leaves the palette, and selecting chapter again leaves
--board-accent and --accent identical. No console errors.
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