From b12725566b8306ba77f7ffc3c9b718c561562b22 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 6 Aug 2026 22:25:06 +0200 Subject: [PATCH] fix: a click target wearing a drag affordance made the controls look dead Tier S (a fix inside a boundary; chaos d8=7 from the previous roll stands for this continuation). Two observations from play that are ONE defect. `play again`, `end session`, `pass` and the move buttons carried `.pick`, which is cursor:grab. The stylesheet has .btn{cursor:pointer} BEFORE .pick{cursor:grab}, so grab won. A GRAB CURSOR INVITES A DRAG. A drag released over nothing posts nothing, so the player picked up the button, let go, and the page did nothing. It looked dead because the affordance told them to do the one thing that does not work. Reported as two separate things -- "the button shows a hand to pick up that it probably shouldn't" and "I can't start another game or stop the server" -- and the first causes the second. The click path itself was never broken: driving again->again and done->done through the JS harness posts correctly. The logic was fine and the invitation was wrong. Click targets now carry `.tap` -- pointer cursor, same press affordance. This extends CB-WP-0017's rule (interactive and inert must not look identical) to: click and drag must not look identical either. The test asserts both directions, because checking only that buttons lost `.pick` would pass for a page with no affordances at all. Registered F20 (applied) and F21. F21 IS THE ONE I COULD NOT REPRODUCE: dragging did not work until after the first note was saved. Ruled out the plausible mechanisms -- the gesture logic posts correctly against the served page, the drag ghost carries pointer-events:none so it cannot intercept the drop, and the markup is identical before and after since the 303 re-renders the same page from the same state. Remaining candidates are a
toggle shifting layout mid-drag, a first-load timing difference, or browser-level pointer capture. Reproducing it needs a browser, which no test here has -- the same gap F19 named. Recorded as unreproduced rather than given a speculative fix. And the fourth observation is confirmation, not a bug: "drawing my cards from the deck is not implemented, I did not need to do that" is exactly what CB-WP-0028 T04 determined and deliberately did not build. It is the first evidence that importing the card text closed the comprehension gap that produced the earlier click-the-deck request. make all: exit 0. 62 render tests, 26 cb-play. Co-Authored-By: Claude Opus 5 --- crates/cb-render-html/src/doc.rs | 17 +++- crates/cb-render-html/src/lib.rs | 49 ++++++++++ specs/FindingRegister.md | 24 +++++ trials/2026-08-06-2213.md | 17 ++++ trials/2026-08-06-2213.yaml | 149 +++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 trials/2026-08-06-2213.md create mode 100644 trials/2026-08-06-2213.yaml diff --git a/crates/cb-render-html/src/doc.rs b/crates/cb-render-html/src/doc.rs index 00ae0dc..3b7c3e9 100644 --- a/crates/cb-render-html/src/doc.rs +++ b/crates/cb-render-html/src/doc.rs @@ -220,6 +220,15 @@ h1,h2{font-size:1rem;margin:1.2rem 0 .4rem;color:#9cf} .pick{cursor:grab;user-select:none;box-shadow:0 2px 0 #0006,0 0 0 1px #5a7a inset; transition:transform .08s,box-shadow .08s} .pick:hover{box-shadow:0 3px 8px #000a,0 0 0 1px #7ca inset;transform:translateY(-1px)} +/* CB-WP-0017's principle, one step further: a thing you CLICK and a thing + you DRAG must not look identical either. `.pick` promises carry-me- + somewhere; these promise press-me. They wore `.pick` and therefore a + grab cursor, which invited a drag -- and a drag released over nothing + posts nothing, so the button appeared dead. Reported both as the button + showing a hand it should not, AND as being unable to start a new game. */ +.tap{cursor:pointer;user-select:none;box-shadow:0 2px 0 #0006,0 0 0 1px #a7d6 inset; + transition:transform .08s,box-shadow .08s} +.tap:hover{box-shadow:0 3px 8px #000a,0 0 0 1px #a7d inset;transform:translateY(-1px)} /* A legal destination for the thing currently held -- and ONLY for that thing. The set is written by Rust into data-targets; the script matches it and never derives it (ADR-0010 D1). @@ -1062,7 +1071,7 @@ fn move_section( if !spatial { let _ = write!( s, - "
{}
", + "
{}
", esc(&format!("{c:?}")) ); } @@ -1075,7 +1084,7 @@ fn move_section( } if may_pass { s.push_str( - "
pass \u{2014} decline to act
", + "
pass \u{2014} decline to act
", ); } } @@ -1336,8 +1345,8 @@ pub fn ending( let _ = write!( s, "
\ -
play again
\ -
end session \u{2014} stops the game server
\ +
play again
\ +
end session \u{2014} stops the game server
\
", ); log_section(&mut s, log); diff --git a/crates/cb-render-html/src/lib.rs b/crates/cb-render-html/src/lib.rs index a58d230..76db89f 100644 --- a/crates/cb-render-html/src/lib.rs +++ b/crates/cb-render-html/src/lib.rs @@ -1275,6 +1275,8 @@ mod played_cards { /// table and a `play again` pointing at a closed port. #[cfg(test)] mod ending_page { + use cb_kernel::PlayerId; + use crate::{doc, jsrun}; fn page() -> String { @@ -1326,6 +1328,53 @@ mod ending_page { ); } + /// **A thing you click must not look like a thing you drag.** + /// + /// Reported two ways at once: *"the button shows a hand to pick up + /// that it probably shouldn't"* and *"I can't start another game"*. + /// They are one defect — a grab cursor invites a drag, and a drag + /// released over nothing posts nothing, so the button looks dead. + #[test] + fn click_targets_do_not_wear_the_drag_affordance() { + let pages = [ + doc::ending(None, "m", "/command?t=x", &[], &[]), + doc::document( + &crate::testfix::view(Some(PlayerId(0))), + &[games_ground::GroundCommand::SelectAction { + action: games_ground::Action::Investigate, + target: None, + problem: Some(2), + }], + "/command?t=x", + Some(PlayerId(0)), + true, + ), + ]; + for html in &pages { + for key in ["again", "done", "pass"] { + let Some(i) = html.find(&format!("data-drop=\"{key}\"")) else { + continue; + }; + let tag = &html[html[..i].rfind('<').expect("an opening tag")..i]; + assert!( + !tag.contains("pick"), + "`{key}` is a click target wearing `.pick`, which is cursor:grab — \ + it invites a drag, and a drag onto nothing posts nothing" + ); + assert!( + tag.contains("tap"), + "`{key}` must still look pressable: {tag}" + ); + } + } + // The inverse, or this passes for a page with no affordances at + // all: a real draggable still carries `.pick`. + assert!( + pages[1].contains("class=\"card act pick\""), + "action cards must still be draggable" + ); + } + /// **CB-WP-0028 T06, asserted both ways.** A test that only checked /// the win case would pass for a page that always says "solved". #[test] diff --git a/specs/FindingRegister.md b/specs/FindingRegister.md index 6c96ef4..be96283 100644 --- a/specs/FindingRegister.md +++ b/specs/FindingRegister.md @@ -47,6 +47,8 @@ kinds, states and metrics: [`GameDesign.md`](GameDesign.md). Reported by | F17 | degenerate | note | — | — | 2026-08-06 | ground-game | | F18 | inert | raised | — | — | 2026-08-06 | clay-borg | | F19 | degenerate | applied | crates/cb-render-html/src/lib.rs::overhead_table | counterexample | 2026-08-06 | clay-borg | +| F20 | inert | applied | crates/cb-render-html/src/lib.rs::ending_page | counterexample | 2026-08-06 | clay-borg | +| F21 | degenerate | note | — | — | 2026-08-06 | clay-borg | @@ -76,6 +78,28 @@ kinds, states and metrics: [`GameDesign.md`](GameDesign.md). Reported by since GROUND-WP-0005 is blocked on exactly this number. The withdrawal was reported (ADR-0012 D5). Its reproduction is `difficulty.rs`, whose policy panel is plural *because of this finding*. +- **F21 — dragging did not work until after the first note was saved.** + Reported 2026-08-06: *"I could not drag and drop at the beginning but + after I added the first comment it worked."* **`note`, and I could not + reproduce it.** The gesture logic is correct against the served page + (the JS harness posts properly), the ghost carries `pointer-events:none` + so it cannot intercept the drop, and the markup is identical before and + after the note — the 303 re-renders the same page from the same state. + **Candidates, none confirmed:** a `
` toggle inside an action + card shifting the layout mid-drag; a first-load timing difference; or a + browser-level pointer capture. **Reproducing it needs a browser, which + no test here has** — the same gap F19 named. Recorded rather than + guessed at. +- **F20 — a click target wearing a drag affordance made the ending + controls look dead.** `play again`, `end session`, `pass` and the move + buttons carried `.pick`, which is `cursor:grab`. A grab cursor invites a + **drag**, and a drag released over nothing posts nothing — so the button + did nothing and appeared broken. Reported as two separate observations + (*"the button shows a hand to pick up that it probably shouldn't"* and + *"I can't start another game or stop the server"*) which are one defect. + **`applied`**: click targets now carry `.tap`. **This extends CB-WP-0017's + rule** — interactive and inert must not look identical — to *click and + drag must not look identical either*. - **F19 — the engine shipped a table nobody could play on, and every gate was green.** CB-WP-0028's overhead view was 620px tall, so the action cards sat a screen below the Problems; dragging between them was diff --git a/trials/2026-08-06-2213.md b/trials/2026-08-06-2213.md new file mode 100644 index 0000000..ecba877 --- /dev/null +++ b/trials/2026-08-06-2213.md @@ -0,0 +1,17 @@ +# Trial log + +What the player said while playing, bound to the position they said it at +(CB-WP-0027, ADR-0014). The recording beside this file is the position; +`state_hash` is what reaches it. + +**These are raw notes and they stay here.** Nothing in this file travels to +`ground-game` (ADR-0014 D4) — a note reaches them only by being promoted to a +register finding, by a human, with the wording chosen then. + + + +| n | round | step | state_hash | comment | +|---|---|---|---|---| +| 1 | 1 | Select | 47369fdf8d30 | Lets check the notes. | + + diff --git a/trials/2026-08-06-2213.yaml b/trials/2026-08-06-2213.yaml new file mode 100644 index 0000000..4a291e8 --- /dev/null +++ b/trials/2026-08-06-2213.yaml @@ -0,0 +1,149 @@ +scenario: ground/cb-play-session +description: recorded by cb-play (CB-WP-0008 T02) +covers: [] +provisional: false +provisional_owner: '' +provisional_raised: '' +ruled: '' +ruled_by: '' +ruled_note: '' +encodes_u_item: '' +seed: 1 +setup: + players: 3 + preset: standard-3p + patch: {} +commands: +- actor: P1 + cmd: select_action + args: + action: INVESTIGATE + problem: 2 +- actor: P2 + cmd: select_action + args: + action: SOLVE + problem: 1 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 1 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SUPPORT + target: P2 +- actor: P2 + cmd: select_action + args: + action: INVESTIGATE + problem: 3 +- actor: P3 + cmd: select_action + args: + action: INVESTIGATE + problem: 3 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: P2 + cmd: respond_to_support + args: + response: accept_bond +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SOLVE + problem: 3 +- actor: P2 + cmd: select_action + args: + action: INVESTIGATE + problem: 4 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 2 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SUPPORT + target: P3 +- actor: P2 + cmd: select_action + args: + action: SUPPORT + target: P1 +- actor: P3 + cmd: select_action + args: + action: SOLVE + problem: 4 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: P3 + cmd: respond_to_support + args: + response: accept_bond +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +- actor: P1 + cmd: select_action + args: + action: SUPPORT + target: P2 +- actor: P2 + cmd: select_action + args: + action: SUPPORT + target: P1 +- actor: P3 + cmd: select_action + args: + action: SUPPORT + target: P1 +- actor: SYSTEM + cmd: reveal + args: {} +- actor: SYSTEM + cmd: resolve + args: {} +- actor: SYSTEM + cmd: end_round + args: {} +expect: + events: [] + state: {} + rejects: [] + state_hash: e2ee858a1b907a4595c230747b65eede6c56574308a7ece7dec8ebe30e56fc10