Repository navigation
fix(bc-kfup,bc-htcr,bc-sbq,bc-wnam,bc-kclr,bc-strt,bc-kenu,bc-plcl): P1 operator fixes, a queued match keeps its score, and the 2026-09-26 rulings - #447
Merged
Conversation
…rection and requeue work on a court bc-kfup: a fusenpai against a competitor another match already barred is their default loss, not a second withdrawal. alreadyBarredRefusal is the one rule both T105 halves ask (the pre-write guard and the K2 check-and-set): a kiken is still refused with already_ineligible, a fusenpai records the loss and leaves the existing competitor status untouched, so it still names the match where they withdrew and an injury kiken stays reinstateable. The Remaining matches panel also lists a match already running. bc-kpnl: the court console pins the kiken-decided match while the Remaining matches panel is open. The editor reports the kiken (onWithdrawal); the panel's close (onClose), Back to court, or starting or correcting another match releases it. A kiken on the court's last bout keeps the editor open. bc-htcr: a hantei tap on a completed match is a correction, so it asks for the reason first and carries it; the server no longer refuses it. bc-sbq: Send back to queue is not offered once a team bout has a result (ending the match keeps them). Its confirm names what it discards from the editor's own board as well as the court feed, which lags; pickMatch's silent defer reads the same. bc-wnam (fixed by #444): no-undef is now on for web-mobile, so an undefined name fails js/lint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pluralize scoring-a-match.md: what Send back to queue's confirm names, that a team match is not offered it once a bout has a result, and that a hantei given to the wrong competitor is corrected with the same reason prompt. CLAUDE.md: the alreadyBarredRefusal rule (bc-kfup), so a fusenpai refusal is not re-added. The requeue confirm counts through ui.jsx's pluralize, and two guards for states that cannot occur are dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; the kiken pin lets go A fusenpai chained onto an earlier bar now returns the loser's existing status as the status in force, so correcting a wrong kiken on that match to the chained fusenpai restores the competitor the kiken wrongly barred (a nil loser read as "could not resolve" and restored nobody). Reopening a chained fusenpai restores nobody, so reopenTargetStatus sends any default win whose barred side is barred by another match to the queue, not just a fusensho; an ordinary kiken or fusenpai still reopens running. The editor tells the chained case from the competitor's status record and says so (Clear default win, back to the queue), and the original withdrawal's list of later default wins includes chained fusenpai. The console's kiken pin ends once its match leaves completed (a reopen), as a correction does, after the feed has first shown it completed; a requeue clears it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling 2026-09-25: in a kachinuki encounter only the very last bout, taisho against taisho, may go to encho. A tie between any other pairing is settled with Record bout. The taisho is the last slot the lineup in force places (domain.TeamLineup.Taisho). domain.KachinukiTaishoPairing is the rule and kachinukiTaishoPairing (lineup_resolver.jsx) its JS twin, both pinned by internal/domain/testdata/kachinuki_taisho.json. The editor withholds Encho otherwise; the server refuses a NEW encho on a provably non-taisho pairing on both score doors (engine.KachinukiEnchoRefusal). Neither refuses when a side has no lineup (the taisho is unknown and a tied knockout bout already has End match held back), and an encho already stored is never re-judged. Docs: team-tournaments.md states the rule. The knockout tie-encho screenshot and the kachinuki demo video are recaptured on a taisho pairing (the demo's knockout uses 3-person teams so the reopened encounter still fits the frame); the capture harness's lineup helper takes a team size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Taisho rule: refuse only a pairing the lineup PROVES is not taisho against taisho (a fighter placed before a team-mate). A fighter the lineup does not place is unknown and never refused: the kachinuki sheet writes row 1 alone into a match lineup, and reading its senpo as the taisho refused Encho on the real taisho tie, leaving a knockout no way to finish. The shared table gains the partial-lineup cases. - The Encho tap re-reads both lineups before applying, so a lineup saved since the sheet opened cannot make it offer what the server refuses. - KachinukiEnchoRefusal finds a new encho before loading anything else. - The kiken pin forgets its match once released, so a second kiken on the same match holds the Remaining matches panel again. - Remaining matches never lists the match the kiken was recorded on. - Clearing the match that recorded a bar moves it to a withdrawal still on record (a chained fusenpai) instead of letting it lapse. - Clear on a withdrawal whose competitor is barred by another match says the match goes back to the queue (kiken included), and waits for the status that decides that before it can be tapped. - Send back to queue counts fought bouts from the sheet when it has reported (a kachinuki mark taken back never leaves the feed), and the engi editor reports its flags. - End match's tooltip stops suggesting encho where it is withheld; the requeue and Finish lists use admin_helpers' joinList. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status fetch that decides Clear's copy is best-effort with no timeout, so a request hanging on venue wifi kept the one-tap correction disabled. The hold is now capped (STATUS_HOLD_MS); after it the tap is available with the copy an unknown status gives. standingWithdrawalOf's bracket branch gets its own test, and its doc names the one row shape it cannot attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The team editor left every unplayed kachinuki row out of its write, and the server merge keeps any stored row a payload omits, so a bout cleared back to 0-0 kept its autosaved point: it came back on reload, on every other surface, and under End match. - The patch now always carries the bout being fought and the bout being corrected in place (kachinukiRowSent), played or not, plus any other row whose stored copy still has a result. It cannot ask the court feed, which lags the editor: in the browser the point was stored while the board's copy of the server row still read 0-0, so a clear based on it was never sent. Untouched trailing positions still never reach the wire. - A bout row the operator edited in the last moment (the autosave window plus a feed round trip) keeps its local value against an incoming snapshot: taking the point back returns the row to the value the server showed before the strike, which the per-row adopt read as untouched, so the lagging snapshot carrying the point was adopted over the clear. - A fighter picked on the current bout is now carried too, so closing the editor flushes it instead of prompting. Verified in the browser: struck, stored, taken back, reloaded; the stored row is cleared (red on the build without the fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
updateSub is only ever called with an index from the rendered board (or one already checked against -1), so subs[idx] always exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t after Start bc-kenu: a kachinuki bout in encho gets an "Undo encho" beside Encho. Each tap takes back one overtime period while nothing has been scored in overtime; taking back the last one returns the bout to its tie. Bout mode hides the per-bout overtime control, so a mis-tapped or double-tapped Encho had no way back. bc-strt: the editors autosave only a match they see as running, and the court list refetches a moment after each save, so a point struck right after Start match stayed on that board alone (it even read "Synced") until the next edit. ScoreEditorModal now treats the scheduled snapshot a start was made from as running: its own Start match (the team editor reports its start through onStartLanded) or the court console's Up next card (`started`). The override ends with that snapshot, so a later send back to the queue, which stamps the match, is never written as running. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator rulings 2026-09-26: sending a match back to the queue never clears its score (the operator removes a wrong mark themselves), matches can always be sent back, and while a match waits in the queue every screen shows it as not started. - RevertMatchToQueue / requeueBracketMatch keep points, penalties, overtime, bouts, flags and rep-bout fighters; only the verdict and audit notes go. - Starting it again keeps the score: startPatch sends the request-only startOnly flag and engine.keepQueuedScore copies the stored score onto that write. The editors' own Start stays unflagged, so an operator who cleared every mark on the board still clears them. - matchShowsScore (bracket.jsx) is the one display gate: IndividualScore and TeamScoreboard apply it to themselves, and the TV headline, lobby, match card chip, schedule rows and Scores tab ask it. The editors do not. - A queued knockout match's kept points no longer make it "fought", so reopening or correcting the match feeding it re-seats it with its points. - Court console: Send back to queue is always offered and its confirm says the score is kept; picking another bout sends a scored one back instead of refusing. The discard counting (requeueLoss, the editors' board reports) is removed. The court-busy remedy keeps the blocker's score. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From a /simplify pass over PR #447's production code. No behaviour change. - matchShowsScore moves to a zero-import leaf (match_shows_score.jsx) that every display ES-imports; seven window reads with opposite defaults go. - A start drops its empty scoreline in toBackendMatchResult, so the display-tab broadcast no longer strips it separately. - keepQueuedScore deep-copies through EnchoMetadata.Clone and the now exported state.CloneSubResults (prior is also the rollback snapshot). - withdrawalStatus is the one constructor of a withdrawal bar, shared by recordIneligibilityFromDecision and standingWithdrawalOf; firstBracketResult replaces the intermediate candidates slice. - bracketMatchStartedOrScored is renamed bracketMatchStartedOrDecided: it no longer reads the score. - Team editor: one curBoutTaishoPairing for the render and the Encho tap, one kachinukiEnchoShown, the redundant tap guard and kachinukiRowCleared folded, Undo encho counts through subTotals. - Individual editor: the correction prompt and its pending action are one piece of state instead of a state flag plus a ref kept in step by hand. - Court console: pinnedMatch names correctingMatch || withdrawalMatch. - RecordedWithdrawal: barredElsewhere's redundant term, STATUS_HOLD_MS moved above the component's doc, stale comments fixed. - Harness: one strike helper in the video recipe; teamBtn in the editor one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A withdrawal cleared after a doctor's reinstatement no longer bars the competitor again: only a fusenpai chained onto the bar carries it on (standingWithdrawalOf), since a kiken beside a later bar was lifted. - A reopen whose bar moves onto a chained fusenpai lands in the queue, not running (reopenTargetStatusTx judges the bars the restore leaves, through restoredStatus, shared with the restore). The editor says so before the tap and waits for both answers that decide its copy. - A start the server refused (court busy, a withdrawn competitor) no longer reads as started: every host hands back what the write came back with, and the editors mark a start only for a write that reached the server. - The Encho tap keeps the loaded lineups when its re-read fails, and a second tap during the read adds no second period. - Encho is judged on who each team has already put up, so a fighter picked out of lineup order does not strand a tied knockout's last bout (KachinukiTaishoPairing and its JS twin, shared table extended). - startOnly with any status but running is a 400. - Comment and log corrections: the recent-edit window, pickMatch, and lineupInForce's log name; CLAUDE.md's matchShowsScore file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re leaves Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Pools tab closed its editor after every landed write: a start, each autosaved point, a kachinuki Record bout, and a write that only queued offline. It now closes only on a saved result that is not a running write, as the Scores tab does. The bracket panel no longer falls back to the result card on a correction that did not land. Every editor host is now pinned to hand back what a write came back with (the five mount sites in one table), replacing the console-only test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
writeKeepsEditorOpen (write_result.jsx) is the one answer to "does this write end the editor's job": a write that did not land keeps the entry with its not-saved banner, a running write with no winner (Start match, a per-point autosave, a kachinuki Record bout) leaves the bout live, anything else closes. The Pools tab, the Scores tab and the bracket panel all ask it; the Pools tab used to close on every write and the bracket panel on a correction that never landed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator rulings: - Overtime is the operator's call on any tied bout; the app records it. The taisho-only rule (bc-kten: editor hid Encho, server refused) is removed in full: KachinukiTaishoPairing, its JS twin and shared table, KachinukiEnchoRefusal and the handler calls, the tap-time lineup re-read. - Recording a withdrawal changes only that match: the post-kiken Remaining matches panel and the console's hold for it (bc-kpnl) are removed; a kiken now advances or closes like any other decision. - A finished later match a confirmed correction reopens keeps its points, bouts and flags; only the verdict goes (reopenBracketMatchKeepingTheFight, shared with the plain reopen). The dialogs and docs say so. - Engi flags are saved as entered: a running engi write is not judged on the flag total, the bracket write carries flags, and the engi editor autosaves while the match is running. Clean-up items from the simplify pass: - The editor's clear control reads the withdrawn side's status from a request-time stamp (withdrawnStatus, annotateEligibility) instead of fetching every competitor status. - The kachinuki per-row adopt guard is keyed on the snapshot's stamp against the edit time (server_clock.jsx, shared with api_client) instead of a 1.5s window, so another device's later change to the bout is adopted. - The court console shows a pushed running score at once and refetches its feed once per burst of events instead of once per event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Recording a withdrawal now advances the court like any other decision, so the advance could pick the withdrawn competitor's next match before this court's list shows it barred, and the server refused the start. The advance passes over that competitor's matches itself. Found in the browser; the test uses the /decision response's real shape (sides as bare names). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The screenshot recipes, the docs screenshot and the video's chapter list were changed to reach the two taisho, because only they could go to overtime. That rule was reversed on 2026-09-26, so they go back to main, and their comments no longer say it. The video is recaptured on main's recipe so it shows Undo encho after the Encho tap; its chapter times are main's. The docs screenshot recaptures to main's image apart from a 2px band along its bottom edge, so main's file is kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Scores tab starts the court's next match after a withdrawal or no-show (mp-nwds item 7), but it picked that match before the decision was recorded, so it tried the withdrawn competitor's own next match: the server refused, the error showed, and the editor stayed on the decided match. Found in the browser. The rule the court console got in 7af3170 now lives in ineligible_match.jsx (sideBarredByDecision, involvesCompetitor) and both screens ask it. When only the withdrawn competitor's matches are left, the Scores tab closes its editor, as it does after a decision with no next match. The docs and CLAUDE.md say the court moves on and passes over those matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removing the Remaining matches panel sent a withdrawal down the no-show path, which never closed its form: it relied on the editor moving on. When the Scores tab cannot move on (the court's next match refused to start, e.g. another competition's match is running there), the decided match stayed on screen with the withdrawal form open, offering Record again and hiding the recorded decision. A stored decision now closes its form whatever the host does next; one that did not land keeps it open with its pending notice, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent review of the work since 946cf3b found three regressions, each now fixed with a test that fails without it: - The withdrawnStatus stamp vanished once no scheduled match was left (a league, or any competition after its final), because the status read was gated on a scheduled match. The score editor then promised that clearing a chained no-show reopens the match running while the server queued it. The gate (anyMatchToAnnotate) now also asks for a finished withdrawal or default win, which is what the stamp needs. - A correction that reopened a finished team final kept its daihyosen row's winner, the team the correction took out, so starting the match again made the replacement team the winner of a bout it never fought. The reopen clears that row's winner and hantei mark and keeps its points (clearDaihyosenVerdict). - The court console's refetch replaced its feed wholesale, so a fetch that read before a write committed could put an older scoreline back over a newer push, and an editor that had caught up with the push adopted it. A refetch now keeps a row the court already shows from a newer write (keepNewerRows), the rule the push already follows. Stale comments naming the removed panel, the old gate and the old downstream reopen are corrected, and two tests are renamed after the functions they test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fetchCourtMatches always answers with an array (it maps the payload's competitions), so keepNewerRows only needs the held feed's first-fetch case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… stamp Two gaps the last review left open, each pinned by tests that fail without the fix and checked both ways in the browser: - The admin competition view (Pools tab, bracket panel) and the public competition page apply a push at once and then take the refetch whole, so a refetch that read before a write committed could put an older score back, and a Pools-tab editor (which now stays open while scoring) adopted it. The court console's rule moves to patch.jsx (keepNewerMatches, keepNewerDetail) and every refetch of those views asks it. Browser: a forced stale refetch left the open editor on M K; without the fix it went back to M. - A pushed withdrawal or default win carried no withdrawnStatus, so an editor reading the pushed row worded the clear wrongly until the refetch. The /decision, /score and bulk-score writes now stamp the result they push (stampWithdrawnStatus); CompetitionStore gains LoadCompetitorStatus for it. Browser: with every refetch held back, the pushed chained no-show offers Clear default win; without the fix, Clear withdrawal and reopen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
keepNewerDetail's held copy is only ever a fetched competition detail, or a push applied to one, so it always has config; the null check alone covers the first load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er score - keepNewerMatches kept a held match over ANY older fetched copy, so a draw discarded and drawn again, which reuses the match ids and carries no stamps, would have left the old draw's played matches on screen on a device that missed the discard (e.g. offline while it happened). Only a stamped fetched copy can be the stale one now; an unstamped one always replaces. - The tournament's competitions (the Scores tab's source) take no pushes, but two refreshes can be in flight at once (after each save, after each server event), and an older answer landing last put the score back for an open Scores-tab editor to adopt. The admin's updates and the app's load now merge through keepNewerTournament. Browser: a fresh refresh then a stale one left the editor on M K; without the fix it went back to M. - keepNewerCompetitions replaces the console's own copy of the list rule. - The two tracked spec inputs that described the removed panel and an automatic resolution of a withdrawn competitor's matches now state the 2026-09-26 ruling, as CLAUDE.md requires of a tracked spec a change contradicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…value Only a loaded tournament or an admin update reaches keepNewerTournament; the paths that clear the tournament set it to null directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A review of the keep-newer rule found three holes: - Stamps come from two clocks. A client write carries the device's estimate of server time (accepted up to 5s ahead), while a reopen, requeue or send-back is stamped by the server's clock, so a newer server stamp could read older than the write before it and the refetch lost. A match sent back a moment after a skewed Start stayed live on screen, and Refresh could not clear it. Every server-stamped change moves the status, so the rule now keeps a held match only when BOTH copies are running (the whole harm: a live score going back under an open editor). Browser: with a device write stamped 2s ahead, a send-back now leaves the board; with the old rule it stayed live. - The TV display's load() merged too, so a result the display showed from an optimistic broadcast and the server then refused could outlive every reload. A display takes the fetch whole again, as its comments promise. - The public competition page merged on some refetches and not others. No score editor reads it (the self-run editor copies its match on open), so it takes refetches whole again. Also corrects deps.go's stale note that a bracket match carries no side ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d residual The comment above reopenBracketMatch still said the reopen leaves ModifiedAt at the completion stamp; it stamps the server's clock (the revert fence). CLAUDE.md now records the one residual the last review of the keep-newer rule found and why it is accepted: a holder that never saw the match leave running can keep a device-stamped running copy over a reopen stamped within that device's clock lead, and both show the same live match and points until the next write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit said the masked copy and the reopened row show the same points. They can differ: a withdrawal replaces the winner's letters with its default-win marks, so a held copy from before it can still show them until the next write to that match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling 2026-09-26: one operator per court, scoring from the court view, and an operator view never switches competition or court by itself. The tournament-wide Scores page lists every competition, so Finish + Start Next and the start after a withdrawal or no-show took the next match on the same court from any competition, switching the operator into another one. They now take the next match of the open match's competition, as the court console always did. Prev/Next, which the operator taps, still move along the whole court. Browser: with two competitions sharing Shiaijo B, Finish + Start Next on the first one's match started its own next match; without the fix it started the other competition's and switched the editor into it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operator accepted it on 2026-09-26, but it was written only in the PR description, where a later review would not see it. CLAUDE.md now says so beside the rule it is an exception to, so it is not reported again as a new defect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator decision 2026-09-26: the app's own text should not bring in people outside the app. Three places still did, all as on main: - the refusal when a competitor who withdrew injured is started: "Reinstate Aoki if the doctor allows" -> "if they can fight again"; - the two injury-withdrawal help texts: "if a doctor and shinpan permit it" -> "if they are fit to fight again" (glossary.go, glossary_data.js regenerated); - the withdrawal reason hint: "e.g. injury, no-show, doctor's stop" -> "e.g. injury, no-show". The organisers' rules reference (running_a_kendo_tournament.md) states FIK Article 30 itself and keeps its wording. Browser: all three read the new text, the message through a refused start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
20 tasks done
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
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 the five open P1 operator bugs from the mp-yqxn.2 review. After a withdrawal, the court can now close the competitor's remaining matches, and a hantei verdict can be corrected. It also carries operator rulings from this session: a match sent back to the queue keeps its score and shows as not started until it starts again (bc-sbq), and a point struck right after Start is saved at once (bc-strt). A mistaken Encho can now be undone in one tap (bc-kenu).
The rulings of 2026-09-26 reversed two things this PR had built. Whether a tied pair fights on is the operator's call, on any bout: the app records what happens (the taisho-only encho rule, bc-kten, is removed in full). Recording a withdrawal changes only that match (the Remaining matches panel, bc-kpnl, is removed).
already_ineligible, from the editor and on both server paths. Nothing else could close the bout, because the server refuses to start it. A fusenpai against a competitor another match already barred now records their default loss. It adds no new eligibility record, so the existing one still names the match where they withdrew, and an injury kiken stays reinstateable. A second kiken from another match is still refused (CHK047). Correcting a mistaken kiken on such a match to that fusenpai restores the competitor the kiken wrongly barred. Clearing a chained fusenpai sends the match back to the queue while the competitor is still withdrawn, as clearing a fusensho does, and the editor says so (Clear default win). Clearing the match where they withdrew does not un-bar a competitor who also has a fusenpai on record elsewhere: the bar moves to that match, the cleared match goes back to the queue rather than in progress, and the editor says so before the tap. An injury withdrawal the operator has already reinstated (the Reinstate button) never takes the bar back.wname). The panel it lived in is now removed (bc-kpnl). As the bead suggested,no-undefis now on for web-mobile, so an undefined name failsmake js/lint. I checked that it flags a reintroducedwname./simplifypass skipped (you asked for them here). Three are done. The score editor reads the withdrawn side's status from a stamp on the match (withdrawnStatus) instead of fetching every competitor's. The kachinuki adopt guard (bc-kclr) compares the snapshot's save time with the edit time instead of using a 1.5s window, so a later change from another device is adopted. The court console shows a pushed running score at once and refetches its list once per burst of pushes instead of once per push. The other three went with code the rulings removed: the encho refusal's transaction, the kiken pin, and the lineup re-read's position keys.Why
recordDecisionTx's T105 guard and the K2 check-and-set inrecordIneligibilityFromDecisionboth refused any withdrawal decision whose loser was already barred, fusenpai included. Both now ask one rule,engine.alreadyBarredRefusal: a kiken is refused, a fusenpai chains onto the existing bar. The chained fusenpai returns the loser's existing status as the status in force. OtherwiserestoreIfWithdrawalRemovedreads a nil loser as "could not resolve" and restores nobody.reopenTargetStatussends any default win whose barred side is barred by another match to the queue, where it used to do so for a fusensho only. A plain kiken or fusenpai still reopens running, becauseBarredSidesignores the match's own record, unless the restore moves its bar onto a chained fusenpai:reopenTargetStatusTxjudges the bars the restore will leave (restoredStatus, shared with the restore).standingWithdrawalOflooks for a fusenpai only, because a second kiken is refused while a bar stands, so a kiken beside a later bar was lifted by a reinstatement before that bar existed.RemainingMatchesPanel,onWithdrawaland the console'swithdrawalKeypin are deleted.makeSubmitDecisionnow takes one path for every decision:onAfterDecisionwhen it is not a correction, elseonClose. The court console'snextActiveAfterand the Scores tab'snextActiveFromskip the competitor the decision barred, through one rule inineligible_match.jsx(sideBarredByDecision,involvesCompetitor), because the list shows their matches barred only after it refreshes. The side comes from the screen's own copy of the match, since the/decisionresponse names sides as bare strings. With only their matches left, the Scores tab closes its editor, as it does after a decision with no next match.makeSubmitDecisioncloses the decision form once the write is stored, before the host decides whether to move on; a write that did not land keeps it open with its pending notice.submitHanteiskipped theisComplete && !correctionReasongate that Finish uses. The gate was added aftersubmitHanteiwas written. The prompt's confirm was hardcoded tobuildPatch("completed"), so the editor now keeps the pending write in a ref.domain.KachinukiTaishoPairing, its JS twin, their shared table,engine.KachinukiEnchoRefusalwith its two handler calls, and the Encho tap's lineup re-read are deleted;team_lineup.go,lineup_resolver.jsxand the handler's kachinuki test are back to main.kachinukiEnchoOfferedalone decides the button. The screenshot recipes, the docs screenshot and the video's chapter list are back to main too; the video is recaptured so it shows Undo encho.mergeKachinukiSubResultskeeps any stored row a payload omits. The fix can't ask what the server holds, because the court list lags the editor: in the browser the point was stored while the editor's copy still read 0-0. So the write now always carries the bout being fought and the bout being corrected (kachinukiRowSent); untouched trailing positions still stay off it. The per-row adopt also read a struck-then-cleared row as untouched, because it equalled the old snapshot again, and adopted the lagging snapshot's point over the clear. A row the operator edited keeps its local value until a snapshot saved after the edit arrives: the snapshot'smodifiedAtagainst the edit time, both in the server's clock frame (server_clock.jsx, whichapi_client.jsxnow uses too).RevertMatchToQueueandrequeueBracketMatchnow keep what a running write can carry and clear only the verdict and audit notes. A start could not simply "not send" a score:toBackendMatchResultalways emits ippon arrays, and hansoku/flags are integers, so a start's empty payload is the same bytes as an operator clearing every mark.startPatchtherefore sends a request-onlystartOnlyflag, andengine.keepQueuedScorecopies the stored score onto that write; the editors' own Start stays unflagged.matchShowsScore(match_shows_score.jsx) is the one display gate, applied insideIndividualScoreandTeamScoreboard.bracketMatchStartedOrDecidedis now status-based, so kept points on a queued knockout match no longer refuse a reopen upstream. The earlier discard counting (requeueLoss, the editors'onBoardChangereports) is removed. A finished later match:reopenBracketMatchKeepingTheFightclears the verdict only, for the plain reopen and the downstream one alike. Engi: a running engi write is no longer judged on its flag total, the bracket write carries flags, and the engi editor autosaves while running (useDebouncedRunningWrite, with the sync pill).ScoreEditorModaltreats a scheduled match as running while the feed still shows the snapshot a landed start was made from (its own Start, or the console'sstartedprop). The next snapshot, running or sent back to the queue (which stamps the match), ends it. A refused write throws insideeditMatchScoreand every host swallows it, so each host now hands back what the write came back with, and nothing marks a start the host returned nothing for.Scope notes:
test.fixmejourneys each acceptance criterion names live on test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442's branch, not on main, so they cannot be removed here; updating them is left to test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442's session (your decision). Re-run against this PR's head before the 2026-09-26 rulings, with test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442's harness copied in locally (not committed): 11 passed, 1 failed, 5 not run. The failure was J5's bout 4 inknockout-mixed-kachinuki.spec.mjs, which expects Encho on a tie between the third fighters. With bc-kten reversed, this branch offers it again; I have not re-run the journeys since. test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442's bc-kpnl tests assert the panel, which is now removed, and its bc-sbq test asserts the old "will be discarded" warning, while the dialog now says the score is kept. These, and the J5 re-check, are handed to test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442's session in a note on its bead (bc-e2eh).Files changed
internal/engine/eligibility.goalreadyBarredRefusal, the one rule both T105 halves ask;checkConcurrentIneligibilitytakes the decision; K2 writes no status for a chained fusenpai and returns the existing oneinternal/engine/scoring_tx.gostandingWithdrawalOfandrestoredStatus: clearing the match that recorded a bar moves it to a fusenpai still on record, never a kiken; both pre-write callers pass the decision;ForceOptions.StartOnlyandkeepQueuedScore(bc-sbq); a running engi write is not judged on its flag totalinternal/engine/scoring.goclearDaihyosenVerdict); the bracket write carries engi flagsinternal/engine/kachinuki.goreopenTargetStatusandreopenTargetStatusTx: a default win whose barred side is barred by another match reopens to the queue, judged on the bars the restore leaves;reopenBracketMatchKeepingTheFight;bracketMatchStartedOrDecidedis status-based;lineupInForcelogs under its own name; the header says encho is the operator's callinternal/engine/errors.gointernal/state/models.go,bracket.go,match.go,pools.goWithdrawnStatusAnnotationon both result types;CloneSubResultsexported forkeepQueuedScoreinternal/mobileapp/handlers_match.go,handlers_viewer.gostartOnlyon/score(a 400 with any status but running);annotateEligibility(wasannotateIneligibleSides) also stamps a completed default win with the withdrawn side's status, and its status read (anyMatchToAnnotate, wasanyScheduledMatchHasBothSides) runs for those too; revert and requeue-blocker docsspecs/openapi.yamlstartOnlyand its 400;withdrawnStatusinternal/engine/{eligibility,reopen_fusensho_status,scoring_tx_internal,start_keeps_queued_score,queued_downstream_reseat,scoring,engi_running_flags}_test.go,internal/mobileapp/{start_keeps_queued_score,kachinuki_cleared_bout,withdrawn_status_annotation,ineligible_sides_annotation}_test.go,internal/state/{pool_roundtrip,bracket}_test.gowithdrawnStatusstampinternal/engine/{downstream_correction_guard,downstream_guard_review,pool_requalify,reopen_bye_unwind,reopen_withdrawal}_test.gointernal/mobileapp/handlers_match_kachinuki_test.goweb-mobile/js/admin_scoring_shared.jsxRemainingMatchesPanelremoved;makeSubmitDecisiontakes one path for every decision and closes the decision form once it is stored;RecordedWithdrawalreads thewithdrawnStatusstamp, recognises a chained fusenpai (Clear default win, back to the queue) and says when a clear goes to the queue; Queue it and reopen keeps the blocker's scoreweb-mobile/js/admin_scoring_individual.jsxsubmitHanteiasks for the reason on a completed match; passes the form's setter tomakeSubmitDecision; a landed start counts as running, a refused one does not (bc-strt)web-mobile/js/admin_scoring_team.jsxmakeSubmitDecision; reports its start (bc-strt);kachinukiRowSentand the adopt guard keyed on the save time (bc-kclr); the Finish list usesjoinListweb-mobile/js/admin_scoring_engi.jsxweb-mobile/js/admin_shiaijo.jsxpickMatchsends a scored bout back;startedfor a match its Up next card started (bc-strt); the editor host hands back what a write came back with; a pushed running score is shown at once, with one refetch per burst, and a refetch keeps a row the court shows from a newer write (keepNewerCompetitions); the advance after a decision passes over the withdrawn competitor's matchesweb-mobile/js/write_result.jsx,admin_competition_bracket.jsx,admin_pools.jsx,admin_schedule_score_editor.jsxwriteKeepsEditorOpen, the one rule for when a host closes its editor (bc-plcl); the downstream dialogs say the points are keptweb-mobile/js/admin_schedule_score_editor.jsx,api_serializers.jsxstartPatchsendsstartOnly; the serializer carries it and leaves a start's empty scoreline off the wire; the Scores tab's advance after a decision passes over the withdrawn competitor's matches, and closes when only theirs are leftweb-mobile/js/patch.jsx,admin.jsx,app.jsxkeepNewerMatches,keepNewerDetail,keepNewerCompetitions,keepNewerTournament: a refetch keeps a RUNNING match the page holds over an older stamped RUNNING copy, and any other copy (a status change, a rebuilt draw) replaces it; asked by the court console, the admin competition view and the tournament's competitions (the Scores tab's source, including the app'sload()outside display mode). The public competition page and the display take refetches wholespecs/003-tournament-gap-closure/inputs/gaps_ARCHITECTURE.md,gaps_spec_brief.mdinternal/mobileapp/deps.go,deps_test.go,handlers_decision.goCompetitionStoregainsLoadCompetitorStatus; the/decision,/scoreand bulk-score writes stamp the result they push (stampWithdrawnStatus)web-mobile/js/server_clock.jsx(new leaf),api_client.jsxweb-mobile/js/match_shows_score.jsx(new leaf),bracket.jsx,match_scoreboard.jsx,display_scoreboard.jsx,display_lobby.jsx,viewer_match.jsx,viewer_schedule.jsx,admin_schedule_page.jsxmatchShowsScore, imported by every display that gates on itweb-mobile/js/admin_helpers.jsx,ineligible_match.jsxjoinListexported;sideBarredByDecisionandinvolvesCompetitor, the one rule both screens ask to skip a competitor a decision just barred; a comment that named the removed panelweb-mobile/.oxlintrc.json,__tests__/{api_client_export_pdfs,bout_side_squad_member}.test.jsx,render/admin_schedule_smoke.render.test.jsxno-undef: error; the three tests importbeforeEach/afterEachfrom vitest (its only hits)web-mobile/js/__tests__/render/{admin_shiaijo,kachinuki_reopen,kachinuki_cleared_bout_reaches_server,individual_editor_hantei_correction,recorded_withdrawal_reopen,admin_scoring_engi,score_editor_mount_sites,start_then_strike_saves,queued_match_reads_not_started,score_editor_dismiss_prompt}.render.test.jsxweb-mobile/js/__tests__/{admin_scoring_modal,write_result_editor_open,write_result_downstream_knockout,match_shows_score,ineligible_match}.test.jsx, fixtures in{api,match_scoreboard,display_white_board,kachinuki_scoreboard}.test.jsxandrender/msb_{centre_sep,name_cell}.render.test.jsxstartOnlyon the wire and the status the display gate readsweb-mobile/js/__tests__/render/remaining_matches_panel_award.render.test.jsxweb-mobile/js/__tests__/render/decision_form_closes.render.test.jsxdocs/user-guide/court-operators/recording-decisions.mddocs/user-guide/court-operators/scoring-a-match.mddocs/user-guide/organisers/team-tournaments.md,docs/videos/kachinuki-demo.webmCLAUDE.mdalreadyBarredRefusal, so it is not re-tightened; the encho reversal, so the gate is not re-added;annotateEligibilityandwithdrawnStatus; a withdrawal changes only its match, and the advance after it skips the withdrawn competitor's matches; send back keeps the score, a finished later match keeps its points, engi flags as entered;writeKeepsEditorOpenScreenshots
All captured in Chromium at the operator iPad profile (1180x820, coarse pointer) against this branch's binary.
The 2026-09-26 rulings, on the final build:
A kiken on the court console: no panel, and the court moved on to Pool A Match 3 (Endo v Chiba), passing over Aoki's next match, which waits in the queue with Record default win for Endo:
A knockout kachinuki tie between the two first fighters: Encho is offered, and the server stored it:
A correction that reaches a finished final: the dialog says its winner is cleared and its points kept (after it, the final read Hara v Ito, its D in place, no winner):
Engi flags 1-2 on a running match, saved as entered (the pill reads Synced); sent back to the queue and started again, it carried on from them:
A no-show on a match of an already withdrawn competitor, opened to correct: the clear's copy now comes from the stamp on the match:
Points saved from another device show on the court console as they are pushed (D for Shiro, K M for Aka):
The Scores tab after a withdrawal. Before 525c35c, it tried the withdrawn competitor's own next match, the server refused, and the editor stayed on the decided match with the refusal showing:
After, it moves on and starts Pool A Match 3 (Endo v Chiba):
The Scores tab after a withdrawal when it cannot move on (another competition's match is running on that court). Before 90da239, the form stayed open with Record, and the recorded decision was hidden:
After, the form is closed and the editor shows what was recorded:
Earlier in this PR:
bc-kfup. A fusenpai on the withdrawn competitor's next bout, from the Scores tab, is recorded (Match 2 reads ○○ vs Fus.):
Correcting that chained fusenpai offers Clear default win and says what it does:
After clearing, the match is back in the queue with its default-win notice:
bc-htcr. Tapping SHIRO wins on a completed hantei asks for the reason first:
After confirming, the verdict moves to Shiro and the bracket follows (the next match now names Judge Bea):
bc-sbq. A match with Aka's M and a Shiro foul, sent back to the queue. The confirm says the score is kept:
While it waits, the TV board and the public match card show it as not started (both drew the M and the foul before this change):
Started again from the Up next card, the board carries on with the M and the foul in place:
bc-kenu. Encho tapped twice by mistake: the header reads (E) OVERTIME x2 and Undo encho is offered beside Encho:
Two taps of Undo encho later the bout is back to its tie, and only Encho is offered:
bc-strt. On the build without the fix, a point struck right after Start stays on the board (which even reads Synced) while the server holds nothing; with the fix it is stored within the autosave window:
bc-kclr. A point struck on a running kachinuki bout, stored, then tapped away, and the page reloaded. On the build without the fix the M comes back:
With the fix the bout is empty, reads Nothing recorded yet, and Send back to queue is offered again:
Second review round. Clearing a kiken when the same competitor also has a no-show recorded later: the editor says the match goes back to the queue, and the button no longer promises a reopen:
Start match tapped while another match runs on the same court: the server refuses, and the editor still offers Start match instead of turning into a live board:
bc-plcl. On the Pools tab, Start match, two points, then Finish with the device offline: the editor stays open and says the result is not saved yet (it used to close after Start):
The bracket panel, a correction saved offline: the editor stays with its notice instead of falling back to the old result:
Test plan
make go/testpasses (lint + security scan + tests), run on the pushed head (9d324c4); the unit suite (4975) and the render suite (846) pass too. The counts are lower than before because the encho rule's golden table and the panel's tests went with the code.startOnlywire flag, every display host showing a queued match as not started, the console's always-offered Send back to queue and switch, bc-strt (editor and host start, refused start, the snapshot ending) and bc-kenu (Undo encho). From the second review round: a withdrawal cleared after a reinstatement, the origin's clear landing in the queue, a refused start through each editor and the console host, thestartOnly400, and the clear's hold for the later list. For the 2026-09-26 rulings and clean-up: Encho offered on a tie between any pair; no panel after a kiken, and the court's advance passing over the withdrawn competitor's matches; a finished later knockout match keeping its points (engine, dialog copy); engi flags stored on a running write (engine, 4 tests) and autosaved by the editor (4 render tests); thewithdrawnStatusstamp; the adopt guard adopting a later change from another device; the console showing a push at once and refetching once per burst.recording-decisions.md(a withdrawal changes only its match, fusenpai on a barred competitor's match, clearing it, where the cleared match goes),scoring-a-match.md(Send back to queue keeps the score, a finished later match keeps its points, engi flags, hantei correction),team-tournaments.md(Encho on any tied bout is your call, Undo encho);make docs/buildanddocs/check_links.pypasskachinuki-knockout-tie-encho.pngare back to main. Recaptured on this branch, the screenshot differs from main's only in a 2px band along its bottom edge, so main's file is kept.kachinuki-demo.webmis recaptured on main's recipe. I read its frames: Undo encho shows after the Encho tap, on a tie between Kaze's first fighter and Nami's third. Its chapter marks (2.0, 8.2, 14.7, 21.1s) match main's list. Before the reversal,make docs/screenshots SINCE=mainreported every screenshot unchanged; the other two videos pass through no changed flow.docs/mediarun logs404on six team/kachinuki captures. Those are the lineup routes, which answer 404 by design until a lineup is saved (handlers_lineup.go), not a request this PR adds.Code review
/code-review highover the whole branch reported 12 issues; I checked each against the code. All 12 are fixed in f208891, with two follow-ups in 77f8c10: the status hold in 8 is capped, so a hung request cannot disable the clear, and the bracket side of 6 has its own test. Each behavioural fix has a test that goes red without it. Items 10, 11 and 12 are copy, read-order and reuse changes covered by the existing suites:joinList.One older issue surfaced here: a mark taken back on a kachinuki bout never cleared on the server. It is now filed as bc-kclr and fixed in 66048b5 (Summary above). The browser check strikes a point while the bout is running, confirms it is stored, takes it back and reloads. It fails on the build without the fix and passes with it.
A
/simplifypass (reuse, simplification, efficiency, depth) over the production diff followed, in 5fb0f71, with no behaviour change. It movedmatchShowsScoreto a leaf every display imports (the window reads had opposite defaults). A start now leaves its empty scoreline off the wire.keepQueuedScoredeep-copies through the existing clone helpers. It added onewithdrawalStatusconstructor and renamedbracketMatchStartedOrDecided. It also folded duplicated editor state (the Encho pairing check, the correction prompt's state plus ref) and replaced repeated expressions (pinnedMatch). The gate, the render suite and the scripted browser flow (11/11) were re-run after it. It skipped six items as outside this PR's scope; you asked for them here, and they are in 838f263. Three are done: the court console applying pushed running scores, the adopt guard keyed on the save time, and the server-side stamp (withdrawnStatus). The other three went with code the rulings removed: the encho refusal's transaction, the kiken pin, and the lineup re-read's position keys.A second
/code-review, scoped to the files this PR changes, reported 11 issues. I checked each against the code; the fixes are in f254ea7 (one comment in c59e095), and each fix has a test that fails without it:Items 4 to 7 went with the encho rule: nothing refuses an encho now, and the tap re-reads nothing.
8. The recent-edit window's comment was wrong. A change another device makes to the same bout inside the window is not adopted later either, which is the rule for any bout two people edit at once. Comment corrected.
9.
startOnlywas honoured on a completing write. It is a 400 with any status but running.10. A stale pickMatch comment still described the removed block.
11.
lineupInForcelogged under its first caller's name. It logs under its own.Checking the gaps left by that round turned up bc-plcl: the Pools tab closed its editor after every save. It is fixed here in 7da2f36 and 946cf3b, following the bead's plan: one rule,
writeKeepsEditorOpen, that every host asks. The first of those also pins every editor host (five mount sites, one table) to hand back a write's result.A separate observation from that check, a point struck before the court list shows the match as running not being autosaved, is now bc-strt, fixed in 81c0166.
Then the 2026-09-26 decisions (838f263). The encho rule and the panel are removed, a finished later knockout match keeps its points, and engi flags are saved as entered; the clean-up items are done. The browser check of those rulings found the court starting a withdrawn competitor's next match after a kiken, which the server refused. That is fixed in 7af3170, with a render test that fails without it, written against the
/decisionresponse's real shape: a first version, written against a match-shaped fixture, passed while the browser still failed. 0bf012e puts the taisho-only screenshot recipes back as they are on main; you chose to keep main's screenshot.Checking what was left open turned up the same fault on the Scores tab, which starts the next match after a withdrawal or no-show too (mp-nwds item 7, so no decision was needed). 525c35c moves the rule into
ineligible_match.jsxfor both screens. Its tests fail without it: the two Scores tab cases (skip, and close when only the withdrawn competitor's matches are left) and the console case.Checking your "no more questions?" turned up one more gap from the panel's removal: the decision form stayed open whenever the Scores tab could not move on. 90da239 closes it once the decision is stored. Its render test fails when the close is taken out, and when either editor stops passing the form's setter.
A third review, by two independent reviewers (server and web app) over everything since 946cf3b, found three regressions from this round's own changes. Each is fixed in 2225748 with a test that fails without it (bcee456 then dropped a guard in the console fix for a case that cannot happen):
withdrawnStatusstamp vanished once no scheduled match was left (a league, or any competition after its final). The status read was gated on a scheduled match existing. The editor then said clearing a chained no-show reopens the match running, while the server queued it. The gate now also counts a finished withdrawal or default win. A unit test and a viewer test on a finished league pin it; the viewer test was first written on a mixed competition, where the knockout's scheduled matches hid the bug, so it passed without the fix and was rebuilt.Also from that round: stale comments naming the removed panel, the old status gate and the old downstream reopen are corrected (CLAUDE.md included), and two tests are renamed after the functions they test. Reviewed and left as they are: the test messages saying a reopened match waits "to be fought again" (it is scheduled and must be finished again). The tracked spec inputs under
specs/003-tournament-gap-closure/inputs/that still described the panel were first left too, then corrected in ed8dc74 (below).Two gaps that review left open are closed in a907a62, each with tests that fail without the fix and a browser check run both ways:
patch.jsxand every refetch of those views asks it. In the browser, with the refetch forced to answer with a copy captured before the other device's last point, the open Pools-tab editor keeps M K; without the fix it goes back to M.withdrawnStatus, so an editor reading the pushed row worded the clear wrongly until the refetch landed. The/decision,/scoreand bulk-score writes now stamp the result they push. In the browser, with every refetch held back, opening a chained no-show recorded on another device offers Clear default win; without the fix it offered Clear withdrawal and reopen, while the server would queue the match.The last pass over this session's findings (ed8dc74) closed three more, each with tests that fail without the fix:
keepNewerTournament. In the browser, a fresh refresh followed by a stale one left the editor on M K; without the fix it went back to M. A first version of this check served only the stale refresh, so the page never saw M K and it measured nothing; it was rebuilt.A further review of those fixes (0516c32) found three holes in the keep-newer rule, each closed with tests that fail without the fix:
A last review of that change found no practical defect. Its one residual is accepted (your decision, 2026-09-26) and recorded in CLAUDE.md: with two devices, one clock a few seconds fast, and a withdrawal recorded by mistake and undone at once, the other device's Scores tab can keep showing the winner's earlier points until the next point is saved on that match. A stale comment it found (the reopen's timestamp) is fixed in 3d85df0.
Your decisions of 2026-09-26, answered at the end of this session (42c9009):
Closes bc-kfup, bc-htcr, bc-sbq, bc-wnam, bc-kclr, bc-strt, bc-kenu, bc-plcl. Supersedes bc-kten and bc-kpnl (their rulings were reversed on 2026-09-26; both beads close with that reason at merge).
🤖 Generated with Claude Code