courthive-components 4.4.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,8 @@ export interface InlineScoringCallbacks {
24
24
  onEndMatch?: (params: {
25
25
  matchUpId: string;
26
26
  matchUpStatus: string;
27
+ /** Resolved from the exiting side; absent for a double exit and the non-directing statuses. */
28
+ winningSide?: number;
27
29
  sideNumber?: number;
28
30
  engine: any;
29
31
  }) => void;
@@ -23,6 +23,17 @@ export declare class ScoringEditorStore {
23
23
  isAllowedFormatDuplicate(format: string, excludeIndex?: number): boolean;
24
24
  addAllowedFormatCategory(index: number, kind: 'categoryNames' | 'categoryTypes', value: string): void;
25
25
  removeAllowedFormatCategory(index: number, kind: 'categoryNames' | 'categoryTypes', valueIndex: number): void;
26
+ /**
27
+ * Add a code to a status group.
28
+ *
29
+ * The operator types a code; the entry stored is an OBJECT. Only `matchUpStatusCode` is set —
30
+ * `matchUpStatusCodeDisplay` and `label` are authored per code and this control has no field for
31
+ * them, so inventing values would be worse than leaving them absent (the reason picker falls back
32
+ * to the code, which is the policy saying "the code IS the label").
33
+ *
34
+ * Entries that arrive already carrying display text are never rewritten by this path, which is
35
+ * the whole point: the previous `string[]` model discarded them on every save.
36
+ */
26
37
  addStatusCode(status: MatchUpStatusKey, value: string): void;
27
38
  removeStatusCode(status: MatchUpStatusKey, index: number): void;
28
39
  addProcessCode(value: string): void;
@@ -1,14 +1,4 @@
1
- /**
2
- * Scoring Policy Editor — Type Definitions
3
- *
4
- * Mirrors the factory's scoring policy data shape (see
5
- * factory/src/fixtures/policies/POLICY_SCORING_DEFAULT.ts).
6
- *
7
- * The factory does not export a TypeScript interface for the scoring
8
- * policy itself — the fixture defines the shape via runtime defaults.
9
- * We re-derive the shape here so the editor can stay type-safe without
10
- * dragging factory internals into courthive-components.
11
- */
1
+ import { StatusCodeEntry } from '../../../scoring/logic/statusCodes';
12
2
  export type MatchUpStatusKey = 'ABANDONED' | 'CANCELLED' | 'DEFAULTED' | 'INCOMPLETE' | 'RETIRED' | 'WALKOVER';
13
3
  export declare const MATCH_UP_STATUS_KEYS: MatchUpStatusKey[];
14
4
  export type RequireAllPositionsTriState = 'default' | 'true' | 'false';
@@ -36,7 +26,7 @@ export interface ScoringPolicyData {
36
26
  requireAllPositionsAssigned?: boolean;
37
27
  allowChangePropagation?: boolean;
38
28
  allowDeletionWithScoresPresent?: AllowDeletionWithScoresPresent;
39
- matchUpStatusCodes?: Partial<Record<MatchUpStatusKey, string[]>>;
29
+ matchUpStatusCodes?: Partial<Record<MatchUpStatusKey, StatusCodeEntry[]>>;
40
30
  stage?: Record<string, {
41
31
  stageSequence?: Record<string, StageSequenceOverride>;
42
32
  }>;
@@ -0,0 +1,5 @@
1
+ import { NormalisedSeedingProfile, SeedingPolicyData, SeedingPolicyDraft } from '../types';
2
+ /** Widen a policy's `seedingProfile` — and every drawType override — to the object form. */
3
+ export declare function normaliseSeedingProfile(profile: SeedingPolicyData['seedingProfile']): NormalisedSeedingProfile | undefined;
4
+ /** Widen a whole policy to the editor's internal draft form. */
5
+ export declare function toDraft(policy: SeedingPolicyData): SeedingPolicyDraft;
@@ -8,3 +8,12 @@ export declare const POSITIONING_OPTIONS: {
8
8
  export declare const DRAW_TYPE_OPTIONS: string[];
9
9
  export declare function emptySeedingPolicy(): SeedingPolicyData;
10
10
  export declare function positioningLabel(value: SeedingPositioning | undefined): string;
11
+ /**
12
+ * `ADJACENT` is the factory's synonym for `CLUSTER` — `generateBlockPattern` and
13
+ * `getContainerBlocks` treat them identically. The picker deliberately offers ONE option
14
+ * rather than two that do the same thing, so either stored value selects it.
15
+ *
16
+ * This is display-only: the stored value is left exactly as it arrived, because rewriting
17
+ * a caller's `ADJACENT` to `CLUSTER` would be an unrequested edit to their policy.
18
+ */
19
+ export declare function canonicalPositioning(value?: SeedingPositioning): SeedingPositioning | undefined;
@@ -5,4 +5,4 @@ export { SeedingEditorControl, createSeedingEditor } from './seedingEditorContro
5
5
  export { SeedingEditorStore } from './seedingEditorStore';
6
6
  export { buildSeedingEditorPanel } from './seedingEditorPanel';
7
7
  export { emptySeedingPolicy, POSITIONING_OPTIONS, DRAW_TYPE_OPTIONS } from './domain/seedingProjections';
8
- export type { SeedingPolicyData, SeedingProfile, SeedingPositioning, SeedsCountThreshold, SeedingEditorState, SeedingEditorSection, SeedingEditorChangeListener, SeedingEditorConfig } from './types';
8
+ export type { SeedingPolicyData, SeedingPolicyDraft, NormalisedSeedingProfile, PolicySeedingProfile, DrawSeedingProfile, SeedingProfile, SeedingPositioning, SeedsCountThreshold, SeedingEditorState, SeedingEditorSection, SeedingEditorChangeListener, SeedingEditorConfig } from './types';
@@ -1,5 +1,5 @@
1
1
  import { SeedingEditorStore } from './seedingEditorStore';
2
- import { SeedingPolicyData, SeedingEditorConfig } from './types';
2
+ import { SeedingPolicyDraft, SeedingPolicyData, SeedingEditorConfig } from './types';
3
3
  import { PolicyEditorInstance as CatalogEditorInstance } from '../../types';
4
4
  export declare class SeedingEditorControl {
5
5
  private readonly store;
@@ -9,7 +9,7 @@ export declare class SeedingEditorControl {
9
9
  constructor(config: SeedingEditorConfig);
10
10
  render(container: HTMLElement): void;
11
11
  destroy(): void;
12
- getData(): SeedingPolicyData;
12
+ getData(): SeedingPolicyDraft;
13
13
  setData(data: SeedingPolicyData): void;
14
14
  getStore(): SeedingEditorStore;
15
15
  static createEditorInstance(config: {
@@ -1,11 +1,11 @@
1
- import { SeedingPolicyData, SeedingEditorState, SeedingEditorSection, SeedingEditorChangeListener, SeedingEditorConfig, SeedsCountThreshold, SeedingPositioning } from './types';
1
+ import { SeedingPolicyDraft, SeedingPolicyData, SeedingEditorState, SeedingEditorSection, SeedingEditorChangeListener, SeedingEditorConfig, SeedsCountThreshold, SeedingPositioning } from './types';
2
2
  export declare class SeedingEditorStore {
3
3
  private state;
4
4
  private readonly listeners;
5
5
  private readonly config;
6
6
  constructor(config: SeedingEditorConfig);
7
7
  getState(): SeedingEditorState;
8
- getData(): SeedingPolicyData;
8
+ getData(): SeedingPolicyDraft;
9
9
  setData(data: SeedingPolicyData): void;
10
10
  toggleSection(sectionId: SeedingEditorSection): void;
11
11
  setPolicyName(value: string): void;
@@ -1,41 +1,42 @@
1
+ import { SeedingProfile as DrawSeedingProfile, PolicySeedingProfile, SeedingProfileUnion, SeedsCountThreshold, SeedingPolicy } from 'tods-competition-factory';
2
+ export type { SeedsCountThreshold, PolicySeedingProfile, DrawSeedingProfile };
3
+ /** `'ADJACENT' | 'CLUSTER' | 'SEPARATE' | 'WATERFALL'` — the factory's closed union. */
4
+ export type SeedingPositioning = SeedingProfileUnion;
5
+ /** The policy-level profile in its object form. */
6
+ export type SeedingProfile = PolicySeedingProfile;
7
+ /** What the editor accepts and emits — the factory's policy shape, verbatim. */
8
+ export type SeedingPolicyData = SeedingPolicy;
1
9
  /**
2
- * Seeding Policy Editor — Type Definitions
10
+ * The editor's INTERNAL draft.
3
11
  *
4
- * Mirrors the factory's seeding policy data shape (see
5
- * factory/src/fixtures/policies/POLICY_SEEDING_*.ts).
12
+ * `SeedingPolicy.seedingProfile` is `PolicySeedingProfile | SeedingProfileUnion` — the
13
+ * factory still honours a bare positioning string where a profile object is expected.
14
+ * The editor holds and mutates an object, so the string form is normalised away on
15
+ * ingest (see `normaliseSeedingProfile`) and the draft is the object form only.
6
16
  */
7
- export type SeedingPositioning = 'SEPARATE' | 'WATERFALL' | 'CLUSTER';
8
- export interface SeedsCountThreshold {
9
- drawSize: number;
10
- minimumParticipantCount: number;
11
- seedsCount: number;
12
- }
13
- export interface SeedingProfile {
14
- positioning?: SeedingPositioning;
15
- drawTypes?: Record<string, {
16
- positioning: SeedingPositioning;
17
- }>;
18
- }
19
- export interface SeedingPolicyData {
20
- policyName?: string;
21
- seedingProfile?: SeedingProfile;
22
- validSeedPositions?: {
23
- ignore: boolean;
24
- };
25
- duplicateSeedNumbers?: boolean;
26
- drawSizeProgression?: boolean;
27
- seedsCountThresholds?: SeedsCountThreshold[];
28
- }
17
+ export type SeedingPolicyDraft = Omit<SeedingPolicyData, 'seedingProfile'> & {
18
+ seedingProfile?: NormalisedSeedingProfile;
19
+ };
20
+ /**
21
+ * A {@link PolicySeedingProfile} whose drawType overrides are known to be objects.
22
+ * The factory types each override as `SeedingProfile | SeedingProfileUnion`; the editor
23
+ * normalises the string form away on ingest, so the draft need not re-narrow at every read.
24
+ */
25
+ export type NormalisedSeedingProfile = Omit<PolicySeedingProfile, 'drawTypes'> & {
26
+ drawTypes?: Record<string, DrawSeedingProfile>;
27
+ };
29
28
  export type SeedingEditorSection = 'profile' | 'flags' | 'thresholds' | 'drawTypeOverrides';
30
29
  export interface SeedingEditorState {
31
- draft: SeedingPolicyData;
30
+ draft: SeedingPolicyDraft;
32
31
  expandedSections: Set<SeedingEditorSection>;
33
32
  dirty: boolean;
34
33
  }
35
34
  export type SeedingEditorChangeListener = (state: SeedingEditorState) => void;
36
35
  export interface SeedingEditorConfig {
36
+ /** Accepts either form the factory accepts; the string form is normalised on ingest. */
37
37
  initialPolicy?: SeedingPolicyData;
38
38
  /** Override the list of drawTypes offered in the overrides picker. */
39
39
  drawTypes?: string[];
40
- onChange?: (policy: SeedingPolicyData) => void;
40
+ /** Emits the normalised object form, which is itself a valid {@link SeedingPolicyData}. */
41
+ onChange?: (policy: SeedingPolicyDraft) => void;
41
42
  }
@@ -24,5 +24,5 @@ export type { PrivacyPolicyData, PrivacyEditorConfig, PrivacyFieldMeta } from '.
24
24
  export type { PolicyCatalogItem, PolicyCatalogState, PolicyCatalogChangeListener, PolicyCatalogConfig, PolicyEditorInstance, PolicyEditorPlugin, PolicySource, PolicyTypeGroup, PolicyTypeMeta, CatalogGroupBy, UIPanel } from './types';
25
25
  export type { SchedulingPolicyData, SchedulingEditorState, SchedulingEditorSection, SchedulingEditorChangeListener, SchedulingEditorConfig, SchedulingValidationResult, ValidationSeverity, MinutesEntry, AverageTimeEntry, RecoveryTimeEntry, MatchUpAverageTime, MatchUpRecoveryTime } from './editors/scheduling';
26
26
  export type { RankingPolicyData, RankingPointsEditorState, RankingEditorSection, RankingPointsEditorChangeListener, RankingPointsEditorConfig, AwardProfileData, QualityWinProfileData, AggregationRulesData, TableLayout } from './editors/ranking';
27
- export type { SeedingPolicyData, SeedingProfile, SeedingPositioning, SeedsCountThreshold, SeedingEditorState, SeedingEditorSection, SeedingEditorChangeListener, SeedingEditorConfig } from './editors/seeding';
27
+ export type { SeedingPolicyData, SeedingPolicyDraft, NormalisedSeedingProfile, PolicySeedingProfile, DrawSeedingProfile, SeedingProfile, SeedingPositioning, SeedsCountThreshold, SeedingEditorState, SeedingEditorSection, SeedingEditorChangeListener, SeedingEditorConfig } from './editors/seeding';
28
28
  export declare function createPolicyCatalog(config: PolicyCatalogConfig, container: HTMLElement): PolicyCatalogControl;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Pure logic for resolving an irregular ending into a matchUpStatus.
3
+ *
4
+ * Every score-entry approach needs the same rule, and until now each one carried its own copy:
5
+ * "walkover with a winner is a WALKOVER, walkover without one is a DOUBLE_WALKOVER". Three copies
6
+ * of a rule is three chances to disagree about it, and they already disagreed about when Submit
7
+ * should be live.
8
+ *
9
+ * The rule this module encodes differs from those copies in one deliberate way. A missing winner
10
+ * used to mean "double exit" — so merely selecting Walkover produced a valid, submittable
11
+ * DOUBLE_WALKOVER before the operator had chosen anything. That is a fail-open default (Mentat
12
+ * architectural standard A3): the absence of an answer was read as a particular answer, and the
13
+ * particular answer it was read as is one of the most consequential outcomes in the draw, because a
14
+ * double exit advances nobody and propagates a walkover downstream.
15
+ *
16
+ * Here, absence is absence. `undefined` is "the operator has not answered yet" and is never valid;
17
+ * a double exit requires explicitly choosing NEITHER_SIDE. No DOM dependencies, no side effects.
18
+ */
19
+ /**
20
+ * The winner-selection value meaning "no side won this matchUp" — the explicit choice that produces
21
+ * a double exit. A sentinel rather than `null` so the three states (unanswered / a side / neither)
22
+ * stay distinguishable in a radio group's string values.
23
+ */
24
+ export declare const NEITHER_SIDE = "NEITHER";
25
+ /** Shown when an irregular ending has been chosen but the winner question is unanswered. */
26
+ export declare const WINNER_REQUIRED_ERROR = "Select a winner";
27
+ /** The statuses whose selection opens the winner question at all. */
28
+ export declare const WINNER_REQUIRING_STATUSES: Set<string>;
29
+ /**
30
+ * Endings that resolve nobody: the match did not produce a result, so there is no winner to name
31
+ * and no winner question to answer. The factory classifies these as non-directing
32
+ * (`nonDirectingMatchUpStatuses`) — nothing advances out of them.
33
+ *
34
+ * These are NOT a smaller version of the winner-requiring endings. Asking "who won an abandoned
35
+ * match" is not a question with a missing answer; it is not a question. So unlike a walkover with
36
+ * no winner selection, one of these is valid the moment it is chosen.
37
+ */
38
+ export declare const NON_DIRECTING_ENDINGS: Set<string>;
39
+ /**
40
+ * Every irregular ending a set-entry approach offers, in display order.
41
+ *
42
+ * These six are exactly the keys the factory's scoring policy can refine with `matchUpStatusCodes`
43
+ * (ABANDONED, CANCELLED, DEFAULTED, INCOMPLETE, RETIRED, WALKOVER), which is why the list is this
44
+ * list: a status with no code group cannot carry a reason, and a code group with no status cannot
45
+ * be reached. Ordered so the three an operator reaches for most sit first.
46
+ */
47
+ export declare const SELECTABLE_ENDINGS: string[];
48
+ /** Whether choosing this ending obliges the operator to answer the winner question. */
49
+ export declare function requiresWinner(selectedOutcome: string | undefined): boolean;
50
+ /**
51
+ * Statuses that carry NO score at all, so there is nothing to validate and nothing to submit.
52
+ *
53
+ * This mirrors the factory, which is the authority: `modifyMatchUpScore.ts:236` blanks the score
54
+ * outright for exactly this set — `Object.assign(matchUp, { ...toBePlayed })`, commented "a walkover
55
+ * has none". Note what is NOT here: `DEFAULTED` and `DOUBLE_DEFAULT` are absent in the factory too,
56
+ * because a player can default part-way through a match that was genuinely played, and that partial
57
+ * score is real. The same goes for `RETIRED` — a retirement keeps whatever was played, and needs no
58
+ * completed set to be a valid result.
59
+ *
60
+ * `CANCELLED` and `DEAD_RUBBER` are deliberately NOT here even though this repo's `validateScore`
61
+ * strips scores for them. They are unreachable from the set-entry approaches today (only freeScore
62
+ * parses them) and the factory does not blank scores for them, so adding them here would be a
63
+ * guess. Revisit when the status list widens — see Mentat SCORING_MODAL_STATUS_CODES.md M2.
64
+ */
65
+ export declare const NO_SCORE_STATUSES: Set<string>;
66
+ /**
67
+ * Whether this ending means "no score exists", so score validation must be skipped rather than run
68
+ * and then overridden.
69
+ */
70
+ export declare function carriesNoScore(selectedOutcome: string | undefined): boolean;
71
+ /**
72
+ * Unanswered (`undefined`), a side (`1` | `2`), or explicitly neither.
73
+ */
74
+ export type WinnerSelection = 1 | 2 | typeof NEITHER_SIDE | undefined;
75
+ /** The double-exit statuses themselves, as opposed to the endings that produce them. */
76
+ export declare const DOUBLE_EXIT_STATUSES: Set<string>;
77
+ /**
78
+ * Whether this status IS a double exit — no side advances.
79
+ *
80
+ * Distinct from `supportsNeitherSide`, which asks whether an *ending* can become one. Use this
81
+ * where a status arrives already resolved, as it does from the inline popover.
82
+ */
83
+ export declare function isDoubleExitStatus(matchUpStatus: string | undefined): boolean;
84
+ /**
85
+ * The winner implied by a per-side exit control, where clicking a side's pill means THAT side is the
86
+ * one exiting and the other therefore wins.
87
+ *
88
+ * Returns `undefined` for a double exit (nobody advances) and for the non-directing statuses —
89
+ * SUSPENDED, CANCELLED, ABANDONED — which resolve nothing. The inline popover previously listed the
90
+ * double exits among the statuses that "produce a winner", so had one ever been selectable it would
91
+ * have stamped a winningSide on an outcome that must carry none.
92
+ */
93
+ export declare function winnerFromExitingSide(matchUpStatus: string | undefined, exitingSideNumber?: number): number | undefined;
94
+ /**
95
+ * Whether "Neither side" is a legitimate answer for this irregular ending.
96
+ */
97
+ export declare function supportsNeitherSide(selectedOutcome: string | undefined): boolean;
98
+ /**
99
+ * Message shown beside a double-exit selection. A double exit is not a scoring detail — it advances
100
+ * nobody and propagates a walkover into the next round — so the modal says what it will do before it
101
+ * is submitted rather than after.
102
+ */
103
+ export declare function doubleExitWarning(selectedOutcome: string | undefined): string;
104
+ export type IrregularEndingResolution = {
105
+ /** The status to submit. `undefined` while the selection is incomplete. */
106
+ matchUpStatus?: string;
107
+ /** Present only when a side was named. A double exit deliberately carries none. */
108
+ winningSide?: number;
109
+ /** Whether this resolution may be submitted. */
110
+ isValid: boolean;
111
+ /** True when the operator still owes a winner answer — the fail-closed state. */
112
+ awaitingWinner: boolean;
113
+ /** True when the resolution is a double exit, so callers can warn before it is submitted. */
114
+ isDoubleExit: boolean;
115
+ };
116
+ /**
117
+ * Resolve an irregular ending plus a winner selection into the status to submit.
118
+ *
119
+ * `COMPLETED` is not an irregular ending — callers keep their own completion handling and this
120
+ * returns an inert resolution for it.
121
+ */
122
+ export declare function resolveIrregularEnding(params: {
123
+ selectedOutcome: string | undefined;
124
+ winnerSelection: WinnerSelection;
125
+ }): IrregularEndingResolution;
126
+ /**
127
+ * Apply a resolution onto a validation object in place, the shape the approaches already pass to
128
+ * `onScoreChange`.
129
+ *
130
+ * It also clears any `error` the score validator left behind, and the two cases that reach here are
131
+ * worth separating because only one of them is legitimate:
132
+ *
133
+ * - **RETIRED / DEFAULTED with a partial score.** The validator correctly reports "this is not a
134
+ * completed match"; the ending is what makes it a valid result anyway. Clearing the error is the
135
+ * right call — the validator answered a question that is no longer the one being asked.
136
+ * - **A walkover.** There is no score to validate, so the validator should never have run. Callers
137
+ * must check `carriesNoScore()` and skip validation entirely; the clear here is a backstop, not
138
+ * the fix. A walkover that reported `isValid: true` beside
139
+ * `error: 'At least one set is required'` was the symptom of exactly that missing check.
140
+ */
141
+ export declare function applyIrregularEndingToValidation(validation: any, selectedOutcome: string | undefined, winnerSelection: WinnerSelection): IrregularEndingResolution;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Reading a scoring policy's `matchUpStatusCodes` vocabulary.
3
+ *
4
+ * The policy refines a matchUpStatus with caller-defined codes — a retirement becomes "Ret [inj]",
5
+ * a default becomes "Def [dq]". The modal renders them; it never invents them. If a tournament's
6
+ * policy carries no codes for the chosen status, there is nothing to show and no control is drawn.
7
+ * That is the designed state, not a degraded one: CA, 2026-09-20 — TMX ships no built-in default,
8
+ * so the reason field exists exactly where a governing body's vocabulary applies and nowhere else.
9
+ *
10
+ * No DOM, no side effects.
11
+ */
12
+ /**
13
+ * One entry of a policy's code group, as the factory's fixtures ship it.
14
+ *
15
+ * CA settled the canonical shape on 2026-09-19: the OBJECT form is authoritative, because a bare
16
+ * string cannot carry the display text the picker exists to render.
17
+ */
18
+ export type StatusCodeEntry = {
19
+ matchUpStatusCode: string;
20
+ matchUpStatusCodeDisplay?: string;
21
+ label?: string;
22
+ description?: string;
23
+ };
24
+ /** A policy's groups, keyed by the matchUpStatus each refines. */
25
+ export type StatusCodeGroups = Record<string, StatusCodeEntry[]>;
26
+ /**
27
+ * The group key a status reads its codes from.
28
+ *
29
+ * A double exit has no group of its own — the policy files "Wo/Wo" inside WALKOVER and "Def/Def"
30
+ * inside DEFAULTED, because they are walkovers and defaults. Mapping them here is what keeps the
31
+ * double-exit choice and the code list from becoming two controls that can disagree.
32
+ */
33
+ export declare function groupKeyForStatus(matchUpStatus: string | undefined): string | undefined;
34
+ /**
35
+ * Normalise one element of a code group, or of a matchUp's `matchUpStatusCodes`, to its code string.
36
+ *
37
+ * Three shapes are legitimate and all three occur. `matchUpStatusCodes` on a matchUp is an `any[]`
38
+ * shared by three tenants (policy codes, propagation provenance, and `{ code }` wrappers), and
39
+ * `updateMatchUpStatusCodes` in the factory rewraps every string element as `{ code }` — so a code
40
+ * written as `'RJ'` reads back as `{ code: 'RJ' }` once propagation has touched the matchUp. A
41
+ * reader that assumes a string is wrong, and it is wrong only sometimes, which is worse.
42
+ *
43
+ * Returns `undefined` for a provenance element, which carries no code at all.
44
+ */
45
+ export declare function normalizeStatusCode(entry: unknown): string | undefined;
46
+ /**
47
+ * The codes a policy offers for this status, or an empty list when it offers none.
48
+ *
49
+ * Entries without a `matchUpStatusCode` are dropped rather than rendered as a blank option — an
50
+ * unpickable row in a picker is worse than a shorter picker.
51
+ */
52
+ export declare function codesForStatus(groups: StatusCodeGroups | undefined, matchUpStatus: string | undefined): StatusCodeEntry[];
53
+ /**
54
+ * What to show for a code: the policy's display form, falling back to its label, then to the code.
55
+ *
56
+ * The three fields are authored per code and mean different things — `matchUpStatusCodeDisplay` is
57
+ * the terse form an operator recognises ("Ret [inj]"), `label` the prose one ("Injury"). Never
58
+ * synthesise a display from the code: a policy that authored none is saying the code IS the label.
59
+ */
60
+ export declare function statusCodeDisplay(entry: StatusCodeEntry): string;
61
+ /** The secondary line beside the display form, when the policy authored one that adds something. */
62
+ export declare function statusCodeSubtext(entry: StatusCodeEntry): string | undefined;
@@ -0,0 +1,13 @@
1
+ import { StatusCodeGroups } from './logic/statusCodes';
2
+ import { ScoringModalLabels } from './types';
3
+ export type StatusCodePickerHandle = {
4
+ element: HTMLElement;
5
+ /** Re-render for a new status. Returns the code still selected, if any. */
6
+ update: (matchUpStatus: string | undefined) => string | undefined;
7
+ /** The chosen code, or undefined when none is chosen or none is offered. */
8
+ getSelectedCode: () => string | undefined;
9
+ };
10
+ export declare function buildStatusCodePicker(params: {
11
+ groups?: StatusCodeGroups;
12
+ labels?: ScoringModalLabels;
13
+ }): StatusCodePickerHandle;
@@ -1,6 +1,4 @@
1
- /**
2
- * Type definitions for scoring modal V2
3
- */
1
+ import { StatusCodeGroups } from './logic/statusCodes';
4
2
  export type SetScore = {
5
3
  setNumber?: number;
6
4
  side1Score?: number;
@@ -17,6 +15,8 @@ export type ScoreOutcome = {
17
15
  scoreObject?: any;
18
16
  winningSide?: number;
19
17
  matchUpStatus?: string;
18
+ /** Policy reason codes chosen for this outcome, as code strings — e.g. `['RJ']`. */
19
+ matchUpStatusCodes?: string[];
20
20
  error?: string;
21
21
  matchUpFormat?: string;
22
22
  score?: string;
@@ -30,13 +30,19 @@ export type ScoringModalLabels = {
30
30
  formatEditTitle?: string;
31
31
  irregularEnding?: string;
32
32
  winner?: string;
33
+ neitherSide?: string;
33
34
  retired?: string;
35
+ abandoned?: string;
36
+ cancelled?: string;
37
+ incomplete?: string;
34
38
  walkover?: string;
35
39
  defaulted?: string;
36
40
  validScore?: string;
37
41
  scoreIncomplete?: string;
38
42
  invalidScore?: string;
39
43
  scoreTips?: string;
44
+ reasonCode?: string;
45
+ noReasonCode?: string;
40
46
  setScores?: string;
41
47
  tiebreaks?: string;
42
48
  matchTiebreaks?: string;
@@ -50,6 +56,13 @@ export type ScoringModalParams = {
50
56
  callback: (outcome: any) => void;
51
57
  onClose?: () => void;
52
58
  labels?: ScoringModalLabels;
59
+ /**
60
+ * The scoring policy's `matchUpStatusCodes` groups, resolved by the CALLER — components never
61
+ * reaches into factory fixtures or the engine. Omit it (or pass groups with no entries for the
62
+ * chosen status) and no reason control is drawn, which is the designed state for a tournament
63
+ * with no governing-body policy attached.
64
+ */
65
+ matchUpStatusCodes?: StatusCodeGroups;
53
66
  };
54
67
  export type ScoreChangeHandler = (outcome: ScoreOutcome) => void;
55
68
  export type RenderScoreEntryParams = {