Skip to content

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
gitrgoliveira merged 33 commits into
mainfrom
bc-p1-operator-fixes
Sep 26, 2026
Merged

gitrgoliveira merged 33 commits into
mainfrom
bc-p1-operator-fixes

Conversation

@gitrgoliveira

@gitrgoliveira gitrgoliveira commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

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).

  • bc-kfup, a default loss after a kiken. Once a competitor withdrew, recording Fusenpai on one of their remaining bouts was refused with 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.
  • bc-kpnl, the panel after a kiken: removed (operator ruling 2026-09-26, "Only change that match. Do not cascade."). The bead asked for the Remaining matches panel to stay on the court console after a kiken. The ruling removes the panel and the console's hold for it instead: a kiken now moves the court on, or closes the editor, like any other decision. Each of the competitor's remaining matches waits in the court's queue with its own Record default win for <opponent> (bc-rawm, fix(bc-tmfn): operator fixes: team bouts, withdrawals, knockout rounds and the Bracket page #444). The court's next match still starts by itself, as it does after a no-show (your request, mp-nwds item 7), and it now passes over those matches on the court console and the Scores tab alike. In the browser, both first tried the withdrawn competitor's own next match, which the server refused (fixed in 7af3170 and 525c35c). A recorded decision now also closes its form in both editors (90da239). Before the panel's removal a withdrawal closed it on the panel's own path, while a no-show relied on the editor moving on. So when the Scores tab could not move on (another competition's match running on that court), the decided match stayed on screen with its form open, offering Record again and hiding the recorded decision.
  • bc-htcr, correcting a hantei. Tapping the other side's hantei button on a completed match sent no correction reason, so the server refused the write and the wrong verdict stood. The editor now asks for the reason first, the same prompt Save correction uses, and sends it with the new verdict.
  • bc-sbq, a match sent back to the queue keeps its score (operator rulings 2026-09-26). Sending a match back used to clear everything entered on it, which lost fought bouts and points. Now:
    • Send back to queue keeps the points, fouls, overtime, bouts and engi flags, and is always offered. The operator removes a wrong mark themselves, on the board, once the match is started again.
    • Starting the match again (Start match, Finish + Start Next) carries on from that score instead of wiping it.
    • While it waits in the queue, every screen shows it as not started: court console, pool list, Scores tab, TV board, lobby and the public match card. The score editors are the one exception.
    • Picking another bout to run sends a scored bout back the same way, instead of refusing to switch, so a mistaken switch is undone with one tap on its Start.
    • The court-busy remedy (Queue it and reopen) keeps the blocking match's score too.
    • Knockout: when the match feeding a queued later match is corrected or reopened, the later match takes the new name and keeps its points; nothing is asked when it starts.
    • Knockout, a FINISHED later match (ruling 2026-09-26, "Keep the points"): a confirmed correction reopens it with the new competitor in, its winner cleared and its points kept. The dialog says so and asks the operator to check them.
    • Engi flags are saved as entered once the match has started (ruling 2026-09-26), so send back and a switch keep them too. Before, they reached the server only with the finished result.
  • bc-kten, kachinuki encho only for taisho against taisho: reversed (operator ruling 2026-09-26: "It's up to the operator to add an encho or not. It's not up to the server or app to decide how matches are run. The app just records what happens."). The rule built here on 2026-09-25 is removed in full, both the editor hiding Encho and the server refusing it. Encho is offered on any tied kachinuki bout, as on main. CLAUDE.md records the reversal so the gate is not re-added.
  • bc-kclr, a kachinuki point taken back stays taken back (found during this PR; you asked for it to be fixed here). Striking a point on a kachinuki bout and tapping it away left the point stored, so it came back on reload, on every other surface, and under End match. The clear is now saved, and the operator's own edit isn't overwritten by the court list's lagging copy.
  • bc-strt, a point struck right after Start is saved at once. The editors autosave only a match they see as running, and the court list refetches a moment after each save. So a point struck just after Start stayed on that board alone, even reading "Synced", until the next edit. A start that has landed now counts as running for the snapshot it was made from: the editor's own Start match, or the court console's Up next card. A start the server refuses (court busy, a withdrawn competitor) does not.
  • bc-kenu, Undo encho. A kachinuki bout in encho gets 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-plcl, the Pools-tab editor stays open while you score (found while checking the second review's fixes; its bead's plan is implemented here). The Pools tab closed its editor after every save: Start match, each point as it saved, a kachinuki Record bout, and a finish that only queued offline, which looked like a saved result. It now stays open for all of those, as the Scores tab does, and closes on a saved finish or correction. The bracket panel no longer falls back to the result card when a correction did not save.
  • bc-wnam, already fixed by fix(bc-tmfn): operator fixes: team bouts, withdrawals, knockout rounds and the Bracket page #444 (e78ec2a removed the undefined wname). The panel it lived in is now removed (bc-kpnl). As the bead suggested, no-undef is now on for web-mobile, so an undefined name fails make js/lint. I checked that it flags a reintroduced wname.
  • The six clean-up items the /simplify pass 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

  • bc-kfup: recordDecisionTx's T105 guard and the K2 check-and-set in recordIneligibilityFromDecision both 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. Otherwise restoreIfWithdrawalRemoved reads a nil loser as "could not resolve" and restores nobody. reopenTargetStatus sends 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, because BarredSides ignores the match's own record, unless the restore moves its bar onto a chained fusenpai: reopenTargetStatusTx judges the bars the restore will leave (restoredStatus, shared with the restore). standingWithdrawalOf looks 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.
  • bc-kpnl: RemainingMatchesPanel, onWithdrawal and the console's withdrawalKey pin are deleted. makeSubmitDecision now takes one path for every decision: onAfterDecision when it is not a correction, else onClose. The court console's nextActiveAfter and the Scores tab's nextActiveFrom skip the competitor the decision barred, through one rule in ineligible_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 /decision response 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. makeSubmitDecision closes 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.
  • bc-htcr: submitHantei skipped the isComplete && !correctionReason gate that Finish uses. The gate was added after submitHantei was written. The prompt's confirm was hardcoded to buildPatch("completed"), so the editor now keeps the pending write in a ref.
  • bc-kten: domain.KachinukiTaishoPairing, its JS twin, their shared table, engine.KachinukiEnchoRefusal with its two handler calls, and the Encho tap's lineup re-read are deleted; team_lineup.go, lineup_resolver.jsx and the handler's kachinuki test are back to main. kachinukiEnchoOffered alone 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.
  • bc-kclr: two causes. The team editor left every unplayed kachinuki row out of its write, and mergeKachinukiSubResults keeps 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's modifiedAt against the edit time, both in the server's clock frame (server_clock.jsx, which api_client.jsx now uses too).
  • bc-sbq: RevertMatchToQueue and requeueBracketMatch now keep what a running write can carry and clear only the verdict and audit notes. A start could not simply "not send" a score: toBackendMatchResult always emits ippon arrays, and hansoku/flags are integers, so a start's empty payload is the same bytes as an operator clearing every mark. startPatch therefore sends a request-only startOnly flag, and engine.keepQueuedScore copies the stored score onto that write; the editors' own Start stays unflagged. matchShowsScore (match_shows_score.jsx) is the one display gate, applied inside IndividualScore and TeamScoreboard. bracketMatchStartedOrDecided is now status-based, so kept points on a queued knockout match no longer refuse a reopen upstream. The earlier discard counting (requeueLoss, the editors' onBoardChange reports) is removed. A finished later match: reopenBracketMatchKeepingTheFight clears 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).
  • bc-strt: ScoreEditorModal treats a scheduled match as running while the feed still shows the snapshot a landed start was made from (its own Start, or the console's started prop). The next snapshot, running or sent back to the queue (which stamps the match), ends it. A refused write throws inside editMatchScore and 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.
  • bc-kenu: bout mode offered Encho, which only adds a period, and hid the control that takes one back.

Scope notes:

Files changed

File What
internal/engine/eligibility.go alreadyBarredRefusal, the one rule both T105 halves ask; checkConcurrentIneligibility takes the decision; K2 writes no status for a chained fusenpai and returns the existing one
internal/engine/scoring_tx.go standingWithdrawalOf and restoredStatus: 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.StartOnly and keepQueuedScore (bc-sbq); a running engi write is not judged on its flag total
internal/engine/scoring.go Requeue keeps the score; a finished later knockout match reopened by a correction keeps its points, and its daihyosen row loses its winner (clearDaihyosenVerdict); the bracket write carries engi flags
internal/engine/kachinuki.go reopenTargetStatus and reopenTargetStatusTx: a default win whose barred side is barred by another match reopens to the queue, judged on the bars the restore leaves; reopenBracketMatchKeepingTheFight; bracketMatchStartedOrDecided is status-based; lineupInForce logs under its own name; the header says encho is the operator's call
internal/engine/errors.go The downstream refusal says the points are kept
internal/state/models.go, bracket.go, match.go, pools.go WithdrawnStatusAnnotation on both result types; CloneSubResults exported for keepQueuedScore
internal/mobileapp/handlers_match.go, handlers_viewer.go startOnly on /score (a 400 with any status but running); annotateEligibility (was annotateIneligibleSides) also stamps a completed default win with the withdrawn side's status, and its status read (anyMatchToAnnotate, was anyScheduledMatchHasBothSides) runs for those too; revert and requeue-blocker docs
specs/openapi.yaml Revert and requeue-blocker keep the score; startOnly and its 400; withdrawnStatus
internal/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.go The fusenpai chain (both doors, a running match, a corrected wrong kiken, the reopen target, the bar moving on); start keeps the score; the knockout re-seat; engi flags as entered; the cleared kachinuki bout; the withdrawnStatus stamp
internal/engine/{downstream_correction_guard,downstream_guard_review,pool_requalify,reopen_bye_unwind,reopen_withdrawal}_test.go A finished later match reopened by a correction keeps its points
internal/mobileapp/handlers_match_kachinuki_test.go As on main, plus a note that its pool-phase encho test stands under the 2026-09-26 ruling
web-mobile/js/admin_scoring_shared.jsx RemainingMatchesPanel removed; makeSubmitDecision takes one path for every decision and closes the decision form once it is stored; RecordedWithdrawal reads the withdrawnStatus stamp, 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 score
web-mobile/js/admin_scoring_individual.jsx submitHantei asks for the reason on a completed match; passes the form's setter to makeSubmitDecision; a landed start counts as running, a refused one does not (bc-strt)
web-mobile/js/admin_scoring_team.jsx Undo encho (bc-kenu); passes the form's setter to makeSubmitDecision; reports its start (bc-strt); kachinukiRowSent and the adopt guard keyed on the save time (bc-kclr); the Finish list uses joinList
web-mobile/js/admin_scoring_engi.jsx Autosaves the flags while the match is running, with the sync pill; closing a running match saves first
web-mobile/js/admin_shiaijo.jsx Send back to queue always offered, and its confirm says the score is kept; pickMatch sends a scored bout back; started for 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 matches
web-mobile/js/write_result.jsx, admin_competition_bracket.jsx, admin_pools.jsx, admin_schedule_score_editor.jsx writeKeepsEditorOpen, the one rule for when a host closes its editor (bc-plcl); the downstream dialogs say the points are kept
web-mobile/js/admin_schedule_score_editor.jsx, api_serializers.jsx startPatch sends startOnly; 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 left
web-mobile/js/patch.jsx, admin.jsx, app.jsx keepNewerMatches, 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's load() outside display mode). The public competition page and the display take refetches whole
specs/003-tournament-gap-closure/inputs/gaps_ARCHITECTURE.md, gaps_spec_brief.md The two lines that described the removed panel and an automatic resolution of a withdrawn competitor's matches now state the 2026-09-26 ruling
internal/mobileapp/deps.go, deps_test.go, handlers_decision.go CompetitionStore gains LoadCompetitorStatus; the /decision, /score and bulk-score writes stamp the result they push (stampWithdrawnStatus)
web-mobile/js/server_clock.jsx (new leaf), api_client.jsx The device's clock in the server's frame: one offset, shared by the API client and the adopt guard
web-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.jsx matchShowsScore, imported by every display that gates on it
web-mobile/js/admin_helpers.jsx, ineligible_match.jsx joinList exported; sideBarredByDecision and involvesCompetitor, the one rule both screens ask to skip a competitor a decision just barred; a comment that named the removed panel
web-mobile/.oxlintrc.json, __tests__/{api_client_export_pdfs,bout_side_squad_member}.test.jsx, render/admin_schedule_smoke.render.test.jsx no-undef: error; the three tests import beforeEach/afterEach from 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.jsx Console send back, switch, push and the advance past a withdrawn competitor; Encho on any tie and Undo encho; the cleared kachinuki bout and a later change adopted; hantei correction; the clear's copy from the stamp; engi autosave; every host hands back a write's result and the Pools tab stays open, and the Scores tab's advance passes over a withdrawn competitor or closes; bc-strt; every display shows a queued match as not started
web-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.jsx and render/msb_{centre_sep,name_cell}.render.test.jsx One decision path; the host-close rule; the points-kept copy; the display predicate; the side a decision bars; startOnly on the wire and the status the display gate reads
web-mobile/js/__tests__/render/remaining_matches_panel_award.render.test.jsx Deleted with the panel
web-mobile/js/__tests__/render/decision_form_closes.render.test.jsx New: in both editors, a stored withdrawal closes its form when the host does not move on, and one that did not land keeps it open
docs/user-guide/court-operators/recording-decisions.md Recording a withdrawal changes only that match, and the court then starts its next match, passing over the withdrawn competitor's; a fusenpai on a barred competitor's match; where a cleared match goes when the bar moves on
docs/user-guide/court-operators/scoring-a-match.md Send back to queue keeps the score; a queued match shows as not started; Queue it and reopen; a finished later knockout match keeps its points; engi flags; correcting a hantei
docs/user-guide/organisers/team-tournaments.md, docs/videos/kachinuki-demo.webm Encho is offered on any tied bout and is your call; Undo encho; the video recaptured to show it (chapter times as on main)
CLAUDE.md alreadyBarredRefusal, so it is not re-tightened; the encho reversal, so the gate is not re-added; annotateEligibility and withdrawnStatus; 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; writeKeepsEditorOpen

Screenshots

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:

Court console after a kiken on Aoki: Pool A Match 3 of 3, Endo vs Chiba, is live on the right; on the left, Up next is Doi vs Baba and Pool A Match 2, Endo vs Aoki, carries the notice Aoki withdrew: record the default win, with a Record default win for Endo button.

A knockout kachinuki tie between the two first fighters: Encho is offered, and the server stored it:

Kachinuki score editor, knockout final, bout 1 W1 against R1 tied with Tie (hikiwake) set; the footer offers Cancel, Record bout, Encho and a held-back End match.

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):

Please confirm dialog: Goto already played Match 3 (Final), which was built on this match's current result. Applying this correction reopens Match 3 (Final) with the new competitor in it: its winner is cleared and its points are kept, so check them and take back any that no longer apply. Buttons Cancel and Apply correction and reopen.

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:

Court console, engi final running: Shiro 1 flag, Aka 2 flags, Total: 3, Aka wins, and the Synced pill in the header.

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:

Score editor correcting Pool A Match 2, Endo vs Aoki with Fus. beside Aoki: Recorded: Fusenpai, Aoki did not appear, a Clear default win button, and The match goes back to the queue. Aoki is still withdrawn, so record the default win again.

Points saved from another device show on the court console as they are pushed (D for Shiro, K M for Aka):

Court console, Push Cup final running: Noda with D, Mori with K and M, Synced.

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:

Scores tab score editor still on Pool A Match 1, Chiba vs Aoki with Kiken beside Aoki, in correction, the withdrawal form still open, and the message Aoki withdrew in Pool A · Match 1 and cannot fight again. Record the default win for Endo.

After, it moves on and starts Pool A Match 3 (Endo v Chiba):

Scores tab score editor on Pool A Match 3 of 3, Endo vs Chiba, running and Synced, with Finish + Start Next.

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:

Scores tab score editor on Tab Cup Pool A Match 1, Chiba vs Aoki with Kiken beside Aoki, in correction, the Kiken - Voluntary form still open with its Record button, and the message Shiaijo B is running Pool A · Match 1. Finish it or send it back to the queue first.

After, the form is closed and the editor shows what was recorded:

The same editor after the fix: the decision buttons, then Recorded: Kiken - Voluntary, Aoki withdrew, with Clear withdrawal and reopen, and the same court-busy message.

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.):

Scores tab: Pool A Match 2 completed with the result circle circle vs Fus. beside Chain Aki, after Match 1's kiken.

Correcting that chained fusenpai offers Clear default win and says what it does:

Score editor in correction on the chained fusenpai: Recorded: Fusenpai, Chain Aki did not appear, a Clear default win button, and the text The match goes back to the queue. Chain Aki is still withdrawn, so record the default win again.

After clearing, the match is back in the queue with its default-win notice:

Scores tab: Pool A Match 2 scheduled again, with the notice Chain Aki withdrew: record the default win, and a Record default win for Chain Eli button.

bc-htcr. Tapping SHIRO wins on a completed hantei asks for the reason first:

Court console correcting a hantei given to Aka: the board shows M and K with Ht on Aka's side, the SHIRO wins button tapped, and the Reason for correction prompt with Scoring error selected and a Confirm button.

After confirming, the verdict moves to Shiro and the bracket follows (the next match now names Judge Bea):

Court console after the correction: the Completed row reads M Ht vs K, with Ht on Shiro's side, and the waiting final lists 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:

Send back to queue? dialog: Doi vs Baba will be returned to the upcoming queue. Any score entered is kept: starting it again carries on from there.

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):

TV board for Shiaijo A: Doi vs Baba at the top of Pool A with empty slots and a plain vs.

Public match card for Doi vs Baba with empty slots and a plain vs.

Started again from the Up next card, the board carries on with the M and the foul in place:

Court console, Doi vs Baba running again: Aka's M in its slot and Shiro's fouls at 1.

bc-kenu. Encho tapped twice by mistake: the header reads (E) OVERTIME x2 and Undo encho is offered beside Encho:

Kachinuki knockout, bout 5 in encho with Record bout, Encho and Undo encho in the footer.

Two taps of Undo encho later the bout is back to its tie, and only Encho is offered:

The same bout back to X with Tie (hikiwake) selected, and Record bout, Encho and a held-back End match.

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:

Court console on the build without the fix: Aka's M on the board and Synced, while the server's copy of the match has no points.

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:

Court console after reload, build without the fix: bout 1 shows an M on Aoki's side although it was taken back.

With the fix the bout is empty, reads Nothing recorded yet, and Send back to queue is offered again:

Court console after reload, fixed build: bout 1 has no mark, the footer reads Nothing recorded yet, and Send back to queue shows at the bottom.

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:

Score editor correcting Aoki's kiken: Recorded: Kiken - Voluntary, Aoki withdrew, a Clear withdrawal button, the text The match goes back to the queue. Aoki also did not appear for a later match, listed below, so they are still withdrawn because of it, and below it Pool A Match 2 Endo vs Aoki keeps its default win.

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:

Score editor on Goto vs Fujii with Start match still offered, and the message Shiaijo B is running Match 1 (Semifinals). Finish it or send it back to the queue first.

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):

Pools tab score editor, Chiba vs Aoki with M and K for Chiba, an Offline badge, the notice Not saved yet: will keep retrying until it lands, and Tap again to finish.

The bracket panel, a correction saved offline: the editor stays with its notice instead of falling back to the old result:

Bracket panel correcting Match M1, Hara vs Goto, with Goto's K cleared, the notice Not saved yet: will keep retrying until it lands, and Save correction.

Test plan

  • make go/test passes (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.
  • New/updated unit tests cover the change. Go: the fusenpai chain (both doors, a running match, a corrected wrong kiken, reopen target, the bar moving on), the taisho table and the encho refusal (engine and both handler doors). Render tests: console pin (held, released, re-pinned), requeue confirm and gating, hantei correction, board reporting (individual, team, engi), chained-fusenpai and barred-elsewhere copy, and Encho withheld, offered and re-read. Each was checked red with its fix reverted. Added since: send back keeps the score (pool, bracket), start keeps it (pool individual, team, engi, bracket, already running; an unflagged empty start still clears), the round trip, the knockout re-seat on reopen, the startOnly wire 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, the startOnly 400, 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); the withdrawnStatus stamp; the adopt guard adopting a later change from another device; the console showing a push at once and refetching once per burst.
  • Manual browser verification: real Chromium at the operator iPad profile (1180x820, coarse pointer) through test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442's harness, overlaid locally and not committed. I ran the five bead journeys (5 passed) and walked each fix through the UI: kiken, panel stays, Award; fusenpai on the next bout from the Scores tab, then Clear default win back to the queue; hantei to the other side via Correct, the reason, applied; Send back to queue confirm with a slowed court feed. Before the review fixes, the three full knockout/mixed journey files also ran: 15 passed, and the one failure was the kachinuki Send back to queue step described above. After the review fixes, I re-ran the five bead journeys (5 passed) and the kachinuki journey file, which now stops at J5's bout-4 encho, the test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442 step this PR changes on purpose (screenshot above). The individual and team journey files were not re-run in full after the review fixes. For the rulings added since, a scripted Chromium run through the real console: score, Send back to queue, the server keeps the score, the console, TV board and match card show it as not started, Start again carries on with the M and the foul, switching bouts sends a scored one back, and a kachinuki encounter with a fought bout is sent back and restarted with it (13/13). bc-strt was A/B-checked against the previous commit's build (point stored only with the fix), and bc-kenu through two harness captures (Undo appears after two Encho taps; two Undo taps return the tie). For the second review round, a scripted Chromium run through the Scores tab (14/14): clearing a kiken with a later no-show shows the queue copy, lands in the queue and moves the bar; a Start refused for a busy court keeps Start match, autosaves nothing and leaves the match scheduled; and Encho on the last bout after an out-of-order pick is offered and stored by the server. After the second review round: Record bout after Remove this bout, and a point struck right after Start, on the court console, Pools tab, bracket panel and Scores tab (21/21); the Pools tab kept open through Start, a point and an offline Finish that then landed, and the bracket panel's offline correction kept with its notice (9/9); test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442's journeys re-run as described in the scope notes. For the 2026-09-26 rulings, a scripted Chromium run against the final build (22/22). It checks Encho on a first-fighters tie, stored by the server. It checks a kiken with no panel, only that match changed, no refused start, and the court moved on past the withdrawn competitor; that run found the refused start, fixed in 7af3170. It checks the chained no-show's clear copy read from the stamp, and the correction dialog's copy with the final reopened, points kept and winner cleared. It checks engi flags autosaved, kept through send back and carried on after the restart, and a pushed score shown on the console. The console's feed fetches for three pushes 300ms apart are reported rather than checked (2, 3, 2 and 2 over four runs): the refetch waits a random 200 to 600ms, so pushes that far apart merge only by chance. The render test pins the merge, and fails without it. After the Scores tab fix, a second scripted run against its build (16/16). It covers a withdrawal and a no-show on the Scores tab, a no-show on the court console, and a team withdrawal on the court console. Each time the decision is recorded, no start is refused, and the match the court starts does not include the withdrawn competitor. For 90da239, a scripted run where another competition's match is running on the court (5/5): the withdrawal is recorded, the Scores tab stays on the match, the form is closed and the recorded decision shows. Against a build with only that fix taken out, the same run fails those two checks (3/5). After the third review round, a scripted run of the court console while another device changes the match (6/6): three quick scores show, the last one survives the refetch, and a send-back, a restart and a finish each replace the row.
  • Screenshots added above
  • Docs updated: 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/build and docs/check_links.py pass
  • Docs screenshots and videos: with bc-kten reversed, the recipes and kachinuki-knockout-tie-encho.png are 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.webm is 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=main reported every screenshot unchanged; the other two videos pass through no changed flow.
  • No new console errors or warnings: none in the capture runs. The docs/media run logs 404 on 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 high over 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:

  1. Partial lineup read the senpo as taisho. Refusal now needed proof that the pairing is not taisho against taisho. (Removed with the encho rule.)
  2. Kiken pin released at once on a second kiken. Its marker now reset whenever the pin was released. (Removed with the panel.)
  3. A kachinuki mark taken back hid Send back to queue for good. Fixed then; superseded by the requeue ruling, which offers the button always.
  4. The editor could offer an encho the server refuses. The tap re-read the lineups. (Removed with the encho rule.)
  5. Remaining matches could list the kiken's own match from a stale read. It was excluded. (Removed with the panel.)
  6. Clearing the origin withdrawal un-barred a competitor with a fusenpai elsewhere. The bar now moves to that match.
  7. Kiken reopen copy could promise running while the match went to the queue.
  8. Clear copy was wrong while the status fetch was pending.
  9. Engi flags were missing from the requeue confirm. Superseded by the requeue ruling, which removed the discard count.
  10. End match tooltip suggested encho where it is withheld. (The tooltip now follows the plain Encho offer.)
  11. Unneeded file reads on every encho autosave. (Removed with the encho rule.)
  12. A third copy of the list join replaced by 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 /simplify pass (reuse, simplification, efficiency, depth) over the production diff followed, in 5fb0f71, with no behaviour change. It moved matchShowsScore to a leaf every display imports (the window reads had opposite defaults). A start now leaves its empty scoreline off the wire. keepQueuedScore deep-copies through the existing clone helpers. It added one withdrawalStatus constructor and renamed bracketMatchStartedOrDecided. 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:

  1. Clearing a withdrawal re-barred a competitor the operator had already reinstated. Only a fusenpai carries a bar on now.
  2. A reopen whose bar moved onto a chained fusenpai landed running, where every score write is refused. It lands in the queue, and the editor says so before the tap.
  3. A refused start flipped the editor to running. The hosts hand back the write's result, and the editors mark only a start that reached the server. Side effect: the team editor's Record bout, after a Remove this bout, adopts the bouts the server returns on the court console, pools and bracket hosts too, as it already did on the Scores tab. That path was not exercised in the browser.
  4. A failed lineup re-read on the Encho tap wiped the sheet's lineups. The loaded ones were kept.
  5. Encho was refused on the real last bout after an out-of-order pick, leaving a tied knockout no way to finish. The rule then counted who had already fought.
  6. A refused encho is a 400, so the offline queue drops the whole board. Skipped at the time.
  7. The Encho tap had no in-flight guard. A second tap during the re-read was ignored.

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. startOnly was 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. lineupInForce logged 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 /decision response'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.jsx for 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):

  1. The withdrawnStatus stamp 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.
  2. A correction that reopened a finished team final kept its daihyosen row's winner, the team the correction took out. Starting the match again made the replacement team the winner of a bout it never fought. The reopen now clears that row's winner and hantei mark and keeps its points.
  3. The court console's refetch could put an older scoreline back over a newer push, when the fetch read the data just before a write committed. An editor that had caught up with the push then adopted it, and its next save wrote the lost point away. A refetch now keeps a row the court shows from a newer write, the rule the push already followed. The existing push test ended on exactly such a stale refetch but never checked the row afterwards; it does now, and fails without the fix.

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:

  1. The same stale refetch on the admin competition view and the public competition page. Both apply a push at once, then took the refetch whole, and the Pools-tab editor now stays open while scoring (bc-plcl), so it could adopt the older score. The court console's rule now lives in patch.jsx and 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.
  2. A pushed withdrawal carried no withdrawnStatus, so an editor reading the pushed row worded the clear wrongly until the refetch landed. The /decision, /score and 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:

  1. A rebuilt draw could be hidden. The keep-newer rule kept a held match over ANY older fetched copy. A draw discarded and drawn again reuses the match ids and carries no stamps, so a device that missed the discard (offline while it happened) would have kept showing the old draw's played matches. Only a stamped fetched copy can be the stale one now.
  2. The Scores tab could put an older score back. Its list takes 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 the open editor to adopt. The admin's updates and the app's load now merge through 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.
  3. Two tracked spec inputs contradicted the 2026-09-26 ruling (the removed panel, and an automatic resolution of a withdrawn competitor's matches). CLAUDE.md requires a tracked spec a change contradicts to be corrected in the same PR, so they now state the ruling. My earlier note that these inputs could stay as a historical record was wrong on that point.

A further review of those fixes (0516c32) found three holes in the keep-newer rule, each closed with tests that fail without the fix:

  1. Stamps come from two clocks. A client write carries the device's estimate of server time, which the server accepts up to 5s ahead; a reopen, requeue or send-back is stamped by the server's own clock. So a newer server stamp could read older than the write before it, and a match sent back a moment after a skewed Start stayed live on screen, which Refresh could not clear. Every server-stamped change moves the status, so a held match is now kept only when both copies are running. In the browser, with a device write stamped 2s ahead, a send-back takes the match off the court console; with the previous rule it stayed live. The Scores-tab and Pools-tab stale-score checks still pass.
  2. The TV display merged on reload, so a result it showed from an optimistic broadcast that the server then refused could outlive every reload. A display takes the fetch whole again.
  3. The public competition page merged on some refetches and not others. No score editor reads it (the self-run editor copies its match when it opens), so it takes refetches whole again. This walks back the public-page half of a907a62.

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):

  • The automatic next match stays in the competition. 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 took the next match on that court from any competition. They now take the next match of the same competition, as the court console always did; Prev/Next, which the operator taps, still move along the whole court. In the browser, with two competitions sharing Shiaijo B, Finish + Start Next stayed in the first competition; without the fix it started the other competition's match and switched the editor into it. The render tests fail without it.
  • A court busy with another competition's match needs no change. With one operator per court and one match per court, that case does not arise in normal use; the server's refusal stays as the safety net.
  • The app's text no longer brings in a doctor (9d324c4), following your remark that the operator persona has no use for it. The refusal for a competitor who withdrew injured now says "Reinstate Aoki if they can fight again, or record the default win for Endo."; the two injury help texts say they "can be reinstated if they are fit to fight again"; the withdrawal reason hint reads "e.g. injury, no-show". In the browser, the help text and the hint were checked on screen, and the message in the server's answer to a refused start. The organisers' rules reference states FIK Article 30 itself and keeps its wording.
  • Checked that every decision was acted on. Two had been acted on only partly and are now finished: the test(bc-e2eh, mp-yqxn.2): e2e harness and a clumsy-operator review of knockout and mixed #442 follow-ups are handed to its bead (bc-e2eh), and the rare re-bar you accepted is recorded in CLAUDE.md (79c6449), not only in this description.

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

gitrgoliveira and others added 5 commits September 25, 2026 21:23
…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>
@gitrgoliveira gitrgoliveira changed the title fix(bc-kfup,bc-kpnl,bc-htcr,bc-sbq,bc-wnam): the P1 operator fixes: kiken chain, hantei correction, requeue fix(bc-kfup,bc-kpnl,bc-htcr,bc-sbq,bc-wnam,bc-kten): P1 operator fixes and the kachinuki taisho encho rule Sep 25, 2026
gitrgoliveira and others added 2 commits September 25, 2026 23:22
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>
@gitrgoliveira gitrgoliveira changed the title fix(bc-kfup,bc-kpnl,bc-htcr,bc-sbq,bc-wnam,bc-kten): P1 operator fixes and the kachinuki taisho encho rule fix(bc-kfup,bc-kpnl,bc-htcr,bc-sbq,bc-wnam,bc-kten,bc-kclr): P1 operator fixes, the kachinuki taisho encho rule, and kachinuki clears Sep 26, 2026
gitrgoliveira and others added 3 commits September 26, 2026 09:36
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>
@gitrgoliveira gitrgoliveira changed the title fix(bc-kfup,bc-kpnl,bc-htcr,bc-sbq,bc-wnam,bc-kten,bc-kclr): P1 operator fixes, the kachinuki taisho encho rule, and kachinuki clears fix(bc-kfup,bc-kpnl,bc-htcr,bc-sbq,bc-wnam,bc-kten,bc-kclr,bc-strt,bc-kenu): P1 operator fixes, the kachinuki encho rule, and a queued match keeps its score Sep 26, 2026
gitrgoliveira and others added 9 commits September 26, 2026 13:07
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>
@gitrgoliveira gitrgoliveira changed the title fix(bc-kfup,bc-kpnl,bc-htcr,bc-sbq,bc-wnam,bc-kten,bc-kclr,bc-strt,bc-kenu): P1 operator fixes, the kachinuki encho rule, and a queued match keeps its score 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 Sep 26, 2026
gitrgoliveira and others added 7 commits September 26, 2026 18:37
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>
gitrgoliveira and others added 7 commits September 26, 2026 21:08
…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>
@gitrgoliveira
gitrgoliveira merged commit b78acf1 into main Sep 26, 2026
13 of 14 checks passed
@gitrgoliveira
gitrgoliveira deleted the bc-p1-operator-fixes branch September 26, 2026 21:59
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant