The screen does what you tell it — three Display items, and a fourth declined

Reviewed with Jesse out of TODO.md's Display section. Stays in the
unshipped v0.7.9.

#17 (hiding the Division map) was DECLINED, and the reason is that its
premise had already died. Gitea#18 replaced the wrapped layout with a
single row, and the reason to fold the map away was that it GREW — a
horseshoe of three or a square of four pushed the board off the screen.
One row is boardH = PAD * 2 + CH + 30: 150px, fixed, at every seat
count. That is not worth a control, three states and a persisted
preference. It was a sixth member of the drawing pass that got closed
with Gitea#18 and stayed open only because it reads as a control
question rather than a drawing one — recorded in TODO.md as an explicit
decision, with the design that had already been worked out kept, and
with the one thing that would justify reopening it: the map growing
again.

#16 THE OFFICE AREA'S AUTO-HIDE COULD NOT REACH EVERY STATE. One button
cycling auto -> pinned -> auto, where the pin was `open ? 'closed' :
'open'` and `open` is what auto is doing AT THAT MOMENT. So the pin a
press offered depended on the phase, and going from always-show to
always-hide meant clicking back to auto, waiting for the phase to turn
over, and clicking again. Three controls now, one per mode. The labels
still say what pressing DOES, which was an earlier deliberate fix; what
the cycle could not do was report the state it was in, and aria-pressed
carries that now.

They are addressed by id rather than queried off the container, and
that is testability rather than style: the stub DOM the web suite runs
against only models markup the page WROTE, so a child query finds
nothing and the control would have shipped green and unexercised. The
test presses always-show to always-hide directly — the transition the
cycle could not make.

#23 THE HISTORY READS NEWEST FIRST. Jesse: "the top line is the most
recent and the further down you go, the older the entry." The phase
headings now trail the lines they announce, ruled acceptable rather
than overlooked: "stage changes will be beneath (prior to / older than)
the following events. That is OK." Reading down is reading backwards.
Grouping by phase and reversing the groups was offered and declined as
more machinery than the complaint needs. replays.ts keeps its
oldest-first log deliberately — it is paired with a frame stepper,
where newest-first would fight the stepping. The slice(-60) cap is
untouched and stays open.

#28 THE SETTINGS MOVED INTO A CARD. The top line carried six things and
now carries four: Revenue, the objective, the collision counts and the
game code. The rest is a This Game card at the foot of the right-hand
column, folded by default. Nothing new travels for it — configFromFrame
already existed and main.ts already called it three times, so
rulesListHtml(configFromFrame(f), ...) needed no refactor, and the card
draws from the same renderer as the lobby's join preview so the two
cannot drift.

THE COLLISION COUNTS ARE NEW ON THE BOARD, NOT MOVED. The Frame has
carried collisionsToday and collisionsTotal since v0.7.0 and nothing
drew them, so the one victory condition that ends a game EARLY ran
invisibly — the second time this release that the Frame had the answer
and the view never asked (see #43's actingPlayer). They stay on the top
line while the limits go in the card: a limit is agreed to once, "2 of
3 today" changes how you play the next Stage.

One stub gap closed to get here: none of the five element factories in
test/web.test.ts had setAttribute, so the first render threw and any
control reporting state through ARIA was untestable.

WHAT IS NOT VERIFIED: the layout. There is no browser on this box, so
nothing has confirmed the segmented control, the card or the reversed
panel look right on screen. The logic is tested; the appearance is not,
and wants the next play session.

881 tests pass, fourteen new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YTaNBL1jVxNqgFdjHkHoo3
This commit is contained in:
Jesse.Markowitz
2026-08-30 18:16:08 -04:00
co-authored by Claude Opus 5
parent 31b942cc38
commit d267f89a82
5 changed files with 576 additions and 80 deletions
+121 -22
View File
@@ -15,7 +15,14 @@ Display were still open `- [ ]` although Next already recorded them as supersede
shipped in v0.7.9. **An item closed in one place and left open in another is worse than no entry at
all**, because it reads as live work. So: Next is now grouped by what each item is WAITING ON, the
closed entries moved to a "Settled" block at the end of it, and the five dead Division-map drawing
items collapsed into one that keeps only the two ideas which outlived them. 65 open items became 57.
items collapsed into one that keeps only the two ideas which outlived them.
**Gitea#18 turned out to have answered a sixth item nobody had connected to it** — #17, hiding the
Division map — found by reviewing the queue with Jesse rather than by reading the file. Worth the
general lesson: **when one change supersedes a batch of items, the batch is rarely the whole list.**
#17 survived because it reads as a control question while the other five read as drawing questions,
so it was not in the group that got closed with them. 65 open items became 56, and 53 once #16, #23
and #28 shipped the same day.
Earlier passes, kept: **Display** was split out of Other on 2026-08-22, when a session at the board
produced seven items about the screen rather than the rules. Reorganized 2026-08-20 from a flat list.
@@ -93,25 +100,39 @@ ever meeting them.
### The screen — v0.8.0 candidates
All six were raised by Jesse from play and all six live in **Display** below, where the reasoning is.
Listed here in rough increasing cost. Two of them entangle with Gitea#20; see the note under each.
Raised by Jesse from play; the reasoning for each lives in **Display** below. **Three shipped
2026-08-30 in v0.7.9** — 28, 16 and 23, each with the decision that settled it recorded under it.
**One was declined** — 17. **Two remain, and both entangle with Gitea#20** rather than being display
work at all; see 15 and 18, which is why v0.8.0 is not finished by this batch.
28. **The game's settings belong in a card, not along the top line** — and show ALL of them, not the
four that fit. **Cheapest of the six**: nothing new has to be sent (`Frame` gained `mode` and
`optionalRules` in v0.7.0) and the renderer already exists — `rulesListHtml` (`settings-form.ts`)
draws exactly this list for the join preview and the seating screen.
28. ~~**The game's settings belong in a card, not along the top line.**~~ — done 2026-08-30 in
v0.7.9. A **This Game** card at the foot of the right-hand column, folded by default; the top
line keeps Revenue, the objective, the collision counts and the game code (Jesse's call). It
turned out cheaper than this entry said: `configFromFrame` already existed and `main.ts` already
called it three times, so `rulesListHtml(configFromFrame(f), …)` needed no refactor at all.
**The collision counts are new on the board rather than moved** — the Frame has carried them
since v0.7.0 and nothing drew them.
16. **Three explicit display options for the Office map** — always hidden, always on, auto-hide. All
three modes already exist (`districtMode`, persisted); only the BUTTON is a cycle, and it cannot
reach every one of them.
16. ~~**Three explicit display options for the Office map.**~~ — done 2026-08-30 in v0.7.9. A
segmented control, one button per mode, replacing the cycle. **The buttons are addressed by id
rather than queried off the container**, because the web suite's stub DOM only models markup the
page writes — a child query finds nothing there, and the control would have shipped green and
unexercised.
17. **The same three options for the Division map**, which today cannot be hidden at all. `auto`
there means something different and useful — hide it now, bring it back at the end of the phase —
so it is a one-shot with an expiry rather than a standing rule, and probably should not share an
implementation with #16.
17. ~~**The same three options for the Division map.**~~ — **DECLINED 2026-08-30 (Jesse): "we are
not going to hide the division map anymore."** Its premise died with Gitea#18 and nobody noticed,
because it reads as a control question rather than a drawing one and so was not on the list of
five that were closed with it. Reasoning in Display below.
23. **History: newest at the top?** Reversing is nearly free; the cap (`slice(-60)`) and the forward-
reading phase headings are not. Carries the timestamps question from #14. **Weaker if #15 lands.**
23. ~~**History: newest at the top.**~~ — done 2026-08-30 in v0.7.9. Flat reversal, `scrollTop = 0`,
the start marker last. **The phase headings now trail their lines and Jesse ruled that acceptable**
— "stage changes will be beneath (prior to / older than) the following events. That is OK."
Grouping by phase and reversing the groups was declined as more machinery than the complaint
needs. `replays.ts` deliberately keeps its oldest-first log: it is paired with a frame stepper,
where newest-first would fight the stepping.
**Two things this did NOT do**, both still open: the `slice(-60)` cap still ends scrollback at
sixty lines whichever way the panel runs, and the timestamps question from #14 is untouched.
15. **INVESTIGATE: a "most recent action" line under the status block.** The text already exists and
is already correct — this is placement, not content. **Overlaps #13 and should be decided with
@@ -1430,8 +1451,22 @@ What is on the screen and where. Split out of Other 2026-08-22; the rules are el
your own last turn ended) answers that better. They may both be right, and one may make the
other pointless; deciding them separately risks building both and needing neither.
- [ ] **INVESTIGATE: three explicit display options for the Office map — always hidden, always on,
auto-hide.** Raised by Jesse 2026-08-22.
- [x] **~~INVESTIGATE: three explicit display options for the Office map — always hidden, always on,
auto-hide.~~** — done 2026-08-30 in v0.7.9 (`Next` #16). A segmented control, one button per
mode, `aria-pressed` on the lit one. The investigation below was right about the cause and
right about the fix; what it did not foresee is the testing trap.
**THE BUTTONS ARE ADDRESSED BY ID (`#dm-auto` / `#dm-open` / `#dm-closed`), NOT QUERIED OFF
THE CONTAINER — and that is worth knowing before the next control like this.** The web suite
runs against a stub DOM whose `querySelectorAll` reads the element's own `innerHTML`, so it
only ever sees markup THE PAGE WROTE. This control lives in `play.html`, so a child query
returns nothing there, the wiring loop does nothing, and the whole control ships green and
completely unexercised. Addressing by id also puts every button under the "asks the page for
no element its page lacks" check.
**A second gap closed on the way:** none of the five element factories in `test/web.test.ts`
had `setAttribute`, so the first render threw. Any control reporting its state through ARIA
was untestable until they got an attribute bag. Original note below.
**All three modes already exist.** `districtMode` is `'auto' | 'open' | 'closed'`, persisted to
`localStorage` with the sound and zoom settings (`main.ts`). Nothing needs adding to the model.
@@ -1448,8 +1483,34 @@ What is on the screen and where. Split out of Other 2026-08-22; the rules are el
("always showing — click for auto-hide") rather than what the panel is currently doing, which
was a deliberate fix and should survive whatever replaces the cycle.
- [ ] **INVESTIGATE: the same three options for the Division map, where `auto` means something
different.** Raised by Jesse 2026-08-22.
- [x] **~~INVESTIGATE: the same three options for the Division map, where `auto` means something
different.~~** — **DECLINED 2026-08-30.** Jesse: "we are not going to hide the division map
anymore."
**Gitea#18 answered this one too, and it was missed.** It was raised 2026-08-22, four days
before that issue closed, and it is really a sixth member of the drawing pass settled below —
it just reads as a control question rather than a drawing one, so it stayed open when the other
five were closed. **The reason to fold the map away was that it grew.** A horseshoe of three or
a square of four was tall enough to push the board off the screen, which is what made "hide it
while I switch" worth a control. A single row is `boardH = PAD * 2 + CH + 30` — **150px, fixed,
at every seat count** (`board-svg.ts`) — and a 150px strip is not worth a control, three states
and a persisted preference. It also scrolls and zooms rather than reflowing, so it costs the
same at one seat as at four.
**What it would have cost, recorded because the design was worked out before it was declined:**
the Division's `auto` is not a third MODE at all. It is two sticky pins plus a transient — "hide
for now", expiring when `f.phaseKey` moves — so it needed `divisionMode: 'open' | 'closed'`
persisted beside `districtMode`, plus a `hiddenDuringPhase` that clears itself. Deliberately not
shared with `districtMode`, whose `auto` is a standing rule keyed on `FOCUS_PHASES`. **And a
folded Division needed a summary line written for it** (Jesse's call, 2026-08-30, before the
decline): `.folded` on `#district` hides `#grid` and `.districtrule` but keeps
`#districtsummary`, so the house idiom is that a folded panel still says something, and the
Division has no such line today.
**If it ever comes back, this is what to check first:** whether the map has started growing
again. That, not the control, is the thing that would justify it.
Original note below.
**Today it cannot be hidden at all.** `#division` is a plain `<div>` in an unnamed `<section>`
in `play.html` with no toggle and no fold rule — `#district` has `.folded` styling and a button,
@@ -1491,7 +1552,28 @@ What is on the screen and where. Split out of Other 2026-08-22; the rules are el
will want it off. Whatever this becomes probably needs a speed control, or to scale with whether
anything actually happened in the phase.
- [ ] **INVESTIGATE: the game's settings belong in a card, not along the top line.** Raised by Jesse
- [x] **~~INVESTIGATE: the game's settings belong in a card, not along the top line.~~** — done
2026-08-30 in v0.7.9 (`Next` #28). A **This Game** card at the foot of the right-hand column,
folded by default and persisted with the other display preferences.
**The open questions below were answered by Jesse, 2026-08-30.** Which of the six stay on the
top line: Revenue, the objective and the game code — *plus the collision counts*, which were
not on it at all. Whether the card folds: yes, like `#district`, with a summary line that
survives folding. Where the collision counts belong: the top line, because a limit is a setting
agreed to once and "2 of 3 today" is a number that changes how you play the next Stage.
**Cheaper than this entry estimated.** It says the renderer already exists; what it misses is
that `configFromFrame` also already exists, is exported, and `main.ts` already called it three
times — so `rulesListHtml(configFromFrame(f), f.players.length, f.days)` needed no refactor
whatsoever. The card is markup plus one call.
**The collision counts were the real find.** The Frame has carried `collisionsToday` and
`collisionsTotal` since v0.7.0 and NOTHING ON THE BOARD DREW THEM, so the one victory condition
that ends a game early ran invisibly — the same shape as #43's `actingPlayer`, and the second
time in one release that the Frame had the answer and the view never asked. Original note
below.
**The original note.** Raised by Jesse
2026-08-23, playing the v0.7.0 build: "the game-specific information in the very top line should
probably be a card like Facilities, timetable or blocked. Off on the side, we can give complete
information about all the game options and not take up valuable real estate at the top of the
@@ -1574,7 +1656,24 @@ What is on the screen and where. Split out of Other 2026-08-22; the rules are el
must not go. Jesse called it definitively superseded, 2026-08-26.
- [ ] **INVESTIGATE: history newest-at-the-top, and timestamps on it.** Raised by Jesse 2026-08-22.
- [x] **~~INVESTIGATE: history newest-at-the-top~~, and timestamps on it.** — the ORDER is done
2026-08-30 in v0.7.9 (`Next` #23); **the timestamps half is still open and still #14.**
Flat reversal, `scrollTop = 0`, the start marker last. Jesse ruled directly on the phase
headings, which the note below calls the thing that is not free: they now trail the lines they
announce, and "stage changes will be beneath (prior to / older than) the following events.
That is OK." Reading down the panel is reading backwards in time. Grouping by phase and
reversing the groups was offered and declined as more machinery than the complaint needs.
**The `slice(-60)` cap is untouched and is the other half of this note** — scrollback still
ends at sixty lines whichever way the panel runs. Worth reopening on its own if anyone ever
tries to scroll back and cannot.
`replays.ts` deliberately keeps its oldest-first log: it is paired with a frame stepper, where
"what just happened" is the step you have this moment clicked, so newest-first would fight the
stepping. Original note below.
**Raised by Jesse 2026-08-22.**
**Timestamps** are item 14 above — the same question, and it should be answered once. Whether
the history DISPLAYS a time is downstream of whether one is recorded at all, and of the sidecar