UX testing strategy — dimension-derived grids, human benchmarks, automated expansion

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.

1 · The idea in one paragraph

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.

2 · Where we stand — the tooling we already have

PieceWhat it isState
kflowFlight recorder + retest grid (/kflow-admin): checkpoints, run ids, named journeys, test:start/test:verdictLive (backend). Frontend not wired in yet — this is the reporting home for UX tests
katrunBackend scenario robot (Alice/Bob scripts, real sockets), 2 continuous loops reporting to kflowLive. The pattern UX automation should copy
boardDiag / kdiagFrontend breadcrumbs (board:paint:moves, board:organic:solve, layout-change traces…) — built during the scatter chase, kept deliberatelyLive. These are the "commentary" a manual benchmark captures
Demo fixtureboard.php?demo=1 — full playable board, fake socket, deterministic reducer, no backendLive. Perfect substrate for pure-frontend classes (fast, isolated)
Playwright harnessThis 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 pushWorking but in a session scratchpad — needs promoting to a versioned qa/ suite
XPROOF headless WCWalletConnect wallet-side bot (pair + auto-sign) — closes the signing leg no browser test canExists in the xproof project; wiring = QA-WCTEST1 option 2
🧪 zone-walls overlayDraws the physics constraint geometry the sim actually enforcesLive. Note: this is a test oracle as much as a debug aid — assertions can read the same functions
lab2/lab3 baselinesPinned known-good checkouts for side-by-side comparisonLive, manual use
kMonBlack-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.

3 · The industry frame — and where we deliberately deviate

The parts of standard practice worth adopting wholesale

Where we break from the classical mould (the AI-era adjustments)

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:

4 · The model: classes → dimensions → instances → benchmarks → canaries

TermDefinitionExample
ClassOne 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"
DimensionAn axis whose value could change the outcome, with few named values (partitions) and marked boundariesplayers-on-board: 2 / 3 / 6=cap / 7=wedges
InstanceOne concrete vector of dimension values, machine-derived (pairwise + boundaries + constraints), stably namedCHIPDROP-04 = 7 players · portrait · phone · →hidden · FT · scroll-mid-connections · shelves
BenchmarkA blessed manual run of an instance: scripted steps + kdiag trace + screenshots + your verdict "OK". The class's reference for what right looks likeyour tablet pass of CHIPDROP-01, recorded
CanaryAn 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.

5 · Worked example — the CHIPDROP class

5.1 Oracle (what "OK" means, checkable)

5.2 Dimensions (with boundaries and constraints)

DimValuesWhy / boundaries
P · players on board2 · 3 · 6 (=maxSegments cap) · 7 (wedge dividers appear)segment angle (obtuse→acute) changes the physics; cap crossing changes the DOM structure
A · own accounts1 · 22 enables the own→own self-transfer path
O · aspectportrait · landscape · squaresquare≈circle hid the oval-walls bug; extremes stress the safe rect
D · device/inputdesktop-mouse · tablet-touch · phone-touchdrag vs tap-to-offer paths; hit-target sizes; dvh/banner behaviour
T · target relationown→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 kindFT slice (pile remains) · whole pile · NFTthree different choreographies (3-point line · pile vanishes · arc flight, no pile segment)
M · motion statestatic · after you-at-bottom rotate · connections drawn, then wheel-scrollthe "lines must survive motion" class of bugs lives here
L · layoutshelves · organic · organic+stickydifferent 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.

5.3 A derived instance set (illustrative pairwise sample — the generator produces the full ~25)

IdPAODTKMLRun
CHIPDROP-0121portraittablet→adjacentFT slicestaticorganic🖐 MANUAL — the everyday case; first benchmark
CHIPDROP-0232landscapedesktopown→ownNFTstaticshelves🖐 MANUAL — self-transfer + the NFT arc flight
CHIPDROP-0361squaredesktop→oppositewhole pilestaticorganicAUTO
CHIPDROP-0472portraitphone→hiddenFT sliceconnections + scrollshelves🖐 MANUAL — the hardest interaction; boundary-heavy
CHIPDROP-0571landscapetablet→adjacentNFTafter rotateorganic+sticky🖐 MANUAL — sticky feel + rotation
CHIPDROP-0622squarephoneown→ownwhole pileafter rotateorganicAUTO
CHIPDROP-0731portraitdesktop→oppositewhole pileafter rotateorganic+stickyAUTO
CHIPDROP-0862landscapephoneown→ownFT sliceconnections + scrollorganicAUTO
CHIPDROP-0971squaretablet→oppositewhole pilestaticorganic+stickyAUTO
CHIPDROP-1021landscapephone→adjacentwhole pileafter rotateshelvesAUTO
CHIPDROP-1132squaretablet→adjacentNFTconnections + scrollorganicAUTO
CHIPDROP-1261portraitdesktop→hidden*NFTstaticshelvesAUTO · *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.

6 · The regression catalogue — history as canaries

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 itCanary assertionStatus
Cosmetic-change stability — visual-state churn must never move layoutPickable scatter (4 rounds, 2026-07-15)toggle pickable/lock/selection → 0 of N chips moveautomated this week (scratchpad)
Geometry truth — enforced walls = visible shapesCircular walls vs oval rim (2026-07-16)basket chips fully inside rim ellipse, store chips fully outside, at portrait AND landscapeautomated this week
Visible-area containmentChips off-screen / under banner/footer (BOARD-SAFEAREA1)0 chips outside safe rect in any stateautomated this week
Attached-artifact tracking — lines/badges follow their chipsStale dotted lines (2026-07-16); ghost alignmenttrail/trade-line endpoints within 4px of chip centres after forced motionautomated this week
Settle discipline — animation must terminateJitter reports (2026-07-16)max movement 0.0px/frame over 30 frames after settle, every modeautomated this week
Paint completeness — everything placed every renderGhost NFTs vanishing / spotlight top-left wipe (rebind bug)0 unplaced elements, 0 at top-left, ghosts+NFT ghosts present, incl. mid/post-replayautomated this week
Hydration click-integrity — async load must not move click targetsSIGN-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 presenceGM-TOAST-X1 (toast ✕ invisible)every toast shows a visible close ✕to build (verify-only item in HANDOFF)
Choreography origins — flights start where the thing wasNFT "flew from nowhere" (ghost origin off-screen); kinked trailsflight start-rect within Npx of the chip's last painted rectto build
Width/format regressionsSIGN-CARDWIDTH1 (cards went narrow around the protobuf change)key containers ≥ expected % of modal width at desktopto 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.

7 · Harness & reporting — proposal and choices

The shape (recommended)

The choices for you to pick (A/B/C are not mutually exclusive)

A · Static pairwise suite — generator produces the ~25 instances per class once; they're reviewed, committed, and run verbatim until a dimension changes. Predictable, diffable, cheap; the classical choice done with modern tooling.
B · Agent-generated instances per run — each nightly run, the agent derives a fresh valid combination set from the dimension file (different seeds), so over weeks the coverage sweeps far beyond pairwise. Catches 3-way+ interactions pairwise misses; slightly nondeterministic (a red needs its vector recorded to reproduce — which the kflow verdict carries anyway).
C · Vision-assert benchmarks — for the MANUAL-flavoured oracles, a vision model compares run screenshots against your blessed benchmark and answers "materially different?" — escalating diffs to you rather than judging. Automates the fuzzy half that hard assertions can't reach; the model is a change-detector, never the authority.

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.

8 · Proposed next steps (each small)

  1. Bank the existing checks (sand/stone): move this week's five scratchpad harnesses into qa/ on the frontend repo with a one-command runner. Instant regression cover for the physics board.
  2. First class file (stone): qa/cases/chipdrop.json per §5 + the pairwise generator (small, constraint-aware). You review the derived ~25 and the 4 picks.
  3. kflow wiring (stone): test:start/test:verdict from the runner; confirm cells appear on the lab grid. (Depends on nothing frontend-specific — the checkpoint API already exists.)
  4. First benchmark recording (stone, with you): CHIPDROP-01 on your tablet against lab1, kdiag + screenshots captured and blessed.
  5. Hydration click-integrity canary (stone, HIGH): the SIGN-LAYOUTJUMP1 bounding-box watch — the one category whose failure class is dangerous rather than cosmetic.
  6. Then: grow classes (SIGNING, ROTATION, SETTINGS-PERSISTENCE…) at the rate reality demands — each new incident lands in a category and leaves a canary.

9 · Task cross-references

TaskRelation
QA-UXGRID1this document — the strategy + its execution
QA-TESTDIM1the dimension registry this derives from (doing) — stays the human-readable checklist
TESTINGthe umbrella strategy boulder (review) — this is a concrete resolution of its frontend half
TESTLOOP1 / KATRUN1 / KATRUN-KFLOW1the backend loop + kflow bridge this copies the pattern from
QA-WCTEST1headless-WC wallet bot — the signing leg for e2e classes
KFLOW1 / KFLOW-MARKERS1the reporting substrate (and the marker-coverage fix that strengthens the grid)
SIGN-LAYOUTJUMP1becomes the first hydration click-integrity canary (§6)
BOARD-ARRANGE1upcoming 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.