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
| # | Decision | Recommendation | Why, in one line |
|---|---|---|---|
| 1 | Model | Players 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). |
| 2 | Confirm rule | Submitter'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. |
| 3 | Silence | Auto-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. |
| 4 | Dispute | 거절 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. |
| 5 | Host override | Host 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. |
| 6 | States | Add 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). |
| 7 | Live recording | Retire 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). |
| 8 | Fix before build | The 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
| Question | Answer | Code |
|---|---|---|
| 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).
| Event | Signal (category, severity) | Recipients | Expiry | Code |
|---|---|---|---|---|
A call row appears (call_match, auto-call, correction) | match_score_pending_verification (matchup, high) | each player not yet in agreed_by | 6 h | 00240:20-86 |
| Call deleted (agreed, rejected, edited, host submit) | resolves those signals | – | – | 00240:91-105 |
| Reject | match_score_disputed (matchup, high; spec says critical, signal-system.md:434) | all players and the caller, except the rejecter | 6 h | 00402:191-212 |
| Tied at the time cap | match_sudden_death_needed (matchup, high) | all players | 2 h | 00242:119-148 |
| Match becomes completed (any path) | match_completed (matchup, medium), then elo_changed (rating, low) | the four players | 24 h | 00525:610-655; 00590:509-531 |
| Host picks an unrated outcome | match_outcome_unrated (rating, medium) | the other players | 7 d | 00682:1093-1122 |
| Not sent today | host 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.
| Path | Moves | Ratings | score_status | Mark left behind |
|---|---|---|---|---|
| Host direct | scheduled or in_progress → completed | at once | unverified, forever | none (no source column) |
| Live consensus | in_progress, score edited by any RSVP, call_match, all agree → completed | at the last agreement | verified | verified_by = the last agreer only (00682:1192) |
| Reject | stays in_progress | none | disputed | call deleted |
| Correction | completed → completed, score_revision + 1 | full replay at the last agreement | verified | none |
verify_match_score | completed; a player appends themself | +10% if one verifier per team | verified when all four | no UI calls it (match.supabase.ts:272) |
1.3 Defects the rework must not inherit
- The consent push is unreadable.
TITLE_KOhas nosignal.matchup.scorePending.title(nor suddenDeath, matchOutcomeUnrated), and the generic body resolver reads onlyclubName,title,sessionTitle,postTitleand 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:71checks push keys ⊆ client keys, never the reverse, so it cannot see this. - And it opens the wrong screen.
matchup+ club + session routes toroutes.matchBoard(signal-routes.ts:99-101); the 대진표 has nouseAgreeToCall,ScoreCallSheetor call lookup (searchedmatch-board/,match-card.tsx). The control lives only on the scorecard's live tab. - 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 freshcall_match. - The host sees a button that always fails.
canCall = (isParticipant || isHost)(live-match-row.tsx:146-151) butcall_matchadmits only players and the referee (00402:247); referee UI was removed, so a non-playing host gets a raw English error. - A rejection erases the evidence. The claimed score is deleted with the call (
00402:221);rejected_byis never written; the host is not notified. - The +10% bonus is dead.
agree_to_callappends only the last agreer toverified_by, somatch_both_teams_verifiedis false (00682:1192,00130:52-83);verifyScorehas no caller. Yet Home shows 확인 필요 on every completedunverifiedmatch 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. - The 대진표 수정 button on a completed match is a dead end. It calls
submit_match_score, which refuses completed matches with hintalready_completedand no localized copy (match-card.tsx:325-344, 515-526→00682:1072-1089). - 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 tocanManageClub(use-live-tab-state.tsx:288). - Anyone can overwrite a live score. Any confirmed RSVP, any match (
00152:122-128);check_auto_callwould open a 0-0 call on a tied time-up withcurrent_score_stands(00242:150-164). - 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). - Players cannot record a best-of-3.
agree_to_callwrites the displayed score only, nevermatch_sets,team*_sets_wonorformat_id(00682:1183-1194); only host submit and corrections carryp_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
| Product | Who reports | Who confirms | Limit → at the limit | Dispute path | Organiser or host override | Tag |
|---|---|---|---|---|---|---|
| Playtomic (padel, tennis) | any player; "up to one week after playing"; outside-app uploads within 30 days | one opposing player validates at once | 24 h → "automatically validated" | rejected = "suspended", must be re-entered; "Playtomic does not have the authority to compel players"; match chat | none for rejections; after acceptance an edit goes through a support form, other team must agree, ≤ 4 working days; 0-0 voids | Doc [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 only | the opponent confirms or denies | none stated | denied = "Rejected", visible only to the denier; poster may delete any time | Verified events: an official present "to mitigate disputes", results within 48 h, formal review within 48 h | Doc [utr-1]; Snippet [utr-2] |
| USTA TennisLink (adult leagues) | coordinator, captain or any player | opposing team, "any team member" | 48 h → "automatically … confirmed by the 48 hour rule" | contact the League Coordinator | coordinator edits or disputes; Championships: coordinator enters | Doc [usta-1] |
| Rankedin (Club League) | "one of the players" | "the opponent" | none stated | not described | "Unconfirmed matches … can be confirmed by the Club League admin"; admins can enter results | Doc [rk-1][rk-2] |
| Tennis Australia Match Centre | home team | away team: "Confirm or Dispute" | losing team 48 h (junior rules); "deemed official" 72 h after start | dispute → the Tennis Association investigates | association | Snippet [ta-1]; Doc [ta-2] |
| Playmore Challenger (squash, tennis ladder) | players | nobody: contested or not | 48 h → "an uncontested result confirms itself" | not described | staff adjust positions | Doc [pm-1] |
| Pickleball.com (events) | winning team by default | opposing team: Confirm or Dispute | director sets minutes; 3 min "a good starting point", 1 min for elimination | both teams go to the tournament desk | director switches player scoring on or off per event | Doc [pb-1] |
| Dinkzter (pickleball ladder) | one winning-side player | one losing-side player ("only one player per side") | 3 h → automatically accepted | "the organizer reviews and resolves the dispute" | organiser | Doc [dz-1] (club handout) |
| Reclub (pickleball, badminton, SEA) | players, if the admin enables it | admin finalises | none | – | admin only finalises or resets; finalising pushes to leaderboard and DUPR | Doc [rc-1] |
| PodPlay → DUPR challenge | automatic | consent in advance: "Submit Results" chosen at the start | – | – | club can make it the default | Doc [pp-1][dupr-3] |
| Battlefy (esports) | the opponent reports | the other side confirms or flags | organiser timer → auto-confirm | flag alerts admins | organiser sets the timer | Doc [bf-1] |
| US Squash ClubLocker | captains (club guide, Snippet) | not documented | – | – | leagues must enter results there to count | Doc [us-1]; Unverified beyond that |
| Matchi | – | – | – | – | no public result-confirmation page found | Unverified |
2.2 How pending results show, and how ratings treat them
| Product | Pending display and notification | Unconfirmed results and ratings |
|---|---|---|
| Playtomic | After 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. |
| DUPR | In-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]. |
| UTR | Email "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]. |
| USTA | The 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. |
| Battlefy | A window pops up for the other side with a countdown. | Not applicable. |
| Rankedin, Playmore, Dinkzter, Reclub, ClubLocker, Matchi | Not 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
- Players record, the other team confirms, the host decides when they cannot. The host's normal day has no scoring in it.
- 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.
- Every request states its own consequence ("답하지 않으면 24시간 뒤 이 점수로 확정돼요"). That sentence is what makes silence consent.
- Consent is visible. Each accepted result shows how it was accepted (동의, 자동, 호스트). Only explicit consent earns the +10% bonus.
- No evidence is deleted. Claims, objections and overrides are kept in an event log.
- Nobody judges their own match. A host who plays is a player in that match.
3.2 States
| State | UI label | matches.status (new) | Call row | score_status | Ratings | Enters the state by |
|---|---|---|---|---|---|---|
| S1 playing | 경기 중 | in_progress | none | unverified | – | host starts the round |
| S2 finished, unrecorded | 점수 입력 전 | awaiting_result | none | unverified | – | host 라운드 종료, the next round starting, or the rotation timer |
| S3 submitted, waiting | 확인 대기 | awaiting_result | status = pending, expires_at | unverified | – | a player's 확인 요청 (from S1 or S2) |
| S4 confirmed | 확정 | completed | closed | verified | applied now | opponent 동의, or 24 h, or host |
| S4h host-entered | 호스트 확정 | completed | – | verified | applied now | host 대신 입력, 바로 확정, 확정하기 |
| S5 disputed | 분쟁 | awaiting_result | status = disputed, claim kept | disputed | – | any player's 거절 |
| S6 resolved without a rating | 기록 제외 | completed, outcome = non_competitive | closed | – | none | host 기록 제외, 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
| State | Submitter | Submitter's partner | Opponents | Host or officer (not in the match) | Everyone else |
|---|---|---|---|---|---|
| S1 경기 중 | – | – | – | 라운드 종료, or 대신 입력 early | names 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 h | score + 호스트 확정 |
| 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_verifiedalready means "at least one verifier per team" (00130:52-83). Explicit confirmation →verified_byholds 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
| Interval | Value | Basis |
|---|---|---|
| Entry prompt to the four | when the match reaches S2; one reminder at +2 h to those who have not entered | Playtomic pushes the entry prompt when the match ends |
| Players may enter | until session date + 7 d, then host or officer only; +30 d → 기록 제외 | Playtomic "up to one week"; 30-day upload window |
| Confirm window | 24 h from the latest submission | Playtomic 24 h (closest product: casual, mobile, after the match) |
| Confirm reminder | once, at 12 h, to opponents who have not answered | design; lands in the day for a late-night entry despite quiet hours |
| Auto-confirm | at 24 h if no dispute | Playtomic; USTA and Playmore use 48 h for multi-day leagues |
| Host decision after a dispute | reminders 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 result | 48 h, once per player | UTR formal review 48 h; USTA 48 h |
| Correction request after acceptance | 7 d | bounds 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
- 거절 (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.
- 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.
- 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 →
- 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.
- Unresolved at 7 days → 기록 제외 and a notice to all four.
- 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_eventsrow, 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 ends | round bar: 라운드 종료; starting the next round ends the previous one | live 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 Sheet | ScoreEntry Sheet (replaces score-edit-sheet, ScoreCallSheet, the correction table) | – | – |
| Waiting | row 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 answers | participant chip 점수 확인 필요 (exists, home.ts:161) | match_score_pending_verification → scorecard, that match (today: host's board) |
| Dispute | row Tag 분쟁; Task Cell "분쟁 N" opens the resolve Sheet | "호스트가 확인하고 있어요" | host chip; Home disputed banner (exists) now ends in an action | match_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 |
| Accepted | Tag 확정 | 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)
| Where | Copy |
|---|---|
| S2 row, player | button 점수 입력 · 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) |
| 거절 Dialog | title 점수를 거절할까요? · body 호스트가 두 팀의 점수를 보고 확정해요. · 취소 / 거절 |
| Dispute sheet | title 어떤 점이 달라요? · 점수가 달라요 · 선수가 달라요 · 경기를 하지 않았어요 · field 내가 아는 점수 · 보내기 |
| S5 Tag / line | 분쟁 (exists) · players: 호스트가 확인하고 있어요 · host: 두 팀의 점수가 달라요 |
| Host decide Sheet | title 점수를 정해 주세요 · rows {이름}이 입력한 6-4 · {이름}이 말한 6-3 · 직접 입력 · 기록 제외 · CTA 확정하기 |
| S4 Tag / Caption | 확정 (exists) · 상대가 동의했어요 · 답이 없어 자동으로 확정됐어요 |
| S4h Tag / Caption / contest | 호스트 확정 · 호스트가 입력했어요. 이의가 있으면 48시간 안에 알려 주세요 · 이의 있어요 → 호스트에게 전했어요 |
| S6 Tag / Caption | 기록 제외 · 레이팅에 반영되지 않아요 |
| Push: enter | title 점수를 입력해 주세요 · body 3라운드, 2번 코트, 경기가 끝났어요 |
Push: confirm (needs a TITLE_KO entry) | title 점수 확인이 필요해요 (exists in-app) · body 3라운드, 2번 코트, 6 : 4 확인 필요 |
| Push: reminder | title 점수 확인이 아직 남았어요 · body 12시간 뒤 자동으로 확정돼요 |
| Push: dispute, to host | title 점수 확정이 필요해요 · body 3라운드, 2번 코트, 두 팀의 점수가 달라요 |
| Push: host set it, to players | title 호스트가 점수를 확정했어요 · 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):
| Area | Change |
|---|---|
matches | status 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_calls | status ∈ 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_events | append-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, new | end_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, changed | agree_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. |
| Retire | private.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 push | new 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. |
| Client | one 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), theexpo-keep-awakedependency note, and the live use ofupdate_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 inflat-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):
- Silence = consent? Recommend yes: 24 h, stated in the request, marked 자동 확정, no bonus. Your words say "explicit"; explicit-only is
autoConfirmHours = nulland 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. - Host-entered results final at once, contestable once? Recommend yes. The alternative (host entries also need a confirm) makes the override toothless.
- A host who plays? Recommend: acts as a player; disputes go to another officer, else 기록 제외.
- Friendly sessions (no rating)? Recommend the same flow: the record still matters; only the rating step is skipped.
- Score-driven formats (KDK, 순위매칭전, 코트승급전) read pending claims as provisional for standings and the next pairing; ELO still waits.
- Rating event time:
completed_at = ended_at. - 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. - A new match status, or
ended_atalone? Recommend the status (awaiting_result):in_progressreads as "playing now" (진행 중 is the live label everywhere, terminology guide), and the live tab, the 지연 badge andcheck_auto_callwould otherwise treat finished matches as live. The cheaper path keepsin_progressand adds onlyended_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.
- Playtomic: [pt-1] How to publish your match results · [pt-2] How to edit incorrect match results · [pt-3] How the level system works · upload outside Playtomic · entry notification · casual vs competitive. Text read through
playerhelp.playtomic.com/api/v2/help_center/en-gb/articles/{id}.json. - DUPR: [dupr-1] Match validation (2024-03-19) · [dupr-2] Verified Event Policy · [dupr-3] Challenge Matches · [dupr-4] How it works.
- UTR: [utr-1] Post a score, self-posting · [utr-3] UTR-P: what counts · [utr-2] Snippet: Provider and Player Guidelines, Verified Events Policy (script-rendered; official on site, 48 h results, 48 h review).
- USTA: [usta-1] Enter League Scores.
- Rankedin: [rk-1] Players enter results, Club League · [rk-2] Admins enter results.
- Tennis Australia: [ta-1] Snippet: How to confirm match results (Cloudflare-blocked; "Confirm or Dispute", junior rules 48 h and 72 h) · [ta-2] Help for Team Captains, WMGTA ("A team receiving a forfeit MUST enter the scores … confirmed by the Opposing Team").
- Playmore: [pm-1] Challenger.
- Pickleball.com: [pb-1] Player scoring and auto-run · ladder league, manager-entered.
- Dinkzter: [dz-1] Ladder League Rules & Format, 2026-03-22 (a club-leader handout on a third-party host; text decoded locally).
- Reclub: [rc-1] Finalize matches. PodPlay: [pp-1] DUPR challenge match.
- Others: [bf-1] Battlefy score confirmation · [ms-1] Matchspace add, edit, verify scores · [us-1] US Squash, manage a league · pattern only: Old World Rankings, Confirm & Edit (submitter edits until confirmed, then locked; organiser overrides).
- Korean apps [kr-1], App Store listings via the iTunes lookup API (country kr): 스포넷 · 쉬운 대진표 · MyTennis · 위꾹 · 베이스라인 · 땡큐볼 · BKPLAY · 배프.
- Repo: component library §10 · IA flow 7 · terminology guide · owner review 1 rulings · signal-system spec.