courthive-components 6.2.1 → 6.4.2

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.
@@ -1,5 +1,5 @@
1
- import { InlineScoringManager } from './inlineScoringManager';
2
1
  import { Composition, EventHandlers, MatchUp } from '../../types';
2
+ import { InlineScoringManager } from './inlineScoringManager';
3
3
  interface RenderInlineMatchUpParams {
4
4
  matchUp: MatchUp;
5
5
  composition: Composition;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The picker's two dropdowns: a plain list of choices, and the game-format chooser. Moved out of
3
+ * matchUpFormat.ts unchanged except that they are given their labels instead of reading module state.
4
+ */
5
+ export interface DropdownItem {
6
+ text: string;
7
+ onClick: () => void;
8
+ }
9
+ export declare function closeCurrentDropdown(): void;
10
+ export declare function createDropdown(e: Event, items: DropdownItem[]): void;
11
+ type GameFormat = {
12
+ type: 'CONSECUTIVE';
13
+ count: number;
14
+ deuceAfter?: number;
15
+ } | {
16
+ type: 'TRADITIONAL';
17
+ deuceAfter?: number;
18
+ };
19
+ export declare function createGameFormatDropdown(e: Event, currentGameFormat: GameFormat | undefined, labels: any, onUpdate: (gameFormat?: GameFormat) => void): void;
20
+ export {};
@@ -0,0 +1,12 @@
1
+ import { Capabilities, FormatState, ResolvedCapabilities } from './types';
2
+ /** Everything the picker offered before capabilities existed is on unless a consumer turns it off. */
3
+ export declare function resolveCapabilities(capabilities?: Capabilities): ResolvedCapabilities;
4
+ export declare const SET_TYPE_CAPABILITY: {
5
+ readonly SET: "set";
6
+ readonly TIEBREAK: "tiebreak";
7
+ readonly TIMED: "timed";
8
+ readonly OUTS: "outs";
9
+ readonly POINTS_TOTAL: "pointsTotal";
10
+ };
11
+ /** The switched-off capabilities this format needs, each named once. Empty means it may be edited. */
12
+ export declare function capabilityReasons(format: FormatState, capabilities: ResolvedCapabilities): string[];
@@ -0,0 +1,20 @@
1
+ import { Capabilities, Codec, EditorState, FormatState, SegmentDraft } from './types';
2
+ export declare const DEFAULT_FORMAT = "SET3-S:6/TB7";
3
+ export declare function defaultSegment(): SegmentDraft;
4
+ /** The parsed shape the factory's stringify reads, built from a segment of the state. */
5
+ export declare function segmentToParsed(segment: SegmentDraft): any;
6
+ export declare function formatFromParsed(parsed: any): FormatState;
7
+ export declare function formatToParsed(written: FormatState): any;
8
+ export declare function toCode(format: FormatState, codec: Codec): string;
9
+ /**
10
+ * The picker's starting state for a code.
11
+ *
12
+ * The code is editable only if the state rebuilds it exactly: compared canonically, so a redundant
13
+ * spelling (`@6` on a set to 6) still counts as representable. Anything else (a set type, a field or
14
+ * a modifier the state has no place for) makes it read-only, so it is returned untouched rather than
15
+ * rebuilt without the parts the controls cannot hold. A code that does not parse falls back to the
16
+ * default format, which is then what Select returns, as the picker always has.
17
+ */
18
+ export declare function fromCode(code: string, codec: Codec, capabilities?: Capabilities): EditorState;
19
+ /** What Select returns: the original code until something is edited, the rebuilt code after. */
20
+ export declare function selectedCode(state: EditorState, codec: Codec): string;
@@ -0,0 +1,16 @@
1
+ import { Capabilities, CombinedPointsConfig, Codec, Edit, EditorState } from './types';
2
+ /** A final set's tiebreak targets outside "Exactly" mode; "Exactly" also offers 1 and 3. */
3
+ export declare const DEFAULT_TIEBREAK_TO_OPTIONS: number[];
4
+ export interface EditContext {
5
+ codec: Codec;
6
+ capabilities?: Capabilities;
7
+ /** The tiebreak targets offered outside "Exactly" mode (the picker's configured options). */
8
+ tiebreakToOptions?: number[];
9
+ /** A policy's permitted points totals and tie rules (Phase 3). */
10
+ combinedPoints?: CombinedPointsConfig;
11
+ }
12
+ /**
13
+ * The picker's single transition function. Pure: it returns a new state and never touches the one it
14
+ * was given. A read-only state accepts only a new code (choosing another format replaces it whole).
15
+ */
16
+ export declare function applyEdit(state: EditorState, edit: Edit, context: EditContext): EditorState;
@@ -0,0 +1,29 @@
1
+ import { CombinedPointsConfig, FormatState, SegmentDraft } from './types';
2
+ /**
3
+ * Points total (`SET1-S:P32`): one rally-scored segment that ends at a combined total.
4
+ *
5
+ * The grammar allows it only as a single set of a SET match: no other root, no count but 1, no Exactly,
6
+ * aggregate, game format, final set, match time limit or match modifiers. Rather than overwrite those
7
+ * values when points total is chosen, the state keeps them and this overlay locks them, so choosing
8
+ * another set type brings back what the operator had.
9
+ */
10
+ export declare const DEFAULT_COMBINED_POINT_TOTALS: number[];
11
+ /** Tie allowed, then win by 1 (the deciding point), 2 or 3 (CA, 2026-10-10). */
12
+ export declare const DEFAULT_TIE_MARGINS: number[];
13
+ export declare const isPointsTotal: (format: FormatState) => boolean;
14
+ /** The format as it is written: a points total locks every match-level value. */
15
+ export declare function effectiveFormat(format: FormatState): FormatState;
16
+ /** A tie is possible only on an even total, so only an even total says what follows one. */
17
+ export declare const tieRuleApplies: (segment: SegmentDraft) => boolean;
18
+ export declare function tieMarginFromParsed(parsedSet: any): number;
19
+ /** The parsed set the factory writes as `P{n}`, `P{n}DP` or `P{n}WB{m}`. Win by 1 is the deciding point. */
20
+ export declare function pointsTotalToParsed(segment: SegmentDraft): any;
21
+ /** The totals offered: the policy's, else the configured list, else the defaults. */
22
+ export declare function offeredTotals(combinedPoints?: CombinedPointsConfig, configured?: number[]): number[];
23
+ /** The tie margins offered: the policy's variants, else all of them. */
24
+ export declare function offeredTieMargins(combinedPoints?: CombinedPointsConfig): number[];
25
+ /**
26
+ * Entering points total, a total or margin the policy does not permit moves to the first one it does,
27
+ * so the picker starts on a permitted format. A code opened with other values keeps them.
28
+ */
29
+ export declare function snapToPermitted(segment: SegmentDraft, combinedPoints?: CombinedPointsConfig): void;
@@ -0,0 +1,200 @@
1
+ /**
2
+ * The format picker as an explicit state machine.
3
+ *
4
+ * One `FormatState` holds the whole format; the DOM holds none of it. A code becomes state through
5
+ * `fromCode`, state becomes a code through `toCode`, every change is an `Edit` applied by `applyEdit`
6
+ * (where each invariant lives once), and `deriveView` says what every control shows. The modal only
7
+ * renders the view and dispatches edits.
8
+ */
9
+ export type SetType = 'SET' | 'TIEBREAK' | 'TIMED' | 'OUTS' | 'POINTS_TOTAL';
10
+ export type CountMode = 'BEST_OF' | 'EXACTLY';
11
+ export type SegmentKey = 'set' | 'finalSet';
12
+ export type GameFormat = {
13
+ type: 'CONSECUTIVE';
14
+ count: number;
15
+ deuceAfter?: number;
16
+ } | {
17
+ type: 'TRADITIONAL';
18
+ deuceAfter?: number;
19
+ };
20
+ /**
21
+ * One segment (the main set or the final set). It keeps a value for every set type's controls, so
22
+ * switching type and back restores what the operator had rather than resetting to defaults.
23
+ */
24
+ export interface SegmentDraft {
25
+ type: SetType;
26
+ /** Standard set: games to win the set. */
27
+ setTo: number;
28
+ /** Standard set: no-advantage games. */
29
+ noAd: boolean;
30
+ /** Standard set: whether it ends in a tiebreak. */
31
+ tiebreak: boolean;
32
+ /** Standard set with a tiebreak: the game score at which it is played. */
33
+ tiebreakAt: number;
34
+ /** Standard set without a tiebreak: the game margin needed to win (2 is the default). */
35
+ setWinBy: number;
36
+ /** A set tiebreak or a tiebreak-only set: points to win. */
37
+ tiebreakTo: number;
38
+ /** A set tiebreak or a tiebreak-only set: 1 is no-advantage, 2 is win by two. */
39
+ tiebreakWinBy: number;
40
+ /** Timed set: minutes. */
41
+ minutes: number;
42
+ /** Timed set: scoring basis. Undefined is games, the grammar's default. */
43
+ based?: string;
44
+ /**
45
+ * Timed set: whether it ends in a tiebreak (`T10P/TB1`). Separate from a standard set's `tiebreak`, so
46
+ * switching a set with a tiebreak to timed does not give the timed set one.
47
+ */
48
+ timedTiebreak: boolean;
49
+ /** Outs set (baseball and similar): outs per side. */
50
+ outs: number;
51
+ /** A tiebreak's or a timed set's modifier, e.g. RALLY. */
52
+ modifier?: string;
53
+ /** Points total (Americano, `P32`): the rallies played; the score is how they split between the sides. */
54
+ combinedPointTotal: number;
55
+ /**
56
+ * Points total: what follows a tie. 0 keeps the tie as a result (`P32`), 1 plays one deciding point
57
+ * (`P32DP`), and 2 or more plays on until one side leads by that margin (`P32WB2`). Written only for an
58
+ * even total, since an odd total cannot tie.
59
+ */
60
+ tieMargin: number;
61
+ }
62
+ export interface FormatState {
63
+ /** SET, HAL, QTR, PER, INN, RND, FRM or MAP. */
64
+ matchRoot: string;
65
+ countMode: CountMode;
66
+ count: number;
67
+ aggregate: boolean;
68
+ gameFormat?: GameFormat;
69
+ set: SegmentDraft;
70
+ finalSetEnabled: boolean;
71
+ /** Kept even while disabled, so turning the final set back on restores it. */
72
+ finalSet: SegmentDraft;
73
+ /** A match-level time limit in minutes (`-M:T60`), or undefined for none. */
74
+ maxMinutes?: number;
75
+ /**
76
+ * Match modifiers the grammar keeps for forward compatibility (`SET3Z`). The picker has no control for
77
+ * them; they are carried through unchanged.
78
+ */
79
+ matchMods: string[];
80
+ /**
81
+ * The code used the standalone timed short form (`T10` for `SET1-S:T10`). Kept while the format can
82
+ * still be written that way, so an edit that keeps it a single timed set keeps its spelling.
83
+ */
84
+ shortForm: boolean;
85
+ }
86
+ /** Each capability can be switched off; a code that needs a switched-off one opens read-only. */
87
+ export interface Capabilities {
88
+ setTypes?: {
89
+ set?: boolean;
90
+ tiebreak?: boolean;
91
+ timed?: boolean;
92
+ outs?: boolean;
93
+ pointsTotal?: boolean;
94
+ };
95
+ /** Choosing a match root other than SET. */
96
+ matchRoots?: boolean;
97
+ /** "Exactly N" set counts. */
98
+ exactly?: boolean;
99
+ aggregate?: boolean;
100
+ gameFormat?: boolean;
101
+ finalSet?: boolean;
102
+ /** A standard or timed set's tiebreak toggle. */
103
+ setTiebreak?: boolean;
104
+ /** A match-level time limit (`-M:T60`). */
105
+ maxMatchTime?: boolean;
106
+ /** A timed set's scoring basis (games or points). */
107
+ scoringBasis?: boolean;
108
+ /** Tiebreak and timed-set modifiers, e.g. RALLY. */
109
+ modifiers?: boolean;
110
+ /** The dropdown of predefined formats. */
111
+ catalog?: boolean;
112
+ }
113
+ export type ResolvedCapabilities = {
114
+ setTypes: {
115
+ set: boolean;
116
+ tiebreak: boolean;
117
+ timed: boolean;
118
+ outs: boolean;
119
+ pointsTotal: boolean;
120
+ };
121
+ matchRoots: boolean;
122
+ exactly: boolean;
123
+ aggregate: boolean;
124
+ gameFormat: boolean;
125
+ finalSet: boolean;
126
+ setTiebreak: boolean;
127
+ maxMatchTime: boolean;
128
+ scoringBasis: boolean;
129
+ modifiers: boolean;
130
+ catalog: boolean;
131
+ };
132
+ /** What the picker is working on: the format, the code it opened with, and whether it may be edited. */
133
+ export interface EditorState {
134
+ format: FormatState;
135
+ /** The code the picker was opened with or last loaded. Returned unchanged until something is edited. */
136
+ original: string;
137
+ /** True once any control has changed the format. */
138
+ dirty: boolean;
139
+ /**
140
+ * Why the format cannot be edited here, or empty when it can. A code the state cannot represent
141
+ * exactly, or one that needs a switched-off capability, is shown and returned unchanged instead of
142
+ * being rebuilt from controls that would silently drop part of it.
143
+ */
144
+ readOnlyReasons: string[];
145
+ }
146
+ export type SegmentField = 'setTo' | 'noAd' | 'tiebreakAt' | 'setWinBy' | 'tiebreakTo' | 'tiebreakWinBy' | 'minutes' | 'based' | 'modifier' | 'outs' | 'combinedPointTotal' | 'tieMargin';
147
+ export type Edit = {
148
+ type: 'loadCode';
149
+ code: string;
150
+ } | {
151
+ type: 'setCountMode';
152
+ mode: CountMode;
153
+ } | {
154
+ type: 'setCount';
155
+ count: number;
156
+ } | {
157
+ type: 'setMatchRoot';
158
+ matchRoot: string;
159
+ } | {
160
+ type: 'setAggregate';
161
+ on: boolean;
162
+ } | {
163
+ type: 'setGameFormat';
164
+ gameFormat?: GameFormat;
165
+ } | {
166
+ type: 'setMaxMinutes';
167
+ minutes?: number;
168
+ } | {
169
+ type: 'setFinalSet';
170
+ on: boolean;
171
+ } | {
172
+ type: 'setSetType';
173
+ segment: SegmentKey;
174
+ setType: SetType;
175
+ } | {
176
+ type: 'setTiebreak';
177
+ segment: SegmentKey;
178
+ on: boolean;
179
+ } | {
180
+ type: 'setSegmentValue';
181
+ segment: SegmentKey;
182
+ field: SegmentField;
183
+ value: any;
184
+ };
185
+ /**
186
+ * What a points total may be, as a consumer's policy permits it (Phase 3). It narrows what the picker
187
+ * offers; it enforces nothing, since the engine validates every write.
188
+ */
189
+ export interface CombinedPointsConfig {
190
+ permittedPointTotals?: number[];
191
+ permittedVariants?: {
192
+ tieResolution: 'ALLOW' | 'DECIDING_POINT' | 'WIN_BY_MARGIN';
193
+ winningMargin?: number;
194
+ }[];
195
+ }
196
+ /** The factory's parse and stringify, injected so the machine stays pure and testable. */
197
+ export interface Codec {
198
+ parse: (code: string) => any;
199
+ stringify: (parsed: any) => string;
200
+ }
@@ -0,0 +1,76 @@
1
+ import { Capabilities, Codec, CombinedPointsConfig, EditorState, GameFormat, SegmentField, SetType } from './types';
2
+ /**
3
+ * Everything the picker shows, derived from the state. The modal renders this and decides nothing
4
+ * itself: which controls are visible, what each one reads, and what each dropdown offers.
5
+ */
6
+ export interface Option {
7
+ value: any;
8
+ text: string;
9
+ }
10
+ export interface ControlView {
11
+ visible: boolean;
12
+ label: string;
13
+ options: Option[];
14
+ /** Shown but not changeable: a value a points total fixes, or the only one a policy permits. */
15
+ locked?: boolean;
16
+ }
17
+ export interface ToggleView {
18
+ visible: boolean;
19
+ checked: boolean;
20
+ }
21
+ export interface SegmentView {
22
+ what: ControlView & {
23
+ setType: SetType;
24
+ };
25
+ setTo: ControlView;
26
+ tiebreakTo: ControlView;
27
+ tiebreakAt: ControlView;
28
+ /** One button, two meanings: the tiebreak's win-by when there is a tiebreak, the set's otherwise. */
29
+ winBy: ControlView & {
30
+ field?: SegmentField;
31
+ };
32
+ advantage: ControlView;
33
+ minutes: ControlView;
34
+ based: ControlView;
35
+ modifier: ControlView;
36
+ outs: ControlView;
37
+ /** Points total: the rallies played. */
38
+ pointsTotal: ControlView;
39
+ /** Points total: tie allowed, or win by 1 (the deciding point), 2 or 3. Hidden for an odd total. */
40
+ tieRule: ControlView;
41
+ tiebreakToggle: ToggleView;
42
+ }
43
+ export interface EditorView {
44
+ code: string;
45
+ /** The catalog entry matching the code, or 'Custom'. */
46
+ catalogValue: string;
47
+ catalogVisible: boolean;
48
+ readOnly: boolean;
49
+ descriptor: ControlView;
50
+ count: ControlView;
51
+ matchRoot: ControlView;
52
+ aggregate: ToggleView;
53
+ gameFormat: ControlView & {
54
+ gameFormat?: GameFormat;
55
+ };
56
+ /** The match-level time limit (`-M:T60`). Its option value 0 means none. */
57
+ maxTime: ControlView;
58
+ finalSetToggle: ToggleView;
59
+ set: SegmentView;
60
+ finalSet: SegmentView & {
61
+ visible: boolean;
62
+ };
63
+ }
64
+ export interface ViewConfig {
65
+ codec: Codec;
66
+ capabilities?: Capabilities;
67
+ labels?: any;
68
+ options?: any;
69
+ /** The codes the catalog dropdown offers. */
70
+ catalogCodes: string[];
71
+ /** A policy's permitted points totals and tie rules: they narrow what is offered (Phase 3). */
72
+ combinedPoints?: CombinedPointsConfig;
73
+ }
74
+ export declare const CUSTOM = "Custom";
75
+ export declare function gameFormatText(gameFormat: GameFormat | undefined, labels?: any): string;
76
+ export declare function deriveView(state: EditorState, config: ViewConfig): EditorView;
@@ -1,4 +1,5 @@
1
- interface MatchUpFormatConfig {
1
+ import { Capabilities, CombinedPointsConfig } from './machine/types';
2
+ export interface MatchUpFormatConfig {
2
3
  labels?: {
3
4
  title?: string;
4
5
  setFormatLabel?: string;
@@ -22,6 +23,10 @@ interface MatchUpFormatConfig {
22
23
  starPoint?: string;
23
24
  games?: string;
24
25
  points?: string;
26
+ /** Shown when a format cannot be edited here and will be returned unchanged. */
27
+ readOnly?: string;
28
+ /** The match-level time limit control. */
29
+ maxTime?: string;
25
30
  descriptors?: {
26
31
  bestOf?: string;
27
32
  exactly?: string;
@@ -48,15 +53,27 @@ interface MatchUpFormatConfig {
48
53
  matchRoots?: string[];
49
54
  gameFormats?: string[];
50
55
  modifiers?: string[];
56
+ /** Outs per side offered for an outs set. */
57
+ outs?: number[];
58
+ /** Minutes offered for the match-level time limit. */
59
+ maxMinutes?: number[];
60
+ /** Totals offered for a points total (`SET1-S:P32`). */
61
+ combinedPointTotals?: number[];
51
62
  };
63
+ /** A policy's permitted points totals and tie rules. Narrows what is offered; the engine still validates. */
64
+ combinedPoints?: CombinedPointsConfig;
65
+ /** Switch parts of the picker off. Everything is on unless set to false. */
66
+ capabilities?: Capabilities;
52
67
  preDefinedFormats?: Array<{
53
68
  code: string;
54
69
  text: string;
55
70
  }>;
56
71
  }
57
- export declare function getMatchUpFormatModal({ existingMatchUpFormat, callback, config, modalConfig }?: {
72
+ export declare function getMatchUpFormatModal({ existingMatchUpFormat, callback, onClose, config, modalConfig }?: {
58
73
  existingMatchUpFormat?: string;
59
74
  callback?: (format: string) => void;
75
+ /** Called however the modal closes — Select, Cancel, or anything else that closes it. */
76
+ onClose?: () => void;
60
77
  config?: MatchUpFormatConfig;
61
78
  modalConfig?: any;
62
79
  }): {
@@ -82,4 +99,3 @@ export declare function getMatchUpFormatModal({ existingMatchUpFormat, callback,
82
99
  label?: string;
83
100
  }) => void;
84
101
  };
85
- export {};
@@ -11,6 +11,8 @@ export declare class SchedulePageControl {
11
11
  setMatchUpCatalog(catalog: CatalogMatchUpItem[]): void;
12
12
  setScheduleDates(dates: ScheduleDate[]): void;
13
13
  setIssues(issues: ScheduleIssue[]): void;
14
+ /** Push several of the above and render once: `control.batch(() => { … })`. */
15
+ batch(writes: () => void): void;
14
16
  setActiveStripVisible(visible: boolean): void;
15
17
  toggleActiveStrip(): void;
16
18
  get activeStripVisible(): boolean;
@@ -18,8 +18,8 @@ export declare function participantLabel(side?: MatchUpSide): string;
18
18
  * set no tooltip at all rather than an empty one.
19
19
  *
20
20
  * Pure and here rather than inline in the renderer so it can be tested without a DOM:
21
- * the ecosystem's DOM layer is Playwright/Storybook, and `test-storybook` cannot
22
- * currently run in this repo (612/612 suites fail in the runner, measured 2026-09-27).
21
+ * the ecosystem's DOM layer is Storybook play functions in a browser (`pnpm test:stories`), and a
22
+ * pure function needs no browser to be checked.
23
23
  */
24
24
  export declare function memberNamesLabel(side?: MatchUpSide | ScheduleCellSide): string;
25
25
  export declare function matchUpSearchKey(item: CatalogMatchUpItem): string;
@@ -3,6 +3,9 @@ export declare class SchedulePageStore {
3
3
  private state;
4
4
  private readonly listeners;
5
5
  private readonly config;
6
+ /** `batch` nesting, and whether a write inside it is waiting to be announced. */
7
+ private batchDepth;
8
+ private emitDeferred;
6
9
  constructor(config: SchedulePageConfig);
7
10
  getState(): SchedulePageState;
8
11
  setMatchUpCatalog(catalog: CatalogMatchUpItem[]): void;
@@ -43,5 +46,11 @@ export declare class SchedulePageStore {
43
46
  toggleInspector(): void;
44
47
  subscribe(listener: SchedulePageChangeListener): () => void;
45
48
  private setState;
49
+ /**
50
+ * Run several writes and notify listeners once, after the last. A consumer's refresh pushes the
51
+ * catalog, the dates and the issues together; written one by one, each push rebuilt every panel.
52
+ * Nested batches notify when the outermost one ends.
53
+ */
54
+ batch(writes: () => void): void;
46
55
  private emit;
47
56
  }
@@ -69,10 +69,25 @@ export declare function isSetTiebreakOnly(format?: SetFormat): boolean;
69
69
  */
70
70
  export declare function isSetTimed(format?: SetFormat): boolean;
71
71
  /**
72
- * Calculate the maximum allowed score for a regular set game score
73
- * based on the opponent's score and set rules
72
+ * The highest game (or tiebreak-point) score one side may enter, given the opponent's.
74
73
  *
75
- * For timed sets, returns Infinity (no maximum) since scores don't need relationships
74
+ * ── Delegated to the factory ──
75
+ *
76
+ * `scoreGovernor.getMaxSetScore` answers for a games set from the format alone: the tiebreak games,
77
+ * `tiebreakAt` above or below `setTo`, a declared `winBy`. It answers `undefined` where the format has no
78
+ * ceiling — a timed set, an advantage set past the tiebreak-less `setTo`, or a tiebreak-only set, whose
79
+ * points are not games — and each of those is handled here by the factory's own tiebreak complement or
80
+ * by the set's margin, not by recomputing tennis.
81
+ *
82
+ * The hand-rolled body this replaced read a tiebreak-only set as a six-game set: `SET1-S:TB10` with no
83
+ * opponent score was capped at **7** — a match tiebreak to ten that could not be entered (measured
84
+ * 2026-10-08, live in the shipping dialog). It now caps at the complement of the opponent's points: 10,
85
+ * then 12 against 10, and `NoAD` ends it at the target.
86
+ *
87
+ * One convention of the calling dialog is kept: an opponent score of 0 means "not entered yet" and allows
88
+ * the format's ceiling. Where the opponent's games already hold the set, the answer is the factory's
89
+ * ceiling (CA, 2026-10-08: "follow the factory") — 7 under `S:6/TB7`, 5 under `S:5/TB9@4` — and the old
90
+ * "extended play" read of 9, a tiebreak set running on like an advantage set, is gone.
76
91
  */
77
92
  export declare function getMaxAllowedScore(setIndex: number, side: 1 | 2, currentScores: {
78
93
  side1: number;
@@ -0,0 +1,28 @@
1
+ import { SetScore } from '../types';
2
+ /** What the card reports on Submit, with the sets the dialog adds. */
3
+ export type ReportedOutcome = {
4
+ matchUpStatus?: string;
5
+ winningSide?: number;
6
+ reasonCode?: string;
7
+ score?: string;
8
+ cleared?: boolean;
9
+ sets?: SetScore[];
10
+ };
11
+ /** The outcome in the shape `tournamentEngine.setMatchUpStatus({ drawId, matchUpId, outcome })` reads. */
12
+ export type EngineOutcome = {
13
+ matchUpStatus?: string;
14
+ winningSide?: number;
15
+ matchUpFormat?: string;
16
+ matchUpStatusCodes?: string[];
17
+ score: {
18
+ sets: SetScore[];
19
+ };
20
+ };
21
+ export declare function toEngineOutcome(outcome: ReportedOutcome, matchUpFormat?: string): EngineOutcome;
22
+ /**
23
+ * The positional array the engine reads a reason from.
24
+ *
25
+ * A single exit: the side that did NOT win, at its index. A double exit: both. An ending that resolves
26
+ * nobody: index 0, since there is no side — the engine files that one as `matchUpStatusCode`.
27
+ */
28
+ export declare function positionalCodes({ reasonCode, matchUpStatus, winningSide }: ReportedOutcome): string[] | undefined;
@@ -156,10 +156,22 @@ export type ScoreEntryCardParams = {
156
156
  initialState?: ScoreEntryState;
157
157
  onClear?: () => void;
158
158
  onSubmit?: (outcome: ScoreEntryOutcome) => void;
159
- onClose?: () => void;
159
+ /**
160
+ * Whether a recorded outcome may be REMOVED. Only the draw knows whether a later match depends on the
161
+ * result, so the host reads it from the engine (the factory's `CLEAR_SCORE` matchUp action) rather than
162
+ * the dialog guessing. `false` disables `[Clear]` on a reopened result and says why in its
163
+ * tooltip (`labels.clearRefused`), so the dialog never offers a
164
+ * submission the engine will refuse. Omitted, `[Clear]` behaves as it always has.
165
+ */
166
+ clearable?: boolean;
160
167
  };
161
168
  export type ScoreEntryCard = {
162
169
  element: HTMLElement;
170
+ /**
171
+ * Close an open menu (the approach switcher's or Other's) and report whether one was open. The dialog's
172
+ * Escape calls it first, wherever focus is, so one Escape closes the menu and a second one cancels.
173
+ */
174
+ closeMenus: () => boolean;
163
175
  /** Re-render the band and the submit gate. Call when the score region's value changes. */
164
176
  refresh: () => void;
165
177
  /** Rebuild everything, including the region's cells. Call when the region's COLUMNS change. */
@@ -1,16 +1,24 @@
1
+ import { EngineOutcome } from './logic/engineOutcome';
1
2
  import { SetScore } from './types';
2
3
  import { ScoreEntryCard, ScoreEntryCardParams, ScoreEntryOutcome } from './scoreEntryCard';
3
4
  /** The three entry approaches. The key is what `onSelectApproach` reports and what a host persists. */
4
5
  export type ScoreEntryApproach = 'dynamicSets' | 'freeScore' | 'dialPad';
5
- export type ScoreEntryDialogParams = Omit<ScoreEntryCardParams, 'region' | 'approachLabel' | 'approaches' | 'onSelectApproach' | 'onSwitchApproach' | 'onEditFormat' | 'onClose' | 'onSubmit'> & {
6
+ export type ScoreEntryDialogParams = Omit<ScoreEntryCardParams, 'region' | 'approachLabel' | 'approaches' | 'onSelectApproach' | 'onSwitchApproach' | 'onEditFormat' | 'onSubmit'> & {
6
7
  /**
7
8
  * Called with the outcome, then the dialog closes.
8
9
  *
9
10
  * `sets` is added to what the card reports, because the dialog knows its regions carry them and a
10
11
  * host saving to the factory needs the structured sets rather than only the formatted string.
12
+ *
13
+ * `outcome` is the same result in the shape the factory reads — hand it to
14
+ * `tournamentEngine.setMatchUpStatus({ drawId, matchUpId, outcome })` as is (pass a copy if you keep
15
+ * what you sent: the engine writes its derived score strings into it). The score as `score.sets`, a
16
+ * chosen reason as positional `matchUpStatusCodes`, a clear as the empty-sets outcome that actually
17
+ * clears, and a format changed through the chip as `matchUpFormat`. See `logic/engineOutcome.ts`.
11
18
  */
12
19
  onSubmit?: (outcome: ScoreEntryOutcome & {
13
20
  sets: SetScore[];
21
+ outcome: EngineOutcome;
14
22
  }) => void;
15
23
  /** Which approach opens. Defaults to Dynamic Sets. */
16
24
  approach?: ScoreEntryApproach;
@@ -77,6 +85,8 @@ export type ScoreEntryDialogParams = Omit<ScoreEntryCardParams, 'region' | 'appr
77
85
  openFormatPicker?: (params: {
78
86
  existingMatchUpFormat: string;
79
87
  callback: (matchUpFormat: string) => void;
88
+ /** Called however the picker closes; the dialog is inert until then. */
89
+ onClose?: () => void;
80
90
  }) => void;
81
91
  };
82
92
  export type ScoreEntryDialog = {
@@ -67,6 +67,13 @@ export type ScoringModalParams = {
67
67
  * with no governing-body policy attached.
68
68
  */
69
69
  matchUpStatusCodes?: StatusCodeGroups;
70
+ /**
71
+ * Whether a recorded outcome may be REMOVED. Only the draw knows whether a later match depends on the
72
+ * result, so the host reads it from the engine (the factory's `CLEAR_SCORE` matchUp action) rather than
73
+ * the dialog guessing. `false` withholds `[Clear]` on a reopened result, so the dialog never offers a
74
+ * submission the engine will refuse. Omitted, `[Clear]` behaves as it always has.
75
+ */
76
+ clearable?: boolean;
70
77
  };
71
78
  export type ScoreChangeHandler = (outcome: ScoreOutcome) => void;
72
79
  export type RenderScoreEntryParams = {
@@ -6,6 +6,8 @@ export interface TopologyNode {
6
6
  structureType: string;
7
7
  drawSize: number;
8
8
  qualifyingPositions?: number;
9
+ /** QUALIFYING nodes: seeds to place in the structure (0 = unseeded). */
10
+ seedsCount?: number;
9
11
  matchUpFormat?: string;
10
12
  structureOptions?: any;
11
13
  position: {