Repository navigation
fix(bc-dtfn, bc-dtip, bc-fsnp, bc-emsl, bc-sync): a bounced tap, a fusensho or a reload no longer changes a recorded result - #450
Merged
Conversation
bc-sync. The score editors hold each edit for a 300ms autosave debounce, and nothing outside the editor knew about that stage: the sync pill read "Synced" over it, and a reload inside the window cancelled the timer and lost the edit. - api_client: API.notePendingEdit adds the pending stage to the sync status (still 'syncing', no new value); API.runDurably makes a running score write go straight into the persisted outbox instead of a fetch the browser would cancel with the page. - useDebouncedRunningWrite registers the pending edit from the first tap and releases it only once the write is dispatched, so the status never reads "synced" in between. On pagehide or the tab being hidden it writes a pending edit through runDurably. Unmount still cancels. - runDurably works only because every host reaches API.recordScore with no await in front of it; pinned at attemptScoreWrite. Browser, 1180x820 with touch: the pill reads "Syncing…" from the tap until the write lands, and a point tapped 40ms before a reload is on the server after it. The same sequence on a main build loses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bc-dtfn, bc-dtip. A bouncing thumb delivers two complete clicks 16-44ms apart, and Preact renders between them, so the second one passed the two-tap Finish / End match guard (finishing the bout and, with Finish + Start Next, starting the next) and recorded a second ippon or foul. web-mobile/js/tap_guard.jsx is now the one owner of "is this pointer tap the bounce of the previous one" (TAP_BOUNCE_MS 400; a keyboard click, detail 0, never is). The five arm sites use useArmedConfirm; the ippon and foul "+" buttons guard per bout side; bc-kbrw's bout-list guard moves onto the leaf. The capture harness now waits TAP_DWELL_MS between such taps. bc-fsnp. A per-bout fusensho replaced the scoreline, erasing the points the other side had struck (FIK Art. 32 keeps them), and after a reload its undo left the two default-win circles behind as ordinary points. It now keeps the other side's struck points (struckIppons, the JS twin of engine.struckIppons), every other edit on the row ends it through clearFusensho, and a fusensho against a side that already won the bout is refused, since circles against two points are a 2-2 the server rejects. bc-emsl. A tap on an empty mark slot, or on a default-win circle, went through setPts and cleared the bout's Tie or Fusensho with a fresh autosave. It is now inert, as in the individual editor. Browser, 1180x820 with touch, against a main build as control: two taps ~70ms apart give one M (main: M M) and only arm Finish (main: finished and started the next match); the fusensho row keeps Shiro's K and its undo after a reload leaves just the K; an empty-slot tap sends nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From a /simplify pass (reuse, simplification, efficiency, altitude). No behaviour change; every reworked mechanism was re-checked by removing it and watching its tests go red. - bc-sync: the pagehide flush now marks its write `durable: true` on the patch instead of setting a module-level depth counter around the call (API.runDurably and _durableDepth are gone). recordScore reads the flag from the patch, never sends it, and no longer touches the in-flight count for a queued write. The hook drops a ref it did not need. - bc-dtip / bc-dtfn: tap_guard.jsx gains acceptTap, the check-then-stamp every guarded button ran by hand at four sites; the team rows compute their tap key once; useArmedConfirm keeps one ref instead of two. - bc-fsnp: the team editor's updateSubScore owns "a scoring edit ends the fusensho and the draw, clear applied last", which six setters and the keyboard waza path used to spell out each. setFusenshoFor loses a check its disabled button made unreachable, and applyFusenshoToggle calls defaultWinMaru directly instead of through window with a dead fallback. - api_client: notePendingEdit sits after unsentWrites, whose doc comment it had split off. - Tests: pointerTap/keyboardClick live in __tests__/helpers/tap_events.js. - Screenshot harness: finishMatch counts the wait for the arm label towards the bounce window, and editors.mjs no longer waits after its last tap. Browser (1180x820, touch): a double tap still records one M; the pill still reads Syncing… from the tap until the write lands; a point tapped 40ms before a reload is on the server after it; correcting a point on a fusensho row leaves no circles. make docs/screenshots SINCE=main: 31 unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eir result Review findings on this PR: - Undoing a switched fusensho on a bout reopened from saved state no longer restores the default-win circles as ordinary points: the base of a row with no snapshot is the row without its circles. - Taking a mark off the side a fusensho went against is a correction and keeps the fusensho (applyBoutScoreEdit); the snapshot its undo restores follows the edit. Adding a point, a foul or a winner-side edit still ends it. - Prev/Next (buttons and arrow keys) save an edit still inside the autosave window before they unmount the editor, as closing does. - A wall clock stepped back is never read as a bounce, in one helper shared by the tap guard and the armed confirm. - A refused Fusensho names the undo first when the side that won the bout holds the fusensho itself, and reads the other side's label. - The scoreboard's fallback circles come from DEFAULT_WIN_IPPON rather than a hand-typed literal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… why - The engi flag "+" buttons guard per side through acceptTap: a bounced double tap adds one flag, as the ippon and foul "+" already do. The a/s keys are never a bounce. The screenshot harness waits TAP_DWELL_MS between same-side flag taps. - The engi editor's Prev/Next (buttons and arrow keys) save flags still inside the autosave window before they unmount it, as the individual and team editors now do. - A refused per-bout Fusensho is aria-disabled instead of disabled: it still looks greyed out, and a tap shows the reason under that side. A title alone never shows on a touchscreen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The persisted outbox (bc_write_queue) is shared by every tab of the app, but each save wrote the tab's own queue over it, and an emptied queue removed the key. A result queued in one tab was dropped from storage when another tab saved or finished its writes, and lost if the first tab then closed. A page hide now queues a write whenever an edit is pending, so this happens far more often than it used to. _persistQueue now merges into what storage holds: this tab's entries are written over it, the entries it settled since the last save are taken out, and everything else is left alone. Every removal goes through _dequeue, which records it; a settled key is pruned only when the stored entry is not newer than the one removed, so another tab's later write for the same match stays. Entries dropped on load (expired or unreadable) are recorded too, so they are announced once. clearQueue stays a hard wipe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s Prev/Next The individual, team and engi editors each carried the same save-then-go closure; the shared autosave hook now owns it, with the running check it already holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… survives a page close Operator rulings 2026-09-27: - The editors behave alike: the engi editor's Save result / Finish + Start Next is the same two-tap guard as Finish (useArmedConfirm). A bounced double tap only arms it, so it can no longer save AND start the next match. A flag change disarms it; a correction and keyboard Enter save directly, as in the other editors. The capture harness saves engi through finishMatch. - The engi flag "+" is not an ippon call, so it has no bounce guard: the guard from d4f422f is removed, with its tests and the recipe pacing. Removal taps (a scored mark, the engi "-") stay unguarded too. CLAUDE.md records both so neither guard is added back. - A running write already sent but not yet answered when the page goes away (pagehide, or the tab hidden) goes into the persisted outbox, as a failed fetch would put it: the page going away cancels that request. A queued entry for the match added after the write was sent is newer and is kept. The server takes an equal rev again and drops a lower one, so a copy that did land is harmless. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… writes Review round 3 of PR 450, with the operator's rulings of 2026-09-27. - An autosave is stamped with the time of the edit it carries (editedPerf), and the editor's unmount writes a pending edit, whatever caused it. Discard cancels first. A tap made before another device finished the match is older than that finish, so it cannot reopen it. Close and Prev/Next share one leave rule: Prev/Next ask before dropping a Mark draw or an unsaved correction. - Every running-match edit arms the autosave. The overtime counter, the rep-player pickers and the team daihyosen verdict did not. Their adopts keep the operator's value until the server holds it. - The stored outbox keeps the newer write per match. A tab takes up the writes other tabs store and drops the ones they settled. A revoked password is scrubbed from every stored entry. - The page-hide copy of a running write still being sent is dated when it was sent, and is not sent twice while its request is open. - A timestamp built from a monotonic age is whole milliseconds: the server refused a fractional one with a 400 (the edit stamp, and the existing clock-skew re-stamp). - enchoOn and defaultWinMaru move to result_slot.jsx, so the scoreboard's default-win count no longer depends on a window global. - A refused Fusensho's explanation clears on the next bout edit. An engi key at a bound writes nothing. The team re-seed reads the previous board when the adopt fires, not inside the updater. - Tests pin the whole host chain before the first await. - Docs: the shared outbox, closing and Prev/Next, the engi armed label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…recordScore Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uling) A bounce on the EnchoControl stepper's + saves one period too many. The count shows only as xN in that editor row: everything else reads only whether overtime happened (enchoOn), so no result, bracket, board or export changes, and one tap on - takes it back. Listed beside the engi flag + and the removal taps so a later review does not re-raise it. Kachinuki bout mode's Encho button stays an open question on bc-kheb. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitrgoliveira
added a commit
that referenced
this pull request
Oct 7, 2026
…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>
Merged
15 tasks done
gitrgoliveira
added a commit
that referenced
this pull request
Oct 8, 2026
…-crpn are plain tests Main merged #462 (bc-dsid, bc-otpl, bc-htsd, bc-dlck, bc-kosc, bc-crpn). The journeys now walk what an operator meets on that build. - bc-crpn: Correct is refused at the court console while a bout is live. J1, J2, J4 and J5 record the refusal (the row notice and the toast), then take the route the notice names: send the live bout back to the queue, correct, start it again. J5's court-busy panel is reached from the Scores tab, where a reopen can still meet a busy court, with its V1 and V4 variants kept. The bc-crpn test asserts the new refusal: Start match while a correction is open names the correction and starts nothing. - bc-dsid: the withdrawal and no-show prompt picks no side and Record is off until one is picked. The hasty variants record that, then the operator picks; fixtures/individual.mjs recordDecision now takes the side. - bc-htsd: the individual hantei buttons pick the side and Finish records it. - bc-dlck: the re-recorded kiken asserts no "Proceed anyway?" confirm shows. - bc-otpl and bc-kosc pass as written and are plain tests. - J5's M1 points won read 7-3 and then 6-3: bout 5's double tap scores one M since bc-dtip (#450). The old 8-3 and 7-3 counted the bounce's second M. - A banned phrase about the outcome of a withdrawal is gone from three audit labels, a J3 step title and the bc-kfup test title. One fixme left, on an open bead: bc-dhrp, which still fails at its own assertion. make e2e: 32 passed, 1 skipped, EXIT 0 (kachinuki re-run after the points fix: 11 passed). make go/test: EXIT 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes five of the P2 operator bugs filed from the mp-yqxn.2 clumsy-operator review. In each one a single tap or a reload silently changed or lost a recorded result. They are the P2s filed since 2026-09-23 that are not waiting on an operator decision and not already fixed (bc-unfx was fixed by #444). Each follows the plan recorded on its bead on 2026-09-25, checked again against main after #447.
Why
web-mobile/js/tap_guard.jsxis now the one owner of that question (TAP_BOUNCE_MS= 400, bc-kbrw's value; a click withdetail === 0comes from the keyboard and is never a bounce). The five arm sites useuseArmedConfirm. The ippon and foul buttons useacceptTap, keyed by side (idx:sideon the team sheet). bc-kbrw's private bout-list guard moves onto the same leaf.applyFusenshoTogglemodelled a default win as "replace the scoreline". It now takes the loser's points from the pre-fusensho snapshot throughstruckIppons(result_slot.jsx, the JS twin ofengine.struckIppons, besideDEFAULT_WIN_IPPON).clearFusenshostrips only the winner's circles. Every scoring edit on a bout row now goes throughupdateSubScore, whoseapplyBoutScoreEditapplies the edit first and ends the fusensho throughclearFusensholast, except taking a mark off the side the fusensho went against: that is a correction, it keeps the fusensho, and the snapshot its undo restores follows it; before, seven places cleared the flag by hand. Without that, keeping the loser's points would bring the defect back on the next ordinary edit.fusenshoAllowedis the refusal, and it disables the button, so a refused tap sends no write. No Go change: the server already stores and counts this row shape (checked in the browser:ipponsA: [○, ○], ipponsB: [K], decision: fusensho).onClickcalledsetPtsfor every slot, andsetPtsalways ends a Tie or Fusensho and marks the editor dirty.api_client, whose sync status counted only in-flight and queued writes. And a fetch started as the page unloads dies with it before its failure path can queue it.API.notePendingEditadds the pending stage to the status; it is stillsyncing, with no new value.durable: truegoes straight into the saved offline queue, exactly the state a failed fetch leaves.recordScorereads the flag from the patch and never sends it.useDebouncedRunningWriteregisters the pending edit on the first tap and releases it only after the write is dispatched, so the pill never reads "Synced" in between. Onpagehideor the tab being hidden, it writes a pending edit at once, markeddurable.API.recordScorebefore the page goes, with noawaiton I/O in front of it.score_write_same_tick.test.jsxpins the whole chain:attemptScoreWrite,editMatchScoreand each of the five hosts'onSubmit.running[0]unless the operator picked a match, so another device finishing the match re-keys the editor by itself, and a write stamped when sent would be newer than that finish and reopen it.useDebouncedRunningWriterecordsperformance.now()at the tap (editedPerf, never sent) andrecordScoretakes its age off the server-relative stamp (_editAge). Discard cancels before closing.flushPendingandleaveAfterSavingare gone._perfAgeMs). The server readsmodifiedAtas an int64 and refused a fraction with a 400, which the browser check caught; the existing clock-skew re-stamp (_restampFor) had the same fraction._onStoredQueueChangedfollows other tabs' writes through thestorageevent, applying the rehydrate's checks (_storedEntryProblem). A write two tabs hold can be sent by both, which the server's last-write-wins makes harmless.Notes:
TAP_BOUNCE_MS, as this PR gives the screenshot harness (TAP_DWELL_MS,sameSideTapPacer). Itstest.fixmeentries for bc-fsnp, bc-emsl and bc-sync can then be switched on, and its double-tap audit rows become assertions.Date.now()when the handler runs, not the event'stimeStamp: that helps only where a browser stamps a tap's click at the touch, and the bout-list guard stamps with no event, so mixing them would compare two clocks (recorded in CLAUDE.md). Reading the stored queue on every save is not material: it holds at most one entry per match.setSubsupdater. Preact, which the app runs on, runs the updater at once, so production was not affected; React, which the render tests run on, runs it a render later, after the board has moved on. It now reads it when the adopt fires.Files changed
web-mobile/js/tap_guard.jsxTAP_BOUNCE_MS,isPointerTap,stampTap/clearTap/tapIsBounce,acceptTap,swallowBounce,useArmedConfirm; onewithinBouncetime test, which ignores a clock stepped backweb-mobile/js/admin_scoring_individual.jsxuseArmedConfirm; ippon buttons guarded per side (tapIppon),removePtclears the guard; Close and Prev/Next share one leave rule (ask before dropping a Mark draw or an unsaved correction; Discard cancels the pending edit); the overtime counter and the rep pickers save as they change, and the rep adopt keeps a pick until the server holds itweb-mobile/js/admin_scoring_team.jsxuseArmedConfirm; ippon and foul+guarded per bout side; bc-kbrw's bout-list guard on the leaf;updateSubScore, through which every scoring edit ends a fusensho; Fusensho refused withfusenshoButtonTitle; empty and circle slots inert; Close and Prev/Next share one leave rule; the overtime counter and the daihyosen hantei pick save as they change, and their adopts keep the operator's value until the server holds it; a refused Fusensho's reason clears on the next bout edit; the re-seed reads the previous board when it firesweb-mobile/js/admin_scoring_shared.jsxapplyFusenshoTogglekeeps the loser's struck points; newfusenshoAllowed,clearFusensho,applyBoutScoreEdit; a reopened row's snapshot base has no circles;FoulCounter's+guardedweb-mobile/js/result_slot.jsxDEFAULT_WIN_IPPON,struckIppons;enchoOnanddefaultWinMaru, moved from bracket.jsxweb-mobile/js/bracket.jsxenchoOnanddefaultWinMarufrom result_slot.jsx, and still exports and window-mirrors themweb-mobile/js/match_scoreboard.jsxdefaultWinMaruinstead of readingwindow.defaultWinMaruwith a fallback that gave two circles in overtimeweb-mobile/js/api_client.jsxAPI.notePendingEdit; the pending stage counts assyncing;recordScorequeues adurablerunning write instead of fetching it;_persistQueuemerges into the stored queue, and every removal goes through_dequeue(no other_writeQueue.deleteremains); a key two tabs hold keeps the newer write;_onStoredQueueChangedfollows other tabs' stored writes;_parkStoredEntries; the page-hide copy of a write still being sent is dated when sent and skipped while its request is open (_heldByOpenFetch); an autosave's stamp is the time of its edit (_editAge); a monotonic age is whole milliseconds (_perfAgeMs, used by_restampFortoo);_storedEntryProblemis shared with the rehydrateweb-mobile/js/admin_scoring_engi.jsxuseArmedConfirm, disarmed by a flag change or a failed save; Close and Prev/Next share one leave rule; a key at a bound writes nothingweb-mobile/css/styles.css.btn[aria-disabled="true"]looks disabled and does not press;.tsm-fusensho-whyweb-mobile/js/admin_scoring_autosave.jsxeditedPerf; thepagehide/ hidden flush, markeddurable;flushPendingandleaveAfterSavingremovedweb-mobile/check-imports.mjsadmin_scoring_shared.jsx,bracket.jsxandmatch_scoreboard.jsx, which gained sibling importsweb-mobile/js/__tests__/tap_guard.test.jsxacceptTap,swallowBounceweb-mobile/js/__tests__/helpers/tap_events.jspointerTap(detail 1) andkeyboardClick(detail 0), shared by the three tap-guard render testsweb-mobile/js/__tests__/render/finish_arm_dwell.render.test.jsxweb-mobile/js/__tests__/render/ippon_double_tap.render.test.jsx+, the other side, the keyboard, a mark taken backweb-mobile/js/__tests__/render/kachinuki_done_bout_double_tap.render.test.jsxTAP_BOUNCE_MSfrom the leaf and its taps fromhelpers/tap_events.jsweb-mobile/js/__tests__/admin_scoring_modal.test.jsxclearFusensho,applyBoutScoreEdit; the two cases that pinned the defect are changedweb-mobile/js/__tests__/result_slot.test.jsxstruckIpponsweb-mobile/js/__tests__/match_scoreboard.test.jsxwindow.defaultWinMarusetweb-mobile/js/__tests__/restamp_rule.test.jsxweb-mobile/js/__tests__/render/team_fusensho_keeps_points.render.test.jsxweb-mobile/js/__tests__/render/autosave_debounce.render.test.jsxweb-mobile/js/__tests__/sync_queue.test.jsxweb-mobile/js/__tests__/render/admin_scoring_engi.render.test.jsxeditedPerf, and closing unmounts the editor as a host doesweb-mobile/js/__tests__/render/autosave_pending_edit.render.test.jsxpagehideand hidden write it at once, markeddurableweb-mobile/js/__tests__/render/autosave_every_scoring_edit.render.test.jsxweb-mobile/js/__tests__/render/editor_every_way_out.render.test.jsxweb-mobile/js/__tests__/render/team_fusensho_refusal_clears.render.test.jsxweb-mobile/js/__tests__/render/{score_editor_dismiss_prompt,kachinuki_cleared_bout_reaches_server}.render.test.jsxweb-mobile/js/__tests__/score_write_same_tick.test.jsxattemptScoreWritereachesrecordScorein the same tick, andeditMatchScoreand every host'sonSubmitreach the write before their firstawaitscripts/screenshots/lib/editor.mjsTAP_DWELL_MS,sameSideTapPacer;finishMatchwaits out the bounce, counted from the arming tap, before its confirming tapscripts/screenshots/recipes/{editors,knockout,scored,public,videos}.mjsfinishMatchdocs/user-guide/court-operators/recording-decisions.mddocs/architecture/network-architecture.mddocs/user-guide/court-operators/scoring-a-match.mddocs/user-guide/organisers/naginata.mdCLAUDE.mdtap_guard.jsx,struckIppons/clearFusensho/updateSubScore/applyBoutScoreEdit,notePendingEditand thedurableflag; the rulings of 2026-09-27 (every way out saves, the tap-time stamp, Prev/Next ask as Close does, the overtime+stays unguarded); the shared outbox rules; why the tap guard keepsDate.now();enchoOnnow in result_slot.jsxScreenshots
Court console, 1180x820 with touch emulation. "Before" is a build of main (b78acf1), "after" this branch, driven with the same CDP touch taps.
bc-dtip: two taps ~70ms apart on Aka's M. Before: M M, the bout reads as won 2-0. After: one M.
bc-dtfn: two taps on Finish + Start Next. After: the button is only armed ("Tap again to finish →") and the match is still running. On main the same double tap completed the match and started the next one; that result is in the test plan, since the finished match leaves the screen.
bc-dtfn: the engi editor after a bounced double tap on Finish + Start Next. It is armed, not saved; three quick + taps gave three flags.
bc-fsnp: Shiro kote on bout 1, then Fusensho for Aka. Before: Shiro's K is erased. After: Aka gets its two circles and Shiro keeps the K.
bc-fsnp: with Shiro at two points, Aka's Fusensho is refused. It is greyed out; a tap on it shows the reason under Aka's side and saves nothing.
bc-fsnp: Shiro kote, Fusensho for Aka, then a tap on Shiro's K to remove it. The fusensho stays: Aka keeps its circles and the button stays pressed. The server holds
ipponsA [○, ○], ipponsB [],decision: fusensho.bc-sync (operator ruling 2026-09-27): Next with a Mark draw not yet saved asks first, with the prompt Close already shows. Scores page, a running individual match.
Not pictured, because nothing changes on screen:
Test plan
make go/testpasses (lint + security scan + tests), run on the exact tree of each commit before it was made/admin/shiaijo/:court, with real CDP touch taps. A build of main was the control for the double taps, the fusensho and the reload.ipponsA [○, ○], ipponsB [K](main: Shiro's K erased); after a reload the undo leavesipponsB [K]and no circlesipponsA [○, ○], ipponsB []docs/(make docs/buildpasses)SINCE=main: all 31 screenshots unchanged, the videos complete; none changed. After the engi Save became two taps,swiss-standings-engirecaptured unchanged through it. Recaptured again after review round 3 (make docs/media SINCE=main): 31 unchanged, 0 changed, the 3 videos complete. The page errors it reports are the lineup 404s on the team and kachinuki surfaces, which main logs too (bc-k404)Closes bc-dtfn
Closes bc-dtip
Closes bc-fsnp
Closes bc-emsl
Closes bc-sync
🤖 Generated with Claude Code