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