Task QA-UXGRID1 · drafted 2026-07-16 from the user's brief (post BOARD-PHYSICS1 plateau 3115272) · builds on the QA-TESTDIM1 dimension registry (dimensions.html) and the QA-OVERVIEW machinery (kflow / katrun / retest grid). Plain language first, jargon in brackets — same convention as QA-OVERVIEW.
We stop writing test cases one by one. Instead we define, per behaviour we care about, a test class (what's being proven, and how "OK" is judged) and a set of dimensions (the axes that could change the outcome: player count, aspect ratio, device, what's being dropped where…). Individual test instances are then derived from dimension combinations — never hand-enumerated. Because full combination counts explode ("a stupid crazy number"), we use the industry's standard trick (pairwise testing, §3) to shrink thousands of combinations to dozens that still catch almost everything, plus the known turning points (boundary values) that history says matter — e.g. player count at exactly the ring cap, and square-vs-portrait aspect (a square screen is what hid the circular-walls bug for weeks). A human (you) runs a cherry-picked handful of instances per class and blesses them — the recorded kdiag trace + screenshots become the class benchmark ("this is expected behaviour"). The expanded set runs automated, replays the same steps across the other combinations, checks itself against hard assertions + the benchmark, and reports pass/fail into kflow — the same retest grid the backend robots already report to.
| Piece | What it is | State |
|---|---|---|
| kflow | Flight recorder + retest grid (/kflow-admin): checkpoints, run ids, named journeys, test:start/test:verdict | Live (backend). Frontend not wired in yet — this is the reporting home for UX tests |
| katrun | Backend scenario robot (Alice/Bob scripts, real sockets), 2 continuous loops reporting to kflow | Live. The pattern UX automation should copy |
| boardDiag / kdiag | Frontend breadcrumbs (board:paint:moves, board:organic:solve, layout-change traces…) — built during the scatter chase, kept deliberately | Live. These are the "commentary" a manual benchmark captures |
| Demo fixture | board.php?demo=1 — full playable board, fake socket, deterministic reducer, no backend | Live. Perfect substrate for pure-frontend classes (fast, isolated) |
| Playwright harness | This week's ad-hoc checks (chromium at /opt/kwallet): overlap audit, oval-wall invariants, jitter watch, trail-tracking check, pickable-moves-nothing check — each verified a live fix headlessly before push | Working but in a session scratchpad — needs promoting to a versioned qa/ suite |
| XPROOF headless WC | WalletConnect wallet-side bot (pair + auto-sign) — closes the signing leg no browser test can | Exists in the xproof project; wiring = QA-WCTEST1 option 2 |
| 🧪 zone-walls overlay | Draws the physics constraint geometry the sim actually enforces | Live. Note: this is a test oracle as much as a debug aid — assertions can read the same functions |
| lab2/lab3 baselines | Pinned known-good checkouts for side-by-side comparison | Live, manual use |
| kMon | Black-box uptime monitor (separate box) | Backlog (KMON1) — out of scope here |
The striking gap: everything below the browser is systematised; everything inside it is artisanal. The pieces exist — they've just never been joined into a repeatable, reported loop.
maxSegments vs one over
(wedge dividers appear), obtuse vs acute segment angle (the ±80° clamp), square vs non-square aspect (hid the circular-walls bug),
0-ghost vs 1-ghost stores (band sizing). Every dimension below marks its boundaries — they're always in the picked set.Classical UX automation assumes tests are expensive to write and brittle to keep — whole industries (page-object patterns, selector-maintenance teams) exist to manage that cost. That assumption is now false here: this week, every bug fix shipped with a purpose-built headless test written in minutes by the agent that wrote the fix (overlap audit, oval invariants, trail tracking…). Three consequences we lean into:
| Term | Definition | Example |
|---|---|---|
| Class | One user-meaningful behaviour + its oracle (the checkable definition of OK: hard assertions, expected kdiag shape, benchmark screenshots) | CHIPDROP — "offering an asset from one account to another renders correctly end-to-end" |
| Dimension | An axis whose value could change the outcome, with few named values (partitions) and marked boundaries | players-on-board: 2 / 3 / 6=cap / 7=wedges |
| Instance | One concrete vector of dimension values, machine-derived (pairwise + boundaries + constraints), stably named | CHIPDROP-04 = 7 players · portrait · phone · →hidden · FT · scroll-mid-connections · shelves |
| Benchmark | A blessed manual run of an instance: scripted steps + kdiag trace + screenshots + your verdict "OK". The class's reference for what right looks like | your tablet pass of CHIPDROP-01, recorded |
| Canary | An instance (often born from an incident) that runs continuously and reports to kflow forever | "pickable toggle moves 0 chips" — already exists in this week's harness |
The lifecycle: a class is defined → the generator derives instances → you run + bless the picks (benchmarks) → the automated expansion replays the rest → all verdicts land on the kflow grid → any incident adds a canary instance to the class. "Something surprises you → pin it down → leave a robot watching" — QA-OVERVIEW's loop, now for the UI.
boardRimAt/boardWallClamp functions the physics uses — the walls overlay made these an oracle); a ghost sits on the giver's
rim band; pile→ghost→basket endpoints stay within 4px of their chips through and after all motion; giver's store balance decrements;
solid-chip invariant (0 overlapping pairs at settle); jitter 0 after settle; no console errors; board:paint:moves goes quiet.| Dim | Values | Why / boundaries |
|---|---|---|
| P · players on board | 2 · 3 · 6 (=maxSegments cap) · 7 (wedge dividers appear) | segment angle (obtuse→acute) changes the physics; cap crossing changes the DOM structure |
| A · own accounts | 1 · 2 | 2 enables the own→own self-transfer path |
| O · aspect | portrait · landscape · square | square≈circle hid the oval-walls bug; extremes stress the safe rect |
| D · device/input | desktop-mouse · tablet-touch · phone-touch | drag vs tap-to-offer paths; hit-target sizes; dvh/banner behaviour |
| T · target relation | own→own · →adjacent player · →opposite side · →hidden (wheel-scroll first) | travel distance changes the flight + line geometry; hidden requires the scroll interaction mid-flow. Constraint: →hidden only valid when P=7; own→own only when A=2 |
| K · asset kind | FT slice (pile remains) · whole pile · NFT | three different choreographies (3-point line · pile vanishes · arc flight, no pile segment) |
| M · motion state | static · after you-at-bottom rotate · connections drawn, then wheel-scroll | the "lines must survive motion" class of bugs lives here |
| L · layout | shelves · organic · organic+sticky | different solvers; sticky changes arrival behaviour |
Full cartesian: 4·2·3·3·4·3·3·3 = 7,776. Pairwise with constraints: ≈25. Picked for hand-running: 4.
| Id | P | A | O | D | T | K | M | L | Run |
|---|---|---|---|---|---|---|---|---|---|
| CHIPDROP-01 | 2 | 1 | portrait | tablet | →adjacent | FT slice | static | organic | 🖐 MANUAL — the everyday case; first benchmark |
| CHIPDROP-02 | 3 | 2 | landscape | desktop | own→own | NFT | static | shelves | 🖐 MANUAL — self-transfer + the NFT arc flight |
| CHIPDROP-03 | 6 | 1 | square | desktop | →opposite | whole pile | static | organic | AUTO |
| CHIPDROP-04 | 7 | 2 | portrait | phone | →hidden | FT slice | connections + scroll | shelves | 🖐 MANUAL — the hardest interaction; boundary-heavy |
| CHIPDROP-05 | 7 | 1 | landscape | tablet | →adjacent | NFT | after rotate | organic+sticky | 🖐 MANUAL — sticky feel + rotation |
| CHIPDROP-06 | 2 | 2 | square | phone | own→own | whole pile | after rotate | organic | AUTO |
| CHIPDROP-07 | 3 | 1 | portrait | desktop | →opposite | whole pile | after rotate | organic+sticky | AUTO |
| CHIPDROP-08 | 6 | 2 | landscape | phone | own→own | FT slice | connections + scroll | organic | AUTO |
| CHIPDROP-09 | 7 | 1 | square | tablet | →opposite | whole pile | static | organic+sticky | AUTO |
| CHIPDROP-10 | 2 | 1 | landscape | phone | →adjacent | whole pile | after rotate | shelves | AUTO |
| CHIPDROP-11 | 3 | 2 | square | tablet | →adjacent | NFT | connections + scroll | organic | AUTO |
| CHIPDROP-12 | 6 | 1 | portrait | desktop | →hidden* | NFT | static | shelves | AUTO · *invalid at P=6 — generator substitutes →opposite (constraint demo) |
Note how the manual picks are exactly where machine judgement is weakest: feel of the everyday case, the two signature animations, sticky's game-feel, and the one requiring a mid-flow human gesture sequence. Everything else asserts fine mechanically. Device rows automate as viewport+touch emulation first (Playwright device profiles); real-device passes stay manual until a device farm is ever justified.
Mined from the last month of tasks/incidents. Pattern-named so future bugs slot into a category (and inherit its canary shape):
| Category (the failure class) | The incident that taught it | Canary assertion | Status |
|---|---|---|---|
| Cosmetic-change stability — visual-state churn must never move layout | Pickable scatter (4 rounds, 2026-07-15) | toggle pickable/lock/selection → 0 of N chips move | automated this week (scratchpad) |
| Geometry truth — enforced walls = visible shapes | Circular walls vs oval rim (2026-07-16) | basket chips fully inside rim ellipse, store chips fully outside, at portrait AND landscape | automated this week |
| Visible-area containment | Chips off-screen / under banner/footer (BOARD-SAFEAREA1) | 0 chips outside safe rect in any state | automated this week |
| Attached-artifact tracking — lines/badges follow their chips | Stale dotted lines (2026-07-16); ghost alignment | trail/trade-line endpoints within 4px of chip centres after forced motion | automated this week |
| Settle discipline — animation must terminate | Jitter reports (2026-07-16) | max movement 0.0px/frame over 30 frames after settle, every mode | automated this week |
| Paint completeness — everything placed every render | Ghost NFTs vanishing / spotlight top-left wipe (rebind bug) | 0 unplaced elements, 0 at top-left, ghosts+NFT ghosts present, incl. mid/post-replay | automated this week |
| Hydration click-integrity — async load must not move click targets | SIGN-LAYOUTJUMP1 (Sign click landed on a link — dangerous class) | Sign button bounding-box stable from first paint through enrichment (Playwright bounding-box watch) | to build — task exists, HIGH |
| Affordance presence | GM-TOAST-X1 (toast ✕ invisible) | every toast shows a visible close ✕ | to build (verify-only item in HANDOFF) |
| Choreography origins — flights start where the thing was | NFT "flew from nowhere" (ghost origin off-screen); kinked trails | flight start-rect within Npx of the chip's last painted rect | to build |
| Width/format regressions | SIGN-CARDWIDTH1 (cards went narrow around the protobuf change) | key containers ≥ expected % of modal width at desktop | to build |
Immediate cheap win: six of ten categories already have working checks sitting in a session scratchpad.
Promoting them into a versioned qa/ folder with a one-command runner banks this week's work before it evaporates.
qa/cases/chipdrop.json: oracle spec, dimensions,
values, boundaries, constraints, picked-manual list). The human registry (qa-test-dimensions.html) stays the readable overview and
links to these; the runner and the docs derive from the same file — no drift.?demo=1 for pure-frontend classes; katrun-driven live backend for e2e classes; XPROOF headless-WC bot for signing legs
(QA-WCTEST1). Device dimension via Playwright's device profiles (touch, DPR, viewport).test:start / test:verdict with
{class, instance, commit, seat} — UX results land on the same retest grid as katrun's, no new dashboard.
Red verdicts follow the same alert path QA-OVERVIEW's next-steps call for.qa/benchmarks/<instance>/; blessing = committing it. Automated runs
attach their equivalents to failures so a red cell always carries its evidence.Recommendation: A + C now, B later. A gives the dependable grid; C is what makes the "benchmark" idea pay off (your one manual pass keeps guarding the class after you've moved on); B becomes attractive once A is green and boring. On reporting there's no real choice to make — kflow is built for exactly this and katrun has proven the pattern.
qa/ on the frontend
repo with a one-command runner. Instant regression cover for the physics board.qa/cases/chipdrop.json per §5 + the pairwise generator (small, constraint-aware).
You review the derived ~25 and the 4 picks.test:start/test:verdict from the runner; confirm cells appear on the lab grid.
(Depends on nothing frontend-specific — the checkpoint API already exists.)| Task | Relation |
|---|---|
| QA-UXGRID1 | this document — the strategy + its execution |
| QA-TESTDIM1 | the dimension registry this derives from (doing) — stays the human-readable checklist |
| TESTING | the umbrella strategy boulder (review) — this is a concrete resolution of its frontend half |
| TESTLOOP1 / KATRUN1 / KATRUN-KFLOW1 | the backend loop + kflow bridge this copies the pattern from |
| QA-WCTEST1 | headless-WC wallet bot — the signing leg for e2e classes |
| KFLOW1 / KFLOW-MARKERS1 | the reporting substrate (and the marker-coverage fix that strengthens the grid) |
| SIGN-LAYOUTJUMP1 | becomes the first hydration click-integrity canary (§6) |
| BOARD-ARRANGE1 | upcoming feature whose dimensions (sticky/pinned) already appear in the CHIPDROP grid |
Maintained under QA-UXGRID1 · first draft 2026-07-16, seat lab1 (KATUI2) · sibling of dimensions.html · plain-first tone per QA-OVERVIEW convention.