Skip to content

Owner review 2, lane A — retroactive score recording with explicit consent (2026-10-11) ​

Status: Draft · Date: 2026-10-11 · Lane A of owner review 2: the score recorder reworked from the ground up for retroactive recording by players, with the host as the override. Inputs: the score-call migrations at their latest definitions (00123, 00149, 00240, 00242, 00308, 00402, 00432, 00507, 00548, 00681, 00682 and the files they chain to), packages/features/sessions/src/{scorecard,match-board,match-card.tsx}, ko/sessions.ts and ko/signals.ts, library §10, the social board, and a benchmark of 14 products (read 2026-10-11). Nothing outside this file was changed; no screens are designed here.

Owner, review 2: "The score recorder isn't likely going to be a live-during-match type of recorder. It is going to be more or less a retroactive one where the recording happens once the match is finished. … emphasis being the retroactive recording for players or hosts? Hosts are there to override. So the primary responsibility of recording should fall on the players, and both parties should agree; so we need the explicit consent workflow."

This reverses review-1 ruling 19 (full-screen live recorder, rulings row 19). Log the reversal in the decision log; the lane-C panel research stays on file for a possible officiated live mode (§3.11).

Evidence tags. Code = read in this repo (path:line). Doc = primary page fetched and quoted. Snippet = search-result excerpt only (page blocked or script-rendered). Unverified = not found.

At a glance ​

#DecisionRecommendationWhy, in one line
1ModelPlayers record after the match, the other team confirms, the host overrides. ELO moves only when a result is accepted (confirmed, auto-confirmed or host-set), never at submission.Pending results are unrated in Playtomic, DUPR and UTR; a changed score later costs a global rating replay here (recompute_all_ratings).
2Confirm ruleSubmitter's team (deemed) + one opposing player. Any player in the match may dispute while it is pending. "Both parties" = both teams.Playtomic, USTA and Dinkzter state one opposing player; Rankedin, UTR, Pickleball.com prompt the opposing side with no all-members rule; only DUPR needs all four, with no deadline. Our DB bonus rule is already this.
3SilenceAuto-confirm 24 h after the latest submission, stated in the request, marked 자동 확정, no +10% bonus. Reminder at 12 h. Owner call: explicit-only strands results.Playtomic 24 h; USTA and Playmore 48 h. Explicit-only (DUPR) has no deadline.
4Dispute거절 keeps the claimed score (today it is deleted) with an optional counter-score; counter accepted = both agree; else the host decides; 7 days unresolved = 기록 제외.Playtomic cannot compel; USTA, Rankedin, Pickleball.com and Dinkzter hand disputes to an organiser.
5Host overrideHost or officer not playing that match can enter, confirm or void at any time. Final, tagged 호스트 확정, every player told, one 48 h contest.Rankedin, Reclub, USTA, DUPR and UTR all give the organiser the last word; UTR keeps officials out of the match.
6StatesAdd matches.status = 'awaiting_result' and ended_at. The next round no longer waits for scores.Today the scorecard's next-round CTA waits until no match of the round is in progress (use-live-tab-state.tsx:381-384).
7Live recordingRetire the ScoreRecorder and the live score feed. The LiveScore glance block becomes a state block (names, then the viewer's next action).Nothing writes live numerals any more; any confirmed RSVP can overwrite them today (00152:122-128).
8Fix before buildThe pending-verification push reads "대진 / 대진" and opens the host's board, which has no confirm control (§1.3 items 1 and 2).The consent request cannot be answered from the notification that carries it.

1. What ships ​

1.1 The eight questions ​

QuestionAnswerCode
Who can submit?Final, alone: the session host (sessions.created_by, 00432) or an active club owner, admin or match_director, through submit_match_score; match scheduled or in progress; a player who is none of these gets 42501. Live score: any confirmed RSVP of the session, even a non-player, through update_live_match_score; it edits the running score and completes nothing. Start a consensus: a player of the match or referee_id (call_match); the cron auto-call-sweep (every 5 min) also opens a call when the live score meets the target or the time cap passes. Correct a completed match: owner, admin or match_director only, not the host.00682:993-1003, 1072-1089; 00152:118-128; 00402:247; 00242:76-90, 241; 00681:441-447
Who must agree?Every player in the match (2 singles, 4 doubles); the caller counts as one. No team structure. The DB also holds a second rule, one per team (match_both_teams_verified), which gates the +10% confidence bonus.00682:1162-1164; 00681:554; 00130:52-83
On disagreement?Any player's reject_call deletes the whole call row (the claimed score is lost), sets score_status = 'disputed', and sends match_score_disputed to every other player and the caller. Nothing resolves it; the exits are a fresh call_match (resets to unverified) or a host submit_match_score. Rejecting a correction also flags the completed match 분쟁. The host is told only if they play or called.00402:175-221, 255-259
Timeout, auto-confirm?None. The call has no expiry column and no sweeper; only its signal expires, after 6 h. A call can sit forever until an edit, a reject or a host submit deletes it.00240:80; no migration deletes or expires match_score_calls on a timer (searched all)
Host or official (00432)?Enters the final score alone, no consent: match → completed, ratings at once, any pending call deleted. Host-chosen abandoned, non_competitive, walkover skip ratings and notify the other players. The host also starts matches (start_match). The host cannot resolve a dispute, never sees a rejected claim, and cannot propose a correction unless also staff.00432:46-58, 112-114; 00682:1093-1122; 00312:65-75
When does ELO apply?The moment the match is written completed: host submit or the last agreement, whatever score_status says. Friendly sessions and unrated outcomes skip. A correction wipes and replays every rating under an exclusive lock. The +10% bonus is unreachable today (§1.3 item 6).00682:467-484, 1124, 1197; 00681:525, 578; 00682:789-853
Which notifications?See 1.1b.
Which states?See 1.2.

1.1b Notifications that fire (signals pipeline; send-push runs every 2 min and applies per-category preferences, a minimum severity and quiet hours; critical severity bypasses quiet hours: send-push/index.ts:1-12).

EventSignal (category, severity)RecipientsExpiryCode
A call row appears (call_match, auto-call, correction)match_score_pending_verification (matchup, high)each player not yet in agreed_by6 h00240:20-86
Call deleted (agreed, rejected, edited, host submit)resolves those signals––00240:91-105
Rejectmatch_score_disputed (matchup, high; spec says critical, signal-system.md:434)all players and the caller, except the rejecter6 h00402:191-212
Tied at the time capmatch_sudden_death_needed (matchup, high)all players2 h00242:119-148
Match becomes completed (any path)match_completed (matchup, medium), then elo_changed (rating, low)the four players24 h00525:610-655; 00590:509-531
Host picks an unrated outcomematch_outcome_unrated (rating, medium)the other players7 d00682:1093-1122
Not sent todayhost on a dispute; players when a host enters a result (only the generic completed and elo signals); any reminder; any expiry notice

1.2 Match paths today ​

matches.status ∈ scheduled, in_progress, completed, cancelled (match.entity.ts:233) · score_status ∈ unverified, verified, disputed (00057) · outcome ∈ decisive, draw, incomplete, abandoned, non_competitive, walkover.

PathMovesRatingsscore_statusMark left behind
Host directscheduled or in_progress → completedat onceunverified, forevernone (no source column)
Live consensusin_progress, score edited by any RSVP, call_match, all agree → completedat the last agreementverifiedverified_by = the last agreer only (00682:1192)
Rejectstays in_progressnonedisputedcall deleted
Correctioncompleted → completed, score_revision + 1full replay at the last agreementverifiednone
verify_match_scorecompleted; a player appends themself+10% if one verifier per teamverified when all fourno UI calls it (match.supabase.ts:272)

1.3 Defects the rework must not inherit ​

  1. The consent push is unreadable. TITLE_KO has no signal.matchup.scorePending.title (nor suddenDeath, matchOutcomeUnrated), and the generic body resolver reads only clubName, title, sessionTitle, postTitle and amounts; the payload carries court, round and scores. Result: title "대진", body "대진" (send-push/notification-copy.ts:54-95, 195-231). push-copy-sync.test.ts:71 checks push keys ⊆ client keys, never the reverse, so it cannot see this.
  2. And it opens the wrong screen. matchup + club + session routes to routes.matchBoard (signal-routes.ts:99-101); the 대진표 has no useAgreeToCall, ScoreCallSheet or call lookup (searched match-board/, match-card.tsx). The control lives only on the scorecard's live tab.
  3. The signal expires; the call does not. Signals expire after 6 h, the call never does (00240:80). After 6 h the call is gone from Home and push (push only claims unexpired signals, 00688:136, 160) but still blocks a fresh call_match.
  4. The host sees a button that always fails. canCall = (isParticipant || isHost) (live-match-row.tsx:146-151) but call_match admits only players and the referee (00402:247); referee UI was removed, so a non-playing host gets a raw English error.
  5. A rejection erases the evidence. The claimed score is deleted with the call (00402:221); rejected_by is never written; the host is not notified.
  6. The +10% bonus is dead. agree_to_call appends only the last agreer to verified_by, so match_both_teams_verified is false (00682:1192, 00130:52-83); verifyScore has no caller. Yet Home shows 확인 필요 on every completed unverified match a player has not verified (session-closeout.ts:51-57, recap-note-badge.tsx:33), which is every host-entered result, with no action to clear it.
  7. The 대진표 수정 button on a completed match is a dead end. It calls submit_match_score, which refuses completed matches with hint already_completed and no localized copy (match-card.tsx:325-344, 515-526 → 00682:1072-1089).
  8. Three gates for one act. 대진표 inputs: host or schedule or matches manager (use-match-board-data.ts:189-190); server submit: host or owner, admin, match_director; correction: staff only, and the scorecard offers it to canManageClub (use-live-tab-state.tsx:288).
  9. Anyone can overwrite a live score. Any confirmed RSVP, any match (00152:122-128); check_auto_call would open a 0-0 call on a tied time-up with current_score_stands (00242:150-164).
  10. A missing score blocks the host. The scorecard's start-next-round CTA shows only when no match of the current round is still in progress (currentRoundInProgressCount === 0, use-live-tab-state.tsx:381-384). completed_at = now() also makes entry time the "종료 HH:MM" and the duration (completed-row.tsx:106).
  11. Players cannot record a best-of-3. agree_to_call writes the displayed score only, never match_sets, team*_sets_won or format_id (00682:1183-1194); only host submit and corrections carry p_sets.

2. Benchmark: retroactive result reporting with confirmation ​

Read 2026-10-11. Links are in §4. Playtomic's help pages refuse scripted fetches; their text was read from the help centre's public JSON API (same article ids).

2.1 The rules ​

ProductWho reportsWho confirmsLimit → at the limitDispute pathOrganiser or host overrideTag
Playtomic (padel, tennis)any player; "up to one week after playing"; outside-app uploads within 30 daysone opposing player validates at once24 h → "automatically validated"rejected = "suspended", must be re-entered; "Playtomic does not have the authority to compel players"; match chatnone for rejections; after acceptance an edit goes through a support form, other team must agree, ≤ 4 working days; 0-0 voidsDoc [pt-1][pt-2][pt-3]
DUPR (recreational)any participant (not stated)all participants "must affirm"none stated"upon rejection by any participant, the match is automatically removed"recreational: none. Verified events: organiser submits within 48 h, corrects; DUPR "will not modify match results without explicit permission from the event organizer"Doc [dupr-1][dupr-2]
UTR (self-posted)any player, casual onlythe opponent confirms or deniesnone stateddenied = "Rejected", visible only to the denier; poster may delete any timeVerified events: an official present "to mitigate disputes", results within 48 h, formal review within 48 hDoc [utr-1]; Snippet [utr-2]
USTA TennisLink (adult leagues)coordinator, captain or any playeropposing team, "any team member"48 h → "automatically … confirmed by the 48 hour rule"contact the League Coordinatorcoordinator edits or disputes; Championships: coordinator entersDoc [usta-1]
Rankedin (Club League)"one of the players""the opponent"none statednot described"Unconfirmed matches … can be confirmed by the Club League admin"; admins can enter resultsDoc [rk-1][rk-2]
Tennis Australia Match Centrehome teamaway team: "Confirm or Dispute"losing team 48 h (junior rules); "deemed official" 72 h after startdispute → the Tennis Association investigatesassociationSnippet [ta-1]; Doc [ta-2]
Playmore Challenger (squash, tennis ladder)playersnobody: contested or not48 h → "an uncontested result confirms itself"not describedstaff adjust positionsDoc [pm-1]
Pickleball.com (events)winning team by defaultopposing team: Confirm or Disputedirector sets minutes; 3 min "a good starting point", 1 min for eliminationboth teams go to the tournament deskdirector switches player scoring on or off per eventDoc [pb-1]
Dinkzter (pickleball ladder)one winning-side playerone losing-side player ("only one player per side")3 h → automatically accepted"the organizer reviews and resolves the dispute"organiserDoc [dz-1] (club handout)
Reclub (pickleball, badminton, SEA)players, if the admin enables itadmin finalisesnone–admin only finalises or resets; finalising pushes to leaderboard and DUPRDoc [rc-1]
PodPlay → DUPR challengeautomaticconsent in advance: "Submit Results" chosen at the start––club can make it the defaultDoc [pp-1][dupr-3]
Battlefy (esports)the opponent reportsthe other side confirms or flagsorganiser timer → auto-confirmflag alerts adminsorganiser sets the timerDoc [bf-1]
US Squash ClubLockercaptains (club guide, Snippet)not documented––leagues must enter results there to countDoc [us-1]; Unverified beyond that
Matchi––––no public result-confirmation page foundUnverified

2.2 How pending results show, and how ratings treat them ​

ProductPending display and notificationUnconfirmed results and ratings
PlaytomicAfter the match ends "you'll receive a notification to confirm players and enter results"; a rejection is "in the match". Push for the opponent's accept prompt: Unverified.Only competitive matches move a level; "results were rejected by one or more players" = no change [pt-3]. Casual never counts.
DUPRIn-app notification to every participant; the match sits in "pending validation"."the match does not affect any player ratings" until all validate. Verified club, league and tournament matches "have a greater impact … than self-posted matches" [dupr-4].
UTREmail "Pending results" and the pending list on the home dashboard; denied results visible only to the denier.Counts "by the next day" once confirmed; denied "do not impact the ratings"; self-posted feed UTR only, never the Verified UTR [utr-3].
USTAThe system "will indicate which user completed the verification". Pending display and email: not described.Auto-confirmed at 48 h, then counts.
Tennis Australia"Review Score" card in the opponent's To Do list (Snippet).Result is official once confirmed or deemed (Snippet).
Matchspace"Unverified scores will be marked in red." [ms-1]Not stated.
BattlefyA window pops up for the other side with a countdown.Not applicable.
Rankedin, Playmore, Dinkzter, Reclub, ClubLocker, MatchiNot documented in the pages read. Pickleball.com: the opposing team "receives a prompt"; disputed or unconfirmed scores send players to the desk.Reclub: unfinalised matches stay out of the leaderboard and DUPR until the admin finalises. Playmore: counts at 48 h. Others: not stated.

2.3 What the evidence says ​

  • One opposing player is the norm. Three products state it outright (Playtomic: "one of the opposing team's players"; USTA: "any team member"; Dinkzter: "only one player per side"). Four more prompt the opposing side with no all-members rule (Rankedin, UTR, Pickleball.com, Tennis Australia). The one all-participants flow, DUPR recreational, states no deadline, so one absent player holds every result back.
  • Silence confirms, after notice. 24 h (Playtomic), 48 h (USTA, Playmore, Tennis Australia juniors), 3 h (Dinkzter, same-day ladder), minutes (live events). Where there is no timer (DUPR recreational, UTR, Rankedin) an unconfirmed result simply waits, or an admin confirms it.
  • No peer system picks a winner between two claims. Playtomic suspends the result and says it cannot compel, DUPR removes the match, UTR excludes it; the organiser-run products (USTA, Dinkzter, Pickleball.com, Rankedin) hand it to a person.
  • Unconfirmed is unrated; organiser-entered is never weighted lower. DUPR and UTR weight organiser-verified results higher or keep them in a separate rating.
  • Pending is shown where the actor already looks (dashboard card, To Do card, red mark) and pushed once at submission; Playtomic also pushes the entry prompt when the match ends.
  • The override is bounded: an official who does not play and a short contest window (UTR verified events: official "not participating in the event", 48 h review; Snippet).
  • Pre-agreement exists (PodPlay/DUPR challenge matches: opt in at the start, no confirmation afterwards). It needs a kiosk at the court; not usable for a retroactive flow, noted only.

2.4 Korean apps, and the gaps ​

No Korean tennis or badminton app I could read documents opponent confirmation, a deadline or a dispute path. All are organiser or scorekeeper tools: 스포넷 (결과 기록 자동 업데이트), 쉬운 대진표 ("선수가 직접 점수를 입력하면 모든 참가자 화면에 즉시 반영됩니다. 운영자 개입 없이 경기가 진행됩니다"), MyTennis, 위꾹 스코어밴드, 베이스라인, 땡큐볼 테니스, 배프, and the federation's BKPLAY (read-only results) [kr-1]. Absence of a described flow is not proof of absence in-app (Unverified). Gaps: Matchi (no public page), ClubLocker beyond "captains enter results", DUPR's current in-app timers (the validation post is from 2024), Playtomic's opponent-side push copy, Tennis Australia pages (blocked; snippets only).

3. Design brief ​

3.1 Rules of the model ​

  1. Players record, the other team confirms, the host decides when they cannot. The host's normal day has no scoring in it.
  2. A result is a proposal until accepted. Accepted = an opponent said 동의, the clock ran out, or the host set it. Only accepted results enter ratings, history, 기록, share cards.
  3. Every request states its own consequence ("답하지 않으면 24시간 뒤 이 점수로 확정돼요"). That sentence is what makes silence consent.
  4. Consent is visible. Each accepted result shows how it was accepted (동의, 자동, 호스트). Only explicit consent earns the +10% bonus.
  5. No evidence is deleted. Claims, objections and overrides are kept in an event log.
  6. Nobody judges their own match. A host who plays is a player in that match.

3.2 States ​

StateUI labelmatches.status (new)Call rowscore_statusRatingsEnters the state by
S1 playing경기 중in_progressnoneunverified–host starts the round
S2 finished, unrecorded점수 입력 전awaiting_resultnoneunverified–host 라운드 종료, the next round starting, or the rotation timer
S3 submitted, waiting확인 대기awaiting_resultstatus = pending, expires_atunverified–a player's 확인 요청 (from S1 or S2)
S4 confirmed확정completedclosedverifiedapplied nowopponent 동의, or 24 h, or host
S4h host-entered호스트 확정completed–verifiedapplied nowhost 대신 입력, 바로 확정, 확정하기
S5 disputed분쟁awaiting_resultstatus = disputed, claim keptdisputed–any player's 거절
S6 resolved without a rating기록 제외completed, outcome = non_competitiveclosed–nonehost 기록 제외, 7 days unresolved, +30 days with no result

Transitions: S1 → S2 → S3 → S4 · S3 → S5 → S3 (once, via counter) or S4h or S6 · S2 → S4h · S3 → S4h (바로 확정) · S3 → S2 (요청 취소) · S4 → correction (existing consent flow, §3.6).

3.3 Who can do what ​

StateSubmitterSubmitter's partnerOpponentsHost or officer (not in the match)Everyone else
S1 경기 중–––라운드 종료, or 대신 입력 earlynames and 경기 중
S2 점수 입력 전점수 입력점수 입력점수 입력대신 입력, 알림 보내기"점수 입력 전", no score
S3 확인 대기수정 (resets clock), 요청 취소거절동의, 거절바로 확정, 수정score + 확인 대기, read-only
S4 확정수정 요청 (7 d)수정 요청수정 요청수정 (proposes a correction)score + 확정
S4h 호스트 확정–이의 (48 h, once)이의 (48 h, once)re-enter alone within 48 hscore + 호스트 확정
S5 분쟁다시 입력 (once)다시 입력 (once)다시 입력 (once)확정하기, 직접 입력, 기록 제외"분쟁", no numerals
S6 기록 제외–––다시 입력 (rare)"기록 제외"

Players only: a confirmed RSVP who is not in the match has no write access (today they do). A host who plays acts as a player; their dispute goes to another officer, else S6 at 7 days.

3.4 The confirm rule for doubles ​

Recommended: one opponent, never the partner. The entering player's team is deemed to agree; one of the two opposing players settles it; any of the other three may dispute while pending; the first action wins (row lock).

  • Evidence. One opposing player, stated: Playtomic ("if one of the opposing team's players accepts … it will validate immediately"), USTA ("any team member may complete the confirmation"), Dinkzter ("only one player per side"). The opposing side prompted once, no all-members rule: Rankedin and UTR ("the opponent"), Pickleball.com, Tennis Australia. All four: DUPR only, no deadline.
  • "Both parties" is met. The two parties are the two teams; each is represented by a named player, and the submitter's own partner can still object.
  • Load. A 4-court, 8-round session has 32 matches. All-agree is 3 agree-taps per match = 96. One opponent is 32. A player submits about 2 matches a session and confirms about 2.
  • It matches the DB. match_both_teams_verified already means "at least one verifier per team" (00130:52-83). Explicit confirmation → verified_by holds the submitter and the confirmer → the existing +10% bonus fires. Auto-confirmed and host results keep base ELO.
  • A confirmation names the version it saw. If the submitter edits a pending claim, earlier responses are void, the clock restarts, the opponents are re-notified.
  • Taps. Submit: open, quick pick, 확인 요청 = 3. Confirm: the AnswerPair in the row = 1 (2 from the push).

3.5 Time limits ​

IntervalValueBasis
Entry prompt to the fourwhen the match reaches S2; one reminder at +2 h to those who have not enteredPlaytomic pushes the entry prompt when the match ends
Players may enteruntil session date + 7 d, then host or officer only; +30 d → 기록 제외Playtomic "up to one week"; 30-day upload window
Confirm window24 h from the latest submissionPlaytomic 24 h (closest product: casual, mobile, after the match)
Confirm reminderonce, at 12 h, to opponents who have not answereddesign; lands in the day for a late-night entry despite quiet hours
Auto-confirmat 24 h if no disputePlaytomic; USTA and Playmore use 48 h for multi-day leagues
Host decision after a disputereminders at 24 h and 72 h; unresolved at 7 d → 기록 제외DUPR removes a rejected match; Playtomic voids with 0-0
Contest of a host-entered result48 h, once per playerUTR formal review 48 h; USTA 48 h
Correction request after acceptance7 dbounds the global rating replay each accepted correction costs

All numbers live in one policy object (not per-RPC literals), editable per club: autoConfirmHours = 24 (null = explicit only), reminderHours = 12, entryWindowDays = 7, contestHours = 48, disputeVoidDays = 7, correctionDays = 7.

3.6 Dispute flow ​

  1. 거절 (an opponent, or the submitter's partner) opens a sheet: 점수가 달라요 · 선수가 달라요 · 경기를 하지 않았어요, and for the first an optional 내가 아는 점수 in the same ScoreEntry table. No destructive Dialog: nothing is lost.
  2. The row becomes 분쟁. Auto-confirm stops. The claim and the counter are kept on the call row. The submitter, the partner and the host are told.
  3. Settle once between players. If a counter was given, the submitter can take it with one tap; both teams then named the same score, so it finalises as S4 (동의). Otherwise →
  4. The host decides: 확정하기 on either version, 직접 입력, or 기록 제외 (also the exit for 기권 or a match not played). The host never decides a match they play in.
  5. Unresolved at 7 days → 기록 제외 and a notice to all four.
  6. After acceptance a player may request a correction within 7 days. It goes to the host as a task. The host may propose it through the existing correction flow (all four players agree, ratings replay); within 48 h of a host-entered result the host may simply re-enter it.

3.7 Host override ​

  • What the host can do: enter for the players (S2), accept a pending claim as is (S3), decide a dispute (S5), void (S6), or correct (S4).
  • How it is marked: a Tag 호스트 확정 (neutral weak, text, never colour alone) beside the score, a Caption naming who and when, and matches.result_source = 'host', result_recorded_by; the event log keeps what the players had claimed.
  • Who is told: every player in the match by push, including the side that lost the argument (match_result_host_recorded, high: it changes their rating); spectators see the Tag only. The submitter is told which version stood.
  • Limits: not for a match the host plays; no +10% bonus; one 48 h contest; every override is in the event log (a host_reliability_events row, 00307, is the existing hook if the owner later wants host accountability).
  • Per-session switch (optional): 호스트만 기록 turns player entry off for 대회 or league sessions (Reclub, Pickleball.com, Rankedin all have it). Default: players record.

3.8 Where each step happens ​

Step대진표 (host)스코어카드 (players)일정 상세Push
Round endsround bar: 라운드 종료; starting the next round ends the previous onelive tab: my match first, button 점수 입력strip chip 점수 입력 필요 (new kind, rank 30, beside score_confirm)match_result_requested to the four → scorecard, that match
Enter대신 입력 per row (tonal sm), same ScoreEntry SheetScoreEntry Sheet (replaces score-edit-sheet, ScoreCallSheet, the correction table)––
Waitingrow Tag 확인 대기 with who and the deadline; 바로 확정submitter: Tag + "상대 팀 확인을 기다려요" + deadline; sticky callout (generalises pendingCallCallout, use-live-tab-state.tsx:488-530)host chip host_action (rank 40) adds "확인 대기 N경기"reminder to opponents at 12 h
Confirm–a 확인할 점수 section at the top (a request list, as AnswerPair requires): the row carries 동의 · 거절, a bare row tap never answersparticipant chip 점수 확인 필요 (exists, home.ts:161)match_score_pending_verification → scorecard, that match (today: host's board)
Disputerow Tag 분쟁; Task Cell "분쟁 N" opens the resolve Sheet"호스트가 확인하고 있어요"host chip; Home disputed banner (exists) now ends in an actionmatch_score_disputed to submitter, partner and host
Host decides확정하기 Sheet: version A · version B · 직접 입력 · 기록 제외Tag 호스트 확정 + Caption + 이의 있어요 (48 h)–match_result_host_recorded to the four
AcceptedTag 확정Tag 확정 + Caption (동의, 자동, 호스트)chip clears; Home 확인 필요 badge clears (now backed by a real action)match_completed (exists); match_result_auto_confirmed low, in-app only

Participants must land on the scorecard, never the host's board: change signal-routes.ts:99-101 for players, keep the board for officers.

3.9 Copy (ko, 해요체; new keys unless marked) ​

WhereCopy
S2 row, playerbutton 점수 입력 · Caption 경기가 끝났어요. 점수를 입력해 주세요
S2 row, host / spectator선수가 입력하는 중이에요 + 대신 입력 · spectator: 점수 입력 전
ScoreEntry CTA, player / host확인 요청 / 점수 저장
ScoreEntry helper, player상대가 동의하면 확정돼요. 24시간 안에 답이 없으면 자동으로 확정돼요
ScoreEntry helper, host저장하면 바로 확정되고 선수들에게 알려요
After sending (Toast, 되돌리기 8 s)결과 확인 요청을 보냈어요 (exists, liveScore.callInitiated)
S3 Tag / submitter line확인 대기 · 상대 팀 확인을 기다려요 + 23시간 뒤 자동 확정 (two facts, MetaSegments)
S3 opponent row / sheet김민준 님이 6-4를 입력했어요 · title 점수를 확인해 주세요 · hint 답하지 않으면 24시간 뒤 이 점수로 확정돼요 · 동의 · 거절 (exist, common.agree, common.decline)
거절 Dialogtitle 점수를 거절할까요? · body 호스트가 두 팀의 점수를 보고 확정해요. · 취소 / 거절
Dispute sheettitle 어떤 점이 달라요? · 점수가 달라요 · 선수가 달라요 · 경기를 하지 않았어요 · field 내가 아는 점수 · 보내기
S5 Tag / line분쟁 (exists) · players: 호스트가 확인하고 있어요 · host: 두 팀의 점수가 달라요
Host decide Sheettitle 점수를 정해 주세요 · rows {이름}이 입력한 6-4 · {이름}이 말한 6-3 · 직접 입력 · 기록 제외 · CTA 확정하기
S4 Tag / Caption확정 (exists) · 상대가 동의했어요 · 답이 없어 자동으로 확정됐어요
S4h Tag / Caption / contest호스트 확정 · 호스트가 입력했어요. 이의가 있으면 48시간 안에 알려 주세요 · 이의 있어요 → 호스트에게 전했어요
S6 Tag / Caption기록 제외 · 레이팅에 반영되지 않아요
Push: entertitle 점수를 입력해 주세요 · body 3라운드, 2번 코트, 경기가 끝났어요
Push: confirm (needs a TITLE_KO entry)title 점수 확인이 필요해요 (exists in-app) · body 3라운드, 2번 코트, 6 : 4 확인 필요
Push: remindertitle 점수 확인이 아직 남았어요 · body 12시간 뒤 자동으로 확정돼요
Push: dispute, to hosttitle 점수 확정이 필요해요 · body 3라운드, 2번 코트, 두 팀의 점수가 달라요
Push: host set it, to playerstitle 호스트가 점수를 확정했어요 · body 3라운드, 2번 코트, 6 : 4
Push: auto-confirmed (in-app only)title 점수가 자동으로 확정됐어요 · body 확인 없이 24시간이 지났어요
일정 상세 Notice (player / host)내 경기 2개의 점수를 확인해 주세요 + 확인하기 · host: 점수 확인이 필요한 경기가 있어요 + facts 점수 입력 전 3경기, 확인 대기 2경기, 분쟁 1경기

Wording: 동의 · 거절 stay the shipped pair (terminology guide: 거절, never 거부, for score claims). A plainer 맞아요 · 달라요 is possible but would be new vocabulary.

3.10 What ships, what needs backend work ​

Reusable as is: match_score_calls (one active row per match; sets, format_id since 00681); private.derive_match_sets and validate_match_sets; host submit_match_score with idempotency key, rate limit submit_score 60/min and the offline queue; private.apply_match_ratings (idempotent); the signals trigger pattern (00240); the score_confirm attention chip and its bulk query (Protocol A); HomeDisputedAlertCard; confidence_bonus machinery; the correction consent flow for accepted results.

Needs backend work (next migration is 00691; entity → mapper → adapter → migration → types in one commit):

AreaChange
matchesstatus CHECK += awaiting_result; ended_at (rule like 00645: awaiting_result and completed require it; backfill ended_at = completed_at); result_source ∈ players, auto, host; result_recorded_by; result_confirmed_at. Reuse verified_by for explicit confirmers only. Decide the rating event time: recommend completed_at = ended_at (play order for replay and history) and result_confirmed_at for acceptance.
match_score_callsstatus ∈ pending, disputed; expires_at; version; dispute_reason; disputed_by; relax match_score_calls_sets_shape (00681:142-145) so confirm calls may carry sets and format_id. The claimed score is mirrored to matches.team*_score while awaiting_result (so score-driven standings and pairing can read it as provisional) and cleared on dispute or withdrawal.
New match_result_eventsappend-only: match, actor, event (entered, confirmed, disputed, withdrawn, auto_confirmed, host_set, voided, contested), scores only, timestamp. RLS: club members read; no direct writes; explicit GRANT in the same migration.
RPCs, newend_round(session, round) (host gate of start_match, 00312:65-75) and cron auto_end_matches (rotation minutes + grace); propose_match_result(match, scores, sets, format_key, idempotency_key) (players only; replaces update_live_match_score + call_match); resolve_match_result(match, action ∈ accept, enter, void) (host or officer, never a player of the match); accept_counter; crons auto_confirm_match_results (10 min), remind_match_results, sweep_disputed_match_results.
RPCs, changedagree_to_call → one opponent; finalise through the same writer as corrections (00681:557-565: sets, format, revision), apply ratings first, then write verified_by (the bonus trigger flips confidence_bonus_applied even when no elo_history rows exist yet, 00682:681-787, 00130:52-83); reject_call → keep the row, store reason and counter, notify the host, flag the match disputed only for confirm calls; private.submit_match_score → accept awaiting_result, stamp result_source = 'host', emit match_result_host_recorded, refuse a caller who plays in the match; propose_score_correction → admit the session host (00681:441-447) and bound it to the correction window.
Retireprivate.check_auto_call and cron auto-call-sweep (00242:241); revoke update_live_match_score from authenticated; delete the dead verifyScore port and adapter method.
Signals and pushnew types in signal.entity.ts (near :111, :355), ko and en signals.ts, and send-push/notification-copy.ts (titles plus a score-aware body from courtNumber, roundNumber, scores); signal expires_at follows the call's deadline, not 6 h; signal-routes.ts sends players to the scorecard.
Clientone pure deriveMatchResultState(match, call) as the single source for Tag, action and copy (extends score-confirm-selectors.ts); matchStatusSchema + MatchStatusBadge + ScoreStatusBadge; live tab filter, getDefaultTab, useViewerRoundContext and session_matches_can_complete learn awaiting_result; drop the currentRoundInProgressCount === 0 gate; new module-scope mutation hooks.

Ship with the build (prevention): pgTAP for the confirm matrix (singles, doubles, partner cannot confirm for opponents, first action wins, auto-confirm, no rating before acceptance, bonus ordering); the reverse push-copy sync (every client signal.matchup.*.title has a push title); a knip or lint check that no port method is client-orphaned; one seeded run-scenario chain per state (S2 to S6) for device QA.

3.11 The glance block and the full-screen recorder ​

  • ScoreRecorder (library §10): retire. Move it to §11 Retired as "deferred, no owner demand". Remove the recorder frames from the social board (flat-v2-lib-social.jsx, LibSocRecorder), the expo-keep-awake dependency note, and the live use of update_live_match_score. If the owner later wants officiated live counting for 대회, build it as an official's tool whose result is host-set (S4h); the lane-C panel pattern is the starting point.
  • LiveScore glance block: becomes a state block (same Block grammar, same Live row above it). Drop live numerals, the serve glyph, the tiebreak column and "호스트가 점수를 기록하고 있어요". Zone A (eyebrow with chevron) names this viewer's next action: 점수 입력, 점수 확인, or the court name. Zone B shows team names only until a claim exists, then numerals plus the state Tag. No control is drawn inside; the block is the door. Boards to redraw: LiveScore in flat-v2-lib-surfaces.jsx; ScoreEntry and ScoreRecorder in flat-v2-lib-social.jsx.
  • ScoreEntry stays (set table, quick picks, tiebreak row, progressive rows) with two additions: the viewer's team on the top row (stored order stays by slot) and CTA 확인 요청 for players. Library edits also touch: ResultRow tag set (확인 대기 and 호스트 확정 are in-session only; history shows confirmed results), Task Cell counts, AnswerPair use in the confirm row, IA flow 7 (H2 becomes the AnswerPair row).
  • Scope: rotation, 번개 and 정기 sessions. The tournament board has its own winner-pick RPC and is unchanged.

3.12 Owner decisions, traps ​

Decisions (recommendation first):

  1. Silence = consent? Recommend yes: 24 h, stated in the request, marked 자동 확정, no bonus. Your words say "explicit"; explicit-only is autoConfirmHours = null and leaves results stuck until the host taps 바로 확정, which at 32 matches a session is the host's old job back. A host bulk action ("확인 대기 N경기 모두 확정") is the middle path if you want explicit-only.
  2. Host-entered results final at once, contestable once? Recommend yes. The alternative (host entries also need a confirm) makes the override toothless.
  3. A host who plays? Recommend: acts as a player; disputes go to another officer, else 기록 제외.
  4. Friendly sessions (no rating)? Recommend the same flow: the record still matters; only the rating step is skipped.
  5. Score-driven formats (KDK, 순위매칭전, 코트승급전) read pending claims as provisional for standings and the next pairing; ELO still waits.
  6. Rating event time: completed_at = ended_at.
  7. Show a predicted ELO change on the request or the confirm? Recommend no: it invites rating-motivated rejection. The shipped live sheet shows it; the actual change arrives as elo_changed.
  8. A new match status, or ended_at alone? Recommend the status (awaiting_result): in_progress reads as "playing now" (진행 중 is the live label everywhere, terminology guide), and the live tab, the 지연 badge and check_auto_call would otherwise treat finished matches as live. The cheaper path keeps in_progress and adds only ended_at; it saves the enum change (the list of touched places is in the traps below) and keeps those three wrong.

Traps found while reading: the bonus trigger order (§3.10); the 6 h signal expiry against a 24 h window; recompute_all_ratings takes an exclusive lock and replays every match, so no dispute or override path may run it before acceptance; completed_at is read by history, recap and streaks, so changing its meaning needs those reads checked; a new match status touches the enum, CHECK, mapper, badge, live filters and the session cascade (00683:82-96).

4. Sources ​

Read 2026-10-11. Doc unless marked.

Markdown remains the source of truth. Run yarn docs:check before handoff.