clay-borg/workplans/CB-WP-0020-the-table-you-can-read.md
tegwick b8894e84b5
Some checks failed
ci / check (push) Failing after 3s
Sync hub state for CB-WP-0020
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 20:21:18 +02:00

261 lines
11 KiB
Markdown

---
id: CB-WP-0020
kind: product
title: "The table you can read: the card in your hand, the border that moves, the game that ends"
status: done
state_hub_workstream_id: "ac9dfd59-959b-4023-8749-88d1c20e928b"
---
# Purpose
```
structural tier S (presentation inside an existing capability — no new
port, no canonical interface, no new dependency)
chaos d8 = 8 → OVERRIDE, tier drawn: S
declared tier S (structural S, chaos 8 → S)
```
Declaration 3 of chaos window 2, and **the first override at d8** — it
fired on the second roll at the new rate. It changed nothing, which is
the first half of window 2's retirement condition (*retire if an override
changes nothing twice running*). **One.**
## What the perceptual check found
The maintainer ran it on 2026-08-03. **Item 1 passes** — the resting
affordance reads. Everything else has a defect, and several are defects in
things the tests call green, because no test here can see a layout.
Quoted, because the wording carries the diagnosis:
1. **Pickable is legible.** ✅ Nothing to do.
2. *"the card is picked up but the linebreak vanishes and the card below
does not vanish. So it is a little as if we have two cards now"*
3. *"the upper and left borders of the dashed lines seem to be hidden for
some items. Also for players the rectangle is quite large. Maybe we
should not have a new dashed line but just set the existing borderline
to dashed so that the layout is more stable and less complicated"*
4. *"replacing the content of the picked up card breaks the visual clue
about a card being moved"*
5. *"the orange status line does not change as you describe. It seems to
switch only if i put back a card"*
6. *"I cant follow the actions of the other players too well because they
do not visualize on the board other than by result and the log"*
7. *"maybe should read 'Game finished' if the players have been
successful… There is no button to start a new game"*
## Task: the border moves, nothing else does
```task
id: CB-WP-0020-T01
status: done
priority: high
state_hub_task_id: "cad5d2a7-2d3a-494d-b2ca-4ff765e152c2"
```
**Adopt the maintainer's design instruction**: a legal drop target changes
its **existing border** to dashed and coloured. No second box.
The current `outline: 2px dashed; outline-offset: 3px` draws a *new*
rectangle outside the element. That is why the top and left edges vanish
on some items — an `outline` on an SVG `<g>` is clipped by the SVG
viewport, and on a card it collides with the neighbour's margin. It is
also why a seat's highlight is *"quite large"*: it outlines a whole card
when only the border needed to change.
- HTML targets: restyle `border-style`/`border-color` in place.
- SVG targets: change the existing `stroke`, not an added outline.
**Control:** the marked set must still equal the set Rust emitted
(ADR-0010 D2 property 1) — the mutation that derives targets instead of
reading them must stay red. This is a style change and must not become a
behaviour change.
**Done 2026-08-03.** `.dropok` restyles `border-style`/`border-color` in
place, and SVG targets change their existing `stroke` with a dash array.
No `outline` remains. **A border already in the layout cannot move the
layout**, which is what the instruction was for.
## Task: the thing in your hand looks like a thing in your hand
```task
id: CB-WP-0020-T02
status: done
priority: high
state_hub_task_id: "b754eeab-76c6-4bbe-b470-c83595e1bb2a"
```
Two reports, one cause. The ghost is a `textContent` copy of the card, so
(2) the `<br>` collapses and it reads as a *second card*, and (4) the
explanation **replaces** the label, so the only thing identifying what you
are carrying disappears exactly when you need it.
- The ghost must not impersonate a card. Make it read as a held token:
smaller, distinct, and it may keep the line break.
- The explanation is **additional**, never a replacement. Carrying
*"Attack"* and hovering P2 should show both — the label and *"commit
Attack against P2"*.
- The element left behind must look **left behind**, so there are not two
cards.
**Control:** the description shown must still be the one Rust wrote for
that pair (ADR-0010 D1) — the neighbouring-pair mutation stays red. And a
new one: the ghost must still name what is held when an explanation is
showing, so a mutation that drops the label goes red.
**Done 2026-08-03.** The ghost is a pill, not a card — it cannot be
mistaken for a second card — and the explanation is appended beside the
label rather than replacing it. The left-behind element is dimmed to 0.35
and dashed, so it reads as *left behind*.
The stub grew `innerHTML` so the test can assert **both** are present at
once; it previously could only see that *something* was displayed, which
is why the replacement went unnoticed.
## Task: say why a drop was refused, in words
```task
id: CB-WP-0020-T03
status: done
priority: high
state_hub_task_id: "5150e851-f8f2-4c18-af3f-3bc16dd301ea"
```
Reported: the status line *"seems to switch only if i put back a card"*,
and then reads `action-attack -> action-attack is not a legal move here`.
Two defects.
**The refusal is written in element ids.** `resolve` returns
`"{down} -> {up} is not a legal move here"`, which is the vocabulary of
the DOM, not of the game. A player reads *"you cannot Attack yourself"*.
**And the no-target path apparently never fires.** CB-WP-0018 added
*"took … , let go over nothing droppable"* for a drop on nothing, and the
maintainer did not see it. Find out why before changing anything —
the likely answer is that there is almost no bare background to drop on,
every part of the page being a card, in which case the message is correct
and unreachable, and the fix is elsewhere. **Do not "fix" a message that
already works; reproduce the report first.**
**Done 2026-08-03. Not reproduced, and recorded as not reproduced.** The
harness test passes and the page has real gaps; without a browser I cannot
see it fail.
The likeliest cause is not the message but **which element the browser
reports**: for touch and pen the pointer is implicitly captured to the
`pointerdown` target, so `e.target` on `pointerup` is where you *started*.
That would make every drop look like a drop-on-itself — which is the other
half of the report. `document.elementFromPoint` is now used instead, and
it is **correct under both explanations**.
**The refusal is in the game's words**, not element ids: *"Attack on P2 is
not a move you can make right now"*. A test asserts no `action-` id leaks
into it.
## Task: show what the other players did
```task
id: CB-WP-0020-T04
status: done
priority: medium
state_hub_task_id: "7896a555-3b36-4143-a9b2-fb9184fb1a41"
```
Reported: *"I cant follow the actions of the other players"*.
After Reveal, selections are public (GR-R04) and the seat card **does**
carry them — as `Selection { action: Solve, target: None, problem: Some(1) }`.
That is a `Debug` impl on a player-facing surface, which is the same
defect the log fixed in CB-WP-0018 and did not fix here.
Render a seat's revealed selection in the same words the log uses. Reuse,
do not re-phrase: a second vocabulary drifts from the first.
**Control:** a seat's rendered selection must match the log's phrasing for
the same command, asserted rather than eyeballed.
**Done 2026-08-03.** `selection_words` renders every field that is set.
**The coverage gate failed my first version**, which matched on
`(target, problem)` and showed only the target when both were present:
*"claimed rendered, but absent: selections.\*.problem"*. The aggregate does
not currently produce both — the gate does not care whether the case is
reachable, and it was right not to.
## Task: the game ends in a way you can act on
```task
id: CB-WP-0020-T05
status: done
priority: medium
state_hub_task_id: "0a889c81-aa01-411f-bfb5-02e9558d4a05"
```
Three things, in the order they matter:
- **"game over" is the wrong headline when the group succeeded.** It
should say what happened — the outcome carries `group_success`, so use
it.
- **A new game.** Reported as *"would be nice"*, and it is more than that:
the only way to play twice today is to go back to a terminal, which
makes the browser a strictly worse client than the CLI it was meant to
replace.
- **The scores read 0 and the winner is nobody.** That is very likely the
stand-in dataset, not a scoring bug — GROUND-WP-0002 T01 has now ruled
the edition data authoritative. **Do not fix scoring here.** Confirm the
cause and record it; the import is its own pass.
**Done 2026-08-03.** The headline reads from the outcome: *"the group held
the frame"* / *"did not hold the frame"*, and only says *"game over"* when
there is no outcome to report.
**"Play again" is real, and its first version was useless.** `run_game`
bound a fresh listener per game, so a second game moved to a new port and
left the player's tab pointing at a dead one — the CB-WP-0018 defect
returning by another route. The listener is bound once per **session**
now. `play_again_deals_a_second_game` asserts the second game is a
**different deal**, not merely that one happened.
**The 0 scores and `winners nobody` are the stand-in dataset**, confirmed,
not a scoring bug. Not fixed here.
## Task: evidence
```task
id: CB-WP-0020-T06
status: done
priority: high
state_hub_task_id: "33786601-914e-4557-8a92-11c5d140118d"
```
`evidence/CB-EV-0018-*.md`.
- **What the perceptual check bought this time**, and the running count:
four runs, four sets of defects no test could reach.
- **Which of these the test suite called green**, and whether any of them
*could* have been caught mechanically — for those that could, say why
they were not, and for those that could not, say so plainly rather than
proposing a check that would not work.
- **Chaos: the first override at d8**, and that it changed nothing. That
is one half of window 2's retirement condition; record it as such.
- **Quote CB-WP-0019's cost by re-running the instrument**, per the rule
it wrote.
**Done 2026-08-03.**
[CB-EV-0018](../evidence/CB-EV-0018-the-table-you-can-read.md). `make all`
exits 0.
- **Four human checks, four sets of defects the suite called green.**
Of these seven, **two** could have been caught mechanically and neither
by a check that exists; the other five are about layout and hit-testing
and cannot be reached from here. Saying otherwise would be the error
this project keeps naming.
- **Two defects found by this project's own tests** while fixing the
reported ones: a renderer dropping a field (coverage gate) and "play
again" moving the game to a dead port.
- **Chaos: the first override at d8 changed nothing** — one half of
window 2's retirement condition.
- **CB-WP-0019 settled at $38.54/117** against $34.80/107 last reported.
**Eight for eight**, and the first under 20% — which is what re-running
at quote time is for.