Repository navigation
test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed - #442
Conversation
…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>
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 mp-yqxn.2 reviewer A: persona audit, INDIVIDUAL competitionsDevice: operator project, 1180x820, Fixtures (all through the wizard): F1 "Men Individual" pools + knockout, 12 entrants, Columns: journey | step | action | variant | what happened | state correct? | recoverable J1: shiaijo console, pools to knockout, same court (F1, shiaijo A and B)
J2: special decisions (F1b shiaijo E; F2 shiaijo C)
J6: confirm + advance OFFLINE (F2, shiaijo C, bronze match on)
J8: the same scoring from the Pools tab (F1c) and the Bracket tab (F2d)
Row count: 83 (J1 35, J2 28, J6 9, J8 11; header rows excluded). |
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 Reviewer B audit: fixed-order TEAM competitions (mp-yqxn.2)Performed in the browser through the harness, operator project (iPad Air 1180x820, Fixtures (all through the wizard, roster paste, Generate draw, Start; members named through the Lineups page, the match panel and the score sheet):
Severity: BLOCKS a running tournament / SLOWS the operator / COSMETIC. "Tap target" measurements are bounding boxes under the coarse pointer (floor 44x44); 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):
Fix direction: make the panel read the same resolution the sheet uses ( J3: lineup entry from all three entry points (F3 size 3, F4 size 5)
J4: fixed-order team scoring at the court (F4, /admin/shiaijo/D)
J7: one result on every surface (court iPad, head-table iPad, spectator phone, TV board)
Findings (severity-ranked; ids are placeholders for the orchestrator)Functional findings carry a self-contained
|
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 Reviewer C: J5 kachinuki persona audit (mp-yqxn.2)Fixture F5, seeded through the UI only: team knockout, kachinuki, team size 5, four teams Device: operator project, 1180x820, Spec: Columns: # | step | action | variant | what happened (observed) | state correct? | recoverable Severity: BLOCKS a running tournament / SLOWS the operator / COSMETIC / ok (no issue). Audit table (84 rows)
kachinukiVisiblePositions verdict (admin_scoring_team.jsx:506)Enough context seven bouts in? For "who fought whom and how": yes. For "where do we
Can the operator return to a mis-scored earlier bout? Yes, through three doors that work. Who stayed on propagated correctly in every performed case: wins (rows 15, 26), hikiwake Findings (severity-ranked; file:line re-derived with grep -n on this worktree)
Harness defects and things not reachable through the UI
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
…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>
…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>
…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>
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>
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>
…-e2eh-operator-review
Audit rows, current run: Kachinuki (J5)Raw rows from
|
Audit rows, current run: Shared shiaijo (bc-shcr: S1 to S9, P1 to P8)Raw rows from
|
Audit rows, current run: Moved match (bc-mvsj) and smokeRaw rows from
|
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>
# Conflicts: # CLAUDE.md
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>
️✅ 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. 🦉 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>
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>
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>
Summary
scripts/e2e/) and the persona-audited operator journeys for knockout and mixed (pools + knockout) competitions, individual and team, plus two shared-shiaijo journeys.make e2ebuilds the binary, startsmobile-appon a free port with a fresh data folder for each spec file, and drives the real UI in Chromium on an iPad Air landscape (1180x820,(pointer: coarse)asserted) and a spectator phone (390x844). Every competition is seeded through the UI: the first-run form, the login form, the create wizard, the roster paste box, Generate draw and Start. Nothing is added tomake go/testor to GitHub CI.web-mobile/,internal/,cmd/anddocs/is unchanged, apart from the docs and spec lines that describe these rules. The review's output is beads parented to mp-yqxn.2. Each functional defect a journey hits is atest.fixme('<bead>: ...')in its spec, asserting the fixed behaviour, so the bead's own PR turns it into a plain test.shared-shiaijo.spec.mjsruns two competitions on one court (S1 to S9, P1 to P8) andmoved-match.spec.mjsmoves a waiting match to another court, watched on two devices. Their findings went to the operator with screenshots; the eight confirmed beads are filed under mp-yqxn.2 and each has atest.fixmein these two specs (section below).fullPageshot. Details under Harness notes.fixtures/clumsy.mjsenacts them, so the audit re-runs on every later PR.Which PR closes which bead
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, souseAdoptFromServertook 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.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=1run; 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 (
startTapRefthroughacceptTap,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.jsxfails 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 inapplyCorrectionReasonand answered inRecordMatchResultWithIneligibilityTxbefore 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 meetsresult_finalizedfirst.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.
promptDialog), the client sendsX-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."TOURNAMENT_ADMIN_PASSWORD_HASHand a storedadmin_passwordare ignored; remove them.Competition.CheckInAppliesis 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 409check_in_not_in_self_run.PoolMatchesSettled), nor a Swiss one while rounds remain (SwissRoundsToDraw).POST /api/tournament/resetanswers only a request made on the server machine: a loopback peer (never judged on forwarded headers) and a loopback Host (closes DNS rebinding). Otherwise 403reset_local_only, which the/resetpage shows. Locked mode answers 404.landedAutosave). The file-mode password compare is constant-time.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:
shared-shiaijo.spec.mjs, last testshared-shiaijo.spec.mjsmoved-match.spec.mjsmoved-match.spec.mjsshared-shiaijo.spec.mjsshared-shiaijo.spec.mjsshared-shiaijo.spec.mjs, first testshared-shiaijo.spec.mjsNot 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.
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:
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
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).Ruling: three lineup entry points is intentional in design and defective in execution.
fallback=best(evidenceJ3-lineups/07-f3-panel-round-default.png).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,
kachinukiVisiblePositionsverdict (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.
"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
4. Special decisions
wname is not definedand 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 codealready_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.decision_lockedconfirm, 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.max_encho_exceededarm 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).5. Confirm and advance
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)
scripts/e2e/, reusingscripts/screenshots/lib, not inweb-mobile/e2e. Seeding is UI-only, through the wizard every time. One PR closes bc-e2eh and mp-yqxn.2.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.
checkConcurrentIneligibilityand the K2 check-and-set). Keep the origin CompetitorStatus, keep refusing a second kiken from another match, and list running bouts in the panel.correctingKeyis pinned until the operator closes the panel, and wire a real close. Do not relax the completed filter.submitHanteithrough ReasonPrompt with a pending-action ref, and add a render test.onDaihyosen's own save, as Finish does.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/:courtonly 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):
fullPagescreenshot had dropped the coarse pointer; the fix is inshots.mjs. Re-measured with coarse asserted, Lineups Add is 44x44, Save is 100x44 and Bracket Edit result is 44px tall.Harness notes
outputDirat the start of every run, so two runs in one checkout deleted each other's traces mid-test (ENOENT on context close).E2E_RESULTS_DIRgives a concurrent run its own results folder, and the journey screenshots andaudit.jsonfollow 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).serverForFilestarts a fresh server whenever the file changes (found in code review; proven with--workers=1).fullPagescreenshots dropped the coarse pointer: measured,(pointer: coarse)read false after afullPageshot, 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.lib/ui.mjs):scripts/screenshots/lib/editor.mjsgainsINLINE_EDITOR, the inline editor's selector, which the e2e fixtures pass as the editor's root. (An optionalrootargument to itsfinishMatchwas dropped when main's tap-guard version of that function merged in: the journeys finish through their own tappingfinishMatchinfixtures/scoring.mjs, andfixtures/pace.mjsimports the library'sTAP_DWELL_MS.) Because it touches the capture harness's shared lib,make docs/screenshots SINCE=mainselects every capture group on this branch.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 e2egreen locally" checkbox.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 matchesCONSOLE_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/scoreor/reopencarryingcourt_busy,ineligible_competitorordownstream_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 attachesconsole.json, and each console error is printed as aCONSOLE {json}line naming the allowlist entry it matched, if any.fixtures/shared-court.mjs): two competitions on one shiaijo, created through the wizard and started through the UI, as every other journey.Files changed
.github/pull_request_template.mdmake e2egreen locally (anyweb-mobile/change)".gitignorescripts/e2e/output/; re-include the harness'spackage.jsonandpackage-lock.jsonMakefilemake e2e(go/build, thenplaywright testwithE2E_ARGS) andmake e2e/deps(npm ci + Chromium, stamped); not in go/test or CIscripts/e2e/README.mdE2E_RESULTS_DIRscripts/e2e/package.json@playwright/testto the same release asscripts/screenshotsscripts/e2e/package-lock.jsonscripts/e2e/playwright.config.mjsscripts/e2e/fixtures/test.mjstest: one server per spec file (serverForFile), an operator page whose coarse pointer is verified, and the console policy (CONSOLE_ALLOWLIST)scripts/e2e/fixtures/pointer.mjsisCoarse,restoreCoarse(in place), andtapSizefor the 44px floorscripts/e2e/fixtures/audit.mjsAUDIT {json}log line, andaudit.jsonwritten beside each journey's screenshotsscripts/e2e/fixtures/devices.mjsscripts/e2e/fixtures/setup.mjsscripts/e2e/fixtures/wizard.mjsscripts/e2e/fixtures/competition.mjsscripts/e2e/fixtures/shiaijo.mjs/admin/shiaijo/:court: the queue, the inline editor andcompletedRowFor(the newest Completed row for a pairing)scripts/e2e/fixtures/scores.mjsscripts/e2e/fixtures/scoring.mjsscripts/e2e/fixtures/public.mjsscripts/e2e/fixtures/pace.mjsTAP_DWELL_MS, imported from the capture harness) after a layer opens or a two-tap button armsscripts/e2e/fixtures/clumsy.mjsloudestButton, the one V4 rankingscripts/e2e/fixtures/shots.mjsjourneyDir, the one owner of a journey's output folder; restores the coarse pointer after afullPageshotscripts/e2e/fixtures/individual.mjsrecordDecisiontakes the side: the prompt picks none), andcorrectCompleted(Correct on a finished bout, sending a live bout back to the queue first when the court refuses)scripts/e2e/fixtures/team.mjsscripts/e2e/fixtures/kachinuki.mjsscripts/e2e/journeys/smoke.spec.mjsscripts/e2e/journeys/knockout-mixed-individual.spec.mjsscripts/e2e/journeys/knockout-mixed-team.spec.mjsscripts/e2e/journeys/knockout-mixed-kachinuki.spec.mjsscripts/e2e/fixtures/shared-court.mjsscripts/e2e/journeys/shared-shiaijo.spec.mjsscripts/e2e/journeys/moved-match.spec.mjsweb-mobile/js/admin_scoring_individual.jsxboardEditedAtRef, set by every board edit, cleared when the server's copy is taken) andadoptServerScore, which keeps the board over a server copy stamped before the last edit; representative-player picks use the plain autosave callweb-mobile/js/__tests__/render/individual_editor_tap_back_kept.render.test.jsxweb-mobile/js/__tests__/render/changed_groups_every_editor.render.test.jsxweb-mobile/js/admin_shiaijo.jsxstartTapRef,tapStart) for every Start match on the court console, the Up next card and the queue rowsweb-mobile/js/__tests__/render/start_match_double_tap.render.test.jsxinternal/engine/scoring_tx.goExactScoreReplayandanswerExactScoreReplay, an exact resend of a completed score write answered as recorded before any refusalinternal/engine/exact_score_replay_test.gointernal/mobileapp/handlers_match.goapplyCorrectionReasonasks no reason for an exact resendinternal/mobileapp/score_exact_replay_test.goweb-mobile/js/api_client.jsxleaveQueuein_flushQueue)web-mobile/js/__tests__/queue_ordered_writes.test.jsxspecs/openapi.yamlcorrectionReasonfield describe the exact-resend answerdocs/architecture/network-architecture.mdCLAUDE.mdinternal/mobileapp/confirm_password.go,middleware.go,server.goX-Confirm-Passwordcheck (new file); one password for every organiser route; self-run main-gated list gains the branding logo and league tie-break routesinternal/mobileapp/handlers_reset.goreset_local_onlyinternal/mobileapp/handlers_competition.go,handlers_participants.go,handlers_match.gointernal/mobileapp/auth_admin.go,handlers_auth_admin.go(and their_test.go)internal/state/models.go,internal/engine/competition.go,scoring_tx.goCheckInApplies;PoolMatchesSettled,SwissRoundsToDraw; the exact-replay probe merges on a copyweb-mobile/js/api_client.jsx,ui.jsx,admin_competition_settings.jsx,competition_shape.jsxX-Confirm-Password;promptDialog; the Delete dialog; no check-in in self-runweb-mobile/js/admin_scoring_autosave.jsx,admin_scoring_individual.jsx,admin_scoring_team.jsxlandedAutosaveclears an edit stamp once a later save landsweb-mobile/js/__tests__/,internal/*/*_test.goadmin_elevated.test.jsxdeleteddocs/user-guide/(organisers, competitors, commands, install),docs/architecture/,specs/openapi.yaml,.claude/rules/match-writes.md,CLAUDE.md,AGENTS.mdscripts/screenshots/lib/editor.mjsINLINE_EDITOR, the inline editor's selector; a comment says "team", not "fixed-order team"scripts/screenshots/lib/ui.mjsScreenshots
The bc-cmpw screenshots:
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/aftermake e2e.Test plan
make go/testpasses (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/engine90.1% andinternal/mobileapp88.4% statement coverage. Nogo.modorgo.sumchange.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-xrplTestScoreHandler_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 e2egreen 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=135 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/buildpasses):docs/architecture/network-architecture.mddescribes bc-xrpl's exact-resend answer, andspecs/openapi.yamlthe 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/makeSINCE=mainselect 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'saudit.json: every console error was an allowlisted entry, and no warning was recorded.make go/testandmake docs/buildpass on the final treeFull
make e2egreen (default workers and--workers=1) with the console clean, and from a fresh cloneBrowser: 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_onlyCloses 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