Repository navigation
fix(bc-tmfn): operator fixes: team bouts, withdrawals, knockout rounds and the Bracket page - #444
Merged
Merged
Conversation
Operator ruling 2026-09-24: every bout of a team match is fought.
- bc-tmfn: Finish (client and PUT /score, bulk-score) refuses a
non-kachinuki team match while a numbered bout has no result, naming
the bouts. A position left empty by both teams in the lineup in force
is skipped; a team with no saved lineup counts as fielding every
position. Untouched bouts are no longer written as hikiwake.
- bc-dscn: closing a running match flushes the autosave instead of
asking "Discard unsaved scoring changes?"; the prompt stays where
closing really discards; Esc does nothing on the court console.
- bc-kbhn: the shortcut hint hides under a coarse pointer and lists only
keys that work on the host; arrow keys need a neighbour match (fixes
a scoreKeyOf(null) throw on the Scores tab).
- bc-kbrw: a double tap on a fought kachinuki bout opens that bout only.
- bc-cpdc: "Copy from previous match" sits at the top of each side.
Follow-on work from operator rulings ("save correction saves what the
operator enters"; "everything can be fixed, the operator is told the
consequences"):
- Save correction keeps a recorded kiken/fusenpai and its eligibility
consequences; the withdrawer's letters stay editable.
- "Clear withdrawal and reopen" (team, individual, kachinuki) returns
the match to running with what was fought kept and restores
eligibility; the eligibility record now follows the ruling.
- Mixed competitions: a pool correction, reopen or rank override
(chusen) that changes who qualified after the knockout started warns
with the affected knockout matches and, on "Apply and reopen",
reopens them and seats the new qualifier; a running affected match is
refused with the fix; chusen is accepted in knockout status.
- Reopens stamp ModifiedAt; queued writes never replay a force.
Closes bc-tmfn, bc-cpdc, bc-kbrw, bc-dscn, bc-kbhn
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling 2026-09-24, superseding the both-vacant exemption in the previous commit: every bout of a non-kachinuki team match has a result or a decision; a team always fields at least half+1 (an assumption, never checked); a bout neither team fields is recorded as a Tie. The finish gate no longer skips positions, so the lineup-vacancy lookup it used (TeamBoutsWithNoFighter, lineupRoundOfTeamMatch, TeamLineup.VacantAt) is deleted. Code review of PR #444: - Standings count an individual draw (IT) only for a bout with a result. - A confirmed reopen also clears the reopened match's winner out of an untouched next round, as the pool path already did. - Moving a withdrawal to the other side through /score or bulk-score restores the first withdrawer, as /decision does. - Quick-score draws are written as hikiwake. - Two discarded prior-lookup errors are propagated. - The reopen confirm uses reopen wording ("Reopen both"); the kachinuki Reopen keeps the editor open; the button reads "Reopened" once the server answers; a reopen notice clears when the match completes again. - formatPositionLabel is gone in favour of Position.Label(). - The kachinuki demo recipe no longer reopens the editor after Reopen; the clip is re-recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sult Chusen (drawing lots) can now be changed after it is recorded: chusen-candidates returns the recorded groups alongside the open ones, and the Pools page shows a "Chusen (drawing lots) recorded" card with Change, writing through the same override-rank path and pool requalification planner as the first entry. Display gaps found in UAT: - Correct on a team match decided by kiken or fusenpai shows the recorded winner and decision in the result band, and the Kiken or Fus. mark beside the withdrawing team's name, instead of DRAW. - The individual editor shows the same side mark on a correction. - A tie settled by chusen is no longer highlighted as tied in the standings (admin and public). 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. |
…me the match bc-kcsh: Correct on a match decided by kiken or fusenpai shows the recorded decision, the winner and the side mark before any edit. bc-rawm: refusals name the competitor, the match in the operator's terms (engine.OperatorMatchLabel, pinned to the JS labels by a shared fixture) and what to do; an individual knockout bout cannot be finished tied; the Remaining matches award no longer throws. Team withdrawal credit: a completed non-kachinuki team match decided by kiken, fusenpai or a default win credits every bout without a result to the other team at 2-0 (one IV, two PW), one rule in Go (state.DefaultWinCreditSide) and its JS mirror. A completed default-win write pads empty rows to the team size; stored data is padded on load; an unreadable bout cell is never padded over. A barred competitor's remaining matches carry a read-only ineligibleSides stamp, are skipped by every next-match picker, show a Withdrawn chip, and offer a one-tap default win (match-level fusensho) and Reinstate for an injury kiken. Clearing that default win returns the match to the queue while the competitor is still barred. Both sides barred: a pool or league match can be recorded as drawn. Roster: the batch replace keeps surviving ids; removed teams' members and lineups are pruned before the draw only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Chusen: a recorded or changed order is one write, checked against the knockout once on the final order; a refusal records none of it. - Reopen: a cleared default win that goes back to the queue ignores a busy court; a reopen or correction whose winner passed through a bye unwinds the bye and answers for the match after it, so a wrong kiken there is fixable again. - A refused correction's rollback no longer re-bars a reinstated competitor. - An unreadable pool or bracket file is reported as such, not as a missing match (lookups and the simultaneity gate). - Decision endpoint: the both-barred draw runs after the clock check and maps its errors like every other decision. - Quick-score runs the team finish gate with /score's withdrawal exemption; bulk-score loads the competition once. - Default-win padding keeps bout rows in position order. - The court console lets go of a correction that went back to the queue. - The JS default-win credit falls back to the winner like the server. - Dead code removed: the resolved-by-bye refusal, single-rank wrappers, ChusenCandidates, two unused JS exports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tmfn-operator-fixes
A bye is the draw's hidden pass-through match; nobody reopens it. The docs now say what happens when the match feeding it is reopened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…heets print The 34th EKC 2026 Junior Individual Male sheet (page 4, court A) prints P2 v P3 alone in column 1 and P4 v P5 beside P1 v Winner F1 in column 2; the 33rd EKC 2025 Men Team five-team courts, and the 2026 Junior Individual Female and Men Individual sheets, print the same way. An earlier reading put P4 v P5 in column 1 and pulled every pair beside an empty pair forward a round (the "risen" classification), so a five-player knockout showed its semifinal pair as a quarterfinal. A bout's round is now its distance from the final on every surface: the app bracket, match numbers, order of play, and the Excel columns. The pairings are unchanged; the rise marks stay as slot geometry only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n load Those releases stored rounds and match numbers from the misread column rule, while the Excel export renumbers with the corrected walk, so the app and the printed sheet named different bouts (and the export places scores and courts by match number). On first load a stored bracket's rounds are recomputed from its own feeders (the final is round 1, each feeder one deeper) and its matches renumbered by the one numbering rule, now state.Bracket.NumberMatches, shared with generation. Pairings, results, courts and scheduled times are untouched; a bracket that cannot be walked is left as it is. A drift guard pins that the stored-feeder rule leaves every freshly generated bracket unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Times were handed out in the bracket's storage order, before the matches were numbered, so a five-player draw put its semifinal (Match 2, the pair beside an empty pair, stored in the first row) at 09:00 ahead of the quarterfinal (Match 1). Slots are now assigned after numbering, in match-number order: deepest round first, then left to right, on every shiaijo. Byes still take no court time. Draws already made keep the times they were given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The docs showed no bracket at all. Three captures now do, kept current by the harness (a new knockout recipe file, two families): - knockout-draw.md: the admin bracket of a five-player knockout, beside the paragraph that describes the block (one first-round bout, three competitors marked BYE, the semifinal pair, the final). - run-tournament.md: a ten-player knockout part-way through, on the public Bracket tab (the admin page puts the scoring panel beside the card, so a four-round tree never fits it without scrolling). - following.md: the same tree on the public Bracket tab at phone width. The Byes section now says what the bracket shows: the one bye the order awards, and the other competitors of an odd block who simply start a round later and are marked BYE too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A confirmed pool correction or a forced knockout correction now unwinds a hidden bye the old winner passed through, clearing that winner from the first real match past it when nobody has touched it; before, a competitor who no longer qualified stayed seated there and could be started. A played match past the bye is left for its own warning. - One producer of a match's round: generation stamps rounds from the stored feeders with the same walk the load-time repair uses (StampRoundsFromFeeders); the draw-tree override and SlotRoundMatches are gone. The Excel parity suites and the numbering golden are unchanged. - The load-time repair is described as what it is: any stored bracket whose rounds or numbers differ from the rule, which includes pre-v2.0 brackets numbered by the old tie-break (pinned with a real v1.1.0 bracket). - An unstarted bracket still scheduled in storage order gets its own times handed out again in match-number order, per court. - bracket.json is parsed and saved once by the load-time repairs, and a bracket that cannot be walked is logged once per competition. - Comments: the numbered() invariant, the scheduler header, estimate.go's reference, the bye note in scoring.go; one ScheduledAt layout constant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on its row Operator decision, from mockups: a competitor who skips a round appears only in the match they first fight, as the European Championship sheets print them, so the placeholder BYE cards and their connectors are gone. A card fed by one match takes that line on the row the winner fills (Aka or Shiro); a card fed by two keeps the join at its centre (connectorTargetY). The docs describe the bracket this way, and the three bracket captures are recaptured by the harness. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A time the operator moved is no longer taken back on load. The one-off repair of old storage-order scheduling ran on every load and could not tell a queue swap from the old scheduler, so moving Match 2 above Match 1 in a five-player draw was reverted at the next restart. A bracket now records TimesSettled: set by the draw, and by the repair the first time it runs, which then never looks at the times again. - Recording a both-barred match as drawn now owes a reason-less reopen's reason, as every other way of ending a match does: refused without one, and stored as the correction reason when given. - The TV board no longer repeats the daihyosen tie test from the bout rows; the shared scoreboard's own test, which reads the server's figures first, is the one gate, as on the viewer card. - numbered() and its two restatements say where a side can still be emptied (a degenerate pool's missing place) and what the load does then. - The tracked EKC spec carries the corrected Junior Male reading in all three places it stated the misread, and CLAUDE.md no longer says no spec plan is tracked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing reason A match can be reopened without any reason (operator ruling), so recording a both-barred match as drawn no longer refuses a request that carries none. The draw still settles the reopen in the same transaction, keeping the request's reason, if any, as the correction reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator ruling: a match can be reopened without any reason, and nothing is gated on that. Ending a reopened match again now works like ending any match: - the server no longer refuses a score or a decision that ends a match reopened without a reason (the reopen-pending refusal and its message are gone); ending it clears the flag and keeps a reason if one is sent; - the team editor's End match no longer opens a "Reason for reopening" prompt, and the withdrawal panel's reason is optional after a reopen, as it is everywhere else; - the docs say so, the two screenshots of the retired prompts and their harness recipes are removed, and the kachinuki video is recaptured with the reopen chapter ending without the prompt. The stored reopen flag stays (the pool CSV reads its columns by position), but nothing is refused for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A card fed by one match is placed so the row that match fills is level with it, so the line between them runs straight instead of bending half a row. The stacking makes room for the shift, so no card overlaps its neighbour. One helper, oneSidedFeed, now states what a one-sided card is for both the connector and the placement. - The admin Bracket page (operator decision): the bracket takes the full width and the scoring panel sits below it; picking a match scrolls the panel into view. The tree's columns narrow to fit, 230px down to a 180px floor with tighter gaps, so a four-round tree (9 to 16 competitors) fits an iPad in landscape without scrolling sideways. The 3rd-place card sits in a row of column slots sized like the tree's, the last under the final. - The connector SVG is sized from the cards rather than from the tree's scroll size, which included the SVG itself and held a narrowed tree at its first, wider width. - The three bracket docs images are recaptured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The competition page keeps its 1400px width everywhere except the Bracket section, which now takes the whole window, so a six-round tree (33 to 64 competitors) fits a large monitor: from 1600px wide, measured. Every other section keeps its readable line length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator decision: like Reopen match, Clear withdrawal and reopen (and Clear default win) reopens in one tap, with no reason. What the clear does is now stated beside the button, before it is tapped, instead of in the confirm step it replaces: the match goes back to running or to the queue, the withdrawn side can compete again, which points are kept, whether it may change who qualifies, and any later default win from the same withdrawal. The one second step left is Reopen both, when a later knockout match has already been fought. The unused reopen reason presets are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gitrgoliveira
added a commit
that referenced
this pull request
Sep 25, 2026
…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>
This was referenced Sep 25, 2026
Merged
7 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
Operator ruling 2026-09-24: every bout of a team match is fought, so a team match cannot be finished while a bout has no result. Plus four smaller operator-review fixes, and the follow-on work the operator's rulings asked for while this was built ("Save correction should just save what the operator enters"; "everything should be able to be fixed, the operator just needs to be aware of the consequences").
The five beads
PUT /scoreand bulk-score) refuses a non-kachinuki team match while a numbered bout has no result: "Bout 3 has no result. Record a score, a Tie, or a Fusensho before finishing." No position is skipped (operator ruling: every bout has a result or a decision; a team always fields at least half+1, assumed and never checked; a bout neither team fields is recorded as a Tie). Kachinuki is out of scope. Standings count an individual draw (IT) only for a bout that has a result. Untouched bouts were being written ashikiwake, which made them indistinguishable from a marked Tie; they are now written with no decision.display:flexbeat the media query) and lists only keys that work on that host; arrow keys need a neighbour match (pressing them at the end of the Scores tab threw onscoreKeyOf(null)).Withdrawals: correct, keep, or clear
Mixed competitions: fixing a pool after the knockout started
Chusen, and what a correction shows
bc-kcsh, bc-rawm, and what testing found around withdrawals
Hardening that came with it
modifiedAt, so an older queued offline save is refused as superseded instead of undoing it; Send back to queue on a pool match does the same.Code review round
PUT .../override-ranknow also takes{ranks: [...]}) and the knockout is checked once, on the final order. Sent one rank at a time, changing Kyoto 1st, Nara 2nd, Sendai 3rd to Sendai, Nara, Kyoto passed through an order nobody chose and named, then reopened, the match Nara had already fought. A refusal now records none of the order./scoremakes for a correction that keeps a recorded withdrawal; bulk-score loads the competition once per request.downstream_knockout_resolved),ChusenCandidates, the single-rank wrappers, two unused JS exports; the simultaneity gate no longer passes on an unreadable file.Knockout rounds, as the European Championship sheets print them
timesSettled(set by the draw, and by the repair the first time it runs), and its times are never examined on load again. A match can be reopened without any reason (operator ruling), so the both-barred draw is never refused for one; it settles the reopen in the same write and keeps the request's reason, if any (the one-tap button sends "auto: ... withdrawn"), as the correction reason, where before nothing was kept. The TV board's daihyosen row takes its tie from the same place as the viewer card (the server's figures first).Why
Files changed
CLAUDE.mddownstream_knockout_scoredguard; default-win credit, barred competitors, operator labels, the Fus. mark; review round: a queued reopen takes no court, the bye unwind, the label list; the specs line: a few plans are force-added and trackeddocs/architecture/data-model.mdTimesSettledfielddocs/screenshots/decision-reason-after-reopen.pngdocs/screenshots/kachinuki-knockout-tie-encho.pngdocs/screenshots/kachinuki-reopen-reason.pngdocs/screenshots/knockout-bracket-five.pngdocs/screenshots/knockout-bracket-in-play.pngdocs/screenshots/viewer-bracket.pngdocs/user-guide/court-operators/recording-decisions.mddocs/user-guide/court-operators/scoring-a-match.mddocs/user-guide/organisers/knockout-draw.mddocs/user-guide/organisers/run-tournament.mddocs/user-guide/organisers/team-tournaments.mddocs/user-guide/spectators/following.mddocs/videos/kachinuki-demo.webminternal/domain/decision.gointernal/domain/glossary.gointernal/domain/glossary_test.gointernal/domain/team_lineup.goPositionForBout,Position.Label()internal/domain/team_lineup_test.gointernal/engine/bc_idfx_test.gointernal/engine/bracket.gostate.Bracket.NumberMatches; slots assigned after numbering; comments on the round rule; rounds stamped by StampRoundsFromFeeders at draw time (the draw-tree override removed); the draw records its times as settledinternal/engine/bracket_identity_test.gointernal/engine/bracket_match_numbers_golden_test.gointernal/engine/bracket_not_ready_label_test.gointernal/engine/bracket_order_of_play_test.gointernal/engine/bracket_rounds_restamp_test.gointernal/engine/bronze_label_test.gointernal/engine/chusen.goChusenStatusalso reports the recorded groups;ChusenCandidatesremoved (the handler usesChusenStatus)internal/engine/chusen_test.goChusenStatus; a changed chusen names only the real moveinternal/engine/competition.goMaybeAutoCompletePoolsAfterWrite: pool pass only for pool writes in knockout statusinternal/engine/daihyosen.godownstream_knockout_scoredhandling; credit argument on ComputeTeamSummary; id grammar moved to stateinternal/engine/daihyosen_test.gointernal/engine/default_win_bout_credit_test.gointernal/engine/default_win_bout_padding_unreadable_test.gointernal/engine/downstream_correction_guard_test.gointernal/engine/downstream_guard_review_test.gointernal/engine/eligibility.gointernal/engine/eligibility_test.gointernal/engine/engi.gointernal/engine/errors.goQualifierChangeon the played error;DownstreamKnockoutRunningError; SentenceCase; labels in refusals; comments nameOverridePoolRanksand the moved numbering ruleinternal/engine/excel_draw_parity_test.gointernal/engine/kachinuki.goReopenMatch/RequeueBlockerAndReopenfor withdrawal-decided matches; downstream warn-and-proceed; eligibility restore; clears an untouched next round;lineupInForceextracted for the kachinuki roster; a default win reopens to scheduled while the competitor is barred; reopen refusal copy; a reopen to the queue skips both court gates; the bye unwind (propagatedDownstreamOf);findMatchHomereturns load errors; retractIntoUntouched unwinds a bye through the shared unwindChain; the reopen flag documented as asking for nothinginternal/engine/kachinuki_export.goPosition.Label()internal/engine/kachinuki_export_test.goTestPosition_Labelinternal/engine/kachinuki_reopen_withdrawal_test.gointernal/engine/kachinuki_test.gointernal/engine/knockout.goresolveSlots(pure, all-or-nothing per pool);advanceMixedPoolsin knockout statusinternal/engine/knockout_skeleton.gointernal/engine/load_error_propagation_test.gointernal/engine/match_label.gointernal/engine/match_label_test.goMatchLabelagainst the shared round tableinternal/engine/match_numbering_parity_test.gointernal/engine/pool_daihyosen_test.gointernal/engine/pool_rank_override.goOverridePoolRanks: a group of ranks as one change, one requalification check and one undo; undo only when uncommittedinternal/engine/pool_rank_override_test.gointernal/engine/pool_requalify.goplanRequalificationand the forced repaint/reopen/retract; label on the played refusalinternal/engine/pool_requalify_test.gointernal/engine/reopen_bye_unwind_test.gointernal/engine/reopen_scheduled_court_test.gointernal/engine/restore_touches_no_eligibility_test.gointernal/engine/pool_write_merge_test.gointernal/engine/prior_lookup_error_test.gointernal/engine/reopen_fusensho_status_test.gointernal/engine/reopen_withdrawal_test.gointernal/engine/retract_through_bye_test.gointernal/engine/scheduler_slots.gointernal/engine/scoring.goKeepsWithdrawalRuling,preserveWithdrawalRuling,keptWithdrawalScoreline;MatchLabel;RevertMatchToQueuestamp;markTiedStandingsskips a settled tie; credit in standings; KeepsWithdrawalRuling keeps any default win; labelled refusals; a rollback restore touches no eligibility; downstream guards look past a bye; the bye note on bracketMatchCarriesOwnResult; comments on the reopen flaginternal/engine/scoring_test.gointernal/engine/scoring_tx.gointernal/engine/scoring_tx_internal_test.gointernal/engine/scoring_tx_test.gointernal/engine/sub_hantei_preserve_test.gointernal/engine/swiss.gointernal/engine/testdata/bracket_match_numbers.jsoninternal/engine/testdata/match_labels.jsoninternal/engine/testdata/round_labels.jsoninternal/engine/tiebreaker.gopendingTieBreaksshared by both injectors;tieSettled,supplementaryGroupWins; id grammar moved to stateinternal/engine/tiebreaker_test.gointernal/engine/tied_standings_test.gointernal/engine/withdrawal_removed_restores_test.gointernal/engine/withdrawal_ruling_kept_test.gointernal/export/builder.gointernal/export/builder_test.gointernal/export/team_bracket_submatch_cells_test.gointernal/export/team_name_fallback_asymmetry_test.gointernal/helper/draw.goslotDepthremoved; comments: rise marks are slot geometry, rounds are distance from the final; SlotRoundMatches removed; walkLeafOffsets' visitor loses its content offsetinternal/helper/draw_ekc_2026_individual_test.gointernal/helper/draw_ekc_test.gointernal/helper/draw_leaf_offsets_test.gointernal/helper/draw_seed_bye_test.gointernal/helper/draw_seed_count_test.gointernal/helper/estimate.gointernal/helper/excel_tree.gointernal/helper/tree.gointernal/helper/tree_test.gointernal/mobileapp/decision_both_barred_hikiwake_test.gointernal/mobileapp/deps.goMaybeAutoCompletePoolsAfterWriteon the engine interfaceinternal/mobileapp/deps_test.gointernal/mobileapp/errors.godownstream_knockout_running;qualifierChange;broadcastReopenedDownstream; refusal sentences; court busy label; the barred sentence labels throughOperatorMatchLabelFromStore; the resolved-by-bye mapping removedinternal/mobileapp/handlers_competition.gorecorded; override-rank takes aranksgroupinternal/mobileapp/handlers_competition_test.gointernal/mobileapp/handlers_daihyosen.gointernal/mobileapp/handlers_daihyosen_test.gointernal/mobileapp/handlers_decision.goReopenPendingfor all formats; running refusal; both-sides-barred draw; refusal sentences; clock check before the both-barred draw; one error mapping for both paths; the both-barred draw settles a reason-less reopen, never refusing for a missing reason, and keeps any reason given; no decision is refused after a reason-less reopeninternal/mobileapp/handlers_decision_test.godownstream_knockout_scored409 test removed; a commentinternal/mobileapp/handlers_match.gointernal/mobileapp/handlers_match_kachinuki_test.gointernal/mobileapp/handlers_match_test.gointernal/mobileapp/pool_rank_override_group_test.gointernal/mobileapp/handlers_participants.gointernal/mobileapp/handlers_participants_test.gointernal/mobileapp/handlers_viewer.gointernal/mobileapp/ineligible_sides_annotation_test.gointernal/mobileapp/middleware_selfrun_test.gointernal/mobileapp/pool_requalify_handler_test.gointernal/mobileapp/reason_human_barred_test.gointernal/mobileapp/reopen_downstream_labels_test.gointernal/mobileapp/reopen_withdrawal_handler_test.gointernal/mobileapp/team_finish_gate.gointernal/mobileapp/team_finish_gate_test.gointernal/mobileapp/testdata/unfinished_team_bouts.jsoninternal/state/bracket.goTimesSettledinternal/state/bracket_rounds.goNumberMatches(the one numbering rule) andRestampRoundsFromFeeders; StampRoundsFromFeeders (one producer); the scope described; storage-order times repaired on an unstarted draw; ScheduledAtLayout; the time repair runs once per bracket (TimesSettled); where a side can still be emptiedinternal/state/bracket_rounds_schedule_test.gointernal/state/bracket_rounds_test.gointernal/state/bracket_rounds_v110_test.gointernal/state/competition.gointernal/state/default_win_bout_padding_unreadable_test.gointernal/state/legacy_team_members_filename_test.gointernal/state/legacy_upgrade.gointernal/state/legacy_upgrade_bracket_rounds_test.gointernal/state/match.gointernal/state/match_id_grammar.gointernal/state/match_id_grammar_test.gointernal/state/models.goSubMatchResult.HasResult();AcceptsPoolRankOverride();Tieddocuments a settled tie; ineligibleSides annotation; Attribution(); DisplayRound doc; the numbered() invariant and the repair scope;Bracket.TimesSettled; the reopen flag documented as asking for nothinginternal/state/models_test.gointernal/state/overrides.goSaveRankOverridesChangedandRestoreRankOverrides: a group of ranks in one writeinternal/state/overrides_test.gointernal/state/participants.gointernal/state/pool_roundtrip_test.gointernal/state/squad.gointernal/state/squad_test.gointernal/state/store.gointernal/state/sub_match_has_result_test.goHasResultinternal/state/team_lineup.gointernal/state/team_lineup_test.gointernal/state/team_result.gointernal/state/team_result_test.gointernal/state/testdata/bracket_v1.1.0_knockout9.jsoninternal/state/testdata/bracket_v2.1.0_knockout5.jsoninternal/state/testdata/participants_v1.1.0_knockout9.csvinternal/state/transactions.goErrTxCommittedon a post-commit Apply failure; commentinternal/state/transactions_test.goknockout-example-large-seeded.xlsxknockout-example-large.xlsxknockout-example-small.xlsxpools-example-large-seeded.xlsxpools-example-large-teams.xlsxscripts/screenshots/README.mdscripts/screenshots/recipes/editors.mjsscripts/screenshots/recipes/index.mjsscripts/screenshots/recipes/knockout.mjsscripts/screenshots/recipes/videos.mjsscripts/screenshots/run.mjsspecs/007-ekc-draw/spec.mdspecs/openapi.yamlrecorded; ineligibleSides; refusal bodies; the both-barred draw; decisionBy wording; lineup positions and memberIds; override-rankranksgroup;downstream_knockout_resolvedremoved;timesSettled; no reason is required to end a reopened matchweb-mobile/css/styles.cssweb-mobile/js/__tests__/api.test.jsxweb-mobile/js/__tests__/api_client_downstream_knockout.test.jsxweb-mobile/js/__tests__/api_client_reopen.test.jsxweb-mobile/js/__tests__/bracket_connector_elbow.test.jsxweb-mobile/js/__tests__/bracket_display_model.test.jsxweb-mobile/js/__tests__/bracket_match_numbers.test.jsxweb-mobile/js/__tests__/bracket_round_label_agreement.test.jsxweb-mobile/js/__tests__/display_placeholders.test.jsxweb-mobile/js/__tests__/ineligible_match.test.jsxweb-mobile/js/__tests__/kachinuki_bout_mode.test.jsxweb-mobile/js/__tests__/match_labels_golden.test.jsxweb-mobile/js/__tests__/match_scoreboard.test.jsxweb-mobile/js/__tests__/name_mark_survives_clipping.test.jsxweb-mobile/js/__tests__/patch.test.jsxweb-mobile/js/__tests__/queue_label.test.jsxweb-mobile/js/__tests__/render/admin_bracket_layout.render.test.jsxweb-mobile/js/__tests__/render/admin_competition.render.test.jsxweb-mobile/js/__tests__/render/admin_pools_chusen.render.test.jsxweb-mobile/js/__tests__/render/admin_pools_tiebreak_buttons.render.test.jsxweb-mobile/js/__tests__/render/admin_scores_row_name_mark_clip.render.test.jsxweb-mobile/js/__tests__/render/admin_scoring_engi.render.test.jsxweb-mobile/js/__tests__/render/admin_shiaijo.render.test.jsxweb-mobile/js/__tests__/render/barred_chip.render.test.jsxweb-mobile/js/__tests__/render/bracket_bye_slot.render.test.jsxweb-mobile/js/__tests__/render/bracket_round_label.render.test.jsxweb-mobile/js/__tests__/render/bracket_structural_bye.render.test.jsxweb-mobile/js/__tests__/render/display_tv_dh_gating.render.test.jsxweb-mobile/js/__tests__/render/display_tv_headline_name_mark_clip.render.test.jsxweb-mobile/js/__tests__/render/display_tv_individual_barred_chip.render.test.jsxweb-mobile/js/__tests__/render/editor_barred_match.render.test.jsxweb-mobile/js/__tests__/render/individual_editor_ko_tie_blocked.render.test.jsxweb-mobile/js/__tests__/render/individual_editor_live_hantei.render.test.jsxweb-mobile/js/__tests__/render/kachinuki_done_bout_double_tap.render.test.jsxweb-mobile/js/__tests__/render/kachinuki_reopen.render.test.jsxweb-mobile/js/__tests__/render/match_lineup_side_editor_order.render.test.jsxweb-mobile/js/__tests__/render/recorded_withdrawal_reopen.render.test.jsxweb-mobile/js/__tests__/render/remaining_matches_panel_award.render.test.jsxweb-mobile/js/__tests__/render/score_editor_barred_match.render.test.jsxweb-mobile/js/__tests__/render/score_editor_dismiss_prompt.render.test.jsxweb-mobile/js/__tests__/render/score_editor_mount_sites.render.test.jsxweb-mobile/js/__tests__/render/score_editor_team_default_win_mark.render.test.jsxweb-mobile/js/__tests__/render/scoring_shortcut_hint.render.test.jsxweb-mobile/js/__tests__/render/team_bout_row_records_no_team_name.render.test.jsxweb-mobile/js/__tests__/render/team_daihyosen_silence.render.test.jsxweb-mobile/js/__tests__/render/team_editor_superseded.render.test.jsxweb-mobile/js/__tests__/render/team_finish_refuses_unfought_bouts.render.test.jsxweb-mobile/js/__tests__/render/use_team_lineups_squads.render.test.jsxweb-mobile/js/__tests__/render/withdrawal_correction_shows_result.render.test.jsxweb-mobile/js/__tests__/score_display.test.jsxweb-mobile/js/__tests__/sync_queue.test.jsxweb-mobile/js/__tests__/team_default_credit.test.jsxweb-mobile/js/__tests__/team_iv_score.test.jsxweb-mobile/js/__tests__/unfinished_team_bouts.test.jsxunfinishedTeamBoutsand its copyweb-mobile/js/__tests__/viewer_competition_bye_results.test.jsxweb-mobile/js/__tests__/viewer_home_secondary_ondeck.test.jsxweb-mobile/js/__tests__/viewer_ondeck_alert.test.jsxweb-mobile/js/__tests__/write_result_downstream_knockout.test.jsxweb-mobile/js/admin_competition.jsxweb-mobile/js/admin_competition_bracket.jsxweb-mobile/js/admin_pools.jsxweb-mobile/js/admin_schedule_lineup.jsxweb-mobile/js/admin_schedule_score_editor.jsxweb-mobile/js/admin_scoring_autosave.jsxflushPendingweb-mobile/js/admin_scoring_engi.jsxweb-mobile/js/admin_scoring_individual.jsxweb-mobile/js/admin_scoring_shared.jsxRecordedWithdrawal,useMatchReopen,ReopenFeedback,withdrawalInForce,WithdrawalMarkedName; default win recorded and cleared; Remaining matches award fixed; court busy panel; the decision reason is always optional; the reopen presets removed; Clear withdrawal and reopen is one tap, its consequence stated beside the button; the reopen presets removedweb-mobile/js/admin_scoring_team.jsxweb-mobile/js/admin_shiaijo.jsxweb-mobile/js/api_client.jsx{candidates, recorded}; refusal sentences preferred; court busy; reopen copy when a downstream match runs;overridePoolRanks; comment; comment: the clear sends no reasonweb-mobile/js/barred_chip.jsxweb-mobile/js/barred_match_notice.jsxweb-mobile/js/bracket.jsxsideMarksfor the editors; fusensho winner mark; teamMatchMarks; Withdrawn chip on the card; comment names the moved numbering rule; no BYE placeholder cards; connectorTargetY puts a one-sided join on its row; oneSidedFeed; a one-fed card placed with its fed row level with the feeder; the connector SVG sized from the cards; bracketColumnCountweb-mobile/js/display_helpers.jsxweb-mobile/js/display_scoreboard.jsxweb-mobile/js/ineligible_match.jsxweb-mobile/js/match_scoreboard.jsxweb-mobile/js/patch.jsxweb-mobile/js/pool_ids.jsxweb-mobile/js/team_default_credit.jsxweb-mobile/js/viewer_alerts.jsxweb-mobile/js/viewer_competition.jsxweb-mobile/js/viewer_home.jsxweb-mobile/js/viewer_match.jsxweb-mobile/js/viewer_schedule.jsxweb-mobile/js/viewer_standings.jsxrankOrdinal; pool rows carry marks and the chipweb-mobile/js/viewer_watchlist_core.jsxweb-mobile/js/write_result.jsxScreenshots
bc-tmfn: Finish refused while bout 3 has no result

bc-cpdc: Copy from previous match at the top of each side

bc-kbhn: court console hint (no arrows, no Esc) · hidden under a coarse pointer


bc-dscn: running match, M then Close at 96 ms, reopened: the M was saved, no prompt · a scheduled match still prompts


bc-kbrw: double tap on bout 3 opens bout 3 only

Withdrawal correction: recorded kiken kept, winner read-only · Clear withdrawal and reopen · after finishing the reopened match the console is on the next live match



Reopen with a later knockout match played: reopen wording and "Reopen both" · the individual editor stays open, running, with the notice · the kachinuki Reopen stays open with the notice



Knockout requalification: warning naming the round · running match refused


Chusen: Change on a recorded chusen after the knockout started · the warning naming the fought match · before: the tie awaiting chusen is highlighted · after: the order is recorded and the pool is no longer highlighted as tied, admin and public





Correcting a withdrawal shows what is recorded: team match, the result band names the recorded winner and decision, Kiken beside the team that withdrew · the Recorded line below · individual match



Team withdrawal credit: the confirm states the consequence · Scores list row (credited totals, Kiken beside the team) · the correction band · the viewer card's bouts (its Recent results row is from before the clipping fix; the next shots are on the final build) · Recent results with a long name, Shiro and Aka · a short-name row, unchanged · the Scores row with a long name · standings









A withdrawn team's remaining match: court console notice, not in Up next · public row with the Withdrawn chip · after Record default win · Clear default win confirm · back in the queue with the notice





Individual: injury kiken offers Reinstate · after Reinstate · both sides barred · recorded as drawn · Clear default win once reinstated





bc-kcsh: fusenpai correction, team and individual · kachinuki: no credit note



bc-rawm: a tied individual knockout bout · the court busy panel names the blocking match


Code review round: a cleared default win goes back to the queue while the court runs another match, and the console returns to that match · a five-player semifinal decided by kiken, reopened (the Final is back to "Winner of M2"; retaken on the final build) · changing a chusen after the knockout: only the real move is named



Knockout rounds (harness captures, final build): a five-player draw on the admin Bracket page (M1 is the only first-round bout; the semifinal pair and the third competitor first appear in the Semifinals; M1's line runs straight into the "Winner of M1" row) · a ten-player draw part-way through (each first-round line runs straight into the row its winner fills; cards fed by two matches join at their centre)


Admin Bracket page at iPad size (1180×820, a sixteen-player naginata knockout): the whole tree and the 3rd-place card fit with no sideways scroll, the panel below the bracket · after picking M1, the page has scrolled to the scoring panel


A 64-player knockout on the admin Bracket page at 1920 wide: the page takes the whole window here, so all six rounds fit at full card width

Clear withdrawal and reopen in one tap (admin Scores tab, Correct on a match decided by Kiken – Voluntary, 1180×820): the recorded withdrawal with the button and, below it, what clearing does · after the one tap, the match is in progress again with the editor open on it; no reason was asked


Reopen needs no reason (frames from the recaptured kachinuki video, a real browser run by the capture harness): the completed encounter with Reopen match · reopened, bouts kept, End match offered · End match armed by the first tap · the second tap ends it and the editor closes to the Scores list, with no reason prompt at any step

Test plan
make go/teston the final code (44ef2613): exit 0. Lint, import checks and security audits pass, JS 4884/4884, every Go package ok. HarnessSINCE=main: 31 captured, 31 unchanged.make docs/buildand the link check: OK (39 pages, 2431 links).make examplesat8c45d626(no workbook changed); nothing since touches the export.SINCE=main: 31 captured, 3 changed (the three bracket images, read and copied in), 28 unchanged.c9f595d7(refused), the kept reason againstcb251d5f(dropped)./score, bulk score, bracket, bronze and/decision; End match arming instead of prompting; the fusenpai panel with no required reason) and passes now; the with-reason cases pass both ways. HarnessSINCE=main: 31 captured, 31 unchanged. The recaptured video read frame by frame: the reopen chapter shows Reopen match, the reopened encounter, End match armed, and the encounter ended with no prompt; chapter times unchanged (0:02, 0:08, 0:15, 0:21). Docs build and link check OK (39 pages, 2431 links).make examplesregenerated; the small knockout example now has two first-round bouts and four in round 2.make docs/buildpasses;check_links.py: 39 pages, 2430 links resolve).make docs/screenshots SINCE=mainon the final build (after the knockout round change) reports all 30 captures unchanged; a full run after adding the three bracket captures reports all 33 unchanged; after the BYE-card change exactly the three bracket captures changed (recaptured, read, copied in) and the other 30 stayed unchanged (an earlier round caught five changed by a name-row alignment regression, fixed before commit). The one video,kachinuki-demo, was not re-recorded: its recipe reaches no withdrawal, default win or long-name mark, and videos are not compared by the harness.GET .../match-lineups/:midanswers "no lineup submitted for this team and match" by design; the lookup code is identical on main), and the browser checks only the 409 of the chusen warning itself.Notes for the reviewer
draw: true, counts as played, and is written back ashikiwake, so the Finish gate does not refuse it.bc-tmfne2etest.fixmelives in PR test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442 (not on main). Switched to a plaintestin a scratch merge of the two branches, it passes with this branch and fails without it (Team Kame W1 IV1 IT2: the unscored bout 3 counted as a draw). It can be switched on in test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442 once this merges.make exampleswas not run: the CLI Excel path (cmd/,internal/helper,internal/excel) is unchanged.modifiedAtwith the server's clock, the fence the bracket requeue already used. A tablet whose learned clock offset has drifted by more than the seconds between a reopen and its next tap could see that tap refused as superseded; the offset is RTT-corrected and relearned on reconnect and on a 3 s heartbeat divergence, and a weaker fence would let a stale queued correction undo the reopen.DELETE .../overrides(the repair path) is not routed through the planner.kachinuki-scoring-buttons; the same capture onmainlogs it too, so it predates this PR.specs/007-ekc-draw/spec.md, tracked) now carries the corrected Junior Male reading, checked again in the PDF: court A prints P2 v P3 alone in the first column and P4 v P5 beside P1 v Winner F1; court B prints P7 v P8 and P9 v P10 the same way.Closes bc-tmfn
Closes bc-cpdc
Closes bc-kbrw
Closes bc-dscn
Closes bc-kbhn
Closes bc-kcsh
Closes bc-rawm
🤖 Generated with Claude Code