Skip to content

test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed - #442

Merged
gitrgoliveira merged 41 commits into
mainfrom
bc-e2eh-operator-review
Oct 13, 2026
Merged

gitrgoliveira merged 41 commits into
mainfrom
bc-e2eh-operator-review

Conversation

@gitrgoliveira

@gitrgoliveira gitrgoliveira commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Summary

Which PR closes which bead

  • This PR (test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442) closes eight: bc-e2eh (the harness), mp-yqxn.2 (the review), bc-shcr (the shared-shiaijo journey), bc-mvsj (the moved-match journey), bc-ltap, bc-sdtp and bc-xrpl (the three fixes below), and bc-cmpw (one admin password). They stay in progress until the merge.
  • Follow-up: bc-e2rv re-plans and re-verifies the harness against main once this PR has merged.
  • Closed already on main: the other 51 review beads, each by the merged PR marked against it in the findings table.
  • Open, each with a fixme here, closed by its own fix PR: bc-mvrq, bc-sypl, bc-stpf, bc-adna, bc-pubq, bc-pace, bc-seqc, bc-rnrm.

The fix: a tapped-back mark was lost (bc-ltap)

What happened. On the individual score editor, an operator who took a mark off and tapped the same mark back while the removal's autosave was still out lost the tap: the board showed it, then the removal's echo from the server wiped it, and the tap's own save named no change. The reverse (add, then remove quickly) brought the mark back. Found when J1 failed once in a fresh clone; reproduced 6 of 6 with the removal's request held 150 ms.

Root cause. The editor judged unsaved work by comparing the board with the server's current value (pointsEdited, isDirty). A tap that puts back the value the server still shows (its echo of the removal not yet arrived) reads as untouched, so useAdoptFromServer took the echo over the newer tap.

Fix. The editor records when the operator last edited the board (points, fouls, overtime, draw) in the server's clock frame and never adopts a server copy stamped before it (adoptServerScore, admin_scoring_individual.jsx), the rule the team sheet's rows already follow (rowTakenFromServer, bc-kclr). Taking the server's copy clears the stamp, so a change made elsewhere after the last tap is still followed. The team sheet was checked and is not affected.

Court console, after the removal's echo: the M tapped back while the removal was saving stays on Aka (bc-ltap)

The same bout after a reload: the server holds the M (bc-ltap)

The second fix: a double tap on Start match started the next match (bc-sdtp)

What happened. On the court console, a double tap on the Up next card's Start match could start the wrong match. The first tap started semi-final 1; on a fast link that start landed inside the app's 400 ms bounce window, the card re-rendered to show semi-final 2 with its Start match enabled, and the second tap (289 ms after the first) sent semi-final 1 back to the queue and started semi-final 2. Found by the shared-shiaijo journey (S3) in a --workers=1 run; the trace shows the two taps and the two start writes.

Root cause. The console's Start match buttons (the Up next card and every queue row) had no bounce guard: each was disabled only while its own match was starting, so the bounce landed on the next match's enabled button. Call to court on the same card was already guarded (bc-cdbl).

Fix. Every Start match on the console shares one keyless bounce guard (startTapRef through acceptTap, tap_guard.jsx, the one owner), so a bounce is ignored whichever Start it lands on; a deliberate tap after the window and a keyboard activation still start. start_match_double_tap.render.test.jsx fails without the fix (the bounce started the next match, from the card or from a queue row) and passes with it.

The third fix: a finish whose answer was lost was refused on resend (bc-xrpl)

What happened. When the server stored a Finish but the device never processed the answer (wifi dropping between request and reply, the iPad sleeping or the tab reloading right after the tap, a reply slower than the app's 12 s wait), the offline queue kept the write and sent it again with the same stamp, as designed. The server read the identical copy as a correction of a finished match with no reason and answered 400, so the operator was told a recorded result was rejected. Found by J6 in a fresh-clone run (an offline reload let the queued Finish out, and the page was gone before the answer arrived).

Root cause. The correction-reason rule treated an equal stamp over a finished match as a correction (applyCorrectionReason), while the queue relied on an equal stamp re-applying. Decisions already answered an exact resend as recorded (exactDecisionReplay); score finishes did not.

Fix (operator decision 2026-10-12, option A). An exact resend of a completed score write (same stamp as the stored result, and nothing it names differing from what is stored, read the way the write path would land it) is answered as recorded, with the stored match and no correction reason (engine.ExactScoreReplay, checked in applyCorrectionReason and answered in RecordMatchResultWithIneligibilityTx before any refusal; nothing is written to the match file, and the history gains a line with every group unchanged, as for a decision). A same-stamp write with a different result still needs a reason. The client also takes a settled queue entry out of browser storage at once, not at the end of the pass, which narrows the window. Limits: a team or kachinuki finish whose bout rows the server rewrote on the first landing can still read as different and keep the old answer; a self-run participant's resend still meets result_finalized first.

The fourth change (bc-cmpw): one admin password

Operator rulings 2026-10-12 and 2026-10-13. Commits dfb552d (the change), 3b45f33 (merge of main, #472), ac60a87 (round-3 fixes, the reset rule), and the reset Host check made after.

  • One password. The separate destructive-ops password is removed, and a signed-in organiser is never asked for the password again, except for Delete competition: it asks to enter the password again (promptDialog), the client sends X-Confirm-Password, the server checks it with the sign-in verifier (confirm_password.go), and a wrong one is 401 "Wrong password. The competition was not deleted."
  • Upgrade note. TOURNAMENT_ADMIN_PASSWORD_HASH and a stored admin_password are ignored; remove them.
  • Self-run. Completing needs no password; anyone can add and edit participants; the branding logo and league tie-break routes are organiser-only. Self-run has no check-in: Competition.CheckInApplies is the one owner the draw, the Swiss field and the estimate ask, so the draw includes everyone, the setting and the registration desk are hidden, and the three check-in writes answer 409 check_in_not_in_self_run.
  • Completion. A competition with no knockout rounds cannot be completed while a match is unfinished (PoolMatchesSettled), nor a Swiss one while rounds remain (SwissRoundsToDraw).
  • Reset. In file mode POST /api/tournament/reset answers only a request made on the server machine: a loopback peer (never judged on forwarded headers) and a loopback Host (closes DNS rebinding). Otherwise 403 reset_local_only, which the /reset page shows. Locked mode answers 404.
  • Review fixes. The exact-replay probe (bc-xrpl) runs the merge on a copy, so held groups are excluded, and covers kachinuki, engi and team shapes. A participant's exact resend of a landed finish is answered as recorded with no history line. Both editors clear an edit stamp once a later save lands (landedAutosave). The file-mode password compare is constant-time.
  • Known gap. Swiss has no Complete competition button, so no podium; recorded on mp-yqxn.4 / bc-e2rv.

Shared shiaijo and the moved match (bc-shcr, bc-mvsj)

Two competitions, Shared Individual (SI, mixed) and Shared Team (ST, knockout), share Shiaijo A; SI also has B. Operator rulings on the findings (2026-10-11 and 2026-10-12), with the screenshots they were made from:

Bead Ruling Fixme
bc-mvrq Moving a running match to another shiaijo warns first; once confirmed it stops running, keeps its score, and goes first in that court's queue shared-shiaijo.spec.mjs, last test
bc-sypl The score editors drop the "Synced" pill: the header's Connected pill and its held-writes count are the one signal shared-shiaijo.spec.mjs
bc-stpf Option A: a Start is checked before the running bout is sent back moved-match.spec.mjs
bc-adna Admin views do not start or advance matches and are not blocked by court status; the court console is where scoring and its queue live moved-match.spec.mjs
bc-pubq Public screens follow the court operator's queue on a shared shiaijo shared-shiaijo.spec.mjs
bc-pace The schedule adapts when courts run late or ahead shared-shiaijo.spec.mjs
bc-seqc Competitions sharing a shiaijo run one after the other by default, start times adjusted as the day goes shared-shiaijo.spec.mjs, first test
bc-rnrm "Run now" on a Later row is the wrong button: remove it shared-shiaijo.spec.mjs

Not filed, by ruling: the court-move control on a queued match is fine as it is (a queued match can always change court), and the amber "Switch to" line already proposes the next match by schedule.

Two bouts running on Shiaijo A after a running match was moved there (bc-mvrq)

A refused point still reads Synced in the editor (bc-sypl)

A start refused after the running bout was already sent back: the court is left idle (bc-stpf)

After a fusenpai recorded from the Scores tab, the app tries to start the court's next match and is refused because another bout runs there (bc-adna)

The TV board's NEXT names the other competition's match (bc-pubq)

The public schedule shows two "Next up" rows on Shiaijo A (bc-pubq)

Default slots: both competitions' first knockout matches at 09:30 on Shiaijo A (bc-seqc)

The idle panel and the amber Switch to line; "Run now" on a Later row (bc-rnrm)

Walkthrough: the five-step operator path

Update, 2026-10-11. Main has since merged #447, #450, #451, #453, #458, #462, #463 and #467 as well, closing 42 more beads (marked in the table below). Where the walkthrough describes one of those beads, it describes the build as reviewed, not main.

Update, 2026-09-25, after main merged #444. The walkthrough below is the review as performed (at 548a936). These statements in it no longer hold on main:

  • §1 and §4: "Copy from previous match" is now at the top of each side (bc-cpdc). The remaining-matches panel's Award button records a fusensho (bc-wnam). The withdrawn competitor's next bout no longer offers Start match. Its queue row reads " withdrew: record the fusensho." with Record fusensho for , which records it and closes that bout (J2 now takes this route). bc-kpnl and bc-kfup still reproduce as filed.
  • §2: the shortcut hint is hidden on the iPad (bc-kbhn). A double tap on a fought kachinuki bout opens that bout (bc-kbrw). Ending a reopened encounter asks for no reason (door 2, and §5's "End match with a reason").
  • §3: closing a running match flushes the autosave with no discard prompt (bc-dscn). Untouched team bouts are no longer written as draws (bc-unfx). Finish refuses a team match while a bout has no result (bc-tmfn).
  • §4: refusals name the match instead of raw codes (bc-rawm). A tied individual knockout bout's Finish is disabled and reads "Needs a winner".

Fixtures, all through the wizard: individual mixed, 12 entrants, two shiaijo; individual knockout-only with the bronze match (joint 3rd unticked) and one on the default joint 3rd; team match, size 3, mixed; team match, size 5, knockout-only (daihyosen is knockout-only); kachinuki, size 5, knockout, one encounter driven eight bouts deep from /admin/shiaijo/:court. Per-row evidence is in the audit comments below.

1. Lineup entry

  • Individual: not applicable.
  • Team, size 3 and 5: size 3 shows bare positions 1, 2, 3 and size 5 shows Senpo, Jiho, Chuken, Fukusho, Taisho (positionsForSize), on all three surfaces. What held: a wrong fighter picked (V1) is repaired in two taps, and each list leaves out members placed elsewhere. A name typed on the score sheet renamed the blank member slot rather than minting a new member, and a re-tap on Save while "Saving…" wrote once. What did not: a double tap on the Lineups page's Add dismisses the confirm it just opened (bc-cfbd). A reload before Save loses the picks with no prompt (bc-lnul). The panel's duplicate refusal names the wrong position ("Dai is already at 2." for Dai at 1) in one of three wordings (bc-lprf). The sheet's refusal renders below the IV/PW band, off screen (bc-lnrf).
  • Kachinuki: all 20 team members were named by typing into the court console's "Enter lineup" panel. Typed names are lost on reload (bc-lnul). "Copy from previous match" is at the bottom of each side, while the docs say the top (bc-cpdc). On a later bout's fighter list, a double tap picks the fighter and then scores the ippon underneath, and a focused name box will not reopen its list (bc-flst).

Ruling: three lineup entry points is intentional in design and defective in execution.

  1. They are three layers, not three copies. The Lineups page writes the team's ROUND default. The at-court panel ("Enter lineup" on Up next, "Lineup" on each Upcoming row) and the score sheet's name boxes write the per-MATCH override, which wins. Each serves a different moment: before the day, at the table before the encounter, and during the bout (the only one on screen while scoring). The docs describe this layering, so having three is not the defect.
  2. They disagree with no operator error. On a pool match the panel showed every Shiro box empty under "Inheriting round default", while the Lineups page held Aoki/Baba/Chiba and the sheet showed them. The panel fetches the match's exact round index. The sheet, viewer and TV board ask with fallback=best (evidence J3-lineups/07-f3-panel-round-default.png).
  3. The disagreement becomes data loss through a realistic slip. Tapping Shiro's Save on the empty-looking side writes an EMPTY match override, and the sheet's Shiro names vanish for that match while the Lineups page still shows them. Repair took three re-entries on the sheet.
  4. The panel is the first lineup surface a hurried operator meets ("Enter lineup" sits beside "Start match"), and it is the one reading a different layer. The Lineups page is not on the court console at all. Nothing on the sheet says which layer a name came from.

Filed as bc-lpfb. Fix direction: the panel reads through the same resolver as the sheet, and a side whose values did not change is never written.

2. Navigating bouts and matches

  • Chained navigation never left the court. Finish + Start Next stayed on Shiaijo A across the pool to knockout boundary (last pool bout, then QF M1, QF M2, SF M5, all on A). On the Scores tab, Prev/Next walked seven editors and every one was on Shiaijo B. The same match and court came back after every V3 interruption: reload, tab hidden, back then forward, and 0.6s offline. Points were kept. V5: the court name, the eyebrow (competition, pool, match N of M) and both names and numbers stayed legible at arm's length.

  • Order is wrong, though. In a pools + knockout competition every knockout bout is scheduled at the day's start time, so Finish + Start Next on court B ran a resolved quarter-final between the remaining Pool D bouts (bc-kosc). The console's arrow keys and Esc do nothing, while the shortcut hint advertising them shows on the iPad (bc-kbhn). After Correct, Start match on Up next starts the next match, but the panel keeps the finished correction and the running match is nowhere on the page (bc-crpn).

  • Team match: bout rows are 205px tall, so about 2.4 bouts fit at 1180x820. The IV/PW band sits below bout 5, and the match identity scrolls away while scoring bouts 3 to 5 (bc-tmfd).

  • Kachinuki, kachinukiVisiblePositions verdict (context seven bouts in): enough context for "who fought whom and how", not for "where do we stand". Only the live slot is editable, but every fought bout renders above it as a read-only row: bout number, both fighters with member numbers, the marks, the centre (vs, X or (E)) and the winner. Nothing states fighters left per side or who is up next, so under exhaustion the operator counts by eye to know End match is due. That is ruled an advisory line (bc-kfnl). From about bout 6, Record bout and End match are below the fold (bc-tmfd). The header keeps "(E) OVERTIME x2" on every regulation bout after one encho bout (bc-kheb).

  • Kachinuki, returning to a mis-scored earlier bout: yes, through three doors that work.

    1. Mid-encounter, tap the fought row. It opens in place with full controls and saves as you go, and a winner flip raises the documented warning. The only cue is a small ▶, and a double tap opens the bout ABOVE it (bc-kbrw).
    2. On a finished encounter, Correct, then Reopen match, fix, and End match with a reason. The fix propagated (PW 8-3 to 7-3, winner and bracket side unchanged).
    3. On a busy court, the panel names the running match and offers the atomic requeue.

    "Remove this bout" works and is recoverable. The dangerous exit is the one not built for a bout log: Send back to queue under a reopened encounter wipes it (step 5, bc-sbq). Who stayed on was correct in every performed case: wins, hikiwake, a taisho tie, removal and re-record, and a correction after reopen.

3. Score entry and the running total

  • Individual: ippon buttons meet 44px. V1 hit K instead of M on the same side, and one tap on the mark clears it; "Tap a scored mark to clear it" shows while a mark exists, so the undo is discoverable. V2 recorded M M, a bout decided 2-0 on one intended point (bc-dtip). A reload inside the 300ms autosave debounce loses the point while the pill already reads Synced (bc-sync). From the Pools tab, the editor closes on Start match and after every autosaved point (bc-plcl), and "Discard unsaved scoring changes?" discards nothing because the autosave already landed (bc-dscn).
  • Team match: the IV/PW totals are right but off screen (bc-tmfd). After the first ippon every untouched numbered bout is written as a hikiwake. It shows X on the TV board and the viewer, and "✓ Tie" on the sheet after a reload; a Tie tap on it then turns the draw OFF (bc-unfx). A pool encounter finished with bout 3 never scored counts it as an individual draw for both teams (IT 2 on the Pools tab). Per the operator ruling, every bout of a team match is fought, so Finish must refuse (bc-tmfn). A stray tap on an EMPTY mark slot clears the bout's Tie or Fusensho (bc-emsl). Fusensho toggle then untoggle, in place, restored the prior score. After a reload the untoggle cannot restore it, because the fusensho had already erased the other side's points on the wire (bc-fsnp).
  • Kachinuki: a double tap on Tie toggles it on then off. A double tap on Encho doubles the count, and bout mode has no stepper (by ruling, not re-litigated). Record bout while offline leaves the bout live with no "Not sent yet" notice; it lands on reconnect (bc-rboq).

4. Special decisions

  • Kiken chain (closing the withdrawn competitor's remaining matches): BLOCKS, three defects, all three must land. After Kiken - Voluntary on the court console the Remaining matches panel shows for about a second, then the court moves on and it cannot be reopened (bc-kpnl, also on a team pool). Where the panel is reachable, its Award button throws wname is not defined and records nothing (bc-wnam). Fusenpai on the withdrawn competitor's next pool bout (Scores tab, the same call Award makes) is refused with the raw code already_ineligible (bc-kfup). Starting that bout, or scoring the opponent and finishing, is refused by design, with the raw text "kiken-voluntary at Pool A-0" (bc-rawm). So the pool never completes and the knockout never seeds from it.
  • Side picker: Kiken - Voluntary's neighbour (V1) is Kiken - Injury, and it only opens a prompt (Cancel recovers). The withdrawal and no-show prompt preselects Shiro, and a hasty Record (V4) withdrew the wrong competitor, and on the team journey the wrong team, with a voluntary kiken, which the docs call permanent (bc-dsid). The sanctioned recovery is to re-record the kiken on the other side through Correct (operator ruling; docs bead bc-kkdc). It took five taps plus the forced confirm below.
  • The 409 decision_locked confirm, judged under time pressure: not comprehensible. Correcting a kiken after a later bout has started shows "Please confirm ... A subsequent match for one of these competitors has already started ... Proceed anyway?". It names neither the match nor what proceeding does. In the individual journey, proceeding left the real withdrawer's running bout on the court, unfinishable (Finish refused, only Send back to queue, where it stays unstartable). In the team journey, it silently swapped the Shiro team in the running final. The paragraph break collapses. An operator at the table either proceeds blind or cancels and loses the correction (bc-dlck). The red "Proceed anyway" button itself is app convention for an irreversible override and was not filed.
  • Fusenpai: a double tap recorded one decision and no duplicate write. Auto-advance started the next bout by itself on a court with nothing stuck. Where the next bout was unstartable, the court sat idle behind a 2.7s toast (bc-aadv).
  • Encho: tapping the word "Overtime" opens the glossary instead of the encho counter, contrary to the docs (bc-otpl). The counter is unbounded as intended, and minus floors at x1. The max_encho_exceeded arm of the 409 no longer exists (mp-m4bn removed the cap), so it was not judged. The + and - steppers (30x44) and the Encho started checkbox (13x13) are under the 44px floor (bc-tp44).
  • Hantei, individual: BLOCKS. SHIRO wins and AKA wins sit 6px apart. A neighbour tap committed the opposite verdict at once and advanced the bracket (bc-htsd). Correct then cannot fix it: no reason prompt appears, the toast reads the raw "correctionReason: correcting a completed match result requires a non-empty correctionReason", and no other UI route exists (bc-htcr). Finish on a tied 1-1 individual knockout bout shows the raw "bracket match m-r1-0: cannot mark completed with no winner ..." over the Finish button (bc-rawm).
  • Daihyosen, team match size 5: a tied encounter disables Finish ("Needs a winner"). Add representative bout appends the row in place with (DH) in its centre. There is no way to pick each team's representative, although the docs and the in-app hint promise one (bc-dhrp). Found on re-running after fix(bc-tmfn): operator fixes: team bouts, withdrawals, knockout rounds and the Bracket page #444: tapped within the last Tie's 300ms autosave window, the row never appears, because that pending write lands after the daihyosen and erases it (bc-dhas). A hantei neighbour tap on the team sheet is recoverable in one tap before Finish. The closed middle held: Ht rode the winner's side and the centre stayed (DH). By operator ruling the bracket card does not carry that Ht, and the docs will be narrowed (bc-tmdc).

5. Confirm and advance

  • Two-tap guard: a double tap (15 to 30ms) on Finish, Finish + Start Next or End match arms and commits in one gesture (bc-dtfn). The armed End match label, verdict included, is dark text on a dark fill (bc-cfcl). A re-tap while "Saving…" was ignored, with exactly one completion.
  • Did Finish land? (V5): only the names change in the editor. A team result decided by daihyosen, kiken or fusenpai shows no winner on the court's Completed list ("IV 1–1 PW 1–1" reads like a draw) (bc-tmwn).
  • Send back to queue: its loud button is the destructive one. On the individual console, in one of two runs, its copy said "No score has been entered, so nothing will be lost" with a synced M on the board. Under a REOPENED kachinuki encounter it says "Any score entered on this bout will be discarded" and wipes the encounter's whole six-bout log (bc-sbq). End match with a reason is the non-destructive exit, and it is on screen.
  • Offline confirm and advance verdict (J6, knockout-only, shiaijo C, bronze match on): Finish on the last semi-final with the context offline. The local bracket advanced correctly: the final and the bronze moved into Up next (keep this, mp-y3nk). But the advance unmounted the editor that holds the "Not saved yet" banner, and the header still read "Connected", so nothing on the page said the result was queued (bc-offl). The same bout was not offered again, so there was no double entry. A reload while offline gives a blank page; that is a documented gap owned by mp-wm4e and not refiled. On reconnect the queued Finish landed and the server bracket agreed: the final carries the winner and the 3rd place match the loser. The bronze bout is labelled only "10:55 · Veterans Knockout" and is absent from Later until both semi-finals finish (bc-bzlb).
  • One result on every surface (J7): the TV board and the viewer followed the court. A court editor left open ADOPTED a hantei correction made at the head table without a tap. A drawn bout never reaches the viewer's Recent results (bc-rrdr). A double tap on Call to court posts two identical "Now calling" banners to the public viewer (bc-cdbl).
  • Pools and Bracket tabs (J8): after Finish on the Bracket tab the operator lands on the read-only result card, one tap from the next semi. Edit result is 44px tall under a coarse pointer.

What passed

Chained navigation never left the court. The same court and match came back after every reload, tab switch, back/forward and short offline. Double taps on Start match, Record bout, Save lineup, Reopen match, Remove this bout and the collapse caret each acted once. The tap-the-mark clear is discoverable. Fusensho untoggle restored the score in place. Kachinuki's who-stays-on was right in every case. A kachinuki correction through Reopen propagated to the result and the bracket. Editors adopted a verdict recorded on another device. The queued offline Finish landed on reconnect.

Operator rulings made during this review (2026-09-24)

  1. The harness lives in scripts/e2e/, reusing scripts/screenshots/lib, not in web-mobile/e2e. Seeding is UI-only, through the wizard every time. One PR closes bc-e2eh and mp-yqxn.2.
  2. Every bout of a team match is fought, so there are no unfinished team matches. There are no "fixed-order" teams: the team sets its lineup before each match, so the formats are "team match" and "kachinuki". Filed as bc-tmfn.
  3. Re-recording a voluntary kiken on the other side through Correct is the sanctioned recovery for a kiken recorded against the wrong side. Docs bead bc-kkdc.
  4. The bracket card does not carry Ht for a team daihyosen hantei, and the docs are narrowed. Filed as bc-tmdc.
  5. Kachinuki: one bout's encho does not mark the encounter (E), in the editor header or on the bracket card. bc-kheb was widened.
  6. Kachinuki bout mode gets an advisory "fighters left / up next" line that never ends, arms or blocks anything. Filed as bc-kfnl.

Findings (severity-ranked)

Every child bead of mp-yqxn.2. Severity: BLOCKS a running tournament, SLOWS the operator, COSMETIC. Reviewers: A individual, B team match, C kachinuki; "smoke" and "harness" are the harness build. Each bead cites current file:line and its evidence screenshots. "(closed, #NNN)" marks a bead closed by that PR on main; "(superseded)" one closed by a later operator ruling.

The kiken chain is broken in three places, and all three must land before a withdrawal can be closed on a court: bc-kpnl, bc-wnam, bc-kfup. Update after #444: the Award button works (bc-wnam), and the withdrawn side's next queue row now offers Record fusensho for , which records it and closes the chain on the court. bc-kpnl and bc-kfup still reproduce as filed (re-verified 2026-09-25). Update 2026-10-07: bc-kfup is fixed (#447; a fusenpai on the withdrawn competitor's next bout records Fus.), and bc-kpnl is superseded: the panel was removed by the 2026-09-26 ruling, so each remaining match is closed from its own queue row.

bead severity P title reviewers fix direction (top severity)
bc-kfup (closed, #447) BLOCKS P1 A fusenpai on a withdrawn competitor's remaining bouts is refused as already ineligible A Treat a fusenpai whose loser is already ineligible as a chain award at BOTH guard sites (checkConcurrentIneligibility and the K2 check-and-set). Keep the origin CompetitorStatus, keep refusing a second kiken from another match, and list running bouts in the panel.
bc-kpnl (superseded) BLOCKS P1 The court console drops the remaining-matches panel as soon as a kiken lands A, B Pin the kiken-decided match the way correctingKey is pinned until the operator closes the panel, and wire a real close. Do not relax the completed filter.
bc-wnam (closed, #447) BLOCKS P1 The remaining-matches panel's Award button throws 'wname is not defined' and records nothing A Use the in-scope withdrawn player's name, add a render test that taps Award, and consider no-undef for web-mobile.
bc-htcr (closed, #447) BLOCKS P1 A hantei result cannot be corrected: the correction never asks for a reason and the server refuses it A Route a correction-mode submitHantei through ReasonPrompt with a pending-action ref, and add a render test.
bc-tmfn (closed, #444) ruling (source B3: SLOWS) P1 A team encounter can be finished while numbered bouts have no result B Finish, client and server, refuses a team match (not kachinuki) while any numbered bout has no result, naming the bouts and never the lineup.
bc-sbq (closed, #447) SLOWS, high (data loss) P1 Send back to queue can wipe a reopened encounter's fought bouts, and its confirm misstates what it discards C, A Not offered on a match with fought bouts or ReopenPending (End match is the exit). The confirm states what it destroys from current data.
bc-htsd (closed, #462) SLOWS (BLOCKS while bc-htcr is open) P2 The hantei SHIRO/AKA wins buttons commit the opposite verdict on a single mis-tap A Pick a side, then confirm with a second tap, or separate the buttons.
bc-dsid (closed, #462) SLOWS, high P2 The withdrawal and no-show prompt preselects Shiro and records on a bare Record A, B No preselected side, Record disabled until one is picked, a button naming the side, and 44px rows.
bc-aadv (closed, #463) SLOWS P2 Auto-advance after Finish or a fusenpai loses the next bout's start refusal on the court console A
bc-cdbl (closed, #463) SLOWS P2 Double tap on Call to court sends two announcements smoke
bc-crpn (closed, #462) SLOWS P2 The running match disappears from the court while a correction holds the panel (both orders) C, A, B
bc-rfsw (closed, #453) SLOWS, high P2 A kiken correction silently swaps a team inside the running final B (J4/26-27) When a forced correction changes a side of a running downstream match, return that match to the queue or refuse, and tell the court
bc-kcsh (closed, #444) SLOWS P2 Correcting a team match decided by kiken shows a blank sheet with no decision or winner B (J4/23)
bc-dhas (closed, #451) SLOWS P2 Adding a representative bout right after the last Tie is erased by that Tie's pending autosave B (after #444) Cancel the pending autosave before onDaihyosen's own save, as Finish does.
bc-dhrp (closed, #463) SLOWS P2 The daihyosen row offers no way to pick each team's representative B
bc-dlck (closed, #462) SLOWS P2 The decision-locked confirm names neither the started match nor what confirming does A, B
bc-dtfn (closed, #450) SLOWS P2 A double tap passes the two-tap Finish and End match guard A, B, C
bc-dtip (closed, #450) SLOWS P2 A double tap on an ippon button records two ippons A
bc-emsl (closed, #450) SLOWS P2 Tapping an empty mark slot on the team sheet clears the bout's Tie or Fusensho C
bc-fsnp (closed, #450) SLOWS P2 A per-bout fusensho erases the other side's points, and its undo cannot restore them after a reload B
bc-kosc (closed, #462) SLOWS P2 Pools + knockout: knockout bouts are scheduled at the start of the day instead of after the pools A
bc-lpfb (closed, #458) SLOWS P2 The at-court lineup panel shows an empty lineup where the score sheet shows the round default B
bc-offl (closed, #453) SLOWS P2 The court console shows no queued or offline signal after a result is finished offline A
bc-otpl (closed, #462) SLOWS P2 Tapping the Overtime pill opens the glossary instead of the encho counter A
bc-plcl (closed, #447) SLOWS P2 The Pools-tab score editor closes on Start match and after every autosaved point A
bc-rawm (closed, #444) SLOWS P2 Operators see raw server codes, engine messages and internal match ids A, B, C
bc-rboq (closed, #453) SLOWS P2 Record bout offline gives no 'Not sent yet' notice C
bc-sync (closed, #450) SLOWS P2 The sync pill reads Synced while a scoring change is still unsent, and a reload in that window loses it A, B, C
bc-tmfd (closed, #458) SLOWS P2 The team sheet keeps its running total and Record/End controls below the fold on an iPad B, C
bc-tmwn (closed, #463) SLOWS (court), COSMETIC (viewer) P2 The court's Completed list shows no winner for a team result decided by daihyosen, kiken or fusenpai B
bc-unfx (closed, #467) SLOWS P2 An unfought bout in a team encounter is written and shown as a draw B
bc-cfbd (closed, #458) SLOWS P3 A double tap dismisses the confirm dialog it just opened B
bc-cfcl (closed, #453) SLOWS P3 The armed End match label is dark text on a dark fill C
bc-dscn (closed, #444) SLOWS P3 'Discard unsaved scoring changes?' appears where it discards nothing C, A
bc-flst (closed, #453) SLOWS P3 Fighter list: a double tap scores the ippon underneath, and a focused name box will not reopen its list C
bc-kbrw (closed, #444) SLOWS (minor) P3 A double tap on a fought kachinuki bout opens the bout above it C
bc-lnrf (closed, #458) SLOWS P3 The score sheet's lineup refusal renders off screen B
bc-tp44 (closed, #458) SLOWS P3 Operator tap targets under 44px on the score editors and lineup panel A, B, C
bc-bzlb (closed, #467) COSMETIC P3 The 3rd-place match is unlabelled on the court console and missing from Later until both semi-finals finish A
bc-cpdc (closed, #444) COSMETIC P3 Docs say 'Copy from previous match' is at the top of the lineup panel; it is at the bottom B
bc-kbhn (closed, #444) COSMETIC P3 The scoring shortcut hint shows on the iPad and advertises keys the court console ignores C, A
bc-kheb (closed, #453) COSMETIC P3 Kachinuki: one bout's encho marks the whole encounter (E) in the editor header and on the bracket card C
bc-lbla (closed, #467) COSMETIC P3 Operator form labels are not associated with their inputs (login, first-run, wizard fields and pills) harness
bc-lnul (closed, #458) COSMETIC P3 Unsaved lineup entries are lost on reload without a prompt B, C
bc-lprf (closed, #467) COSMETIC P3 The lineup panel's duplicate-member refusal names the wrong position, in three wordings across surfaces B
bc-rrdr (closed, #463) COSMETIC P3 A drawn bout never appears in the public viewer's Recent results smoke
bc-kfnl (closed, #467) feature (ruling) P3 Kachinuki bout mode: an advisory line with fighters left and who is up next C
bc-kkdc (closed, #444) docs (ruling) P3 Docs: describe correcting a kiken recorded against the wrong side B
bc-tmdc (closed, #467) docs (ruling) P3 Docs: team encounter marks appear in the exported workbook, not on the bracket card B, C
bc-ktih (closed, #467) not rated P2 Kachinuki: an unscored bout reads 'Tied bout' after a tie that left one team's last fighter on #467 mockups
bc-kbrc (closed, #467) not rated P2 Kachinuki: after Record bout at exhaustion the bout stays open and nothing shows it was recorded #467 mockups

50 beads: 4 BLOCKS, 1 ruling bug (P1), 33 SLOWS, 8 COSMETIC, 3 docs or feature from rulings, and 1 review-gap task (bc-shcr, below). A 51st child, bc-mvsj (filed 2026-10-03 on PR #453), asks for a new journey and is not a finding. Two more, bc-ktih and bc-kbrc, were found on 2026-10-11 while making #467's mockups, are added at the end of the table, and closed with #467.

Coverage gap, disclosed (bc-shcr), now closed by the shared-shiaijo journey above: every journey here put each competition on its own shiaijo of a 16-shiaijo tournament, so a shiaijo shared by two competitions was not reviewed in this pass: the competition picker on /admin/shiaijo/:court only renders when a court hosts more than one competition (admin_shiaijo.jsx:1256), and no cross-competition court flow was walked. bc-rfsw, bc-kcsh and the widened bc-crpn were found afterwards, re-examining J4-team-scoring/23 at the operator's request; every step behind that screen is a UI tap (no API writes, no injection).

Rejected after adversarial verification (not filed):

  • A13, the correction staying pinned after Save correction. This is by design and documented ("use Back to court to return to the live match").
  • A8's offline-reload part. It duplicates mp-wm4e, a documented gap.
  • A11's button-colour clause. A red button for an irreversible override matches app convention; the copy half is bc-dlck.
  • B16, "+ Add new member" minting an extra position while reserves are blank. The guard works as designed per the team-members rules.
  • C12, the reopen reason prompt preselecting "Ended by mistake", and the matching preset on the individual correction reason. Both are intended and shown that way in the docs.
  • B15's 26px centre mark slot. It is a recorded layout trade-off.
  • Tap-target numbers that were a harness artifact. A fullPage screenshot had dropped the coarse pointer; the fix is in shots.mjs. Re-measured with coarse asserted, Lineups Add is 44x44, Save is 100x44 and Bracket Edit result is 44px tall.

Harness notes

  • Concurrent runs: Playwright empties its outputDir at the start of every run, so two runs in one checkout deleted each other's traces mid-test (ENOENT on context close). E2E_RESULTS_DIR gives a concurrent run its own results folder, and the journey screenshots and audit.json follow it (shots.journeyDir), so two runs never empty each other's folders. It is read from the environment, so every worker resolves the same folder (README).
  • One server per spec file: Playwright hands a worker one file after another, so a worker-scoped server greeted a second file with the first file's tournament, and the suite passed or failed on the machine's core count. serverForFile starts a fresh server whenever the file changes (found in code review; proven with --workers=1).
  • fullPage screenshots dropped the coarse pointer: measured, (pointer: coarse) read false after a fullPage shot, which switched off every tap-floor rule for the rest of the test. fixtures/pointer.mjs (restoreCoarse) re-enables touch emulation in place, with no reload, through a CDP session kept for the page's life. Detaching the session clears the emulation it set. Every tap-target measurement (tapSize) asserts a coarse pointer first.
  • One edit to the capture harness's shared lib (plus a comment wording fix in lib/ui.mjs): scripts/screenshots/lib/editor.mjs gains INLINE_EDITOR, the inline editor's selector, which the e2e fixtures pass as the editor's root. (An optional root argument to its finishMatch was dropped when main's tap-guard version of that function merged in: the journeys finish through their own tapping finishMatch in fixtures/scoring.mjs, and fixtures/pace.mjs imports the library's TAP_DWELL_MS.) Because it touches the capture harness's shared lib, make docs/screenshots SINCE=main selects every capture group on this branch.
  • Also outside the harness: the Makefile (make e2e, make e2e/deps, E2E_ARGS), .gitignore (scripts/e2e/output/, the harness's package files re-included), and the PR template's new "make e2e green locally" checkbox.
  • Console policy (fixtures/test.mjs): every context and page a test opens is watched. A console error or an uncaught page error fails the test unless it matches CONSOLE_ALLOWLIST, which holds three entries, each with its reason: a failed /api/ fetch while the test has taken the context offline; the court console's own "Failed to fetch court matches" log while offline; and a 409 on /score or /reopen carrying court_busy, ineligible_competitor or downstream_knockout_running, a refusal the app shows the operator (the court is running another bout, the competitor is fighting elsewhere, or the knockout match it feeds is being fought). Warnings are recorded, not failed. A test whose pages logged anything attaches console.json, and each console error is printed as a CONSOLE {json} line naming the allowlist entry it matched, if any.
  • Code and docs review (2026-10-12), with sub agents: the journeys had checks that could not fail, now fixed and proved able to fail: bc-tmfn asserts the unscored-bout refusal on each tap; bc-htcr and J2 check that Ht sits in the corrected side's cell; J8 and J2 assert the court moves on after a fusenpai; J1's Scores-tab Next needs two distinct editors on court B; the moved-match, S4a and "nothing started" checks wait for the event rather than a sleep; the individual spec's bead guards are no longer serial with its journeys. The README says where clumsy outcomes are asserted and what "green" counts.
  • Review rounds on bc-cmpw: round 1: security clean; docs 1 critical (branding logo route public in self-run) and 3 majors, fixed; correctness 1 major (the bc-xrpl probe compared held groups), fixed. Round 2: docs narration rewritten as facts; security 1 major (anyone could complete an unfinished league in self-run), fixed; correctness clean, 1 minor (team-sheet stamp), fixed. Round 3: docs 2 majors (openapi roster wording), fixed; code 1 major (file-mode reset bypassed the single password), resolved by the server-machine-only ruling. Round 4: no critical or major; the Host check and a comment fixed. Harness: two review rounds (infrastructure, journeys, README); round 1's critical (J7 recorded the TV text without asserting) and majors (silent-pass helpers, a too-broad allowlist entry, leaked servers, serial cascades, unasserted results, racy reads) fixed; round 2 had no critical or major, its minors fixed. Antigravity (Gemini) reviewed it independently in three rounds; its verified majors (a refusal body or a late console line failing a test, no server.log on some failures, fixed sleeps in wizardDefaults and bc-ltap) were fixed and round 3 found nothing. The password is called the admin password on every screen; the laptop-shortcut docs state which editors take Left, Right and Esc.
  • Shared-court seeding (fixtures/shared-court.mjs): two competitions on one shiaijo, created through the wizard and started through the UI, as every other journey.

Files changed

File What
.github/pull_request_template.md Test plan gains "make e2e green locally (any web-mobile/ change)"
.gitignore Ignore scripts/e2e/output/; re-include the harness's package.json and package-lock.json
Makefile make e2e (go/build, then playwright test with E2E_ARGS) and make e2e/deps (npm ci + Chromium, stamped); not in go/test or CI
scripts/e2e/README.md How to run one spec or test, headed mode, layout, the fixme convention, E2E_RESULTS_DIR
scripts/e2e/package.json Harness package pinning @playwright/test to the same release as scripts/screenshots
scripts/e2e/package-lock.json Lockfile for the harness
scripts/e2e/playwright.config.mjs Projects "operator" (iPad Air landscape, coarse) and "public" (phone), Chromium only, output folders
scripts/e2e/fixtures/test.mjs The shared test: one server per spec file (serverForFile), an operator page whose coarse pointer is verified, and the console policy (CONSOLE_ALLOWLIST)
scripts/e2e/fixtures/pointer.mjs The coarse pointer: isCoarse, restoreCoarse (in place), and tapSize for the 44px floor
scripts/e2e/fixtures/audit.mjs Audit rows: the AUDIT {json} log line, and audit.json written beside each journey's screenshots
scripts/e2e/fixtures/devices.mjs The operator iPad and the spectator phone, shared by projects and hand-made contexts
scripts/e2e/fixtures/setup.mjs First-run tournament form, the real login form, Sign out
scripts/e2e/fixtures/wizard.mjs The create-competition wizard; the one file that knows its copy
scripts/e2e/fixtures/competition.mjs Roster paste box, Generate draw, Discard draw, Start
scripts/e2e/fixtures/shiaijo.mjs /admin/shiaijo/:court: the queue, the inline editor and completedRowFor (the newest Completed row for a pairing)
scripts/e2e/fixtures/scores.mjs The Scores tab and its overlay editor
scripts/e2e/fixtures/scoring.mjs Scoring an individual bout on either editor mount
scripts/e2e/fixtures/public.mjs The public viewer and the TV board
scripts/e2e/fixtures/pace.mjs The operator's pace: waits out the app's 400ms tap guard (TAP_DWELL_MS, imported from the capture harness) after a layer opens or a two-tap button arms
scripts/e2e/fixtures/clumsy.mjs Persona helpers: tapNeighbour, doubleTap, retapWhileSaving, interrupt, hastyConfirm, hastyShiaijoConfirm, and loudestButton, the one V4 ranking
scripts/e2e/fixtures/shots.mjs Per-step screenshots; journeyDir, the one owner of a journey's output folder; restores the coarse pointer after a fullPage shot
scripts/e2e/fixtures/individual.mjs Individual journey helpers: rosters, the decision panel (recordDecision takes the side: the prompt picks none), and correctCompleted (Correct on a finished bout, sending a live bout back to the queue first when the court refuses)
scripts/e2e/fixtures/team.mjs Team match helpers: naming team members, the three lineup entry points, team bout scoring
scripts/e2e/fixtures/kachinuki.mjs Kachinuki sheet helpers: lineup panel, bout controls, fought rows, correction doors
scripts/e2e/journeys/smoke.spec.mjs Seed through the UI, score on a shiaijo, see it on a public phone; exercises each clumsy helper
scripts/e2e/journeys/knockout-mixed-individual.spec.mjs J1, J2, J6, J8 with their variants, plus the findings' own tests and bc-ltap's (both orders, the save held 150 ms, checked after a reload); J1's reset waits for a removal to land; no fixme left
scripts/e2e/journeys/knockout-mixed-team.spec.mjs J3, J4, J7 with their variants, plus the findings' own tests; no fixme left
scripts/e2e/journeys/knockout-mixed-kachinuki.spec.mjs J5 across seven tests with their variants, plus the findings' own tests; no fixme left
scripts/e2e/fixtures/shared-court.mjs Two competitions on one shiaijo, seeded through the UI; the readers the shared-court journeys share
scripts/e2e/journeys/shared-shiaijo.spec.mjs bc-shcr: two competitions on Shiaijo A (S1 to S9, P1 to P8), plus the fixmes for bc-seqc, bc-pubq, bc-rnrm, bc-pace, bc-sypl and bc-mvrq
scripts/e2e/journeys/moved-match.spec.mjs bc-mvsj: a waiting match moved from court A reaches court B's console without a reload, on two devices; plus the fixmes for bc-stpf and bc-adna
web-mobile/js/admin_scoring_individual.jsx bc-ltap: the board-edit stamp (boardEditedAtRef, set by every board edit, cleared when the server's copy is taken) and adoptServerScore, which keeps the board over a server copy stamped before the last edit; representative-player picks use the plain autosave call
web-mobile/js/__tests__/render/individual_editor_tap_back_kept.render.test.jsx New: a mark tapped back while its removal is saving stays and is sent; a mark taken off while its add is saving stays off; a change made elsewhere after the last tap is still followed (the first two fail without the fix)
web-mobile/js/__tests__/render/changed_groups_every_editor.render.test.jsx One fixture's server copies now carry a stamp after the tap, as the server's do
web-mobile/js/admin_shiaijo.jsx bc-sdtp: one keyless bounce guard (startTapRef, tapStart) for every Start match on the court console, the Up next card and the queue rows
web-mobile/js/__tests__/render/start_match_double_tap.render.test.jsx New: a bounce on the card or on a queue row starts nothing; a tap after the window and a keyboard activation start (the first two fail without the fix)
internal/engine/scoring_tx.go bc-xrpl: ExactScoreReplay and answerExactScoreReplay, an exact resend of a completed score write answered as recorded before any refusal
internal/engine/exact_score_replay_test.go New: the exact-replay rule, 12 cases
internal/mobileapp/handlers_match.go bc-xrpl: applyCorrectionReason asks no reason for an exact resend
internal/mobileapp/score_exact_replay_test.go New: an exact resend of a finish is answered as recorded (pool, knockout, with and without named groups; fails without the fix); a same-stamp finish that differs still needs a reason
web-mobile/js/api_client.jsx bc-xrpl: a settled queue entry leaves browser storage at once (leaveQueue in _flushQueue)
web-mobile/js/__tests__/queue_ordered_writes.test.jsx A sent or refused entry leaves browser storage at once (fails without the change)
specs/openapi.yaml PUT /score: the 400 correction-reason case and the correctionReason field describe the exact-resend answer
docs/architecture/network-architecture.md The order-of-writes row: an exact resend of a finish is answered as recorded
CLAUDE.md The correction-reason rule and the queue's replay sentence now agree (exact resend answered as recorded); a settled queue entry leaves storage at once
internal/mobileapp/confirm_password.go, middleware.go, server.go bc-cmpw: X-Confirm-Password check (new file); one password for every organiser route; self-run main-gated list gains the branding logo and league tie-break routes
internal/mobileapp/handlers_reset.go bc-cmpw: file-mode reset only from a loopback peer and Host, else 403 reset_local_only
internal/mobileapp/handlers_competition.go, handlers_participants.go, handlers_match.go bc-cmpw: Delete competition needs the confirm password; completion rules; self-run roster open and check-in writes 409; a participant's exact resend answered as recorded
internal/mobileapp/auth_admin.go, handlers_auth_admin.go (and their _test.go) bc-cmpw: deleted. The separate admin password and its endpoints
internal/state/models.go, internal/engine/competition.go, scoring_tx.go bc-cmpw: CheckInApplies; PoolMatchesSettled, SwissRoundsToDraw; the exact-replay probe merges on a copy
web-mobile/js/api_client.jsx, ui.jsx, admin_competition_settings.jsx, competition_shape.jsx bc-cmpw: no admin-password flow; X-Confirm-Password; promptDialog; the Delete dialog; no check-in in self-run
web-mobile/js/admin_scoring_autosave.jsx, admin_scoring_individual.jsx, admin_scoring_team.jsx bc-cmpw: landedAutosave clears an edit stamp once a later save lands
web-mobile/js/__tests__/, internal/*/*_test.go bc-cmpw: tests for each rule above; admin_elevated.test.jsx deleted
docs/user-guide/ (organisers, competitors, commands, install), docs/architecture/, specs/openapi.yaml, .claude/rules/match-writes.md, CLAUDE.md, AGENTS.md bc-cmpw: one password, Delete's re-entry, self-run rules, completion rules, reset from the server machine
scripts/screenshots/lib/editor.mjs INLINE_EDITOR, the inline editor's selector; a comment says "team", not "fixed-order team"
scripts/screenshots/lib/ui.mjs Comment only: "team", not "fixed-order team" (operator ruling)

Screenshots

The bc-cmpw screenshots:

Awards tab of a knockout completed from the admin console, with no password asked (bc-cmpw)

Delete competition asks for the admin password; a wrong one reads "Wrong password. The competition was not deleted." (bc-cmpw)

A self-run tournament's dashboard has no Registration desk card (bc-cmpw)

The bc-ltap fix's screenshots are in its section above (the defect is a lost tap, so the before is the trace and the failing test; there is no layout change). The shared-shiaijo evidence is in its own section too. Below are the knockout and mixed review's evidence shots of the top findings, at operator size (1180x820, coarse pointer) unless noted, captured at 548a936 before main merged #444. Every bead these shots show is now closed on main (bc-kpnl superseded). bc-cdbl's fix (#463) has no e2e test and was not re-checked through the harness. Every step's shot is under scripts/e2e/output/ after make e2e.

After Kiken - Voluntary on the court console, the Remaining matches panel is gone and cannot be reopened; the court moved on (bc-kpnl)

Fusenpai on the withdrawn competitor's next pool bout is refused with the raw code already_ineligible (bc-kfup, bc-rawm)

Starting that bout is refused with the raw text "kiken-voluntary at Pool A-0", which names nothing the operator can act on (bc-rawm)

Correcting a hantei recorded for the wrong side: no reason prompt, a raw correctionReason toast, and the result stands (bc-htcr)

Team match, size 5: bouts 3 and 4 were never fought, yet read X and "✓ Tie (hikiwake)" (bc-unfx)

Team pool standings after Finish with bout 3 never scored: both teams get IT 2 (bc-tmfn)

Send back to queue under a reopened kachinuki encounter: "Any score entered on this bout", with the destructive button as the loud one (bc-sbq)

After confirming, the six fought bouts are gone and the semi-final restarts at bout 1 (bc-sbq)

Spectator phone (390x844): a double tap on Call to court stacks two identical "Now calling" banners (bc-cdbl)

A knockout bout finished offline: the local bracket advanced, the header still reads Connected, and nothing says the result is not saved yet (bc-offl)

Test plan

  • make go/test passes (lint + security scan + tests): exit 0 on c64a120's tree and again on 9ef805f after main merged through docs(bc-cmtr): trim CLAUDE.md and add terraform and yaml to Serena #471, JS tests 6855 + 76, govulncheck "No vulnerabilities found"; internal/engine 90.1% and internal/mobileapp 88.4% statement coverage. No go.mod or go.sum change.

  • New/updated unit tests cover the change: individual_editor_tap_back_kept.render.test.jsx (bc-ltap), start_match_double_tap.render.test.jsx (bc-sdtp), and for bc-xrpl TestScoreHandler_ExactResendOfAFinishIsAnsweredAsRecorded (5 subtests, pool and knockout, all failing with the 400 before the fix), TestScoreHandler_SameStampFinishThatDiffersStillNeedsAReason, TestScoreHandler_UnstampedFinishOverAFinishStillNeedsAReason, TestExactScoreReplay (12 cases) and the queue test "a write that leaves the queue leaves browser storage at once" (fails without the client change). The console's related render tests (10 files, 198 tests) and the queue tests pass.

  • Manual browser verification, in Chromium on the court console at 1180x820 with a coarse pointer: bc-ltap with the save held 150 ms (before the fix the tap was lost 6 of 6 times; after it 0 of 4 in each order and in the control, each tap's own save carrying the change); bc-sdtp through the shared-shiaijo S3 double tap (before the fix one run started SF1, then sent it back and started SF2; after it both runs send one start and SF1 runs). The journeys (J1 to J8, the shared-shiaijo S1 to S9 and P1 to P8, the moved match) were performed through the harness with every clumsy variant V1 to V5.

  • make e2e green locally at 303daf6: default workers, 35 passed and 8 skipped (the eight fixmes), 4.3 min, every console error allowlisted, no console row in any audit.json (35, not 39: the kachinuki bead guards run as steps inside the final's tests). At 303daf6 too: --workers=1 35 passed, 8 skipped, 12.0 min; from a fresh clone 35 passed, 8 skipped, 4.3 min; console clean in both. (Earlier fresh-clone and one-worker runs found bc-ltap, bc-sdtp and bc-xrpl.)

  • The review's "cannot fail" fixes were proved able to fail: bc-tmfn with a wrong refusal text, bc-htcr corrected to the other side, the individual spec with J1 forced to fail (all eight bead guards still ran).

  • Each open fixme (bc-seqc, bc-pubq, bc-rnrm, bc-pace, bc-sypl, bc-mvrq, bc-stpf, bc-adna) asserts the fixed behaviour through the UI and names its operator ruling; its fix PR turns it into a plain test. Every fixme of the knockout and mixed review is already a plain test (bc-kpnl's two deleted as superseded).

  • Screenshots added above (bc-ltap, the shared-shiaijo evidence, and the review's evidence shots). bc-sdtp changes no pixel (a tap guard), so its evidence is the trace and the writes above.

  • Docs updated under docs/ (make docs/build passes): docs/architecture/network-architecture.md describes bc-xrpl's exact-resend answer, and specs/openapi.yaml the PUT /score correction-reason case. bc-ltap makes taking back a strike and re-entering it behave as the operator docs already describe, and bc-sdtp makes a double tap on Start match do what one tap does; neither changes a feature, flag or wording.

  • Docs screenshots and videos recaptured for bc-ltap, bc-sdtp and bc-xrpl: none changed. Neither fix changes layout or copy, and the rest of the PR changes no app UI. Note for the next capture: this PR's edits to scripts/screenshots/lib/ make SINCE=main select every capture group.

  • No new console errors or warnings: the harness enforces it. Every test fails on a console error or uncaught page error not on CONSOLE_ALLOWLIST (three reasoned entries), and records warnings. In each run listed above, checked in the run log and in every journey's audit.json: every console error was an allowlisted entry, and no warning was recorded.

  • make go/test and make docs/build pass on the final tree

  • Full make e2e green (default workers and --workers=1) with the console clean, and from a fresh clone

  • Browser: Delete competition with a wrong password deletes nothing and shows the message; with the right one it deletes

  • Browser: a self-run tournament shows no check-in; completing a knockout asks for no password

  • Reset from 127.0.0.1 works; another machine or a non-loopback Host gets 403 reset_local_only

Closes bc-e2eh
Closes mp-yqxn.2
Closes bc-shcr
Closes bc-mvsj
Closes bc-ltap
Closes bc-sdtp
Closes bc-xrpl
Closes bc-cmpw

🤖 Generated with Claude Code

gitrgoliveira and others added 7 commits September 24, 2026 00:49
…nd persona helpers

Local gate only (make e2e, make e2e/deps); not in go/test or CI. Reuses
scripts/screenshots/lib server/net/editor; finishMatch gains a root
argument for the inline shiaijo editor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each playwright run empties outputDir at start, so two runs in one
checkout deleted each other's traces (ENOENT on context close).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…order team, kachinuki)

Persona-audited journeys J1-J8; findings are test.fixme until their beads
land. shots.mjs re-applies touch emulation after a fullPage shot, which
otherwise drops (pointer: coarse) for the rest of the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urement

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gitrgoliveira

gitrgoliveira commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner Author

2026-10-12: this is the review as performed on 2026-09-24 (at 548a936), with the reviewers' judgement; every finding it names is now closed on main except where the PR body says otherwise. The same journeys' raw rows from a current run are in this comment.

Persona audit A: individual (J1, J2, J6, J8)

Every row is one operator action x clumsy variant, performed in the browser through the harness at 1180x820 with a coarse pointer. Finding ids in this table map to beads as follows: A1 bc-kpnl, A2 bc-kfup, A3 bc-otpl, A4 bc-htcr, A4b bc-htsd, A5 bc-plcl, A6 bc-sbq, A7 bc-kosc, A8 bc-offl (offline reload: mp-wm4e), A9 bc-dtfn, A10 bc-dsid, A11 bc-dlck, A12 bc-rawm, A13 rejected (documented behaviour), A14 bc-dscn, A15 bc-tp44, A16 bc-dtip, A17 bc-aadv, A18 bc-kbhn, A19 bc-sync, A20 bc-offl, A21 bc-bzlb; wname (found in verification) bc-wnam.

Written before the operator ruling that retires the term "fixed-order": read it as "team match" (the team sets its lineup before each match). Tap-target sizes measured before the shots.mjs coarse-pointer fix were re-measured; the bead bodies carry the final numbers.


mp-yqxn.2 reviewer A: persona audit, INDIVIDUAL competitions

Device: operator project, 1180x820, (pointer: coarse) verified. Spec:
scripts/e2e/journeys/knockout-mixed-individual.spec.mjs. Screenshots are relative to
scripts/e2e/output/knockout-mixed-individual/ (last green run). Raw rows as the spec
recorded them: scripts/e2e/output/knockout-mixed-individual/audit.json (gitignored, rewritten each run).

Fixtures (all through the wizard): F1 "Men Individual" pools + knockout, 12 entrants,
shiaijo A and B, prefix M. F1b "Women Individual" pools + knockout, 6, shiaijo E, prefix W
(decisions). F1c "Cadets Individual" pools + knockout, 6, shiaijo F, prefix C (Pools tab).
F2 "Veterans Knockout" knockout only, joint 3rd UNTICKED (bronze match), 4, shiaijo C,
prefix V. F2d "Juniors Knockout" knockout only, default joint 3rd, 4, shiaijo D, prefix J.

Columns: journey | step | action | variant | what happened | state correct? | recoverable
by the operator alone? | taps to recover | undo discoverable without help text? | severity |
screenshot. "size" rows are the 44px coarse-pointer measurement of the control the row
acts on.

J1: shiaijo console, pools to knockout, same court (F1, shiaijo A and B)

journey step action variant what happened state correct? recoverable alone? taps to recover undo discoverable? severity screenshot
J1 start bout Start match (Up next card) correct + size Bout started, inline editor holds the Up next pairing; button 100x44 yes n/a 0 n/a none J1/01-court-A-idle.png
J1 start bout Start match (Up next card) V1 tapNeighbour not performed: the right neighbour is Call to court, whose double-announcement defect is already known; no scoring state is touched by it n/a n/a n/a n/a n/a n/a
J1 start bout Start match (Up next card) V2 doubleTap (18-35ms) One bout started; second tap landed on "Starting…" (disabled); the next bout moved into Up next and was NOT started yes n/a 0 n/a none J1/02-start-double-tapped.png
J1 start bout Start match V3 / V5 covered by the running-bout interruption rows below n/a n/a n/a n/a n/a n/a
J1 start bout Start match V4 hastyConfirm n/a: no confirm on Start match n/a n/a n/a n/a n/a n/a
J1 award ippon ippon button size 58x44 yes n/a n/a n/a none n/a
J1 award ippon Aka M V1 tapNeighbour right Hit K on the same side; K recorded no (wrong waza) yes 1 (tap the K mark; slot 44x44) yes: "Tap a scored mark to clear it" shows under the slots while a mark exists SLOWS J1/03-ippon-neighbour-tapped.png
J1 award ippon Aka M V2 doubleTap (21-57ms) Two M awarded (M M): the bout reads decided 2-0 no (double award) yes 1 (tap one mark) yes (hint) SLOWS J1/04-ippon-double-tapped.png
J1 clear mark tap a scored mark V2 doubleTap First tap cleared the mark, second tap was a no-op on the emptied slot yes n/a 0 yes none J1/05-mark-double-tapped.png
J1 running bout Shiro K, then reload inside 300ms V3 interrupt reload (inside the autosave debounce) Pill read "Synced" at the tap; after the reload the K was gone (outcome varies with timing: once it survived) no (point lost while the pill said Synced) yes, from memory 1 n/a COSMETIC J1/06-fast-reload-after-tap.png
J1 running bout, one ippon interruption V3 reload Same court A, same match (eyebrow identical), M kept yes n/a 0 n/a none J1/07-interrupt-reload.png
J1 running bout, one ippon interruption V3 hidden (visibilitychange) Event stream reconnected; same match; M kept yes n/a 0 n/a none J1/08-interrupt-hidden.png
J1 running bout, one ippon interruption V3 back Back landed on /admin (dashboard); Forward returned to Shiaijo A, same match, M kept yes yes 1 (Forward, or the court link) n/a none J1/09-interrupt-back.png
J1 running bout, one ippon interruption V3 offline (0.6s) Same match, M kept, pill back to Synced yes n/a 0 n/a none J1/10-interrupt-offline.png
J1 running bout identity after interruptions V5 lost place Court name (h1), eyebrow "MEN INDIVIDUAL · POOL A · MATCH 1 OF 3", both names and numbers visible; eyebrow is small caps but the names are large yes n/a 0 n/a none J1/07-interrupt-reload.png
J1 finish Finish + Start Next size 159x44 yes n/a n/a n/a none n/a
J1 finish Finish + Start Next V2 doubleTap (15-29ms) The two-tap guard did not hold: the double tap armed AND submitted; bout finished and the next bout started result right only because the score was already the intended one yes (Correct) 3-4 (Correct, fix, Save correction, Confirm reason) Correct is on the Completed row SLOWS J1/11-finish-double-tapped.png
J1 finish armed Finish V1 tapNeighbour left not performed: no control within 96px (inline editor has no Cancel; Start match only shows before start). Still armed, court A n/a n/a n/a n/a n/a J1/13-finish-neighbour-tapped.png
J1 finish armed Finish V2 retapWhileSaving Second tap during "Saving…" ignored; exactly one completion; next bout started on A yes n/a 0 n/a none J1/14-finish-retapped-while-saving.png
J1 finish Finish V4 hastyConfirm n/a: no dialog; the two-tap arm is the guard n/a n/a n/a n/a n/a n/a
J1 finish + start next the next bout starts V5 did Finish land? Only the names change in the editor; the finished result is in the Completed list below the fold or the "Just played" strip; no toast yes n/a 0 n/a COSMETIC J1/12-bout-2-started-by-finish-next.png
J1 send back Send back to queue size 129x44, ghost, below the editor at the page bottom yes n/a n/a n/a none n/a
J1 send back Send back to queue confirm (a synced M on the board) V4 hastyShiaijoConfirm Loud button is the destructive "Send back to queue" (primary); score discarded. In one of two final runs the dialog said "No score has been entered, so nothing will be lost." with the M on the board and the pill Synced no (copy false; point lost) yes, from memory 2+ (Start match, re-enter) n/a SLOWS J1/15-send-back-dialog.png, J1/16-send-back-hasty.png
J1 send back restart the sent-back bout recovery Bout back at the top of Up next; restarted with its marks gone yes yes 1 + re-entry yes none n/a
J1 send back Send back to queue V1 / V2 not performed: the dialog is modal, a second tap lands on the dialog; its neighbours are Cancel (safe) n/a n/a n/a n/a n/a n/a
J1 correct Correct (Completed row) size 63x44 yes n/a n/a n/a none n/a
J1 correct Save correction, reason prompt answered by its loud button V4 hasty (inline prompt) Confirm with preset "Scoring error" saved "– vs MK"; the panel STAYED on the corrected bout (CORRECTION pill, Save correction still offered) yes yes 1 ("← Back to court", page bottom right) only by scrolling SLOWS J1/19-correction-reason.png, J1/20-correction-saved.png
J1 correct while correcting V5 lost place Nothing on screen shows the live bout running on the same court (Up next area gone, editor shows the old bout) partial yes 1 only by scrolling SLOWS J1/20-correction-saved.png
J1 court B pools Finish + Start Next through B correct path Every bout stayed on B, but Finish + Start Next started QUARTERFINALS MATCH 3 between Pool D bouts: knockout bouts are scheduled at the day's start time (09:00), so a resolved QF jumps the remaining pool bouts valid but out of order n/a 0 n/a SLOWS J1/22-court-B-pools-done.png
J1 pool -> knockout boundary Finish + Start Next on A's last pool bout correct path Stayed on Shiaijo A and started QF M1 yes n/a 0 n/a none J1/23-boundary-after-last-pool-bout.png
J1 knockout bouts 1-2 Finish + Start Next correct path QF M1 then QF M2 then SF M5, all on A yes n/a 0 n/a none J1/24-court-A-knockout-chained.png
J1 console keys ArrowRight / ArrowLeft / Esc on the console correct path (docs: laptop keys) Nothing moved or closed; the docs (Score a match) say Left/Right move to the previous/next match and Esc closes; the hint row still advertises them on the iPad no (docs divergence) n/a n/a n/a COSMETIC J1/25-console-keys.png
J1 Scores tab nav Prev / Next size Next 62x44; Prev absent on the first row yes n/a n/a n/a none J1/26-scores-overlay-B.png
J1 Scores tab nav Next x3, ArrowLeft/Right correct path All seven editors stayed on Shiaijo B; order interleaves QF and pool bouts (both at 09:00) and walks on into completed bouts court rule holds n/a 0 n/a COSMETIC J1/27-scores-overlay-after-nav.png
J1 Scores tab nav Next V1 tapNeighbour not performed: the left neighbour is Finish + Start Next, whose single stray tap only arms it n/a n/a n/a n/a n/a n/a

J2: special decisions (F1b shiaijo E; F2 shiaijo C)

journey step action variant what happened state correct? recoverable alone? taps to recover undo discoverable? severity screenshot
J2 decision controls Kiken - Voluntary / Kiken - Injury / Fusenpai / Decide by hantei… size 115x44 / 93x44 / 73x44 / 124x44 yes n/a n/a n/a none n/a
J2 kiken Kiken - Voluntary V1 tapNeighbour right Hit Kiken - Injury (25px away): the injury prompt opened, nothing committed yes yes 1 (Cancel) yes none J2/01-kiken-neighbour-tapped.png
J2 kiken side radio "Which side withdrew?" size radio 13x13, label 165x18 n/a n/a n/a n/a SLOWS J2/02-kiken-prompt.png
J2 kiken Record, side left on its default V4 hasty (inline prompt) Prompt defaults to SHIRO; Aka withdrew; Shiro recorded as withdrawn (Kiken beside the wrong competitor) no yes, via the undo below 5 + a forced confirm Correct is visible; nothing flags the mistake SLOWS J2/03-kiken-recorded-default-side.png
J2 kiken chain remaining-matches panel, 3s later V3 interrupt (looked away) The panel was gone before 3s (and, in two runs, before the first read): the court refetch drops the completed bout and unmounts the editor; court idle no no: the panel cannot be reopened on the console n/a no BLOCKS J2/04-kiken-chain-after-3s.png
J2 kiken chain Start match on the withdrawn competitor's next bout recovery attempt (console) Refused with "kiken-voluntary at Pool A-0" (internal id, no advice) refusal right, copy not no n/a no SLOWS J2/05-start-withdrawn-next-bout.png
J2 kiken chain Fusenpai against the withdrawn competitor (Scores tab overlay, the same call "Award default win" makes) correct path (the chain) Refused with the raw code "already_ineligible"; nothing recorded no no n/a no BLOCKS J2/06-chain-fusenpai-scores-tab.png
J2 kiken chain score M for the opponent and Finish (Scores tab) recovery attempt Refused: "kiken-voluntary at Pool A-0" no no n/a no BLOCKS J2/07-chain-scored-instead.png
J2 kiken undo the court moves on (Up next started; involves the real withdrawer) correct path Bout 2 of pool A running yes n/a 0 n/a none n/a
J2 kiken undo Correct bout 1 -> Kiken - Voluntary on Aka V4 hastyConfirm (409 decision_locked) Dialog "Please confirm": "A subsequent match for one of these competitors has already started... Proceed anyway?"; names neither the match nor what happens to it; loud button is the red "Proceed anyway"; forced: bout 1 now "○○ vs Kiken" (Kiken beside Aka) intended outcome, but see next rows yes 1 n/a SLOWS J2/09-decision-locked-confirm.png, J2/10-kiken-undo-forced.png
J2 kiken undo remaining-matches panel after the undo correct path (the chain) "No remaining scheduled matches" for Aka: her running bout is not listed incomplete no n/a no SLOWS J2/11-undo-chain-award.png
J2 kiken undo back to the court V5 lost place The bout with the now-withdrawn competitor is still running on E with no marker no no (next row) n/a no BLOCKS J2/12-after-undo-court-E.png
J2 kiken undo finish that bout (M for the opponent) recovery attempt Refused "kiken-voluntary at Pool A-0"; only exit is Send back to queue, where it stays unstartable. Pool A cannot complete no no n/a no BLOCKS J2/14-finish-bout-of-withdrawn.png, J2/13-after-undo-pools-tab.png
J2 fusenpai Record (Aka did not show) V2 doubleTap (19-26ms) One decision: "○○ vs Fus.", no duplicate write, no 409 yes n/a 0 n/a none J2/15-fusenpai-recorded.png
J2 fusenpai auto-advance to the next bout correct path Did NOT advance: the court's Up next was the withdrawn competitor's unstartable bout, the start failed silently, court idle with no message no yes 1 (Start on another Upcoming row) yes SLOWS J2/15-fusenpai-recorded.png
J2 fusenpai Fusenpai V1 tapNeighbour not performed separately: its neighbours are the "?" glossary chip and the disabled Fusensho; the Kiken V1 row shows a mis-tap only opens a prompt n/a n/a n/a n/a n/a n/a
J2 knockout tie Mark draw on a 1-1 knockout bout correct path Disabled; explanation only in a title tooltip (unreadable on touch) yes n/a n/a n/a COSMETIC n/a
J2 knockout tie Finish on a 1-1 knockout bout V1 (Finish instead of Overtime) Finish enabled and armable; refused with toast "bracket match m-r1-0: cannot mark completed with no winner; resolve the tie first (daihyosen, or encho on the final kachinuki bout)"; the toast sits on top of the Finish button yes (not completed) yes 0 n/a SLOWS J2/16-finish-on-knockout-tie.png
J2 encho Overtime pill size 110x44 yes n/a n/a n/a none n/a
J2 encho tap the Overtime pill (centre) correct path Opened the glossary popover ("延長 · Overtime ...") instead of the encho counter; docs say "open the Overtime control and tick Encho started" no (docs divergence) yes 2 (dismiss, tap the timer icon) no SLOWS J2/17-overtime-pill-tapped.png
J2 encho Encho started checkbox size checkbox 13x13, label 165x19 n/a n/a n/a n/a SLOWS J2/18-encho-counter.png
J2 encho + / - size + 30x44, - 29x44 n/a n/a n/a n/a SLOWS J2/18-encho-counter.png
J2 encho + x6, double tap +, - to the floor V2 doubleTap (+) ×7 after six taps, ×9 after the double tap (both counted: unbounded as intended); - floors at ×1 and disables yes yes 2 (- twice) yes none J2/18-encho-counter.png
J2 hantei SHIRO wins / AKA wins size 87x44 / 72x44, 6px apart n/a n/a n/a n/a none J2/19-hantei-armed.png
J2 hantei SHIRO wins (intended) V1 tapNeighbour right Hit AKA wins: committed at once, no confirm; semi-final completed for the wrong competitor and the bracket advanced no no (next row) n/a Correct visible, but refused BLOCKS J2/21-hantei-neighbour-tapped.png
J2 hantei the recorded hantei result correct path First read "M vs K"; updated in place within 6s to "M (E) K Ht" (docs shape) yes n/a 0 n/a none J2/20-hantei-result-after-reload.png
J2 hantei Correct -> SHIRO wins recovery No reason prompt; toast "correctionReason: correcting a completed match result requires a non-empty correctionReason"; result unchanged no no n/a no BLOCKS J2/22-hantei-correction-open.png, J2/23-hantei-corrected.png
J2 decisions any decision V3 interrupt mid-decision not performed: the editor interruptions are covered in J1; the decision-specific interruption (looking away after kiken) is the "3s later" row above n/a n/a n/a n/a n/a n/a

J6: confirm + advance OFFLINE (F2, shiaijo C, bronze match on)

journey step action variant what happened state correct? recoverable alone? taps to recover undo discoverable? severity screenshot
J6 finish offline the last semi-final running correct path Semi-final 2 (Wen Zhao vs Vic Lange) running on C yes n/a 0 n/a none n/a
J6 finish offline the screen 300ms after Finish, offline V5 did Finish land? Editor gone (it holds the "Not saved yet" banner, so no banner anywhere); header still reads "Connected"; local bracket advanced: final Taro Ono vs Vic Lange, bronze Wen Zhao vs Uma Patel in Up next locally yes, write queued n/a 0 n/a SLOWS J6/01-offline-finish-300ms.png, J6/02-offline-finished.png
J6 finish offline the bronze bout in Up next V5 lost place Labelled only "10:55 · Veterans Knockout": no "3rd place", no match number; before the semis it was absent from Later yes n/a 0 n/a COSMETIC J6/02-offline-finished.png
J6 finish offline Retry now while offline V2 doubleTap not performed: no Retry button exists (the banner that carries it never renders) n/a n/a n/a n/a n/a J6/04-offline-retry-double-tapped.png
J6 finish offline Start the same bout again V5 lost place Not offered again (Up next moved to the bronze), so no double entry yes n/a 0 n/a none J6/05-offline-restart-same-bout.png
J6 finish offline reload while offline V3 interrupt reload (offline) net::ERR_INTERNET_DISCONNECTED, blank page: the console is unusable until the network returns no (console lost) yes, when online 1 (reload) n/a SLOWS J6/06-offline-reload.png
J6 reconnect back online, reopen the court V5 lost place The queued Finish (persisted across the failed reload) landed: semi-final "– vs M"; Up next = bronze yes n/a 0 n/a none J6/07-online-again.png
J6 reconnect a second page reads the Bracket tab correct path Server bracket: final carries the winner, 3rd place match carries the loser yes n/a 0 n/a none J6/08-server-bracket-after-reconnect.png
J6 finish offline Finish V1 / V4 n/a: V1 covered in J1 (no neighbour in reach); no dialog on this path n/a n/a n/a n/a n/a n/a

J8: the same scoring from the Pools tab (F1c) and the Bracket tab (F2d)

journey step action variant what happened state correct? recoverable alone? taps to recover undo discoverable? severity screenshot
J8 pools tab match row (opens the editor) size 376x44 yes n/a n/a n/a none J8/01-pools-tab-editor-open.png
J8 pools tab Start match in the Pools-tab editor correct path The editor CLOSED as soon as the start saved started, but the operator is thrown out yes 1 (reopen the row) yes SLOWS J8/02-pools-tab-after-start.png
J8 pools tab one ippon correct path The editor closed again ~300ms later (the autosave write closes it); M kept on reopen point kept, editor lost yes 1 per point yes SLOWS J8/03-pools-tab-after-ippon.png
J8 pools tab Finish ("Finish", no Start Next) V5 lost place Editor closes; back on the Pools tab with the row reading "– vs M"; nothing points at the next bout (the pool's rows are right there) yes n/a 1 (next row) n/a COSMETIC J8/04-pools-tab-after-finish.png
J8 pools tab ✕ Close with a freshly tapped ippon V4 hastyConfirm "Please confirm / Discard unsaved scoring changes?" with a red "Discard changes"; chosen, yet the M was still recorded on reopen (autosave had saved it) no (dialog promises a discard it does not do) yes 1 (tap the mark) yes SLOWS J8/05-pools-tab-reopened.png
J8 bracket tab Finish on a semi-final card V5 lost place Falls back to the read-only result card with "Edit result"; the next semi is one tap away on its card yes n/a 1 n/a none J8/07-bracket-tab-after-finish.png
J8 bracket tab Edit result size 720x44 (re-measured with (pointer: coarse) asserted; the earlier 720x26 was taken after a fullPage shot had dropped the coarse pointer) yes n/a n/a n/a none J8/07-bracket-tab-after-finish.png
J8 bracket tab Edit result confirm V4 hastyConfirm Loud button "Edit result" (primary) only reopens the editor; nothing changes until Save correction yes yes 1 (✕ Close) yes none J8/08-bracket-tab-edit-result.png
J8 bracket tab second semi-final, default joint 3rd places correct path Both semis scored from the Bracket tab; no 3rd place match rendered yes n/a 0 n/a none J8/09-bracket-tab-semis-done.png
J8 fusenpai (court F) Record fusenpai on the console correct path (auto-advance) On a court with nothing stuck the court moved on to its next bout on F by itself (so the J2 non-advance is the A2 stuck bout plus a silent start failure) yes n/a 0 n/a none J8/10-fusenpai-auto-advance-F.png
J8 pools / bracket tab Finish V2 / V3 not performed here: identical editor body to J1 (same Finish guard, same autosave); J1 rows apply n/a n/a n/a n/a n/a n/a

Row count: 83 (J1 35, J2 28, J6 9, J8 11; header rows excluded).

@gitrgoliveira

gitrgoliveira commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner Author

2026-10-12: this is the review as performed on 2026-09-24 (at 548a936), with the reviewers' judgement; every finding it names is now closed on main except where the PR body says otherwise. The same journeys' raw rows from a current run are in this comment.

Persona audit B: team, sizes 3 and 5 (J3, J4, J7)

Every row is one operator action x clumsy variant, performed in the browser through the harness at 1180x820 with a coarse pointer. Finding ids in this table map to beads as follows: B1 bc-lpfb, B2 bc-lprf, B3 bc-unfx and bc-tmfn (ruling: every bout is fought), B4 bc-fsnp, B5 bc-sync, B6 bc-dhrp, B7 bc-dsid, B8 bc-kpnl, B9 bc-dlck, B10 bc-dtfn, B11 bc-tmwn, B12 bc-tmfd, B13 bc-lnrf, B14 bc-lprf, B15 bc-tp44 (centre mark slot rejected; Lineups Add/Save re-measured at 44px), B16 rejected (intended), B17 bc-cfbd, B18 bc-lnul, B19a bc-cpdc, B19b bc-tmdc (ruling: narrow the docs), B20 bc-kkdc (ruling: Correct is the sanctioned recovery).

Written before the operator ruling that retires the term "fixed-order": read it as "team match" (the team sets its lineup before each match). Tap-target sizes measured before the shots.mjs coarse-pointer fix were re-measured; the bead bodies carry the final numbers.


Reviewer B audit: fixed-order TEAM competitions (mp-yqxn.2)

Performed in the browser through the harness, operator project (iPad Air 1180x820, (pointer: coarse) verified), spec scripts/e2e/journeys/knockout-mixed-team.spec.mjs, helpers scripts/e2e/fixtures/team.mjs. Every row below was performed and observed (the raw observation is the AUDIT {...} line of the same step in the run's log), except rows marked "not performed" or "n/a". Screenshot paths are relative to scripts/e2e/output/. Judgement columns (discoverable, severity) are mine, formed from the screenshots at operator size.

Fixtures (all through the wizard, roster paste, Generate draw, Start; members named through the Lineups page, the match panel and the score sheet):

  • F3: "Team Mixed", team, Regular (fixed order), team size 3, Pools + Knockout, 6 teams, shiaijo A + B, prefix M.
  • F4: "Team KO J3" (shiaijo C, prefix K) and "Team KO J4" (shiaijo D, prefix S): team, Regular, size 5, Knockout only, 4 teams. "Team KO J7" (shiaijo E, prefix F): same, 2 teams (a single final).

Severity: BLOCKS a running tournament / SLOWS the operator / COSMETIC. "Tap target" measurements are bounding boxes under the coarse pointer (floor 44x44); team.tapBox asserts (pointer: coarse) before every measurement (re-measured after the shots.mjs fix d12ad9c).

Row count: 86 rows (J3: 39, J4: 35, J7: 12).

Ruling: three lineup entry points, defect or intentional?

Intentional in design, defective in execution.

Evidence (J3 rows 12, 17, 19, 22, 24, 27, 30):

  1. The three are three LAYERS, not three copies. The Lineups page writes the ROUND default for one team (putTeamLineup(..., round), a "Round" number input); the at-court panel ("Enter lineup" on the Up next card, "Lineup" on each Upcoming row) and the score sheet's name boxes both write the per-MATCH override (putMatchLineup), which wins over the round default. Each serves a different moment: before the day (Lineups page, reachable only from the competition's side navigation, not from the court console), before the encounter at the table (panel), and during the bout (sheet, the only one on screen while scoring). The docs describe exactly this layering ("Each team encounter has its own lineup", "Copy from previous match", "An empty position also shows a box directly on its score sheet row"), so having three is not itself the defect.
  2. They CAN disagree, and did, with no operator error: for a pool match the panel showed all Shiro boxes empty under "Inheriting round default" (row 12, J3-lineups/07-f3-panel-round-default.png) while the Lineups page held Aoki/Baba/Chiba and the score sheet showed Aoki on bout 1. Cause: the panel asks for the round default by the match's exact round index (1 for a pool match) while the Lineups page's "Round 1" is index 0; the score sheet (and viewer/TV) ask with ?fallback=best and find it (FINDING-B1). On a first-round knockout match (index 0) the panel showed the round default correctly (row 27). The knockout FINAL (round index 1) is expected to show the same gap as a pool match; not verified (row 32).
  3. The disagreement turns into data loss through a realistic slip: tapping Shiro's "Save lineup" (the panel shows it empty) writes an EMPTY match override, after which the score sheet's Shiro names vanish for that match while the Lineups page still shows them (row 17, J3-lineups/12-f3-panel-shiro-empty-saved.png, 13-f3-sheet-after-shiro-empty-save.png). Three re-entries on the sheet repaired it (rows 21-24).
  4. Which one a hurried operator reaches first: at the court, "Enter lineup" sits beside "Start match" on the Up next card, so the panel is the first lineup surface they meet; once the match starts the sheet's name boxes are the only one. The Lineups page is never on the court console. So the surface the operator reaches first (the panel) is the one that reads a different layer from the surface everything else reads (the sheet, viewer, TV).
  5. Nothing on the panel or the sheet says which layer a name comes from beyond the small "Inheriting round default" / "Override for this match" label on the panel; the sheet shows no provenance at all.

Fix direction: make the panel read the same resolution the sheet uses (?fallback=best, or better, one shared resolver), and do not write an override for a side whose values did not change.

J3: lineup entry from all three entry points (F3 size 3, F4 size 5)

# step action variant what happened state correct? recoverable alone? taps to recover undo discoverable w/o help? severity screenshot
1 F3 Lineups page open, size-3 team correct Positions read bare 1, 2, 3; no FIK names (positionsForSize) yes n/a 0 n/a none J3-lineups/01-f3-lineups-fresh.png
2 EP1 name slot 1 "+ Add new member…", type Aoki, Add V2 doubleTap (29 ms) The second tap landed on the confirm's backdrop and dismissed it: 0 dialogs open, nothing named no (the add silently did not happen) yes 1 (tap Add again) yes (Add still visible with the name typed) COSMETIC J3-lineups/02-f3-lineups-add-doubletap-dialog.png
3 EP1 name slot 1 "Name slot" confirm V4 hastyConfirm Loudest button "Name slot" (primary), message 'Name M2.1 as "Aoki"?'; the loud button is the intended one, and naming a blank slot is reversible (Rename) yes n/a 0 n/a none J3-lineups/03-f3-lineups-named.png
4 EP1 Add button tap target (re-measured under asserted coarse pointer) 44 x 44 px (the earlier 44 x 35 was measured after a fullPage shot had dropped the coarse pointer) n/a n/a n/a n/a none J3-lineups/02-f3-lineups-add-doubletap-dialog.png
5 EP1 wrong fighter pick position 3 V1 wrong option (reserve M2.4) Wrong member placed; list correctly omits members placed elsewhere (offered: Chiba, M2.4, M2.5) yes (as picked) yes 2 (open select, pick Chiba) yes none J3-lineups/03-f3-lineups-named.png
6 EP1 duplicate type Aoki at 2 (Aoki at 1) refusal copy Red alert "Aoki is already at Position 1."; understood, but the page labels the position "1", not "Position 1" yes yes 1 (Cancel) yes COSMETIC (wording differs per surface: row 16 "at 2", row 22 "at Position 2", row 26 "at Senpo") J3-lineups/04-f3-lineups-duplicate-refused.png
7 EP1 unsaved lineup reload before Save V3 interrupt reload Position picks silently gone (all three back to "none"), no unsaved-change prompt; the names minted by the confirms survive in Team members partly (picks lost, members kept) yes 4 (3 picks + Save) n/a COSMETIC J3-lineups/05-f3-lineups-after-reload-unsaved.png
8 EP1 unsaved lineup reload V5 lost place Back on the same team and round; nothing says the picks were never saved yes n/a n/a n/a COSMETIC J3-lineups/05-f3-lineups-after-reload-unsaved.png
9 EP1 save Save lineup V2 retapWhileSaving "Saving…" seen, re-tap made, one "Lineup saved" toast, lineup correct after reload yes n/a 0 n/a none J3-lineups/06-f3-lineups-saved.png
10 EP1 save Save lineup button tap target (re-measured under asserted coarse pointer) 100 x 44 px (the earlier 100 x 33 was measured after a fullPage shot had dropped the coarse pointer) n/a n/a n/a n/a none J3-lineups/06-f3-lineups-saved.png
11 EP1 any V3 hidden / offline not performed: the Lineups page holds no live connection-dependent state beyond the Save; reload (row 7) covers the interruption
12 EP2 open panel Enter lineup (Up next), pool match correct "Inheriting round default" but every Shiro box empty; Lineups page holds Aoki/Baba/Chiba and the sheet shows them (FINDING-B1) no no (the operator cannot make the panel show the default) n/a no SLOWS J3-lineups/07-f3-panel-round-default.png
13 EP2 Aka typed type Dai / Eto / Fuji correct Typed names accepted; position 2's list omits Dai (placed at 1) yes n/a 0 n/a none J3-lineups/08-f3-panel-aka-typed.png
14 EP2 save Aka Save lineup (Aka) V1 tapNeighbour left (71 px) Hit Shiro's "Copy from previous match": red "No previous match found to copy from." stays on the Shiro side yes (nothing written) yes 1 (the intended Save) yes COSMETIC (a harmless slip leaves a red error) J3-lineups/09-f3-panel-v1-neighbour-of-aka-save.png
15 EP2 save Aka Save lineup (Aka) correct "Override for this match"; Save is 86 x 44 yes n/a 0 n/a none J3-lineups/10-f3-panel-aka-saved.png
16 EP2 duplicate type Dai at 2 (Dai at 1), Save refusal copy "Dai is already at 2." names the position being typed, not the one Dai holds (1) (FINDING-B2); both boxes still read Dai, the slot label under 2 reads M1.2 no (wrong copy) yes 3 (open 2, pick Eto, Save) partly SLOWS J3-lineups/11-f3-panel-duplicate-refused.png
17 EP2 wrong Save Save lineup (Shiro), boxes shown empty V1 wrong side Wrote an empty Shiro match override ("Override for this match"); the score sheet then showed Shiro bouts with NO names although the Lineups page still holds the round default no yes 3 (re-enter three names on the sheet) no (nothing says the round default was overridden) SLOWS (consequence of B1) J3-lineups/12-f3-panel-shiro-empty-saved.png, 13-f3-sheet-after-shiro-empty-save.png
18 EP2 panel V2 / V3 / V4 not performed: the panel has no confirm (V4 n/a); a reload closes the panel (the operator reopens it, 1 tap); double tap on Save was covered on the Lineups page (row 9)
19 EP3 open row 1 list tap Shiro row 1 name box correct List offers M2.1 Aoki … M2.5 (no name yet); it appears only after the team members load (up to ~1 s after the sheet renders) yes n/a 0 n/a COSMETIC J3-lineups/14-f3-sheet-row1-list.png
20 EP3 name box tap target 157 x 36 px bar (below 44 tall) n/a n/a n/a n/a COSMETIC J3-lineups/14-f3-sheet-row1-list.png
21 EP3 wrong fighter pick row 1 Shiro V1 option below Baba placed on row 1, slot label moved to M2.2 at once (write is immediate, no Save) yes (as picked) yes 2 (open, pick Aoki) yes none J3-lineups/15-f3-sheet-wrong-fighter.png
22 EP3 duplicate type Baba into row 1 (Baba at row 2) refusal copy Refused ("Baba is already at Position 2."), box reverts to Aoki; the message renders in the amber strip BELOW the IV/PW band, off screen while the operator is at row 1, so the typed name just vanishes yes yes 0 no (the reason is out of view) SLOWS J3-lineups/16-f3-sheet-duplicate.png
23 EP3 name box V2 / V3 / V4 not performed: a pick writes at once (no Save to double tap, no confirm); interruption behaviour of the sheet is audited in J4
24 three entry points compare Shiro positions consistency After the sheet repair: sheet Aoki/Baba/Chiba = Lineups page M2.1 Aoki/M2.2 Baba/M2.3 Chiba, but on different layers (override vs default) yes (by value) n/a n/a n/a see ruling J3-lineups/17-f3-sheet-final.png
25 F4 Lineups page open, size-5 team correct FIK names Senpo, Jiho, Chuken, Fukusho, Taisho (positionsForSize) yes n/a 0 n/a none J3-lineups/28-f4-lineups-fik-names.png
26 F4 EP1 duplicate type Ito at Taisho (Ito at Senpo) refusal copy "Ito is already at Senpo." clear yes yes 1 yes none J3-lineups/29-f4-lineups-duplicate-refused.png
27 F4 EP2 open panel Enter lineup (semifinal, round index 0) correct Panel shows the round default Ito…Ueda (the B1 fallback gap does not bite in round 0) yes n/a 0 n/a none J3-lineups/30-f4-panel-fik-names.png
28 F4 EP2 pick by number pick Aka Senpo slot K1.1 (no name) correct Slot label K1.1 shows under Senpo; saved yes n/a 0 n/a none J3-lineups/31-f4-panel-aka-senpo-by-number.png
29 F4 EP3 sheet type Nakamura into Aka Senpo correct Aria-label "Senpo AKA player" (FIK name kept in the label; bout number column shows 1 by the bc-dnst ruling); label stays K1.1 yes n/a 0 n/a none J3-lineups/32-f4-sheet-row1.png
30 F4 EP3 → EP1 name typed on the sheet reaches the Lineups page consistency Team members list shows Nakamura once (the blank slot was renamed, not a new member minted) yes n/a 0 n/a none J3-lineups/33-f4-lineups-aka-member-named-from-sheet.png
31 docs "Copy from previous match at the top of the lineup panel" docs check The button is at the BOTTOM of each side, beside Save lineup docs diverge n/a n/a n/a COSMETIC J3-lineups/07-f3-panel-round-default.png
32 F4 final round panel on the final (round index 1) V5 not performed: the J3 F4 final was never reached; J4's final was scored by fusenpai without opening the panel. Expected to show B1 (same exact-round fetch)
33 EP2 Rename Rename control on the panel tap target (measured under asserted coarse pointer) 47 x 14 px text link under the position name (admin_schedule_lineup.jsx styles it fontSize 11, height auto, minHeight 0) n/a n/a n/a n/a COSMETIC J3-lineups/11-f3-panel-duplicate-refused.png
34 EP1 add a substitute "+ Add new member…" at position 3 (Fuji holds slot .3; reserves .4 and .5 are blank) V4 hastyConfirm Loudest button "Add member" (primary) on "Add "Goto" as a new member of Team Ryu? This adds a new position to the team. Once added, it can be cleared but never removed."; members 5 → 6. The irreversible choice is the loud one, and it minted a sixth position although two blank reserve slots existed no (a permanent extra slot the operator did not need) partly (clear name only, never removed) n/a n/a COSMETIC (the copy names the consequence; the unused reserves are not offered as the alternative) J3-lineups/18-f3-lineups-add-member-confirm.png, 19-f3-lineups-after-add-member.png
35 pool standings Finish the F3 pool encounter with bout 1 won, bout 2 drawn, bout 3 never scored consistency Sheet showed bout 3 as X before Finish; Pools tab: Team Kame W1 L0 T0 IV1 IL0 IT2 PW1 PL0, Team Ryu IT2: the unscored bout counts as an individual draw (FINDING-B3, standings consequence) no no (the operator cannot tell an unfought bout from a draw on the sheet) n/a no SLOWS; BLOCKS where IT decides a pool place J3-lineups/20-f3-pool-encounter-bout3-unfought.png, 21-f3-pools-standings.png
36 pool kiken chain Kiken – Voluntary, Shiro (Team Kuma) withdraws from the running pool encounter correct "Remaining matches for Team Kuma" appears with "Team Kame · Pool A · Shiaijo A · 09:32 · Award default win to opponent" yes (for about a second) n/a n/a n/a none J3-lineups/22-f3-kiken-remaining-matches.png
37 pool kiken chain the operator reads the panel before acting V5 lost place 1.5 s later the panel is gone: the court moved on ("Ready when you are"), Up next is the withdrawn team's remaining match (Kuma vs Kame), and nothing offers the award again (FINDING-B8) no partly (see row 39) n/a no SLOWS J3-lineups/23-f3-kiken-1500ms-later.png
38 pool kiken chain Award default win to opponent V1 tapNeighbour / V2 doubleTap not performed: the award button was no longer on screen (row 37); the Pools tab still shows Kuma vs Kame as unplayed ("vs") J3-lineups/26-f3-pools-after-kiken-chain.png
39 pool kiken chain Start match on the withdrawn team's remaining match (Up next) V5 lost place Refused, but the only copy is the raw "kiken-voluntary at Pool A-1" (inline and as a toast): it names neither team nor what to do (record a fusenpai / award the default win) yes (refused) yes, if the operator knows to open the match and record fusenpai 4+ no SLOWS J3-lineups/27-f3-start-withdrawn-team-match.png

J4: fixed-order team scoring at the court (F4, /admin/shiaijo/D)

# step action variant what happened state correct? recoverable alone? taps to recover undo discoverable w/o help? severity screenshot
1 start SF1 Start match (Up next) V2 doubleTap (25 ms) One start, editor holds SF1, Up next moved to SF2, no error; button 100 x 44 yes n/a 0 n/a none J4-team-scoring/01-sf1-started.png
2 start first strike right after Start strike within ~200 ms of Start Observed in a throwaway harness spec (since removed): the sheet opens in PRE-MATCH before the start lands; a strike tapped then shows in its slot, the pill reads Synced, but no write carries it and it is gone after an immediate reload (any later edit would carry it). With writes held 2.5 s the sheet only opens after the start lands, so the window is the fast-network ~200 ms no yes (re-enter) 1 no (pill said Synced) COSMETIC (B5) none kept
3 bout 1 Shiro kote V2 doubleTap (22 ms) Two marks (M K instead of M): the double tap awarded two points no yes 1 (tap the K mark) yes ("Tap a scored mark to clear it" shows above bout 1) COSMETIC J4-team-scoring/02-bout1-double-tap-kote.png
4 bout 1 ippon button / mark slot tap target Ippon 44 x 44; the centre mark slot the operator taps to clear is 26 x 44 n/a n/a n/a n/a COSMETIC J4-team-scoring/02-bout1-double-tap-kote.png
5 bout 2 Aka men V1 tapNeighbour left (71 px) Hit bout 2's centre "Tie (hikiwake)": bout 2 marked X no yes 1 (tap Tie again) yes (button reads "✓ Tie") COSMETIC J4-team-scoring/03-bout2-neighbour-tap.png
6 running total after bouts 1-2 correct IV 1 · PW 1 each, RESULT "DAIHYOSEN"; but bouts 3-5, never touched, already read X / "✓ Tie" (FINDING-B3) no (unfought bouts shown as draws) no n/a no SLOWS J4-team-scoring/04-after-bout2-totals.png
7 running total legibility R6 glance (re-measured under asserted coarse pointer) The IV/PW band sits BELOW bout 5 (band top at 1359 px, viewport 820); bout rows are 205 px tall and 2.4 fit on screen, so the running total is off screen while scoring bouts 1-4 n/a n/a n/a n/a SLOWS J4-team-scoring/04-after-bout2-totals.png
8 bout 3 Fusensho Aka, then untoggle correct ○○ on Aka, untoggle restores Shiro K; Fusensho 76 x 44 yes n/a 1 yes (title "Click to undo fusensho") none J4-team-scoring/05-bout3-fusensho-aka.png
9 bout 3 Fusensho Aka, reload, untoggle V3 interrupt reload The fusensho had already erased Shiro's K on the wire (docs: the withdrawing side's points are kept). After reload "✓ Fusensho" ○○ persisted; untoggle clears the flag but leaves ○○ as ordinary points and cannot restore K (FINDING-B4) no yes 3 (clear two ○, re-award K, if remembered) no SLOWS J4-team-scoring/06-bout3-after-reload-before-untoggle.png, 07-bout3-untoggle-after-reload.png
10 bout 4 look at a bout not yet fought V5 which bout is live Bout 4 reads X with "✓ Tie (hikiwake)" before anyone fought it (FINDING-B3) no no n/a no SLOWS J4-team-scoring/08-bout4-before-it-is-fought.png
11 bout 4 Aka do while offline V3 interrupt offline Pill "Offline" while down, "Synced" after; D kept yes n/a 0 n/a none J4-team-scoring/09-bout4-offline.png
12 bout 4 Aka kote then reload within 300 ms V3 interrupt reload (instant) Pill read "Synced" at the reload; after reload only D, the K is gone (B5, judgement: the trigger, an unload inside 300 ms of a tap, is rare) no yes 1 no (pill said Synced) COSMETIC none (state recorded)
13 bout 4 tab hidden and back V3 interrupt hidden Event stream reconnected, same match, D kept yes n/a 0 n/a none none
14 bout 4 Back then Forward V3 back + V5 lost place Back lands on the competition's Scores tab; Forward returns to Shiaijo D with the same encounter open and D kept yes yes 1 (Forward) yes none J4-team-scoring/10-bout4-after-back.png
15 bout 5 Tie on the unfought bout 5 correct Bout 5 already showed "✓ Tie"; the tap turned it OFF (reads vs): the correct action produced the opposite of what the operator meant (FINDING-B3) no yes 1 (tap again) no SLOWS J4-team-scoring/11-bout5-tie-tapped.png
16 bout 5 scoring view R5 / R6 glance While on bout 5 the page has scrolled: match identity (eyebrow, team names) is off screen, IV/PW band a sliver at the bottom edge n/a n/a n/a n/a SLOWS J4-team-scoring/12-bout5-scoring-view.png
17 tied encounter all five scored, level correct IV 1-1 PW 1-1, "DAIHYOSEN", Finish reads "Needs a winner" and is disabled; the tie-breaker block is highlighted yes n/a 0 n/a none J4-team-scoring/13-tied-daihyosen-offer.png
18 daihyosen Add representative bout correct DH row appears in place (no reload needed although the inline host's onClose is a no-op); centre (DH); button 153 x 44 yes n/a 0 n/a none J4-team-scoring/14-daihyosen-added.png
19 daihyosen pick each team's representative correct No picker: both sides read "-" (FINDING-B6, docs: "The score editor lets you pick each team's representative from its roster") no (docs diverge) no n/a n/a SLOWS J4-team-scoring/15-daihyosen-row.png
20 daihyosen Add representative bout V2 doubleTap not performed: the button disables itself ("Adding…") and the row replaces the block; one attempt only
21 hantei SHIRO wins V1 tapNeighbour right (6 px gap) Hit "AKA wins": Ht chip on Aka no yes 1 (tap SHIRO wins) yes (both buttons visible, chosen one filled) COSMETIC J4-team-scoring/16-hantei-wrong-side.png
22 hantei SHIRO wins correct Ht on Shiro's slot, centre stays (DH) (closed middle respected), RESULT "SHIRO WIN"; buttons 87 x 44 yes n/a 0 n/a none J4-team-scoring/17-hantei-shiro.png
23 finish SF1 Finish + Start Next V2 doubleTap (18 ms) The double tap passed the two-tap guard: SF1 finished and SF2 opened in the editor yes (intended here) yes (Correct, with a reason) 2+ no SLOWS (the guard does not stop an impatient double tap) J4-team-scoring/18-sf1-finished-sf2-open.png
24 finish SF1 did Finish land? V5 Completed row reads "IV 1–1 / PW 1–1" with no winner mark; the sheet now shows SF2 scrolled mid-page; only the bracket preview shows who won partly n/a n/a n/a SLOWS J4-team-scoring/18-sf1-finished-sf2-open.png
25 SF2 kiken Kiken – Voluntary V1 tapNeighbour right (25 px) Hit "Kiken – Injury": its prompt opened yes (nothing recorded yet) yes 1 (Cancel) yes none J4-team-scoring/19-sf2-kiken-neighbour.png
26 SF2 kiken Record withdrawal (intended: Aka) V4 hasty (loudest button "Record") The side picker PRESELECTS Shiro; a hasty Record withdrew the wrong team (Shiro, J4 Tanuki) with a voluntary kiken, which the docs call permanent; the side radios' label rows are 18 px tall no yes, but only by the undocumented path in rows 31-34 6 no SLOWS (high; see B7) J4-team-scoring/20-sf2-kiken-prompt.png, 21-sf2-kiken-recorded.png
27 SF2 kiken recovery Correct on the semifinal V4 recovery Correct opens a blank team sheet with "Save correction"; nothing on it shows that a kiken is recorded, no reinstate control no see row 33 n/a no SLOWS J4-team-scoring/23-sf2-correct-after-kiken.png
28 SF2 kiken default-win chain (Remaining matches panel) correct Not shown: in a knockout the withdrawn team has no scheduled matches left; the chain was exercised in the F3 pool instead (J3 rows 36-39) n/a n/a n/a n/a n/a J4-team-scoring/21-sf2-kiken-recorded.png
29 final fusenpai Fusenpai (Aka did not show), side picked, on the corrected final (J4 Tanuki vs J4 Inoshishi) correct Recorded, no eligibility refusal for the reinstated Tanuki; "Fus." beside the no-show on the bracket, "complete on Shiaijo D"; the Completed rows' result cells are EMPTY for the kiken and fusenpai team results partly n/a n/a n/a SLOWS J4-team-scoring/28-final-fusenpai-prompt.png, 29-final-fusenpai-recorded.png
30 J4 Prev/Next match buttons, retap while Finish saves, encho on a team bout various not performed: time; Finish was covered by the double tap (row 23); encho is out of scope for fixed-order ties (daihyosen path)
31 SF2 kiken recovery the operator starts the final before noticing V5 lost place The final (J4 Kitsune vs J4 Inoshishi) started normally; nothing on the court flags the kiken as unusual yes (as recorded) n/a n/a n/a none J4-team-scoring/22-final-started-with-wrong-team.png
32 SF2 kiken recovery in the correction: Withdrawal disclosure → Kiken – Voluntary → Aka → Record correct (the kiken-undo) Accepted after the confirm in row 33 yes yes 5 taps to get here (Correct, disclosure, Kiken – Voluntary, Aka, Record) no (not documented; recording-decisions.md calls voluntary kiken permanent) SLOWS J4-team-scoring/24-sf2-rekiken-aka-prompt.png
33 SF2 kiken recovery 409 decision_locked confirm V4 hastyConfirm Loudest button "Proceed anyway" (danger). Message: "A subsequent match for one of these teams has already started. Overwriting the prior decision now may make those downstream results inconsistent. Proceed anyway?" It does not name the final, nor say that its Shiro team will be swapped. Here proceeding was the intended action yes yes 1 n/a SLOWS (comprehension under time pressure: generic, names no match, no consequence) J4-team-scoring/25-sf2-decision-locked-confirm.png
34 SF2 kiken recovery the court after the recovery V5 lost place The running final now reads J4 Tanuki vs J4 Inoshishi (the Shiro team silently swapped in the open editor); SF2 bracket shows Kiken beside Kitsune yes n/a n/a n/a none J4-team-scoring/26-sf2-after-rekiken.png, 27-court-after-kiken-recovery.png
35 J4 autosave pill during the 300 ms debounce R8 feedback The pill reads "Synced" from the tap until the debounced write goes out (row 12), and in PRE-MATCH before the start lands (row 2) no (the pill overstates) n/a n/a n/a COSMETIC none

J7: one result on every surface (court iPad, head-table iPad, spectator phone, TV board)

# step action variant what happened state correct? recoverable alone? taps to recover undo discoverable w/o help? severity screenshot
1 bout 1 live Shiro men at the court correct TV board: "IV 1 PW 1", "#1 M vs #1" yes n/a 0 n/a none J7-one-result/01-tv-after-bout1.png
2 bout 1 live TV board, bouts 2-5 correct TV and viewer show X on bouts 2-5 that have not been fought (FINDING-B3, public) no no n/a n/a SLOWS J7-one-result/01-tv-after-bout1.png, 02-viewer-after-bout1.png
3 daihyosen live Add representative bout at the court correct TV shows the DH row with (DH) in its centre yes n/a 0 n/a none J7-one-result/03-tv-with-daihyosen.png
4 finish elsewhere head table: hantei Shiro, Finish two devices Court console (left open) followed: Completed 1, "complete on Shiaijo E" yes n/a 0 n/a none J7-one-result/04-head-table-hantei-shiro.png, 05-court-after-head-table-finish.png
5 finish elsewhere court's Completed row V5 who won? Row reads "IV 1–1 PW 1–1", no winner mark partly n/a n/a n/a SLOWS J7-one-result/05-court-after-head-table-finish.png
6 adopt verdict court opens Correct on the final and leaves it correct Court correction editor shows Ht on Shiro yes n/a 0 n/a none J7-one-result/06-court-correction-open-shiro.png
7 adopt verdict head table corrects hantei to Aka (reason "Hantei was for Aka") two devices Reason prompt required and understood; correction saved yes n/a 0 n/a none J7-one-result/07-head-table-correction-reason.png, 08-head-table-after-correction.png
8 adopt verdict court's editor left open two devices ADOPTED without a tap: Ht moved to Aka, RESULT "AKA WIN" (CLAUDE.md "Editors adopt the server's verdict" holds for the team daihyosen) yes n/a 0 n/a none J7-one-result/09-court-correction-after-remote-change.png
9 every surface viewer Recent results correct "IV 1–1 PW 1–1" between the two teams, nothing says Aka won; a knockout win reads like a draw partly n/a n/a n/a SLOWS (distinct from the known "drawn match missing from Recent results") J7-one-result/10-viewer-final.png
10 every surface viewer Bracket tab correct ✓ beside J7 Asahi (Aka), matching the corrected result; no Ht mark on the card yes (winner) n/a 0 n/a COSMETIC (docs say marks for a team encounter appear on the bracket) J7-one-result/12-viewer-bracket-final.png
11 every surface TV board after completion correct "All matches completed", no result shown (by design, nothing wrong) yes n/a 0 n/a none J7-one-result/11-tv-final.png
12 J7 V1 / V2 / V3 / V4 on the two-device path variants not performed here: every control on this path was already probed in J4; J7 is about agreement across surfaces

Findings (severity-ranked; ids are placeholders for the orchestrator)

Functional findings carry a self-contained test.fixme('FINDING-B<n>: ...') at the end of knockout-mixed-team.spec.mjs; each was run once with fixme removed and failed at its docs assertion today. B5 was withdrawn from functional to judgement (its trigger is a synthetic sub-300 ms unload), so the numbering gap is deliberate. B3 has TWO fixmes with one root cause: file one bead.

id title severity kind
B3 An unfought bout is stored as a hikiwake: it reads X / "✓ Tie" on the sheet, the TV board and the viewer, a Tie tap on it turns the draw OFF, and a pool encounter finished with a bout unscored counts it in IT SLOWS (BLOCKS where IT decides a pool place) functional (2 fixmes)
B8 On the court console the withdrawn team's "Remaining matches" panel vanishes about a second after a team kiken; the default win is never offered again and the next start is refused with "kiken-voluntary at Pool A-1" SLOWS functional
B1 The match lineup panel shows a pool match's round-default lineup as empty ("Inheriting round default"), while the sheet, viewer and TV show it; saving the empty side then wipes those names from the sheet SLOWS functional
B7 Kiken / fusenpai prompt preselects Shiro and its button reads a bare "Record": a hasty tap withdraws the wrong team (voluntary kiken, "permanent" per the docs); the only way back is undocumented and the correction sheet shows no kiken SLOWS (high) judgement
B4 A per-bout fusensho erases the other side's points already scored (docs: they stay valid); after a reload its undo cannot restore them and leaves the maru as ordinary points SLOWS functional
B6 The daihyosen row offers no way to pick each team's representative (docs: "lets you pick each team's representative from its roster") SLOWS functional
B2 The panel's "already at" refusal names the position being typed, not the one the member holds ("Dai is already at 2." for Dai at 1) SLOWS functional
B9 decision_locked confirm is generic ("A subsequent match for one of these teams has already started ... Proceed anyway?"), names no match and not the swap it causes; the loud button is "Proceed anyway" SLOWS judgement
B10 A double tap on Finish passes the two-tap guard (finished SF1 and opened SF2) SLOWS judgement
B11 Who won a team encounter decided by daihyosen, kiken or fusenpai is not shown on the court's Completed list ("IV 1–1 PW 1–1" or an empty cell) nor on the viewer's Recent results ("IV 1–1 PW 1–1") SLOWS judgement
B12 Team sheet layout at 1180x820: 2.4 bouts per screen (rows 205 px, re-measured under the coarse pointer); the IV/PW band is below bout 5 and the match identity scrolls away while scoring bouts 3-5 SLOWS judgement
B13 The sheet's lineup refusal renders below the IV/PW band, off screen while the operator is at bout 1; the typed name just vanishes SLOWS judgement
B14 Three wordings for one refusal: "at Position 1" (Lineups page), "at 2" (panel), "at Position 2" (sheet); the page labels the position "1" COSMETIC judgement
B15 Tap targets below 44 px under the coarse pointer (re-measured with coarse asserted before each measurement): sheet name box 157x36, centre mark slot 26x44, kiken side radio rows 166x18, panel Rename link 47x14. Lineups Add (44x44) and Save (100x44) meet the floor; their earlier 44x35 and 100x33 were a harness artefact COSMETIC judgement
B5 The autosave pill reads "Synced" through the 300 ms debounce and in PRE-MATCH before the start lands; a strike in either window is lost if the page unloads before the next edit COSMETIC judgement
B16 V4 on "+ Add new member…": the loud button mints a permanent extra position ("can be cleared but never removed") although two blank reserve slots exist COSMETIC judgement
B17 A double tap on the Lineups page's Add dismisses its own confirm (second tap lands on the backdrop) COSMETIC judgement
B18 Lineups page loses unsaved picks on reload without a prompt COSMETIC judgement
B19 Docs: "Copy from previous match at the top of the lineup panel" (it is at the bottom of each side); team-encounter marks "appear on the bracket" but a daihyosen hantei shows no Ht on the bracket card COSMETIC docs divergence
B20 Docs say a voluntary kiken is permanent, yet a correction reinstated the team (J4 rows 31-34). Operator decision, not a code fix: that path is B7's only recovery n/a docs divergence (decision)

@gitrgoliveira

gitrgoliveira commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner Author

2026-10-12: this is the review as performed on 2026-09-24 (at 548a936), with the reviewers' judgement; every finding it names is now closed on main except where the PR body says otherwise. The same journeys' raw rows from a current run are in this comment.

Persona audit C: kachinuki, size 5 (J5)

Every row is one operator action x clumsy variant, performed in the browser through the harness at 1180x820 with a coarse pointer. Finding ids in this table map to beads as follows: C1 bc-sbq, C2 bc-crpn, C3 bc-emsl, C4 bc-kheb (ruling: drop the encounter (E) on the header and the bracket card), C5 bc-sync, C6 bc-dtfn, C7 bc-cfcl, C8 bc-tmfd and bc-kfnl (ruling: add an advisory fighters-left line), C9 bc-kbrw, C10 bc-rawm, C11 bc-rawm, C12 rejected (deliberate preset), C13 bc-flst, C14 bc-rboq, C15 bc-kbhn, C16 bc-dscn, C17 bc-tp44, C18 bc-lnul.

Written before the operator ruling that retires the term "fixed-order": read it as "team match" (the team sets its lineup before each match). Tap-target sizes measured before the shots.mjs coarse-pointer fix were re-measured; the bead bodies carry the final numbers.


Reviewer C: J5 kachinuki persona audit (mp-yqxn.2)

Fixture F5, seeded through the UI only: team knockout, kachinuki, team size 5, four teams
(Hayabusa, Kawasemi, Tsubame, Washi), shiaijo A and B, number prefix T. Draw: M1 on A
(Kawasemi v Hayabusa), M2 on B (Washi v Tsubame), final M3 on A. All 20 team members named
by typing into the court console's "Enter lineup" panel.

Device: operator project, 1180x820, (pointer: coarse) verified at every measurement (see
harness defect H1: a fullPage screenshot silently switches the page to a fine pointer; the
spec restores it and records coarseLost).

Spec: scripts/e2e/journeys/knockout-mixed-kachinuki.spec.mjs (7 tests pass, 5 fixme).
Screenshots: scripts/e2e/output/knockout-mixed-kachinuki/NN-*.png
numbered as taken on the last green run. Raw audit records: the AUDIT {...} lines in
the run's log and audit.json in the output folder.

Columns: # | step | action | variant | what happened (observed) | state correct? | recoverable
alone? | taps to recover | undo discoverable without help text? | severity | screenshot

Severity: BLOCKS a running tournament / SLOWS the operator / COSMETIC / ok (no issue).

Audit table (84 rows)

# step action variant what happened state correct? recoverable alone? taps undo discoverable? severity screenshot
1 seed Enter lineup (Up next card) correct path Lineup panel opens over the console; both teams, five FIK positions each yes n/a 0 n/a ok 01-lineup-A-saved.png
2 seed Enter lineup V1 not performed separately: the neighbour of Start match IS Enter lineup, see row 9 - - - - - -
3 seed Enter lineup V2 not performed: opening a panel twice is idempotent by construction (one modal) - - - - - -
4 seed Enter lineup V4 n/a: no confirm - - - - - -
5 M2 lineup type five names, not saved V3 reload Panel closes on reload; all five typed names gone (no draft, no prompt) yes (nothing was saved) yes, retype 1 + 5 names + Save n/a SLOWS (FINDING-C18) 02-v3-lineup-reload-unsaved.png
6 M2 lineup type names V1/V2 n/a: keyboard entry, no tap target to miss - - - - - -
7 M2 lineup Save lineup V2 doubleTap (18ms) One save, one toast "Match lineup saved", names intact yes n/a 0 n/a ok 03-v2-lineup-save-double-tap.png
8 M2 lineup Save lineup V4 / V5 n/a: no confirm; interruption covered by row 5 - - - - - -
9 M1 start Start match (Up next) V1 right Thumb hits "Enter lineup" (10px away): lineup panel opens instead of starting yes (nothing changed) yes, "Close" 1 yes (big Close) ok 05-v1-start-neighbour.png
10 M1 start Start match V2 doubleTap (30ms) Started once; inline editor on M1 bout 1 yes n/a 0 n/a ok 06-v2-start-double-tap.png
11 M1 start Start match V3 / V4 n/a: instant action, no confirm - - - - - -
12 M1 bout 1 Shiro ippon M V2 doubleTap (15ms) Two M awarded (bout decided 2-0 on one intended point) no yes, tap the mark 1 yes: "Tap a scored mark to clear it" shows while a mark exists SLOWS 08-v2-ippon-double-tap.png
13 M1 bout 1 Shiro ippon M V1 right K awarded instead of M (same side, 6px gap) no (wrong waza) yes, tap the mark 1 yes SLOWS 09-v1-ippon-neighbour.png
14 M1 bout 1 scored bout before Record V3 reload Same court, same match, same bout; M K kept (autosaved); pairing Fujii v Aoki yes n/a 0 n/a ok 10-v3-reload-bout-1.png
15 M1 bout 1 Record bout V2 re-tap while "Saving…" Exactly one bout recorded; next pairing Fujii v Baba (winner stayed on) yes n/a 0 n/a ok 11-v2-record-retap.png
16 M1 bout 2 Record bout V1 right Thumb hits End match: it ARMS; its armed label (tap again + "AKA WIN") is dark text on a dark fill, barely readable yes (only armed) yes, Record disarms it 0 extra partly: armed label illegible (FINDING-C7) SLOWS 12-v1-record-neighbour-end-armed.png
17 M1 bout 2 Record bout V4 n/a: one tap by design (operator ruling in code), no confirm - - - - - -
18 M1 bout 3 × Remove this bout V2 doubleTap (20ms) Appended pairing removed once; bout 2 (Aka 2-0) is the live row again; second tap harmless yes yes, Record bout re-appends Goto v Baba 1 yes (Record bout) ok 13-v2-remove-bout-double-tap.png
19 M1 bout 3 × Remove this bout V1 not performed: no second control within reach in the tests' layout pass - - - - - -
20 M1 bout 3 × Remove this bout V4 n/a: no confirm (removes only an unscored bout, per docs) - - - - - -
21 M1 bout 3 Tie (hikiwake) V2 doubleTap (31ms) Toggled on then off: no tie, centre back to "vs" no (net: nothing marked) yes, one more tap 1 yes (button state visible) SLOWS 14-v2-tie-double-tap.png
22 M1 bout 3 Tie (hikiwake) V1 up Thumb lands on the EMPTY mark slot above the Tie button; the tie is CLEARED (centre X to vs) no yes, tap Tie again 1 no: an empty slot is not an undo, nothing says the tie went SLOWS (FINDING-C3) 15-v1-tie-neighbour.png
23 M1 bout 3 Record bout on a tie correct path Both retire, next pair Hara v Chiba; fought row reads X in the centre yes n/a 0 n/a ok 16-bout-4-tied-encho-offered.png
24 M1 bout 4 Encho V2 doubleTap (30ms) (E) in the bout centre (right); overtime count x2; header "(E) OVERTIME x2"; no stepper in kachinuki bout mode (removed by operator ruling), so the extra period stays partly (count wrong) no (count cannot be reduced in bout mode) - no COSMETIC (part of FINDING-C4) 17-v2-encho-double-tap.png
25 M1 bout 4 encho running V3 hidden Event stream resumed; same bout, (E) kept yes n/a 0 n/a ok 18-v3-hidden-encho.png
26 M1 bout 4 Aka scores in encho, Record correct path Chiba stays on v Ishii; fought row reads "(E)" with Aka's K yes n/a 0 n/a ok 21-bout-7-open-seven-deep.png
27 M1 bouts 5-8 header after the encho bout V5 Header keeps "(E) OVERTIME x2" on bouts 5, 6, 7 and 8 (all regulation) no (false statement) no - - COSMETIC (FINDING-C4) 23-v5-seven-deep-scrolled-top.png
28 M1 bout 5 Shiro ippon V2 doubleTap M M recorded where the truth was M K (operator does not notice, Records) no yes, row 36 3 yes (tap the fought row) SLOWS 21-bout-7-open-seven-deep.png
29 M1 bout 6 Shiro ippon M V3 offline Pill "Offline" while down; point shown; resynced to "Synced" on reconnect yes n/a 0 n/a ok 19-v3-offline-bout-6.png
30 M1 bout 6 bout with one point V3 back (then forward) Back lands on /admin; Forward returns to shiaijo A, same bout, point kept yes n/a 0 n/a ok 20-v3-back-bout-6.png
31 M1 bout 7 glance seven bouts in V5 lost place At scroll top: the live bout's names are visible, Record bout / End match are below the fold; nothing states fighters remaining per side or who is next (IV/PW band suppressed in bout mode by design) yes n/a scroll n/a SLOWS (FINDING-C8) 23-v5-seven-deep-scrolled-top.png, 22-bout-7-open-seven-deep-full.png
32 M1 bout 7 tap targets (coarse) measurement ippon 44x44, Fusensho 76x44, Tie 92x44, foul + 44x44, Record 106x44, End 96x44, Remove 127x44, Add next 159x44, fought row 642x44, name box 159x54, clear x 44x44, Send back 129x44; mark slot 26x44 (below 44 wide) - - - - COSMETIC (FINDING-C17) -
33 M1 correction tap fought bout 5 V1 down Bout 6 opens for correction instead (framed) yes (nothing changed) yes, collapse triangle 1 yes (frame + triangle) ok 24-v1-fought-row-neighbour.png
34 M1 correction tap fought bout 5 V2 doubleTap (32ms) First tap opens bout 5, the row grows, the second tap lands on the row ABOVE and bout 4 opens instead (bout 5 closes) yes (nothing changed) but wrong bout open yes 2 yes SLOWS (FINDING-C9) 25-v2-fought-row-double-tap.png
35 M1 correction collapse triangle measurement 44x36 under coarse (below 44 tall) - - - - COSMETIC (FINDING-C17) 26-bout-5-open-for-correction.png
36 M1 correction fix bout 5 M M to M K correct path Opens in place with full controls, saves as you go; no winner-flip warning (winner unchanged); the live bout 7 keeps its pairing yes n/a 3 (open, remove M, add K) yes ok 27-bout-5-corrected-open.png
37 M1 correction bout 5 open while bout 7 is live V5 Two rows carry full scoring controls at once; the corrected one is framed and has the down triangle, the live one is plain yes n/a - partly SLOWS 28-bout-5-and-live-bout-both-editable.png
38 M1 correction bout 5 open V3 back Correction closed on return (component state), the fix was already saved (M K) yes n/a 0 n/a ok 29-v3-back-mid-correction.png
39 M1 correction collapse triangle V2 doubleTap (20ms) Collapsed; did not reopen yes n/a 0 n/a ok 30-v2-collapse-double-tap.png
40 M1 correction flip bout 1's winner, then restore correct path Warning "You changed who won bout 1..." appears; later bouts untouched (per docs); restored cleanly yes yes 5 yes ok 31-bout-1-winner-flipped-warning.png
41 M1 bout 7 Tie on the Aka taisho, Record correct path Aka out of fighters; app appends bout 8 Jin v Endo (the fighter who tied kept on, per docs) yes n/a 0 n/a ok 32-bout-7-taisho-tie.png
42 M1 bout 8 Fusensho (Shiro) V1 left Thumb hits "H": a hansoku point awarded to Shiro no yes, tap the mark 1 yes SLOWS 33-v1-fusensho-neighbour.png
43 M1 bout 8 Fusensho V2 doubleTap (27ms) Ends ON (✓ Fusensho, two maru): the second tap did not toggle it back yes n/a 0 n/a ok 34-v2-fusensho-double-tap.png
44 M1 end End match (two-tap guard) V2 doubleTap (26ms) The double tap COMPLETES the encounter: the guard does not stop a double tap yes here (intended) but guard defeated yes, Reopen + reason 2 + reason yes (Correct, Reopen) SLOWS (FINDING-C6) 36-v2-end-match-double-tap.png
45 M1 end Completed row result IV 4-2 PW 8-3 (correct); final M3 on A now names Kawasemi yes n/a 0 n/a ok 37-m1-completed.png
46 M1 end Completed row V5 In one earlier run the row showed the previous write's IV/PW (3-2 / 6-3) for about a second before settling; not reproduced on the last run transient yes 0 n/a COSMETIC 37-m1-completed.png
47 M1 reopen Correct (Completed row) V1 left No other control within reach (63x44 button) yes n/a 0 n/a ok 38-v1-correct-neighbour.png
48 M1 reopen Correct correct path Finished sheet with all 8 bouts, band "FINAL · 8 BOUTS · SHIRO WIN", Reopen match at the very bottom yes n/a 0 n/a ok 39-m1-correct-completed-view.png
49 M1 reopen stray tap on the finished sheet V1 Ippon buttons disabled; nothing changes; no Save correction (by design) yes n/a 0 n/a ok 40-v1-stray-tap-completed-sheet.png
50 M1 reopen Reopen match V2 doubleTap (24ms) One reopen, no error; running with 7 fought rows + bout 8 live yes n/a 0 n/a ok 41-v2-reopen-double-tap.png
51 M1 reopen Reopen match V4 n/a: one tap by design (reason owed on the way out, per docs) - - - - - -
52 M1 reopen reopened encounter V3 reload Back on the same encounter, running, same bouts yes n/a 0 n/a ok 42-v3-reload-reopened.png
53 M1 reopen undo fusensho, award M on bout 8 correct path Fusensho re-tap restores the pre-walkover score; M awarded; pill "Synced" yes n/a 3 yes ok 43-m1-bout-8-rescored.png
54 M1 reopen bout 8 correction, 2s idle V3 reload Correction persisted (M, no fusensho) yes n/a 0 n/a ok 44-v3-reload-after-bout-8-fix.png
55 M1 reopen End match -> reason prompt open V3 reload Prompt gone, encounter still running, correction kept; End again re-asks yes yes 1 yes ok 46-v3-reload-reason-prompt.png
56 M1 reopen reason prompt V4 hasty Loudest = primary "Confirm" with "Ended by mistake" preselected; the real reason (a scoring error) is recorded wrongly without the operator choosing partly (audit reason wrong) no (reason not editable later) - - COSMETIC (FINDING-C12) 47-v4-reason-hasty.png
57 M1 reopen ended again result IV 4-2 PW 7-3, winner still Kawasemi, final keeps its side yes n/a 0 n/a ok 48-m1-ended-again.png
58 M1 earlier run bout 8 fixed, then End + reload within about 1s V3 reload Earlier run only: after the reload bout 8 was back to the walkover, and the ensuing End recorded PW 8-3 (the correction silently lost). Same mechanism as row 83 no yes if noticed 3 no SLOWS (FINDING-C5) (earlier run, overwritten; reproduced as row 83)
59 court busy move M2 to A (Scores tab court picker) setup Organiser step to reach the court-busy door; the Up next card itself has no court picker yes n/a - - - 49-m2-moved-to-A.png
60 court busy Reopen M1 while M2 runs on A correct path Panel names M2, warns the requeue clears its score; also shows the raw server line "Court A already has a running match (m-r1-1). Finish that match before reopening this one." yes n/a - - COSMETIC (FINDING-C11) 50-m1-reopen-court-busy.png
61 court busy "Clear its score, queue it, and reopen" V1 right Thumb hits "Leave it running" (6px): panel dismissed, nothing changed yes yes, Reopen again 1 yes ok 51-v1-requeue-neighbour.png
62 court busy court-busy panel V4 hasty Loudest = red danger "Clear its score, queue it, and reopen": M2's point is wiped, M2 requeued, M1 reopened. The destructive choice is the prominent one, but its consequence is spelled out above it as chosen M2's score: no - - SLOWS 52-v4-requeue-hasty.png
63 court busy End M1 again, then Start match on Up next (M2) V5 M2 starts but the panel stays on the finished M1 correction; M2 is nowhere on the page (not in the queue, not in the panel) no (hidden running match) yes, "← Back to court" under the sheet 1 no (button below the fold, name does not say M2 is there) SLOWS (FINDING-C2) 53-v5-m2-started-but-not-shown.png, 54-v5-m2-after-back-to-court.png
64 court busy M2 restarted result Bout 1 empty: its point was cleared, as the panel and the docs say yes n/a - - ok 55-m2-restarted-on-A.png
65 M2 bout 2 pick the Shiro fighter (one tap) correct path Ueno picked; no stray mark yes n/a 0 n/a ok 57-m2-fighter-picked-one-tap.png
66 M2 bout 2 fighter pick V2 doubleTap (13ms) Ueno picked by tap 1; the list closes and tap 2 falls through onto the "D" ippon button underneath: a D point for Shiro no yes, tap the mark 1 yes SLOWS (FINDING-C13) 58-m2-v2-fighter-pick-double-tap.png
67 M2 bout 2 reopen the fighter list V5 Tapping the name box again does not reopen the list (it still has focus): tap elsewhere, then tap it - yes 3 no SLOWS (FINDING-C13) 58-m2-v2-fighter-pick-double-tap.png
68 M2 bout 2 Esc to close the fighter list (laptop) V4 hastyConfirm Esc (list open, pill "Synced") raises "Discard unsaved scoring changes?"; loudest = danger "Discard changes"; after it the pairing is intact (Ueno v Kudo) but the list is STILL open: Esc never closed it yes yes, tap elsewhere 1 n/a COSMETIC (FINDING-C16) 59-m2-esc-discard-dialog.png, 60-m2-after-esc-discard.png
69 M2 bout 2 fighter pick V1 down Wada picked instead of Ueno (the option below, 0px gap, 31px tall rows) no yes 4 yes (visible name) SLOWS 61-m2-v1-fighter-pick-neighbour.png
70 M2 bout 3 bout with one point V3 hidden Resumed, same bout, point kept yes n/a 0 n/a ok 62-m2-v3-hidden.png
71 M2 bout 4 Record bout V2 doubleTap (31ms) Exactly one bout appended (Zaitsu v Kudo); Record disabled on the fresh bout yes n/a 0 n/a ok 63-m2-v2-record-double-tap.png
72 M2 bout 5 Tie on Shiro's taisho, Record correct path Taisho stays on v Aka's jiho (per docs table) yes n/a 0 n/a ok 64-m2-taisho-stays-on.png
73 M2 end End match, taisho defeated correct path First tap arms (164x44); armed label illegible again; second tap completes; IV 0-5 PW 0-10 yes n/a 0 n/a SLOWS (FINDING-C7) 65-m2-end-armed.png, 66-m2-completed.png
74 M2 reopened Send back to queue (under a reopened encounter) V4 hasty Dialog: "Any score entered on this bout will be discarded"; loudest = primary "Send back to queue"; the encounter's 6-bout log is WIPED, the semifinal restarts at bout 1 and the final loses its side no only by re-entering every bout from the paper sheet ~20 no BLOCKS proposed (FINDING-C1; see the docs note in the findings) 67-m2-send-back-confirm.png, 68-m2-after-send-back.png
75 M2 re-enter and End result Re-entered from the sheet, ended with a reason; final regains Tsubame yes - ~20 - - 69-m2-completed-again.png
76 final lineup Copy from previous match V2 doubleTap (27ms) Last run: copied once, 5 names. Earlier run: the rows grew "Rename" links and tap 2 opened Rename on the taisho (Save/Cancel shown, nothing saved) yes yes, Cancel 0-1 yes COSMETIC 70-final-copy-lineup-double-tap.png
77 final lineup Aka side V5 Kawasemi's semifinal lineup does not carry over ("Inheriting round default", empty): Copy is needed per side yes yes 1 yes COSMETIC 71-final-lineups-copied.png
78 final running Reopen M1 while the final it feeds runs on A correct path Refused: "cannot reopen: a downstream knockout match has already started or recorded a result" (lowercase server text, no match named, no remedy; the court-busy panel is not offered although the blocker is on this court) yes (refusal matches docs) yes about 6 (Back to court, Send final back, confirm, Correct, Reopen) no SLOWS (FINDING-C10) 73-m1-reopen-refused-downstream.png
79 final bout 2 Record bout V3 offline Pill "Offline"; the tapped bout stays the live row, Record re-enables, no "Not sent yet" banner; after reconnect the bout is recorded and the next pairing appears yes (eventually) yes 0 n/a SLOWS (FINDING-C14) 74-final-v3-record-offline.png, 75-final-after-reconnect.png
80 final play out, End correct path Tsubame's senpo Kudo wins five; End; knockout complete yes n/a 0 n/a ok 77-final-completed.png
81 final spectator phone result Viewer Recent results: final IV 5-0, M2 IV 0-5, M1 IV 4-2 PW 7-3 yes n/a 0 n/a ok 78-viewer-knockout-complete.png
82 all keyboard hint on the iPad V5 "M K D T H Shiro · ⇧ Aka · ← → prev/next · Esc close" shows under the coarse pointer although styles.css hides it there - - - - COSMETIC (FINDING-C15) 21-bout-7-open-seven-deep.png
83 final bout 4 Shiro M, tab reloads 150ms later V3 reload Pill read "Synced" at 150ms; after the reload the point is gone, no warning no yes if noticed 1 no SLOWS (FINDING-C5) 76-final-v3-reload-150ms-after-tap.png
84 all Call to court V2 not performed on this journey: known item (admin_shiaijo.jsx callToCourt double announcement) - - - - known -

kachinukiVisiblePositions verdict (admin_scoring_team.jsx:506)

Enough context seven bouts in? For "who fought whom and how": yes. For "where do we
stand": no.
kachinukiVisiblePositions returns only the live slot, but the sheet renders
every fought bout above it as a read-only row (kachinukiDoneBoutIdxs, :1611;
renderReadOnlyBout, :2139): bout number, both fighters with member numbers (T2.4 Ishii),
the marks, the centre (vs / X / (E)) and the winner's name in its side colour. Evidence:
22-bout-7-open-seven-deep-full.png, 23-v5-seven-deep-scrolled-top.png. From that list the
operator can work out who stayed on and who retired, but nothing states it:

  • no per-side "fighters remaining" or "next up" indication. Under exhaustion the operator
    must count retired fighters by eye to know End match is due (row 41 was only recognisable
    by knowing Endo was the taisho). The IV/PW band is suppressed in bout mode by design
    (:3250-3254, user-confirmed brief) and IV/PW does not decide a kachinuki encounter, so
    that absence is not the finding (FINDING-C8);
  • at 1180x820 the sheet is taller than the screen from about bout 6: at scroll top the live
    bout's names are in view but Record bout / End match are below the fold (row 31);
  • the header says "(E) OVERTIME x2" through bouts 5-8 and the bracket card marks the whole
    encounter "(E)" (FINDING-C4), which is wrong context.

Can the operator return to a mis-scored earlier bout? Yes, through three doors that work.
(1) Mid-encounter: tap the fought row; it opens in place with full controls, saves as you
go, and a winner flip raises the documented warning (rows 36, 40). Discoverability rests on
a small ▶ in the number column; the only hint on the sheet ("Tap a scored mark to clear it")
does not mention it. A double tap opens the wrong bout (row 34, FINDING-C9). (2) Finished
encounter: Correct, Reopen match (one tap), fix, End match with a reason (rows 48-57); the
fix propagated (PW 8-3 to 7-3, winner and bracket side unchanged). (3) Court busy: the panel
names the running match and offers the atomic requeue (rows 60-64). Remove this bout works
and is recoverable (row 18). The dangerous exit is the one not built for a bout log: "Send
back to queue" under a reopened encounter wipes it (row 74, FINDING-C1). After ending a
reopened correction the panel stays pinned, so the next started match vanishes (row 63,
FINDING-C2).

Who stayed on propagated correctly in every performed case: wins (rows 15, 26), hikiwake
(row 23), a taisho tie under both readings (rows 41, 72), removal and re-record (row 18),
and the bout 8 correction after reopen (row 57).

Findings (severity-ranked; file:line re-derived with grep -n on this worktree)

  • FINDING-C1 [BLOCKS proposed, judgement: no fixme] "Send back to queue" under a REOPENED kachinuki encounter wipes its whole bout log. admin_shiaijo.jsx:1629 (button, rendered for any running selectedMatch; comment :1617 names it the sanctioned exit for a reopened correction), requestRevert :1136 (scored: scoringStarted(m) :1142), dialog copy :1697 ("Any score entered on this bout"), server POST .../revert-to-queue internal/mobileapp/handlers_match.go:978. Repro: finish M2 (6 bouts), Completed row Correct, Reopen match, tap "Send back to queue" under the sheet, confirm. M2 restarts at bout 1 with nothing recorded; the final loses its side. Evidence 67-m2-send-back-confirm.png, 68-m2-after-send-back.png (row 74). Docs note for the orchestrator: team-tournaments.md promises "Reopening keeps every bout that was fought"; scoring-a-match.md says Send back to queue clears a running match's partial score; a reopened encounter is running, so docs and code agree literally and the spec does not assert it. Fix direction: for a match with fought bouts, do not offer Send back to queue (offer End match / Back to court), or make revert-to-queue refuse a match carrying subResults; at minimum the confirm must count what it destroys ("6 bouts and the result").
  • FINDING-C2 [SLOWS, functional: test.fixme] After a finished match is pinned for correction, Start match on Up next starts the next match but the panel keeps the correction; the running match is on no part of the page. admin_shiaijo.jsx:806 selectedMatch = correctingMatch || pickedMatch || running[0]; pickMatch :1018 never clears correctingKey (correctMatch :1059, stopCorrecting :1067). Repro: Completed row Correct (or Reopen then End again), then Start match on Up next. Recover: "Back to court" (1 tap, below the sheet). Evidence 53-v5-m2-started-but-not-shown.png, 54-v5-m2-after-back-to-court.png.
  • FINDING-C3 [SLOWS, functional: test.fixme] Tapping an EMPTY mark slot clears the bout's Tie (and a fusensho). admin_scoring_team.jsx:2994 (slot onClick calls rs.setPts even for an empty slot), setPts :2923/:2951 reset draw/fusensho. Repro: mark Tie on a bout, tap an empty "·" slot beside the centre: X goes back to vs. Evidence 15-v1-tie-neighbour.png.
  • FINDING-C4 [COSMETIC, functional: test.fixme] Encho on one kachinuki bout marks the whole encounter: header "(E) OVERTIME x2" on every later regulation bout, bracket card "(E)" on the encounter. applyKachinukiEncho admin_scoring_team.jsx:1690 bumps the match-level enchoPeriodCount, enchoBlock :1962 writes it on the wire, eyebrow :2647. CLAUDE.md: in a TEAM match the mark lands on the bout's row, never the encounter. A double tap on Encho doubles the count, with no stepper to undo it in bout mode (operator ruling, not re-litigated). Evidence 17-v2-encho-double-tap.png, 22-bout-7-open-seven-deep-full.png.
  • FINDING-C5 [SLOWS, functional: test.fixme; autosave is shared, reviewer A may report the same] For the 300ms autosave debounce after a tap the sync pill already reads "Synced"; a reload in that window loses the point with no warning. SyncStatusPill admin_scoring_autosave.jsx:43 tracks the write queue only; markDirty :108 (AUTOSAVE_DEBOUNCE_MS :7) is not reflected. Repro: tap an ippon, reload within ~150ms: point gone. Evidence 76-final-v3-reload-150ms-after-tap.png (row 83; an earlier run lost a whole reopened-bout correction the same way, row 58).
  • FINDING-C6 [SLOWS, judgement] End match's two-tap guard is defeated by a double tap (26ms): the encounter completes. admin_scoring_team.jsx:3826 arms with no minimum interval before the confirming tap. Recoverable by Correct + Reopen + reason. Evidence 36-v2-end-match-double-tap.png.
  • FINDING-C7 [SLOWS, judgement] The armed End match label (tap again, then the verdict "AKA WIN") is dark text on the dark confirm fill: the verdict is unreadable at the one moment it must be read. End is btn + btn--confirm (admin_scoring_team.jsx:3817), .btn--confirm (styles.css:562) sets background but no color. Evidence 12-v1-record-neighbour-end-armed.png, 65-m2-end-armed.png.
  • FINDING-C8 [SLOWS, judgement] Seven bouts deep there is no per-side "fighters remaining / next up" indication, and from about bout 6 Record bout / End match sit below the fold at 1180x820. See the kachinukiVisiblePositions verdict. Evidence 22-..., 23-v5-seven-deep-scrolled-top.png.
  • FINDING-C9 [SLOWS, judgement] A double tap on a fought bout opens the bout ABOVE it (the row expands under the finger). renderReadOnlyBout admin_scoring_team.jsx:2139 (onClick :2158 -> openDoneBoutEdit :2108). Evidence 25-v2-fought-row-double-tap.png.
  • FINDING-C10 [SLOWS, judgement] Reopen refused while the downstream final runs shows the raw engine error ("cannot reopen: a downstream knockout match has already started or recorded a result", internal/engine/kachinuki.go:749) at admin_scoring_team.jsx:3643, names no match and offers no remedy, although the blocker is running on this same court (the court-busy panel is not offered). Recovery about 6 taps. Evidence 73-m1-reopen-refused-downstream.png.
  • FINDING-C11 [COSMETIC, judgement] The court-busy panel prints the raw server line "Court A already has a running match (m-r1-1). Finish that match before reopening this one." (admin_scoring_team.jsx:3665), an internal id and advice that contradicts the button beneath it. Evidence 50-m1-reopen-court-busy.png.
  • FINDING-C12 [COSMETIC, judgement] The reopen reason prompt preselects "Ended by mistake" (REOPEN_PRESETS admin_scoring_shared.jsx:1152); a hasty Confirm records it even when the reopen was a scoring correction. Evidence 47-v4-reason-hasty.png.
  • FINDING-C13 [SLOWS, judgement] Fighter pick on a later bout: the list drops over the ippon buttons, so a double tap picks the fighter and the second tap awards the ippon underneath (a D here); and a name box that still has focus does not reopen its list on tap (3 taps). LineupNameInput admin_scoring_shared.jsx:908 (open only onFocus :1026, option commits on mousedown :1052). Evidence 58-m2-v2-fighter-pick-double-tap.png.
  • FINDING-C14 [SLOWS, judgement] Record bout tapped offline: the pill reads "Offline" but the bout stays the live row, Record re-enables and no "Not sent yet" banner appears (pendingWrite is only set when the host returns {queued:true}, admin_scoring_team.jsx:2496/:3697; the cause of the missing banner was not established). It lands on reconnect. Evidence 74-final-v3-record-offline.png, 75-final-after-reconnect.png.
  • FINDING-C15 [COSMETIC, judgement] The keyboard shortcut line shows on the iPad: ScoringShortcutHint's inline display: "flex" (admin_scoring_shared.jsx:116) overrides .scoring-shortcut-hint { display: none } under (pointer: coarse) (styles.css:5203). Evidence 21-bout-7-open-seven-deep.png.
  • FINDING-C16 [COSMETIC, judgement, laptop only] Esc with the fighter list open raises "Discard unsaved scoring changes?" (danger default) while the pill reads Synced (handleDismiss admin_scoring_team.jsx:2584, Esc :2603), and the list stays open afterwards. Evidence 59-m2-esc-discard-dialog.png, 60-m2-after-esc-discard.png.
  • FINDING-C17 [COSMETIC, judgement] Under the coarse pointer everything measured 44px except the mark slots (26px wide: the "tap a scored mark to clear it" target, admin_scoring_team.jsx:2987) and the collapse triangle (44x36, .tsm-caret-btn styles.css:7372 with margin -10px).
  • FINDING-C18 [SLOWS, judgement; may overlap reviewer B's J3] Lineup panel: names typed but not saved are lost on reload with no draft or prompt; each side has its own Save. MatchLineupSideEditor admin_schedule_lineup.jsx:80 (values state :111). Evidence 02-v3-lineup-reload-unsaved.png.

Harness defects and things not reachable through the UI

  • H1 (shared fixtures/shots.mjs, read-only for C): a { fullPage: true } screenshot leaves the operator page at a FINE pointer for the rest of the page's life (measured: coarse true before, false after), so every tap-floor rule switches off and later tap-target measurements are wrong. Reviewers A and B should re-check any measurement taken after a fullPage shot. Workaround in fixtures/kachinuki.mjs ensureCoarse (CDP touch emulation + reload).
  • H2 (clumsy.hastyConfirm): only answers confirmDialog; the shiaijo page's .shiaijo-move-confirm dialogs, the inline reason prompt and the inline court-busy panel need the local helpers at the end of the spec (candidates for clumsy.mjs).
  • H3: a LineupNameInput with its list open holds the QUERY as its value and the name as placeholder; boutNames in fixtures/kachinuki.mjs now reads the placeholder in that case (an earlier draft misread a fighter as blanked).
  • Unreachable: the court-busy requeue door cannot be reached with the natural blocker in a 2-court knockout (the final on the same court is downstream, so the downstream refusal fires first); reaching it needed the Scores tab court picker (the Up next card has none). "+ Add next bout manually" was never needed (the server appended every pairing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitrgoliveira and others added 2 commits September 25, 2026 20:15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n and bc-unfx

PR #444 changed three behaviours the journeys asserted:
- J2: a withdrawn competitor's next bout offers "Record default win"
  instead of Start match (e78ec2a), and a tied individual knockout
  bout's Finish reads "Needs a winner", disabled.
- J5: ending a reopened encounter asks for no reason (9df3980); the
  reload interrupt moves to the armed End match.
- J3: Copy from previous match moved to the top (bc-cpdc), so nothing
  sits beside Save lineup; bout 3 is now recorded as a Tie before
  Finish, per the every-bout-is-fought ruling.

bc-tmfn (closed by #444) and bc-unfx (fixed by #444) are plain tests
now. New fixme bc-dhas: a representative bout added inside the last
Tie's autosave window is erased by that write; J7 and bc-dhrp wait
past it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitrgoliveira added a commit that referenced this pull request Oct 6, 2026
…sult

Found by PR #442's kachinuki journey run against this build: Correct on a
finished kachinuki encounter, Reopen match, then End match with nothing
else changed sent the score write with "changed": [], and the server,
told nothing changed, kept the encounter running with no error.

useChangedGroups judges a group against the server value the editor last
agreed with, seeded when it opened and moved only when it builds a write.
A reopen is the server's own write, so the agreed result stayed the
finished one, and End rebuilt that same verdict. Once the match the editor
renders turns reopenPending, the editor now takes the reopened result as
agreed, and that group alone, so every other group keeps its protection
against putting back a value another device changed.

Two render tests, both seen failing first: End after a reopen names the
result; a held edit when the match turns reopened still names no other
group (it fails if a reopen re-seeds every group).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitrgoliveira added a commit that referenced this pull request Oct 6, 2026
…nges

- lineupPositionLabel moves to lineup_resolver.jsx, beside
  alreadyPlacedNote. The at-court panel named a position by its row label
  ("is already at 1.") where the score sheet and the Lineups page said
  "is already at Position 1."; all three now use it, and the sheet drops
  its window lookup and type check.
- Changing a position takes a refusal down on the panel and the Lineups
  page: the refusal was for the lineup as it was, and the next Save judges
  it again. Found by PR #442's journeys (the refusal stayed up with Save
  off).
- CLAUDE.md records both, and the changed-groups rule for a reopen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…journeys

Run against the build of PR #458's head (51e32fb), not #442's own: all four
journeys pass there (18 passed, 17 test.fixme skipped).

Lineup UI
- bc-lpfb is a plain test: the at-court panel shows the lineup in force with
  its source line, Shiro's Save lineup is off on a side nothing changed on,
  and the score sheet names the same fighters after Start match.
- J3: Shiro's wrong-side Save becomes "Save lineup is disabled". The V1
  neighbour tap now lands on the clear x of Shiro's third name box (the
  nearest control left of Aka's Save), so the step puts that position back
  first and records it.
- The Lineups page's Round box is the "Lineup for" select: openLineups takes
  lineupFor (default "Starting lineup"); a new J3 step opens a match's own
  lineup through it. The unsaved-draft notice is recorded and asserted.
- panelSave and typeLineup no longer wait for Save lineup to be enabled
  again: it stays off until something changes. The saved side is read from
  its source line.

Tap guard (400 ms)
- fixtures/pace.mjs holds the dwell. login, hastyConfirm, answerDialog,
  finishMatch (now local), armFinish, finishTeam, endMatch, awardBoutIppon,
  the shiaijo confirms and the direct two-tap sites wait it out.

Behaviour that moved
- Kachinuki: the final carries its teams' lineups (no Copy from previous
  match); send back to queue keeps the score, so M2 restarts with Aka's M and
  bout 1 needs only the K; a double tap on Encho can land on Undo encho; a
  double tap on the first ippon can open the fighter list; a bounce no longer
  records M M, the mistake is made with a deliberate second tap.
- Individual: no remaining-matches panel; the barred notice reads "record the
  fusensho" and its button is barred-match-record-fusensho.

J5 test 4 found a product defect and now asserts the fix (a freshly loaded
console lists M1 as completed): End match on a reopened encounter with
nothing else changed sent changed:[] and the server left the match running.
Fixed in #458 (329000e, useChangedGroups agrees with the reopened result).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitrgoliveira added a commit that referenced this pull request Oct 6, 2026
…e layer

- The team sheet publishes its pinned header's and footer dock's heights
  (published_height.jsx, now also the owner of the topbar's), and its own
  content controls carry them as scroll margin: a control a keyboard Tab or a
  scrollIntoView reaches stops clear of the topbar, the pinned header and the
  dock (inline), or of the pinned header (overlay); the inline head clears the
  topbar. Margin on the content, never padding on the scroller: a bar's own
  buttons lie in a padded strip, and focusing one scrolled the page by about
  300 px (measured). Found by PR #442's kachinuki journey, whose double tap on
  "x Remove this bout" landed on the footer once the held clear-mark line had
  moved that button 28 px lower.
- Escape on an open fighter list closes the list only; the Scores-page score
  editor used to close with it.
- Docs: the two kachinuki captures that show the held spaces, the
  re-recorded kachinuki demo (chapters unchanged), a sentence on Undo
  encho's kept place, and the completion step of the round-lineup
  conversion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitrgoliveira added a commit that referenced this pull request Oct 7, 2026
PR #442's journeys found that the at-court lineup panel, made a dialog in
this round, swallowed every tap for the bounce window after it opened, so
a Close tapped at once did nothing. The panel is an editor like the
overlay score editors, so it now guards only its backdrop against the
opening tap, under the same operator ruling (2026-10-06), and its
controls work at once; its role, Escape and focus stay.

On the Lineups page, the rename box of a member with no name yet was
labelled "Rename ", naming nothing to a screen reader; it is labelled
with the member's slot, as the rest of the page names it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitrgoliveira and others added 5 commits October 7, 2026 15:30
Conflict in scripts/screenshots/lib/editor.mjs: kept main's tap dwell
(TAP_DWELL_MS, sameSideTapPacer), its Finish that also arms engi's Save result
and waits out the bounce window, and main's finishMatch signature. Kept the
branch's INLINE_EDITOR. The branch's `root` parameter on finishMatch is
dropped: no caller passes it, the journeys finish through
scripts/e2e/fixtures/scoring.mjs, which taps.

fixtures/pace.mjs now imports TAP_DWELL_MS from the library instead of
declaring a second 500, and scoring.mjs's note no longer says the library's
finishMatch skips the bounce window, since main's waits it out.

make e2e: 18 passed, 17 skipped (fixmes), EXIT 0. make go/test: EXIT 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nl's

Main now carries #447, #450 and #453. Their beads' fixmes were run unskipped
on the merged build:

- Plain tests now, passing as written: bc-kfup, bc-htcr, bc-plcl (individual),
  bc-fsnp, bc-dhas (team), bc-emsl, bc-kheb, bc-sync (kachinuki).
- bc-offl: the court header reads "Offline: 1 result not sent" while the
  Finish is queued (captured offline); the test looked for "Not saved yet".
- bc-sbq: #447 made Send back to queue keep the score, and the confirm now
  says "Any score entered is kept". The test asserts that, then starts the
  bout again and finds its point still on the board.
- bc-kpnl's two tests are removed: the panel they wait for was taken out by
  the 2026-09-26 ruling (recording a withdrawal changes only that match).

Follow-on edits:
- The two 800 ms waits that kept bc-dhas from erasing the daihyosen row are
  gone (J7 and bc-dhrp); bc-dhas's own test proves the fix.
- bc-fsnp waits for the sync pill to read Synced instead of 1.5 s: since
  bc-sync the pill reads Syncing from the first tap.
- J5 final plays on from the first unfought bout: bc-kheb, which runs before
  it in this serial file, records the final's bout 1.

Five fixmes remain, all on open beads: bc-otpl, bc-kosc, bc-crpn, bc-lprf,
bc-dhrp. make e2e: 28 passed, 5 skipped, EXIT 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the fixme removed the test passes on main: since 51e32fb (PR #458,
"Lineup refusals name a position one way everywhere") the at-court lineup
panel refuses a typed duplicate with "Dai is already at Senpo.", composed by
lineup_resolver.jsx for every surface. The bead itself is still open.

The four fixmes left fail at their own defect assertions with the fixme
removed: bc-otpl (no encho checkbox), bc-kosc (a knockout time before the
last pool time), bc-dhrp (no name box on the daihyosen row), bc-crpn (no
Record bout after Start match, inside the serial flow).

make e2e journeys/knockout-mixed-team: 9 passed, 1 skipped, EXIT 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gitrgoliveira

Copy link
Copy Markdown
Owner Author

Audit rows, current run: Kachinuki (J5)

Raw rows from make e2e on d3894b4 (2026-10-12, main merged through #467), read from each journey's audit.json. One row per operator action x clumsy variant; the Observed column is what the journey recorded, with screenshot paths under scripts/e2e/output/. These rows carry no judgement: the 2026-09-24 audit comments above are the review as performed, with the reviewers' judgement.

Journey Step Action Variant Observed
M2 lineup type five names, not saved V3 interrupt reload {"kind":"reload","before":"/admin/shiaijo/B","after":"/admin/shiaijo/B","coarseLost":false,"panelStillOpen":false,"namesKept":["Taki","Ueno","Wada","Yano","Zaitsu"],"draftNotice":"Unsaved lineup changes restored Discard","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/02-v3-lineup-reload-unsaved.png"}
M2 lineup Save lineup V2 doubleTap {"gapMs":37,"withinWindow":true,"toasts":["Connected","✅\nMatch lineup saved"],"saved":["Taki","Ueno","Wada","Yano","Zaitsu"],"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/03-v2-lineup-save-double-tap.png"}
M1 start Start match (Up next card) V1 tapNeighbour right {"intended":{"tag":"button","label":"Start match","testid":null,"className":"btn btn--primary","disabled":false},"hit":{"tag":"button","label":"Enter lineup","testid":null,"className":"btn btn--sm","disabled":false},"gapPx":10,"lineupOpened":true,"startBox":{"x":46,"y":393.5,"width":100,"height":44},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/05-v1-start-neighbour.png"}
M1 start Start match (Up next card) V2 doubleTap {"gapMs":78,"withinWindow":true,"eyebrow":"TEAM KACHINUKI · SEMIFINALS · MATCH 1","running":true,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/06-v2-start-double-tap.png"}
M1 bout 1 Shiro M V2 doubleTap {"gapMs":46,"withinWindow":true,"marksAfter":["M"],"fighterListOpened":false,"mMovedByPx":0,"recoverTaps":0,"hintShown":true,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/08-v2-ippon-double-tap.png"}
M1 bout 1 Shiro M (second point) V1 tapNeighbour right {"intended":{"tag":"button","label":"M","testid":null,"className":"ipt-btn ipt-btn--sm ","disabled":false},"hit":{"tag":"button","label":"K","testid":null,"className":"ipt-btn ipt-btn--sm ","disabled":false},"gapPx":6,"marksAfter":["M","K"],"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/09-v1-ippon-neighbour.png"}
M1 bout 1 scored bout, not yet recorded V3 interrupt reload {"kind":"reload","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","coarseLost":false,"marksKept":["M","K"],"doneRows":0,"pairing":{"shiro":"Fujii","aka":"Aoki"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/10-v3-reload-bout-1.png"}
M1 bout 1 Record bout V2 retapWhileSaving {"savingSeen":true,"retapped":true,"labelAfter":"Record bout","doneRows":1,"current":{"shiro":"Fujii","aka":"Baba"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/11-v2-record-retap.png"}
M1 bout 2 Record bout V1 tapNeighbour right {"intended":{"tag":"button","label":"Record bout","testid":null,"className":"btn btn--primary","disabled":false},"hit":{"tag":"button","label":"End match","testid":"kachinuki-end-match-button","className":"btn ","disabled":false},"gapPx":6,"endLabel":"Tap again — AKA WIN","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/12-v1-record-neighbour-end-armed.png"}
M1 bout 3 × Remove this bout V2 doubleTap {"gapMs":40,"withinWindow":true,"removeBox":{"x":888.078125,"y":697.296875,"width":127,"height":44},"afterRemove":{"doneRows":1,"current":{"shiro":"Fujii","aka":"Baba"},"currentShiroMarks":0,"currentAkaMarks":2},"recoveredTo":{"shiro":"Goto","aka":"Baba"},"recoverTaps":1,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/13-v2-remove-bout-double-tap.png"}
M1 bout 3 Tie (hikiwake) V2 doubleTap {"gapMs":75,"withinWindow":true,"tieLabel":"Tie (hikiwake)","middle":"vs","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/14-v2-tie-double-tap.png"}
M1 bout 3 Tie (hikiwake) V1 tapNeighbour up {"intended":{"tag":"button","label":"✓ Tie (hikiwake)","testid":"scoring-modal-tie-button","className":"btn btn--sm btn--primary","disabled":false},"hit":{"tag":"button","label":"·","testid":null,"className":"editor-side__pt ","disabled":false},"gapPx":6,"tieStill":"✓ Tie (hikiwake)","middle":"X","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/15-v1-tie-neighbour.png"}
M1 bout 4 Encho V2 doubleTap {"gapMs":31,"withinWindow":true,"eyebrow":"TEAM KACHINUKI · SEMIFINALS · MATCH 1","inOvertimeAfterDoubleTap":true,"enchoStillOffered":true,"middle":"(E)","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/17-v2-encho-double-tap.png"}
M1 bout 4 encho running V3 interrupt hidden {"kind":"hidden","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","resumed":true,"method":"document.hidden override + dispatched visibilitychange","coarseLost":false,"pairing":{"shiro":"Hara","aka":"Chiba"},"middle":"(E)","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/18-v3-hidden-encho.png"}
M1 bout 5 Shiro M V2 doubleTap {"gapMs":56,"withinWindow":true,"marksAfter":["M"]}
M1 bout 6 Shiro M V3 interrupt offline {"kind":"offline","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","coarseLost":false,"whileOffline":"Offline","resynced":true,"marks":["M"],"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/19-v3-offline-bout-6.png"}
M1 bout 6 bout with one point V3 interrupt back {"kind":"back","before":"/admin/shiaijo/A","landedOn":"/admin","hadHistory":true,"after":"/admin/shiaijo/A","coarseLost":false,"pairing":{"shiro":"Ishii","aka":"Doi"},"marks":["M"],"doneRows":5,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/20-v3-back-bout-6.png"}
harness fullPage screenshot measurement {"coarseBeforeFull":true,"coarseAfterFull":true,"coarseLost":false}
M1 bout 7 glance at the sheet seven bouts in (scrolled to top) V5 lost place {"liveBoutNamesInView":true,"recordBoutInView":true,"endMatchInView":true,"runningTotalShown":0,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/23-v5-seven-deep-scrolled-top.png"}
M1 bout 7 measure tap targets (coarse pointer, floor 44px) measurement {"sizes":{"ippon M":"44x44","Fusensho":"76x44","Tie (hikiwake)":"92x44","foul +":"44x44","empty mark slot":"26x44","Record bout":"106x44","End match":"96x44","× Remove this bout":"127x44","+ Add next bout manually":"159x44","fought bout row":"642x44","fighter name box":"159x46","clear fighter ×":"44x44","Send back to queue":"129x44"},"coarse":true,"tieMinHeight":"44px","tieHeight":"44px"}
M1 correction tap fought bout 5 to correct it V1 tapNeighbour down {"intended":{"tag":"div","label":"Correct bout 5","testid":"kachinuki-done-bout-4","className":"team-sub-match team-sub-match--readonly team-sub-match--editable","disabled":false},"hit":{"tag":"div","label":"Correct bout 6","testid":"kachinuki-done-bout-5","className":"team-sub-match team-sub-match--readonly team-sub-match--editable","disabled":false},"gapPx":4,"openedForCorrection":{"shiro":"Ishii","aka":"Doi"},"recoverTaps":1,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/24-v1-fought-row-neighbour.png"}
M1 correction tap fought bout 5 to correct it V2 doubleTap {"gapMs":25,"withinWindow":true,"boutOpenAfter":5,"bout5MarksBefore":{"shiro":["M","M"],"aka":[]},"bout5MarksAfter":{"shiro":["M","M"],"aka":[]},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/25-v2-fought-row-double-tap.png"}
M1 correction measure the collapse triangle measurement {"caret":"44x44"}
M1 correction tap fought bout 5, fix M M to M K correct path {"winnerFlipWarning":false,"liveBout":{"shiro":"Ishii","aka":"Endo"},"editableRows":2,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/28-bout-5-and-live-bout-both-editable.png"}
M1 correction bout 5 open for correction V3 interrupt back {"kind":"back","before":"/admin/shiaijo/A","landedOn":"/admin","hadHistory":true,"after":"/admin/shiaijo/A","coarseLost":false,"stillOpen":0,"bout5":{"shiro":"Ishii","aka":"Chiba"},"bout5Marks":{"shiro":["M","K"],"aka":[]},"live":{"shiro":"Ishii","aka":"Endo"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/29-v3-back-mid-correction.png"}
M1 correction collapse triangle V2 doubleTap {"gapMs":14,"withinWindow":true,"openAfter":1,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/30-v2-collapse-double-tap.png"}
M1 correction flip bout 1 winner by correction, then restore correct path {"warned":true,"warningText":"You changed who won bout 1. The later bouts were fought based on the old result, so check they still show the right competitors and fix any that are wrong.","liveBoutAfterFlip":{"shiro":"Ishii","aka":"Endo"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/31-bout-1-winner-flipped-warning.png"}
M1 bout 8 Fusensho (Shiro) V1 tapNeighbour left {"intended":{"tag":"button","label":"Fusensho","testid":"scoring-modal-fusensho-button","className":"btn btn--sm ","disabled":false},"hit":{"tag":"button","label":"H","testid":null,"className":"ipt-btn ipt-btn--sm ipt-btn--h","disabled":false},"gapPx":6,"shiroMarks":["H"],"recoverTaps":1,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/33-v1-fusensho-neighbour.png"}
M1 bout 8 Fusensho (Shiro) V2 doubleTap {"gapMs":26,"withinWindow":true,"labelAfter":"✓ Fusensho","shiroMarks":["○","○"],"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/34-v2-fusensho-double-tap.png"}
M1 end End match (two-tap guard) V2 doubleTap {"gapMs":26,"withinWindow":true,"endBox":{"x":820.03125,"y":768,"width":96,"height":44},"completedByDoubleTap":false,"endLabelAfter":"Tap again — SHIRO WIN","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/36-v2-end-match-double-tap.png"}
M1 end Completed row after End match result {"expected":"IV 4–2 PW 7–3","shownFor6s":"IV 4–2 PW 7–3","settledWithoutRefresh":true,"afterRefresh":"IV 4–2 PW 7–3","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/37-m1-completed.png"}
M1 reopen Correct (Completed row) V1 tapNeighbour left {"none":"clumsy.tapNeighbour: no control within 96px left of locator('.shiaijo-completed .shiaijo-qrow').filter({ hasText: 'Team Kawasemi' }).filter({ hasText: 'Team Hayabusa' }).last().locator('.shiaijo-row__correct')","correctBox":"63x44","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/38-v1-correct-neighbour.png"}
M1 reopen Correct (Completed row) correct path {"boutRowsShown":8,"band":"SHIRO (WHITE) IV: 4 · PW: 7 FINAL · 8 BOUTS SHIRO WIN AKA (RED) IV: 2 · PW: 3","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/39-m1-correct-completed-view.png"}
M1 reopen tap an ippon on the completed sheet V1 stray tap {"buttonEnabled":true,"akaMarksBefore":0,"akaMarksAfter":1,"saveButton":0,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/40-v1-stray-tap-completed-sheet.png"}
M1 reopen Reopen match V2 doubleTap {"gapMs":52,"withinWindow":true,"reopenBox":{"x":761.03125,"y":768,"width":102,"height":44},"error":"","doneRows":7,"current":{"shiro":"Jin","aka":"Endo"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/41-v2-reopen-double-tap.png"}
M1 reopen reopened encounter V3 interrupt reload {"kind":"reload","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","coarseLost":false,"editorBack":true,"eyebrow":"TEAM KACHINUKI · SEMIFINALS · MATCH 1","doneRows":7,"current":{"shiro":"Jin","aka":"Endo"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/42-v3-reload-reopened.png"}
M1 reopen undo fusensho, award M on bout 8 correct path {"wasFusensho":true,"pill":"Synced","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/43-m1-bout-8-rescored.png"}
M1 reopen bout 8 corrected on the live row, 2s idle V3 interrupt reload {"kind":"reload","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","coarseLost":false,"bout8MarksAfterReload":["M"],"fusenshoAfterReload":"Fusensho","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/44-v3-reload-after-bout-8-fix.png"}
M1 reopen End match armed V3 interrupt reload {"kind":"reload","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","coarseLost":false,"armedLabel":"Tap again — SHIRO WIN","stillRunning":true,"endedByReload":false,"bout8":{"shiro":"Jin","aka":"Endo"},"bout8Marks":["M"],"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/46-v3-reload-end-armed.png"}
M1 reopen End match (two-tap guard) V2 doubleTap {"gapMs":19,"withinWindow":true,"completedByDoubleTap":false,"endLabelAfter":"Tap again — SHIRO WIN","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/47-v2-end-match-double-tap-reopen.png"}
M1 reopen ended again: Completed row result {"expected":"IV 4–2 PW 6–3","immediately":"IV 4–2 PW 6–3","settledWithin8s":true,"after":"IV 4–2 PW 6–3","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/48-m1-ended-again.png"}
M1 reopen, court busy Correct (Completed row) while M2 runs on A refused (bc-crpn) {"notice":"Shiaijo A is running Match 2 · Team Washi vs Team Tsubame. Finish it or send it back to the queue first. Then correct this match.","toast":"⚠️\nShiaijo A is running Match 2 · Team Washi vs Team Tsubame. Finish it or send it back to the queue first. Then correct this match.\n×","correctionOpened":false,"liveBout":{"shiro":"Taki","aka":"Kudo"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/50-m1-correct-refused-m2-running.png"}
M1 reopen, court busy Reopen match (Scores tab) while M2 runs on A correct path {"panel":"Shiaijo A is running Match 2 · Team Washi vs Team Tsubame. Finish it or send it back to the queue first. Sending it back to the queue keeps any score already entered for it: it carries on from there when it is started again. Queue it and reopen Leave it running","requeueBox":"132x44","leaveBox":"109x44","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/51-m1-reopen-court-busy.png"}
M1 reopen, court busy Queue it and reopen (Scores tab) V1 tapNeighbour right {"intended":{"tag":"button","label":"Queue it and reopen","testid":"kachinuki-reopen-requeue-button","className":"btn btn--sm btn--danger","disabled":false},"hit":{"tag":"button","label":"Leave it running","testid":"kachinuki-reopen-conflict-dismiss","className":"btn btn--sm btn--ghost","disabled":false},"gapPx":6,"panelStill":false,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/52-v1-requeue-neighbour.png"}
M1 reopen, court busy court-busy panel (Scores tab) V4 hasty (loudest button) {"label":"Queue it and reopen","prominence":"danger","otherLabels":["Leave it running"],"m1Reopened":{"shiro":"Jin","aka":"Endo"},"doneRows":7,"upNext":{"shiro":"Team Washi","aka":"Team Tsubame"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/53-v4-requeue-hasty.png","console":"scripts/e2e/output/knockout-mixed-kachinuki/54-v4-requeue-hasty-console.png"}
M1 reopen, court busy End M1 again, then Start match on Up next (M2) V5 lost place {"upNextStill":false,"panelShows":"TEAM KACHINUKI · SEMIFINALS · MATCH 2","correctionBadge":0,"m2Live":true,"backToCourt":0,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/55-v5-m2-started-after-m1-ended.png"}
M1 reopen, court busy recover the started M2 V5 lost place {"recoverTaps":0,"live":{"shiro":"Taki","aka":"Kudo"}}
M1 reopen, court busy M2 restarted after the requeue result {"m2Bout1AkaMarks":1,"panelSays":"Sending it back to the queue keeps any score already entered for it: it carries on from there when it is started again","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/56-m2-restarted-on-A.png"}
M2 bout 2 pick the Shiro fighter (one tap) correct path {"pairing":{"shiro":"Ueno","aka":"Kudo"},"shiroMarksAfterPick":[],"optionBox":"198x31","recoverTaps":0,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/58-m2-fighter-picked-one-tap.png"}
M2 bout 2 pick the Shiro fighter V2 doubleTap {"gapMs":15,"withinWindow":true,"tapsToReopenList":1,"pairing":{"shiro":"Ueno","aka":"Kudo"},"shiroMarks":[],"recoverTaps":0,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/59-m2-v2-fighter-pick-double-tap.png"}
M2 bout 2 Esc to close the fighter list V4 hastyConfirm {"dialogShown":false,"editorStill":true,"pairing":{"shiro":"Ueno","aka":"Kudo"},"listStillOpen":false,"pill":"Synced","doneRows":1,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/60-m2-esc-discard-dialog.png","after":"scripts/e2e/output/knockout-mixed-kachinuki/61-m2-after-esc-discard.png"}
M2 bout 2 pick the Shiro fighter V1 tapNeighbour down {"intended":{"tag":"button","label":"T4.2 Ueno","testid":null,"className":"pmf__option ","disabled":false},"hit":{"tag":"button","label":"T4.3 Wada","testid":null,"className":"pmf__option ","disabled":false},"gapPx":0,"pairingAfterMistap":{"shiro":"Wada","aka":"Kudo"},"shiroMarks":[],"recoverTaps":2,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/62-m2-v1-fighter-pick-neighbour.png"}
M2 bout 3 bout with one point V3 interrupt hidden {"kind":"hidden","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","resumed":true,"method":"document.hidden override + dispatched visibilitychange","coarseLost":false,"marks":["M"],"pairing":{"shiro":"Wada","aka":"Kudo"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/63-m2-v3-hidden.png"}
M2 bout 4 Record bout V2 doubleTap {"gapMs":27,"withinWindow":true,"doneRows":4,"current":{"shiro":"Zaitsu","aka":"Kudo"},"recordEnabled":false,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/64-m2-v2-record-double-tap.png"}
M2 end End match (first tap arms) correct path {"armed":"Tap again — AKA WIN","endBox":"164x44","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/66-m2-end-armed.png"}
M2 reopened Send back to queue (reopened encounter) V4 hasty confirm {"label":"Send back to queue","prominence":"primary","otherLabels":["Cancel"],"title":"Send back to queue?","message":"Team Washi vs Team Tsubame will be returned to the upcoming queue. Any score entered is kept: starting it again carries on from there.","boutsBefore":5,"upNext":{"shiro":"Team Washi","aka":"Team Tsubame"},"boutsAfterRestart":5,"current":{"shiro":"Zaitsu","aka":"Maeda"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/69-m2-after-send-back.png"}
bc-crpn Start match (Up next) while M1 is open for correction refused {"notice":"Save the correction of Match 1 · Team Kawasemi vs Team Hayabusa, or leave it with Back to court, then start this match.","toast":"⚠️\nSave the correction of Match 1 · Team Kawasemi vs Team Hayabusa, or leave it with Back to court, then start this match.\n×","screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/71-bc-crpn-start-refused-during-correction.png"}
final lineup open the lineup panel for the final carried over {"carried":[{"team":"Team Tsubame","source":"Same as Match 2","lineup":["Kudo","Maeda","Noda","Ono","Sato"]},{"team":"Team Kawasemi","source":"Same as Match 1","lineup":["Fujii","Goto","Hara","Ishii","Jin"]}],"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/72-final-lineups-carried.png"}
final running Correct M1 (Completed row) while the final runs on A refused (bc-crpn) {"notice":"Shiaijo A is running Match 3 · Team Tsubame vs Team Kawasemi. Finish it or send it back to the queue first. Then correct this match.","toast":"⚠️\nShiaijo A is running Match 3 · Team Tsubame vs Team Kawasemi. Finish it or send it back to the queue first. Then correct this match.\n×","correctionOpened":false,"liveBout":{"shiro":"Kudo","aka":"Fujii"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/74-m1-correct-refused-final-running.png"}
final running Reopen M1 (Scores tab) while its downstream final runs correct path {"message":"Match 3 (Final) is being fought now on Shiaijo A. Finish it or send it back to the queue, then reopen this match again.","courtBusyPanel":0,"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/75-m1-reopen-refused-downstream.png"}
final bout 2 Record bout V3 interrupt offline {"kind":"offline","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","coarseLost":false,"whileOffline":{"doneRows":1,"banner":"Not sent yet: saved on this device, and sent when the connection returns.","recordLabel":"Record bout","live":{"shiro":"Kudo","aka":"Goto"}},"appendedAfterReconnect":true,"doneRows":2,"live":{"shiro":"Kudo","aka":"Hara"},"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/77-final-after-reconnect.png"}
final bout 4 Shiro M, then the tab reloads 150ms later V3 interrupt reload {"kind":"reload","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","coarseLost":false,"pillAt150ms":"Syncing…","marksAfterReload":["M"],"screenshot":"scripts/e2e/output/knockout-mixed-kachinuki/78-final-v3-reload-150ms-after-tap.png"}

@gitrgoliveira

Copy link
Copy Markdown
Owner Author

Audit rows, current run: Shared shiaijo (bc-shcr: S1 to S9, P1 to P8)

Raw rows from make e2e on d3894b4 (2026-10-12, main merged through #467), read from each journey's audit.json. One row per operator action x clumsy variant; the Observed column is what the journey recorded, with screenshot paths under scripts/e2e/output/. These rows carry no judgement: the 2026-09-24 audit comments above are the review as performed, with the reviewers' judgement.

Journey Step Action Variant Observed
S1-S8-S7 S8 slots wizard default Start times (both created before any roster) P6 record {"si":{"startTime":"09:00","sharedCourtHint":"Concurrency for this competition equals the number of shiaijo (courts) assigned. Different competitions can share shiaijo (courts); the schedule prevents conflicts."},"st":{"startTime":"09:30","sharedCourtHint":"Concurrency for this competition equals the number of shiaijo (courts) assigned. Different competitions can share shiaijo (courts); the schedule prevents conflicts."}}
S1-S8-S7 S8 slots every slot on Shiaijo A after Start P6 record (D3) {"slotsOnA":[{"comp":"Shared Individual","match":"Pool A · Match 1","time":"09:00"},{"comp":"Shared Individual","match":"Pool A · Match 2","time":"09:05"},{"comp":"Shared Individual","match":"Pool A · Match 3","time":"09:10"},{"comp":"Shared Individual","match":"Pool B · Match 1","time":"09:15"},{"comp":"Shared Individual","match":"Pool B · Match 2","time":"09:20"},{"comp":"Shared Individual","match":"Pool B · Match 3","time":"09:25"},{"comp":"Shared Individual","match":"M1","time":"09:30"},{"comp":"Shared Team","match":"M1","time":"09:30"},{"comp":"Shared Individual","match":"M2","time":"09:35"},{"comp":"Shared Individual","match":"M5","time":"09:40"},{"comp":"Shared Individual","match":"M7","time":"09:45"},{"comp":"Shared Team","match":"M2","time":"09:46"},{"comp":"Shared Team","match":"M3","time":"10:02"}],"sameTimeOnA":[{"comp":"Shared Individual","match":"M1","time":"09:30"},{"comp":"Shared Team","match":"M1","time":"09:30"}],"siKnockout":[{"match":"M1","court":"A","time":"09:30"},{"match":"M2","court":"A","time":"09:35"},{"match":"M3","court":"B","time":"09:30"},{"match":"M4","court":"B","time":"09:35"},{"match":"M5","court":"A","time":"09:40"},{"match":"M6","court":"B","time":"09:40"},{"match":"M7","court":"A","time":"09:45"}],"stKnockout":[{"match":"M1","court":"A","time":"09:30"},{"match":"M2","court":"A","time":"09:46"},{"match":"M3","court":"A","time":"10:02"}]}
S1-S8-S7 S1 picker open /admin/shiaijo/A correct path {"officiating":"Shared Individual","options":["Shared Individual","Shared Team"],"why":"no bout running: the default is the first competition with a scheduled match on the court (effectiveCompId)","upNext":{"head":"09:00 · Shared Individual · Match 1 of 3","names":["Eri Kato","Aoi Sato"]},"nudges":[{"text":"2 Shared Team matches also waiting on this court.","quiet":true}],"shot":"scripts/e2e/output/shared-shiaijo/04-s1-court-A-picker.png"}
S1-S8-S7 S1 picker competition picker V1 tapNeighbour (left, else down) {"direction":"down","hit":{"tag":"button","label":"2 Shared Team matches also waiting on this court. Switch to Shared Team","testid":"shiaijo-nudge","className":"alert shiaijo-nudge shiaijo-nudge--also","disabled":false},"gapPx":12,"url":"/admin/shiaijo/A","officiating":"Shared Team","shot":"scripts/e2e/output/shared-shiaijo/05-s1-picker-neighbour-tapped.png"}
S1-S8-S7 S1 picker competition picker size {"w":213,"h":53,"meets44":true}
S1-S8-S7 S1 picker open /admin/shiaijo/B (SI alone) correct path {"officiating":"Shared Individual","picker":false}
S1-S8-S7 S8 slots Auto-schedule competition: Shared Team from 09:12 P6 record (D3) {"spacing":"3:00","stAfter":[{"match":"M1","court":"A","time":"09:12"},{"match":"M2","court":"A","time":"09:15"},{"match":"M3","court":"A","time":"10:02"}],"siUnchanged":true}
S1-S8-S7 S7 public TV board A and the public schedule before play P8 record (D2) {"tv":"Shared Court Cup · SHIAIJO A Shared Individual POOL A · 0 / 3 MATCHES SI2 Eri Kato vs Aoi Sato SI1 SI3 Jun Oda vs Aoi Sato SI1 SI3 Jun Oda vs Eri Kato SI2 UP NEXT Pool B 3 bouts SI5Hana ItovsChie MoriSI4 SI6Lea RothvsChie MoriSI4 SI6Lea RothvsHana ItoSI5","schedule":"← SHARED COURT CUP Schedule All matches across courts and competitions Filter by player, team, dojo or number… All competitions Shared Individual Shared Team All Shiaijo Shiaijo A Shiaijo B 14 matches of 14 12 Oct 2026 SHIAIJO A 8 matches 09:00 POOL A Next up Shiro: SI2 Eri Kato Aka: SI1 Aoi Sato Shared Individual 09:05 POOL A #2 Shiro: SI3 Jun Oda Aka: SI1 Aoi Sato Shared Individual 09:10 POOL A #3 Shiro: SI3 Jun Oda Aka: SI2 Eri Kato Shared Individual 09:12 SEMIFINALS Next up Shiro: ST2 Inoshishi Aka: ST1 Hayabusa Shared Team 09:15 POOL B #4 Shiro: SI5 Hana Ito Aka: SI4 Chie Mori Shared Individual 09:15 SEMIFINALS #2 Shiro: ST4 Tanuki Aka: ST3 Kitsune Shared Team 09:20 POOL B #5 Shiro: SI6 Lea Roth Aka: SI4 Chie Mori Shared Individual 09:25 POOL B #6 Shiro: SI6 Lea Roth Aka: SI5 Hana Ito Shared Individual SHIAIJO B 6 matches 09:00 POOL C Next up Shiro: SI8 Finn Hale Aka: SI7 Ben Carter","tvShot":"scripts/e2e/output/shared-shiaijo/07-s7-tv-A-before-play.png","scheduleShot":"scripts/e2e/output/shared-shiaijo/08-s7-public-schedule-before-play.png"}
S2-S9-S3 S2 advance Finish + Start Next → V2 doubleTap {"gapMs":29,"withinWindow":true,"committedByDouble":false,"stillArmed":true,"shot":"scripts/e2e/output/shared-shiaijo/09-s2-finish-double-tapped.png"}
S2-S9-S3 S2 advance Finish + Start Next (confirmed) correct path {"next":{"comp":"Shared Individual","eyebrow":"Shared Individual · Pool A · Match 2 of 3","court":"A","shiro":"Jun Oda","aka":"Aoi Sato"},"stUpNextAt":"09:12","shot":"scripts/e2e/output/shared-shiaijo/10-s2-next-si-bout.png"}
S2-S9-S3 S4a correct Correct an SI bout while SI's next bout runs correct path (refused, bc-crpn) {"liveBout":{"shiro":"Jun Oda","aka":"Aoi Sato"},"notice":"Shiaijo A is running Pool A · Match 2 · Jun Oda vs Aoi Sato. Finish it or send it back to the queue first. Then correct this match.","toast":"⚠️\nShiaijo A is running Pool A · Match 2 · Jun Oda vs Aoi Sato. Finish it or send it back to the queue first. Then correct this match.\n×","editorStillOnLiveBout":true,"sendBack":{"label":"Send back to queue","prominence":"primary","otherLabels":["Cancel"],"title":"Send back to queue?","message":"Jun Oda vs Aoi Sato will be returned to the upcoming queue. Any score entered is kept: starting it again carries on from there."},"shot":"scripts/e2e/output/shared-shiaijo/11-s4a-correction-open.png"}
S2-S9-S3 S4a correct Back to court V5 lost place {"officiating":"Shared Individual","upNext":{"head":"09:05 · Shared Individual · Match 2 of 3","names":["Jun Oda","Aoi Sato"]},"editor":null,"idle":true,"shot":"scripts/e2e/output/shared-shiaijo/12-s4a-back-to-court.png"}
S2-S9-S3 S2b advance Kiken - Voluntary (Shiro), side picked by the operator V4 hasty (inline prompt, bc-dsid) {"preselected":null,"recordDisabledBeforePick":true,"recordLabel":"Record: SHIRO withdrew","chosen":"shiro","withdrawn":"Jun Oda","next":{"comp":"Shared Individual","eyebrow":"Shared Individual · Pool B · Match 1 of 3","court":"A","shiro":"Hana Ito","aka":"Chie Mori"},"nextSiAt":"09:15","stEarliestAt":"09:12","barredRow":"Jun Oda withdrew: record the fusensho. Record fusensho for Eri Kato","shot":"scripts/e2e/output/shared-shiaijo/13-s2b-advance-after-kiken.png"}
S2-S9-S3 S2b advance clock check note {"stEarlierThanNextSi":true}
S2-S9-S3 S7 public TV board A and the public schedule while SI runs on A P8 record (D2) {"consoleRunsNext":"the next SI bout on A (the console stays on SI)","tv":"Shared Court Cup · SHIAIJO A Shared Individual POOL B · 0 / 3 MATCHES SI5 Hana Ito vs Chie Mori SI4 SI6 Lea Roth vs Chie Mori SI4 SI6 Lea Roth vs Hana Ito SI5 NEXT ST2Inoshishi vs HayabusaST1","schedule":"← SHARED COURT CUP Schedule All matches across courts and competitions Filter by player, team, dojo or number… All competitions Shared Individual Shared Team All Shiaijo Shiaijo A Shiaijo B 14 matches of 14 12 Oct 2026 SHIAIJO A 8 matches · in progress ● NOW 09:15 POOL B Shiro: SI5 Hana Ito Aka: SI4 Chie Mori Shared Individual 09:10 POOL A Shiro: SI3 Jun Oda Withdrawn Aka: SI2 Eri Kato Shared Individual 09:12 SEMIFINALS Next up Shiro: ST2 Inoshishi Aka: ST1 Hayabusa Shared Team 09:15 SEMIFINALS #2 Shiro: ST4 Tanuki Aka: ST3 Kitsune Shared Team 09:20 POOL B Next up Shiro: SI6 Lea Roth Aka: SI4 Chie Mori Shared Individual 09:25 POOL B #2 Shiro: SI6 Lea Roth Aka: SI5 Hana Ito Shared Individual 09:00 POOL A Shiro: SI2 Eri Kato Aka: SI1 Aoi Sato Shared Individual – vs M 09:05 POOL A Shiro: SI3 Jun Oda Aka: SI1 Aoi Sato Shared Individual Kiken vs ○○ SHIAIJO B 6 matches 09:00 POOL C Next up Shi","tvShot":"scripts/e2e/output/shared-shiaijo/14-s7-tv-A-while-si-runs.png","scheduleShot":"scripts/e2e/output/shared-shiaijo/15-s7-public-schedule-while-si-runs.png"}
S2-S9-S3 S9 scores page Finish + Start Next → correct path {"listOnA":["09:15 Pool B · Match 1 Shared Individual A ▾ Shiro: SI5 Hana Ito Thames vs Aka: Chie Mori SI4 Musashi Score","09:10 Pool A · Match 3 Shared Individual A ▾ Shiro: SI3 Jun Oda Seishinkan vs Aka: Eri Kato SI2 Mumeishi Score Jun Oda withdrew: record the fusensho. Record fusensho for Eri Kato","09:12 Match 1 Shared Team A ▾ Shiro: ST2 Inoshishi Minami Dojo vs Aka: Hayabusa ST1 Kita Dojo Score Lineup","09:15 Match 2 Shared Team A ▾ Shiro: ST4 Tanuki Nishi Dojo vs Aka: Kitsune ST3 Higashi Dojo Score Lineup","09:20 Pool B · Match 2 Shared Individual A ▾ Shiro: SI6 Lea Roth Hizen vs Aka: Chie Mori SI4 Musashi Score","09:25 Pool B · Match 3 Shared Individual A ▾ Shiro: SI6 Lea Roth Hizen vs Aka: Hana Ito SI5 Thames Score","09:00 Pool A · Match 1 Shared Individual A ▾ Shiro: SI2 Eri Kato Mumeishi – vs M Aka: Aoi Sato SI1 Gyokusen Correct","09:05 Pool A · Match 2 Shared Individual A ▾ Shiro: SI3 Jun Oda Seishinkan Kiken vs ○○ Aka: Aoi Sato SI1 Gyokusen Correct"],"next":{"eyebrow":"SHARED INDIVIDUAL · POOL B · MATCH 2 OF 3","title":"Shiaijo A · 09:20","names":["Lea Roth","Chie Mori"],"court":"A","key":"SHARED INDIVIDUAL · POOL B · MATCH 2 OF 3 | Shiaijo A · 09:20 | Lea Roth | Chie Mori","shiro":"Lea Roth","aka":"Chie Mori"},"expected":{"time":"09:20","label":"Pool B · Match 2","court":"A","shiro":"Lea Roth","aka":"Chie Mori","status":"scheduled","button":"Score"},"shot":"scripts/e2e/output/shared-shiaijo/16-s9-scores-page-finish-next.png"}
S2-S9-S3 S9 scores page Next x2, Prev x2 correct path (allowed to walk the court) {"walked":["SHARED INDIVIDUAL · POOL A · MATCH 3 OF 3","SHARED TEAM · SEMIFINALS · MATCH 1","SHARED INDIVIDUAL · POOL A · MATCH 3 OF 3","SHARED INDIVIDUAL · POOL B · MATCH 2 OF 3"],"comps":["Shared Individual","Shared Team","Shared Individual","Shared Individual"],"shot":"scripts/e2e/output/shared-shiaijo/17-s9-prev-next-walked.png"}
S2-S9-S3 S9 scores page Fusenpai (Aka) in the overlay correct path (after-decision advance) {"preselected":null,"recordDisabledBeforePick":true,"recordLabel":"Record: AKA did not show up","chosen":"aka","next":{"eyebrow":"SHARED INDIVIDUAL · POOL B · MATCH 2 OF 3","title":"Shiaijo A · 09:20","names":["Lea Roth","Chie Mori"],"court":"A","key":"SHARED INDIVIDUAL · POOL B · MATCH 2 OF 3 | Shiaijo A · 09:20 | Lea Roth | Chie Mori","shiro":"Lea Roth","aka":"Hana Ito"},"shot":"scripts/e2e/output/shared-shiaijo/18-s9-after-fusenpai.png"}
S2-S9-S3 S9 scores page SI's last startable bout on A correct path {"finishLabel":"Finish"}
S2-S9-S3 S3 switch picker -> Shared Team while an SI bout runs with a point correct path {"editor":{"eyebrow":"Shared Individual · Pool B · Match 3 of 3","court":"A","title":"Shiaijo A · 09:25 ● Synced","pill":null,"names":["Lea Roth","Hana Ito"]},"upNext":{"head":"09:12 · Shared Team · Match 1","names":["Inoshishi","Hayabusa"]},"nudges":[{"text":"1 Shared Individual match also waiting on this court.","quiet":true}],"shot":"scripts/e2e/output/shared-shiaijo/19-s3-picked-st-si-running.png"}
S2-S9-S3 S3 switch reload with Shared Team picked and an SI bout running V3 interrupt reload (P1) {"officiatingAfter":"Shared Individual","shot":"scripts/e2e/output/shared-shiaijo/20-s3-reload-after-pick.png"}
S2-S9-S3 S3 switch Start match (ST's Up next) V1 tapNeighbour right {"hit":"Enter lineup","lineupPanelOpened":true,"stillRunning":{"comp":"Shared Individual","eyebrow":"Shared Individual · Pool B · Match 3 of 3","court":"A","shiro":"Lea Roth","aka":"Hana Ito"},"shot":"scripts/e2e/output/shared-shiaijo/21-s3-start-neighbour-tapped.png"}
S2-S9-S3 S3 switch Start match (ST's Up next) while the SI bout runs V2 doubleTap {"gapMs":27,"withinWindow":true,"writes":["POST Pool B-2/revert-to-queue -> 200 (+100ms)","PUT m-r1-0/score -> 200 (+102ms)","POST Pool B-2/revert-to-queue -> 200 (+188ms)","PUT m-r1-0/score -> 200 (+196ms)"],"runningOnA":[{"court":"A","comp":"Shared Team","label":"Match 1","shiro":"Inoshishi","aka":"Hayabusa","status":"running"}],"alsoRunning":null,"nudges":[{"text":"2 Shared Individual matches also waiting on this court.","quiet":true}],"shot":"scripts/e2e/output/shared-shiaijo/22-s3-st-started.png"}
S2-S9-S3 S3 switch Finish + Start Next on Shared Team's semi-final correct path {"played":{"shiro":"Inoshishi","aka":"Hayabusa"},"next":{"comp":"Shared Team","eyebrow":"Shared Team · Semifinals · Match 2","court":"A","shiro":"Tanuki","aka":"Kitsune"},"shot":"scripts/e2e/output/shared-shiaijo/23-s3-st-finish-next.png"}
S2-S9-S3 S3 switch Start the sent-back SI bout (ST semi-final running) correct path (bc-sbq) {"runningOnA":[{"court":"A","comp":"Shared Individual","label":"Pool B · Match 3","shiro":"Lea Roth","aka":"Hana Ito","status":"running"}],"marksBefore":["M"],"marksAfter":["M"],"shot":"scripts/e2e/output/shared-shiaijo/24-s3-si-bout-back-with-point.png"}
S2-S9-S3 S2b advance Fusenpai on SI's last startable bout on A correct path (nothing to start) {"preselected":null,"recordDisabledBeforePick":true,"recordLabel":"Record: AKA did not show up","chosen":"aka","runningOnA":[],"court":{"officiating":"Shared Individual","pickerOptions":["Shared Individual","Shared Team"],"upNext":null,"upcoming":[{"head":"09:10 · Shared Individual · Match 3 of 3","names":["Jun Oda","Eri Kato"]}],"later":[{"head":"09:40 · Shared Individual · Match 5","names":["Winner of M2","Winner of M1"]},{"head":"09:45 · Shared Individual · Match 7","names":["Winner of M6","Winner of M5"]}],"completed":5,"editor":null,"nudges":[{"text":"1 Shared Team match also waiting on this court.","quiet":true}],"alsoRunning":null,"done":null,"idle":true},"shot":"scripts/e2e/output/shared-shiaijo/25-s2b-nothing-left-to-start.png"}
S4b-S5 S4b correct Shared Team semi-final started again correct path (bc-sbq) {"bout1Shiro":["M"]}
S4b-S5 S4b correct picker -> Shared Individual to reach its completed bouts P4 record {"completedWhileST":1,"completedWhileSI":5,"shot":"scripts/e2e/output/shared-shiaijo/26-s4b-si-completed-while-st-runs.png"}
S4b-S5 S4b correct Correct the SI kiken while Shared Team runs correct path (refused, bc-crpn; P2 copy) {"liveBout":{"eyebrow":"Shared Team · Semifinals · Match 2","court":"A","shiro":"Tanuki","aka":"Kitsune"},"notice":"Shiaijo A is running Match 2 · Tanuki vs Kitsune. Finish it or send it back to the queue first. Then correct this match.","toast":"⚠️\nShiaijo A is running Match 2 · Tanuki vs Kitsune. Finish it or send it back to the queue first. Then correct this match.\n×","sendBack":{"label":"Send back to queue","prominence":"primary","otherLabels":["Cancel"],"title":"Send back to queue?","message":"Tanuki vs Kitsune will be returned to the upcoming queue. Any score entered is kept: starting it again carries on from there."},"namesCompetition":false,"shot":"scripts/e2e/output/shared-shiaijo/27-s4b-correction-open.png"}
S4b-S5 S6d reopen head table starts Shared Team's semi-final while the console holds an SI correction two devices {"alert":"Another bout is running on Shiaijo A Tanuki vs Kitsune · Shared Team Save or cancel the correction (Back to court) to score it here.","shot":"scripts/e2e/output/shared-shiaijo/28-s6d-also-running-during-correction.png"}
S4b-S5 S6d reopen Clear kiken and reopen while ST runs V4 loudest button in the court_busy panel {"conflictHead":"Shiaijo A is running Match 2 · Tanuki vs Kitsune. Finish it or send it back to the queue first.","namesCompetition":false,"loud":"Queue it and reopen","others":["Leave it running"],"runningOnA":[{"court":"A","comp":"Shared Individual","label":"Pool A · Match 2","shiro":"Jun Oda","aka":"Aoi Sato","status":"running"}],"stSemi":{"time":"09:15","label":"Match 2","court":"A","shiro":"Tanuki","aka":"Kitsune","status":"scheduled","button":"Score"},"conflictShot":"scripts/e2e/output/shared-shiaijo/29-s6d-reopen-court-busy-panel.png","shot":"scripts/e2e/output/shared-shiaijo/30-s6d-after-queue-it-and-reopen.png"}
S4b-S5 S6d reopen reload right after the reopen V3/V5 interrupt reload (P1, P7) {"court":{"officiating":"Shared Individual","pickerOptions":["Shared Individual","Shared Team"],"upNext":{"head":"09:10 · Shared Individual · Match 3 of 3","names":["Jun Oda","Eri Kato"]},"upcoming":[],"later":[{"head":"09:40 · Shared Individual · Match 5","names":["Winner of M2","Winner of M1"]},{"head":"09:45 · Shared Individual · Match 7","names":["Winner of M6","Winner of M5"]}],"completed":4,"editor":{"eyebrow":"Shared Individual · Pool A · Match 2 of 3","court":"A","title":"Shiaijo A · 09:05 ● Synced","pill":null,"names":["Jun Oda","Aoi Sato"]},"nudges":[{"text":"1 Shared Team match also waiting on this court.","quiet":true}],"alsoRunning":null,"done":null,"idle":false},"shot":"scripts/e2e/output/shared-shiaijo/31-s6d-reload.png"}
S4b-S5 S5 SI done on A play the rest of SI on A correct path {"played":[{"shiro":"Jun Oda","aka":"Aoi Sato"},{"shiro":"Jun Oda","aka":"Eri Kato"}]}
S4b-S5 S5 SI done on A the console with SI pools done on A, B pools still to play P3 / P7 record {"court":{"officiating":"Shared Individual","pickerOptions":["Shared Individual","Shared Team"],"upNext":null,"upcoming":[],"later":[{"head":"09:40 · Shared Individual · Match 5","names":["Winner of M2","Winner of M1"]},{"head":"09:45 · Shared Individual · Match 7","names":["Winner of M6","Winner of M5"]}],"completed":6,"editor":null,"nudges":[{"text":"Switch to Shared Team: 1 match waiting on this court.","quiet":false}],"alsoRunning":null,"done":null,"idle":true},"nudges":[{"text":"Switch to Shared Team: 1 match waiting on this court.","quiet":false,"amber":true}],"shot":"scripts/e2e/output/shared-shiaijo/32-s5-ii-si-complete-on-A.png"}
S4b-S5 S5 SI done on A reload (the explicit pick is dropped) V3 interrupt reload (P1) {"officiatingAfter":"Shared Team","shot":"scripts/e2e/output/shared-shiaijo/33-s5-ii-after-reload.png"}
S4b-S5 S5 SI knockout on A court B finishes its pools; court A, untouched correct path (P7: held, no reload) {"bPlayed":6,"court":{"officiating":"Shared Individual","pickerOptions":["Shared Individual","Shared Team"],"upNext":{"head":"09:30 · Shared Individual · Match 1","names":["Finn Hale","Aoi Sato"]},"upcoming":[{"head":"09:35 · Shared Individual · Match 2","names":["Goro Abe","Lea Roth"]}],"later":[{"head":"09:40 · Shared Individual · Match 5","names":["Winner of M2","Winner of M1"]},{"head":"09:45 · Shared Individual · Match 7","names":["Winner of M6","Winner of M5"]}],"completed":6,"editor":null,"nudges":[{"text":"1 Shared Team match also waiting on this court.","quiet":true}],"alsoRunning":null,"done":null,"idle":true},"shot":"scripts/e2e/output/shared-shiaijo/34-s5-quarter-finals-arrive-on-A.png"}
S4b-S5 S5 SI knockout on A quarter-finals on both courts correct path {"quarters":[{"court":"B","played":{"shiro":"Eri Kato","aka":"Ben Carter","winner":"aka"}},{"court":"B","resolved":"Record fusensho for Dan Evans"},{"court":"A","played":{"shiro":"Finn Hale","aka":"Aoi Sato","winner":"aka"}},{"court":"A","played":{"shiro":"Goro Abe","aka":"Lea Roth","winner":"shiro"}}]}
S4b-S5 S5 SI knockout on A Finish on SI semi-final M5 V2 retapWhileSaving {"pair":{"shiro":"Goro Abe","aka":"Aoi Sato"},"savingSeen":true,"retapped":true,"labelAfter":null}
S4b-S5 S5 SI final waits the console with SI waiting on a feeder from B P3 / P7 record {"court":{"officiating":"Shared Individual","pickerOptions":["Shared Individual","Shared Team"],"upNext":null,"upcoming":[],"later":[{"head":"09:45 · Shared Individual · Match 7","names":["Winner of M6","Aoi Sato"]}],"completed":8,"editor":null,"nudges":[{"text":"Switch to Shared Team: 1 match waiting on this court.","quiet":false}],"alsoRunning":null,"done":null,"idle":true},"nudges":[{"text":"Switch to Shared Team: 1 match waiting on this court.","quiet":false,"amber":true}],"shot":"scripts/e2e/output/shared-shiaijo/35-s5-i-final-waits-on-B.png"}
S4b-S5 S5 SI final waits tap the nudge line (Shared Team) correct path {"officiating":"Shared Team","upNext":{"head":"09:15 · Shared Team · Match 2","names":["Tanuki","Kitsune"]},"shot":"scripts/e2e/output/shared-shiaijo/36-s5-switched-by-tap.png"}
S6a S6a move schedule page: move running SI M6 from B onto A while ST runs on A P5 record {"askedFirst":false,"runningOnA":[{"court":"A","comp":"Shared Team","label":"Match 2","shiro":"Tanuki","aka":"Kitsune","status":"running"},{"court":"A","comp":"Shared Individual","label":"Match 6","shiro":"Dan Evans","aka":"Ben Carter","status":"running"}],"alert":"Another bout is running on Shiaijo A Dan Evans vs Ben Carter · Shared Individual A court runs one bout at a time. Score or correct these from their competition's admin view.","consoleShot":"scripts/e2e/output/shared-shiaijo/37-s6a-two-running-on-A.png","scheduleShot":"scripts/e2e/output/shared-shiaijo/38-s6a-schedule-after-move.png"}
S6a S6a move a point on the bout the console shows, two bouts running P5 record {"held":{"comp":"Shared Team","eyebrow":"Shared Team · Semifinals · Match 2","court":"A","shiro":"Tanuki","aka":"Kitsune"},"pointTried":{"comp":"Shared Team","before":0,"after":1},"sync":"Synced","messages":"⚠️ Shiaijo A is running Match 6 (Semifinals). Finish it or send it back to the queue first. ×","shot":"scripts/e2e/output/shared-shiaijo/39-s6a-point-with-two-running.png"}
S6a S6a move Send back to queue (the shown bout) V4 hastyShiaijoConfirm (recovery) {"label":"Send back to queue","prominence":"primary","otherLabels":["Cancel"],"title":"Send back to queue?","message":"Tanuki vs Kitsune will be returned to the upcoming queue. Any score entered is kept: starting it again carries on from there.","runningOnA":[{"court":"A","comp":"Shared Individual","label":"Match 6","shiro":"Dan Evans","aka":"Ben Carter","status":"running"}],"court":{"officiating":"Shared Team","pickerOptions":["Shared Individual","Shared Team"],"upNext":{"head":"09:15 · Shared Team · Match 2","names":["Tanuki","Kitsune"]},"upcoming":[],"later":[{"head":"10:02 · Shared Team · Match 3","names":["Winner of M2","Inoshishi"]}],"completed":1,"editor":{"eyebrow":"Shared Individual · Semifinals · Match 6","court":"A","title":"Shiaijo A · 09:40 ● Synced","pill":null,"names":["Dan Evans","Ben Carter"]},"nudges":[],"alsoRunning":null,"done":null,"idle":false},"shot":"scripts/e2e/output/shared-shiaijo/40-s6a-after-send-back.png"}
S6a S6a move the refused point, read back on the head table after the send back P5 record {"bout2ShiroOnBoardBefore":1,"bout2ShiroStored":0,"shot":"scripts/e2e/output/shared-shiaijo/41-s6a-refused-point-read-back.png"}

@gitrgoliveira

Copy link
Copy Markdown
Owner Author

Audit rows, current run: Moved match (bc-mvsj) and smoke

Raw rows from make e2e on d3894b4 (2026-10-12, main merged through #467), read from each journey's audit.json. One row per operator action x clumsy variant; the Observed column is what the journey recorded, with screenshot paths under scripts/e2e/output/. These rows carry no judgement: the 2026-09-24 audit comments above are the review as performed, with the reviewers' judgement.

Journey Step Action Variant Observed
MV setup wizard defaults, both competitions created before any roster seed {"defaults":{"IND":{"startTime":"09:00","sharedCourtHint":"Concurrency for this competition equals the number of shiaijo (courts) assigned. Different competitions can share shiaijo (courts); the schedule prevents conflicts."},"TEAMS":{"startTime":"09:30","sharedCourtHint":"Concurrency for this competition equals the number of shiaijo (courts) assigned. Different competitions can share shiaijo (courts); the schedule prevents conflicts."}}}
MV setup draw read back (Scores tabs) seed {"individual":[{"time":"09:00","label":"League table · Match 1","court":"A","shiro":"Pia Lund","aka":"Oto Kudo","status":"scheduled","button":"Score"},{"time":"09:05","label":"League table · Match 2","court":"A","shiro":"Rin Saito","aka":"Nils Berg","status":"scheduled","button":"Score"},{"time":"09:10","label":"League table · Match 3","court":"A","shiro":"Oto Kudo","aka":"Mika Endo","status":"scheduled","button":"Score"},{"time":"09:15","label":"League table · Match 4","court":"A","shiro":"Pia Lund","aka":"Rin Saito","status":"scheduled","button":"Score"},{"time":"09:20","label":"League table · Match 5","court":"A","shiro":"Nils Berg","aka":"Mika Endo","status":"scheduled","button":"Score"},{"time":"09:25","label":"League table · Match 6","court":"A","shiro":"Rin Saito","aka":"Oto Kudo","status":"scheduled","button":"Score"},{"time":"09:30","label":"League table · Match 7","court":"A","shiro":"Pia Lund","aka":"Nils Berg","status":"scheduled","button":"Score"},{"time":"09:35","label":"League table · Match 8","court":"A","shiro":"Rin Saito","aka":"Mika Endo","status":"scheduled","button":"Score"},{"time":"09:40","label":"League table · Match 9","court":"A","shiro":"Nils Berg","aka":"Oto Kudo","status":"scheduled","button":"Score"},{"time":"09:45","label":"League table · Match 10","court":"A","shiro":"Pia Lund","aka":"Mika Endo","status":"scheduled","button":"Score"}],"teams":[{"time":"09:30","label":"Match 1","court":"B","shiro":"Inoshishi","aka":"Hayabusa","status":"scheduled","button":"Score"},{"time":"09:46","label":"Match 2","court":"B","shiro":"Tanuki","aka":"Kitsune","status":"scheduled","button":"Score"}]}
MV 1 setup a bout running on each court correct path {"courtA":{"shiro":"Pia Lund","aka":"Oto Kudo"},"courtB":{"shiro":"Inoshishi","aka":"Hayabusa"},"shotA":"scripts/e2e/output/moved-match/01-court-A-bout-running.png","shotB":"scripts/e2e/output/moved-match/02-court-B-teams-running.png"}
MV 2 court B before officiating title on court B correct path {"title":"Teams","picker":false,"state":{"officiating":"Teams","pickerOptions":null,"upNext":{"head":"09:46 · Teams · Match 2","names":["Tanuki","Kitsune"]},"upcoming":[],"later":[{"head":"10:02 · Teams · Match 3","names":["Winner of M2","Winner of M1"]}],"completed":0,"editor":{"eyebrow":"Teams · Semifinals · Match 1","court":"B","title":"Shiaijo B · 09:30 ● Synced","pill":null,"names":["Inoshishi","Hayabusa"]},"nudges":[],"alsoRunning":null,"done":null,"idle":false}}
MV 3 move court control on the queue row size {"w":30,"h":44,"meets44":false}
MV 3 move Move to Shiaijo B confirm V4 hastyShiaijoConfirm {"label":"Move to Shiaijo B","prominence":"primary","otherLabels":["Cancel"],"title":"Move to Shiaijo B?","message":"Rin Saito vs Mika Endo leaves Shiaijo A and joins the queue on Shiaijo B.","moved":{"shiro":"Rin Saito","aka":"Mika Endo"},"confirmShot":"scripts/e2e/output/moved-match/03-move-confirm.png"}
MV 3 move court A's queue after the move correct path {"queue":["Rin Saito vs Nils Berg","Oto Kudo vs Mika Endo","Pia Lund vs Rin Saito","Nils Berg vs Mika Endo","Rin Saito vs Oto Kudo","Pia Lund vs Nils Berg","Nils Berg vs Oto Kudo","Pia Lund vs Mika Endo"],"shot":"scripts/e2e/output/moved-match/04-court-A-after-move.png"}
MV 4 court B live court B after the move (no reload) correct path {"options":["Individual","Teams"],"officiating":"Teams","queue":["Tanuki vs Kitsune"],"nudges":[{"text":"1 Individual match also waiting on this court.","quiet":true,"amber":false}],"shot":"scripts/e2e/output/moved-match/05-court-B-picker-appeared.png"}
MV 5 pick Individual competition picker -> Individual correct path {"upNext":{"shiro":"Rin Saito","aka":"Mika Endo"},"editorStill":{"eyebrow":"Teams · Semifinals · Match 1","court":"B","title":"Shiaijo B · 09:30 ● Synced","pill":null,"names":["Inoshishi","Hayabusa"]},"shot":"scripts/e2e/output/moved-match/06-court-B-individual-picked.png"}
MV 5a start refused Start match on the moved match from the Scores tab while Teams runs on B correct path (court gate) {"refusal":"⚠️ Shiaijo B is running Match 1 (Semifinals). Finish it or send it back to the queue first. ×","runningBout":"Teams · Semifinals · Match 1","namesCompetition":false,"namesTeams":false,"shot":"scripts/e2e/output/moved-match/07-scores-tab-start-refused.png"}
MV 5b console start Start match (Up next) while the Teams bout is fought correct path (bc-sbq: the running bout is sent back) {"runningOnB":[{"court":"B","comp":"Individual","label":"League table · Match 8","shiro":"Rin Saito","aka":"Mika Endo","status":"running"}],"alsoRunning":null,"nudges":[{"text":"2 Teams matches also waiting on this court.","quiet":true}],"shot":"scripts/e2e/output/moved-match/08-court-B-moved-match-started.png"}
MV 5b console start Teams semi-final started again recovery {"pointKept":true,"shot":"scripts/e2e/output/moved-match/09-court-B-teams-restarted-with-point.png"}
MV 6 finish elsewhere head table finishes the Teams semi-final two devices {"headFinishLabel":"Finish + Start Next →","courtB":{"officiating":"Teams","pickerOptions":["Individual","Teams"],"upNext":null,"upcoming":[],"later":[{"head":"10:02 · Teams · Match 3","names":["Winner of M2","Inoshishi"]}],"completed":1,"editor":{"eyebrow":"Teams · Semifinals · Match 2","court":"B","title":"Shiaijo B · 09:46 ● Synced","pill":null,"names":["Tanuki","Kitsune"]},"nudges":[{"text":"1 Individual match also waiting on this court.","quiet":true}],"alsoRunning":null,"done":null,"idle":false},"shot":"scripts/e2e/output/moved-match/10-court-B-after-head-table-finish.png"}
MV 6 finish elsewhere court B plays the other semi-final correct path {"pair":{"shiro":"Tanuki","aka":"Kitsune"}}
MV 6 nudge court B once Teams has nothing left, 3s later correct path {"courtB":{"officiating":"Teams","pickerOptions":["Individual","Teams"],"upNext":null,"upcoming":[],"later":[],"completed":3,"editor":null,"nudges":[{"text":"Switch to Individual: 1 match waiting on this court.","quiet":false}],"alsoRunning":null,"done":"Teams is complete on Shiaijo B3 matches scored. Another competition still has matches on this court: switch above.","idle":false},"nudges":[{"text":"Switch to Individual: 1 match waiting on this court.","quiet":false,"amber":true}],"shot":"scripts/e2e/output/moved-match/11-court-B-switch-to-individual.png"}
MV 6 nudge tap the Switch line correct path {"officiating":"Individual","upNext":{"shiro":"Rin Saito","aka":"Mika Endo"},"shot":"scripts/e2e/output/moved-match/12-court-B-switched-by-tap.png"}
MV 7 other door head table moves a waiting match A -> B from the Scores tab correct path {"moved":{"shiro":"Pia Lund","aka":"Mika Endo"},"confirmAsked":false,"courtA":["Rin Saito vs Nils Berg","Oto Kudo vs Mika Endo","Pia Lund vs Rin Saito","Nils Berg vs Mika Endo","Rin Saito vs Oto Kudo","Pia Lund vs Nils Berg","Nils Berg vs Oto Kudo"],"courtB":["Rin Saito vs Mika Endo","Pia Lund vs Mika Endo"],"shotA":"scripts/e2e/output/moved-match/13-court-A-after-scores-tab-move.png","shotB":"scripts/e2e/output/moved-match/14-court-B-after-scores-tab-move.png"}
MV 8 second start Start match on Pia Lund vs Mika Endo at once after starting Rin Saito vs Mika Endo V2 hurried second start (inside the list refresh) {"notice":"Shiaijo B is running League table · Match 8. Finish it or send it back to the queue first.","courtB":{"officiating":"Individual","pickerOptions":["Individual","Teams"],"upNext":{"head":"09:45 · Individual · Match 10 of 10","names":["Pia Lund","Mika Endo"]},"upcoming":[],"later":[],"completed":0,"editor":{"eyebrow":"Individual · League table · Match 8 of 10","court":"B","title":"Shiaijo B · 09:35 ● Synced","pill":null,"names":["Rin Saito","Mika Endo"]},"nudges":[],"alsoRunning":null,"done":null,"idle":false},"runningOnB":[{"court":"B","comp":"Individual","label":"League table · Match 8","shiro":"Rin Saito","aka":"Mika Endo","status":"running"}],"shot":"scripts/e2e/output/moved-match/15-court-B-second-start-at-once.png"}
MV 8 competitor busy Start match on Pia Lund vs Mika Endo (one of them fights on A) while Rin Saito vs Mika Endo runs on B V5 lost place (the moved match's competitor is elsewhere) {"notice":"Pia Lund is fighting now in League table · Match 1 on Shiaijo A. Finish that match first.","courtB":{"officiating":"Individual","pickerOptions":["Individual","Teams"],"upNext":{"head":"09:35 · Individual · Match 8 of 10","names":["Rin Saito","Mika Endo"]},"upcoming":[{"head":"09:45 · Individual · Match 10 of 10","names":["Pia Lund","Mika Endo"]}],"later":[],"completed":0,"editor":{"eyebrow":"Individual · League table · Match 8 of 10","court":"B","title":"Shiaijo B · 09:35","pill":"PRE-MATCH","names":["Rin Saito","Mika Endo"]},"nudges":[],"alsoRunning":null,"done":null,"idle":false},"runningOnB":[],"shot":"scripts/e2e/output/moved-match/16-court-B-start-refused-competitor-busy.png"}
Discard draw V4 hastyConfirm {"label":"Discard draw","prominence":"danger","otherLabels":["Cancel"],"title":"Please confirm","message":"Discard the generated draw for \"Men Individual\"? The pools/bracket will be removed so you can edit participants and settings, then generate a new draw.","drawDiscarded":true}
Call to court V2 doubleTap {"gapMs":16,"withinWindow":true,"labelAfter":"Call again"}
running bout V3 interrupt {"kind":"reload","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","editorBack":true,"sameMatch":true}
bout with one ippon V3 interrupt {"kind":"hidden","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","resumed":true,"method":"document.hidden override + dispatched visibilitychange","sameMatch":true}
bout with one ippon V3 interrupt {"kind":"back","before":"/admin/shiaijo/A","landedOn":"/admin/competition/men-individual/scores","hadHistory":true,"after":"/admin/shiaijo/A","editorBack":true,"sameMatch":true,"shiroPointsKept":1}
Shiro M V1 tapNeighbour {"intended":{"tag":"button","label":"M","testid":null,"className":"ipt-btn ","disabled":false},"hit":{"tag":"button","label":"K","testid":null,"className":"ipt-btn ","disabled":false},"gapPx":4}
Aka M tapped offline V3 interrupt {"kind":"offline","before":"/admin/shiaijo/A","after":"/admin/shiaijo/A","stateWhileOffline":"Syncing…","resynced":true}
Finish (armed) V2 retapWhileSaving {"savingSeen":true,"retapped":true,"labelAfter":null,"completedIsBout2":true,"result":"K X M"}

gitrgoliveira and others added 10 commits October 12, 2026 11:17
A fresh-clone run lost J1's reset tap: removing Aka's M and tapping M again
while the removal's write was out put back the value the server still
showed, so the board read clean and the removal's SSE echo wiped the new M.
J1's reset steps now wait for Synced first; they reset the bout and are not
the place to test that race.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the individual score editor, taking a mark off and tapping it back while
the removal's autosave was out returned the board to the value the server
still showed. The editor judged unsaved work by comparing the board with the
server, so it read the board as untouched and adopted the removal's SSE echo
over the newer tap; the tap's own save named no change. The reverse (add,
then remove quickly) brought the mark back.

The editor now records when the operator last edited the board (points,
fouls, overtime, draw) in the server's clock frame and never adopts a server
copy stamped before it, the rule the team sheet's rows already follow
(rowTakenFromServer, bc-kclr). Adopting the server's copy clears the stamp.
Representative-player picks are not board edits and keep their own adopt.

Render test pins both orders and that a later change made elsewhere is still
followed (fails without the fix); the e2e test holds the save 150 ms and
checks the mark on the board and after a reload (fails 2 of 2 without the
fix). One changed-groups fixture now stamps its server copies, as the server
does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code review of PR #442 found checks that passed without testing anything:
- bc-tmfn tapped Finish twice inside the bounce window with errors
  swallowed; it now asserts the unscored-bout refusal on each tap.
- bc-htcr and J2 checked only that Ht existed or the text changed; they
  now check Ht is in the corrected side's cell.
- The kachinuki viewer check matched a team name shown before the final;
  it now waits for the final's winner mark.
- J8 and J2 never asserted that the court moves on after a fusenpai
  recorded on the court console; they do now.
- J1's Scores-tab Next could pass with no navigation; it now needs two
  distinct editors, all on court B.
- moved-match, S4a and the shared-court "nothing started" checks read
  after a fixed sleep or compared empty with empty; they now wait for
  the event, score b2 first, and poll the whole window.
- The shared-court double tap asserts the taps were a real double tap.
- enterAdmin and enterTournament no longer take the sign-in branch when
  the first paint is slower than 5 s.
- The individual spec's bead guards are no longer serial with the
  journeys, so a failing journey skips none of them.

Docs review: the README no longer says clumsy outcomes are never
asserted, lists the prerequisites and what "green" counts, and names
both second-device kinds; the allowlist reason names a barred competitor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the court console the first start can land inside the 400 ms bounce
window of its tap. The Up next card then shows the next match with its
Start match enabled, and the bounce started it and sent the match just
started back to the queue (found by PR #442's shared-shiaijo journey in a
--workers=1 run: SF1 started, then SF1 sent back and SF2 started).

Every Start match on the console (the Up next card and the queue rows)
now shares one keyless bounce guard through tap_guard.jsx's acceptTap, as
Call to court does (bc-cdbl). A tap after the window and a keyboard
activation still start. The render test fails without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the server stored a Finish but the device never processed the answer
(a dropped connection, a reload or sleep right after the tap, a reply past
the 12 s wait), the queue resent it with the same stamp, as designed. The
correction-reason rule read that identical copy as a correction of a
finished match and answered 400, so the operator was told a recorded
result was rejected (found by J6 in a fresh-clone run).

Operator decision, option A: an exact resend of a completed score write
(the stored result's own stamp, nothing it names differing from what is
stored once read as the write path would land it) is answered as
recorded, with the stored match and no correction reason, before any
refusal, writing nothing to the match file (engine.ExactScoreReplay, the
score counterpart of exactDecisionReplay). A same-stamp write that differs
still needs a reason. The client also takes a settled queue entry out of
browser storage at once, narrowing the window.

CLAUDE.md's correction rule and the queue's replay sentence now agree;
openapi and the network architecture page describe the answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rules

Operator rulings 2026-10-12/13:
- The separate destructive-ops password is gone. A signed-in organiser
  is never asked for the password again, except Delete competition,
  which sends the re-typed password in X-Confirm-Password and is
  refused (401) unless it matches, checked by the sign-in verifier.
- Self-run: completing a competition needs no password; adding and
  editing participants is open to anyone; organiser setup, corrections
  and destructive actions need the main password (branding logo and
  league tie-break routes added to that list).
- Self-run mode has no check-in: Competition.CheckInApplies is the one
  owner the draw, the Swiss field and the estimate ask; the three
  check-in writes answer 409 check_in_not_in_self_run; the SPA hides
  the toggle and the registration desk.
- A competition with no knockout rounds cannot be completed while a
  match is unfinished, nor a Swiss one while rounds remain to draw.
- TOURNAMENT_ADMIN_PASSWORD_HASH and a stored admin_password are
  ignored.

Review fixes: the exact-replay probe runs the merge on a copy (held
groups excluded) and covers kachinuki, engi and fixed-order teams; a
self-run participant's exact resend of a landed finish is answered as
recorded; the individual and team editors clear an edit stamp once a
save built after it lands (shared landedAutosave). Docs state current
behaviour throughout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling 2026-10-13: in file mode POST /api/tournament/reset
answers only a loopback peer (judged on the TCP peer address, never on
forwarded headers); anyone else gets 403 reset_local_only, "Reset the
password on the machine running the server.", which the /reset page
shows. Locked mode still answers 404.

Review fixes: a self-run participant's exact resend of a finish adds
no history line; the file-mode password check is constant-time; the
self-run setup text names the admin password and says Delete
competition asks for it again; the API spec and docs state that anyone
can add and edit participants in self-run and when a competition can
be completed; gosec annotation on the confirm header name; a useMemo
dependency the linter flagged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page whose name rebinds to 127.0.0.1, opened in a browser on the
server machine, passed the loopback-peer and same-origin checks. The
reset now also requires the request's Host to be localhost or a
loopback address, else the same 403 reset_local_only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gitguardian

gitguardian Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

GitGuardian read the fixture value as a generic password.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gitrgoliveira
gitrgoliveira marked this pull request as ready for review October 13, 2026 09:35
Copilot AI balanced review requested due to automatic review settings October 13, 2026 09:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

gitrgoliveira and others added 3 commits October 13, 2026 11:48
The sign-in dialog, the reset page and confirm, the discarded-writes
message, the server's error sentences and startup log, the API spec
and the docs said "tournament password" where the setup screens and
the operator docs say "admin password". Identifiers, the
X-Tournament-Password header and wire codes are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Infrastructure: retapWhileSaving and doubleTap fail instead of passing
silently (the double-tap limit derives from the app's TAP_BOUNCE_MS);
the start/reopen 409 allowlist entry matches only a start or a reopen,
and console lines pair one-to-one with failed answers; the shared
server helper kills its child and removes its data folder on process
exit, awaits the child on stop and catches a port race; a failing test
gets the server log; afterAll output is attributed to its own file.

Journeys: J7 asserts the finished TV board; the team guards no longer
hang off one serial chain and the kachinuki guards cannot skip the
final; the kachinuki totals, the team kiken and the one-mark double
tap are asserted; runningOnCourt waits for the list; J6's second
device is its own context; lineup names are read after the source.

Docs: the harness README is developer documentation (adding a journey,
traces, network needs); the developer guide links to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kachinuki guard cleanup is capped and cannot replace a guard's own
failure, and each guard failure is annotated when it happens; the
overtime guard checks Encho landed before its negative check; dead
withinWindow assertions and a vacuous viewer assertion are gone; J7
says what the TV board can show; the shared server helper also cleans
up on SIGINT/SIGTERM when nothing else handles them; README states
the default timeout, parallel workers, when a server stops and that
the results folder is emptied.

Docs: the laptop shortcuts sentence states which editors take Left,
Right and Esc, as the code does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 11:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

gitrgoliveira and others added 2 commits October 13, 2026 13:49
A failed answer is recorded before its body arrives and the console
check awaits pending bodies, so an allowed refusal cannot fail a test;
wizardDefaults waits for the schedule estimate it expects instead of
fixed sleeps; a failing test gets its file's server log even when it
never asked for the server; the bc-ltap hold is released after the
tap-back rather than after a fixed 150 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An answer no console line claimed yet is kept for the pairing window,
so a refusal whose console line arrives in the next test still pairs;
a refusal body is awaited up to the expect timeout (one shared
constant) and its timer cleared; consolePolicy is torn down before
serverLog, so a test failed by the console policy carries server.log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 13:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gitrgoliveira
gitrgoliveira merged commit 723e1fd into main Oct 13, 2026
13 checks passed
@gitrgoliveira
gitrgoliveira deleted the bc-e2eh-operator-review branch October 13, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants