Repository navigation
Dock cards around the board, so a theme can arrange the instrument - #79
Merged
Merged
Conversation
The page's furniture was in index.html. Adding a readout meant editing the markup, the stylesheet and app.js, which is three files and a fork for anybody who wants a different instrument on the same lab. The feed made the data shareable last week; this makes the layout data too. There are four docks now — top, left, right, bottom — and a card in one of them is a widget: a folder under ui/hud/widgets/ with a widget.json saying what it is, a widget.js, and a widget.css scoped to its own card. widgets.go walks the embedded UI at startup and serves the registry at /api/widgets, so adding a widget is a folder and a rebuild. The same shape as the theme store, and panels.go before it. What a widget is handed is the whole seam: a div, the feed, and the options its placement carried. No board object, no page elements, no controller. The board is twenty-five coupled methods over 1300 lines of three.js and publishing it as a plugin API would freeze hud/scene.js the day somebody wrote against it — a widget that wants to know what the lab is doing reads the feed, which is what the feed is for. Where a card goes is the theme's business. theme.json grows a layout, the server prunes it against the registry at startup — unknown widget, or a dock the widget does not fit — and each drop says why at the console, so the page only mounts placements that can be built. warroom docks three; chapter docks none, for the same reason its stylesheet overrides nothing, and a test fails if either starts to. Three widgets to have something real in the docks, all three reading the feed and nothing else. wire is the feed reading itself — open or not, how far the sequence has got, and what the server threw away because a reader could not keep up, because a recording made through a gap is worth knowing about before it is posted. tape is every frame written down, which is the closest thing on the page to what a replay will work from. tally counts verdicts by the rung that decided them, allowed and denied apart: "rung 3 allowed eleven and denied none" is a finding and "rung 3 answered eleven times" is not. The board is not a widget and does not pretend to be. app.js still creates it and drives it; the docks' part is reporting what they cover so the camera frames the diagram in the gap the cards leave. Making it a real widget under the contract above means the board reading the feed instead of being called, which is the next piece of work and not this one — a privileged contract that handed one widget the board object would be the plugin API this change exists to avoid. One trap, found by driving it. --pad-* is measured against the canvas and consumed against the window, and those differ by the height of the rung ladder, which sits below the viewport rather than over it. A card measured one way and positioned the other is short by exactly one ladder, which put the verdict 40px inside the bottom card. So the docks are measured twice, against both rectangles, and --rail-* is split out from --pad-*: the docks are positioned from the furniture alone, because a dock positioned from a number that includes its own size is a layout that never settles. Measured against the running lab on 2026-09-12, Chromium 1600x1000. chapter: no layer in the DOM, no cards, --rail-* and --pad-* equal. warroom: three cards in three docks, three stylesheets loaded, --pad-r 616 against --rail-r 346. A probe from lab-ubuntu reached rung 5 and the tally read 1/0; the attacker to :22 was denied at rung 4 and the tally read 0/1, which is a denial and not a tick. Folding the readouts rail moved the tape card 984..1244 -> 1276..1536 and the board re-framed. At 820px the cards stack at the foot of the page with no horizontal scroll. Verdict, view controls and the board's gauges clear every card: no overlapping rectangles. The only console error on the page is the favicon 404 it already had. make -C lab check passes: ten new Go tests, and the JavaScript parse check now finds the widget folders rather than carrying a list of them, since a list here would have been the third place to edit and the one nobody remembers.
This was referenced Sep 13, 2026
aenawi
added a commit
that referenced
this pull request
Sep 13, 2026
…rd (#83) The HUD overlay insets itself by whatever the furniture is covering on three sides and by a flat 12px on the fourth. Since #79 gave a theme a bottom dock, that fourth side has something under it: on Warroom the verdict panel was drawn 30px behind the tally card and the transcript — the one line on the board that is a command rather than a rendering of a number — was inside it completely. --pad-b could not simply be added to the padding. It is measured against the window, because the page verdict and the view controls have to clear the rung ladder, and the ladder sits below the viewport rather than over it. The overlay is laid out inside the viewport, so reading --pad-b there clears the ladder a second time and lifts the whole bottom stack a ladder higher than the card it only had to clear by a gap. So publish the number the camera already gets — what covers the board, not what is occupied at the foot of the window — as --pad-b-canvas, and read that. The other three sides need no second token: the viewport starts at the top-left corner of the window, so there the two rectangles agree. Measured in Chrome 147 at 1440x760 on 2026-09-13, Warroom, after a probe: before after tally card #hud-verdict 393-514 393-514 597-700 #hud-transcript 670-698 (inside) 557-585 overlay pad-b 12px 125px The default page is untouched: with nothing docked --pad-b-canvas is 0px and the padding stays at 12px, the transcript at 670-698 exactly as before. replay.html gets the same treatment, where the thing below the board is the transport rather than a card. The gauges do not move. The top row is an `auto` row above a `1fr`, so the padding comes out of the gap; at 625px — one pixel above the height where the layout floats and the question stops applying — the overlay already overflows with the old rule and both rules put the bottom stack in the same place. Closes #81
17 of 26 tasks
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.
Target
develop: the normal case for a fix, a chapter, alab change, a typo
develop→main, shipping to the published guidemain, because published advice iscurrently locking readers out or leaving them exposed
What this changes
The page's furniture was in
index.html, so adding a readout meant editing themarkup, the stylesheet and
app.js— three files, and a fork for anyone whowants a different instrument on the same lab. Now there are four docks around
the board (
top,left,right,bottom), a card in one of them is a widgetfolder under
ui/hud/widgets/, and which card goes where is declared in atheme's
theme.json. The feed made the lab's data shareable; this makes thelayout data too.
Third of the four HUD branches. #77 gave the board a palette, #78 put the lab on
one wire, and this docks cards that read that wire. Record-and-replay is next and
is not in here.
Closes #
Kind
Checks
make -C lab checkpassesgofmtis clean (make checkfails on any output at all)SPDX-License-IdentifierheaderIf this touches the lab or sandbox engine
dangerstill outranksoklab/README.mdmoved with it (atest asserts these against a fixture, so a mismatch will fail loudly)
The engine is untouched — no scores moved. The boxes above are about the new
tallywidget, which is the one thing here that draws a judgement. It countsallowed and denied apart rather than merging them, because "rung 3 allowed eleven
and denied none" is a finding and "rung 3 answered eleven times" is not. A rung
nothing reached shows a zero and no bar, not an empty bar that reads as a
measurement. Driven against the lab: the attacker to
lab-vps:22was denied atrung 4 and the card read
0 / 1.What a widget is handed
A div, the feed, and the options its placement carried. No board object, no page
elements, no controller.
hud/scene.jsis twenty-five coupled methods over 1300lines of three.js and publishing it as a plugin API would freeze it the day
somebody wrote against it.
The board is therefore not a widget in this change, and that is deliberate
rather than unfinished:
app.jsstill creates and drives it, and the docks' partis reporting what they cover so the camera frames the diagram in the gap the
cards leave. Making it a real widget under the contract above means the board
reading the feed instead of being called — next branch's work. A privileged
contract handing one widget the board object would be the plugin API this change
exists to avoid.
The trap this hit
--pad-*is measured against the canvas and consumed against the window, andthose differ by the height of the rung ladder, which sits below the viewport
rather than over it. A card measured one way and positioned the other is short by
exactly one ladder, which put the verdict 40px inside the bottom card. Fixed by
measuring the docks against both rectangles, and by splitting
--rail-*out of--pad-*: the docks are positioned from the furniture alone, because a dockpositioned from a number that includes its own size is a layout that never
settles.
Measured, not expected
Against the running lab on 2026-09-12, Chromium at 1600x1000, control rebuilt
with
docker compose up -d --build --no-deps control:Two things changed because of what driving it showed, not because of what the
type list said. The tape hid only
statat first; with the three-second pollsin, one probe put nineteen lines on the tape and two of them were the probe, so
statusandnetcheckare hidden by default too andhide: []puts them back.And an empty tape card read as a broken one, so it says what it is waiting for
until there is a frame to replace the line with.
evil-boxwas already running when I started and I did not stop or startanything. Only the
controlcontainer was rebuilt; the lab's machines were nottouched.
Tests
Ten new Go tests. The two that matter:
theme.jsonresolves against the registry —the failure it catches is silent in a browser, because a theme naming a widget
that is not there gets a layout with that card quietly missing
widget.cssis anchored to.card[data-widget="<id>"]— the same test, and the same reason, as the one holding a theme to the nine
--board-*propertiesThe JavaScript parse check now finds the widget folders rather than carrying a
list of them. A list there would have been the third place to edit when adding a
widget, and the one nobody remembers — and an unparsed widget is a card that says
"did not load" with the reason only in the console.
Licence
CC BY-SA 4.0 (guide) or GPL-3.0-or-later (code), per the file I touched