@bitcobblers/wod-wiki-lang 0.1.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.
@@ -0,0 +1,4076 @@
1
+ import { IMetric, MetricContainer, MetricOrigin, MetricType, IDialect, ICodeStatement, DialectAnalysis, ParsedCodeStatement, IOutputStatement, OutputStatementType, BlockKey, ScriptBlock, FenceDialect, Segment, AnalyticsGroup, StoredOutputStatement, OutputStatement } from '@bitcobblers/wod-wiki-core';
2
+ export { ParseError } from '@bitcobblers/wod-wiki-core';
3
+ import { Tree } from '@lezer/common';
4
+ import { W as WhiteboardScript, I as IScript, a as IEffortResolver, b as IEffortRegistry, E as EffortResolverOptions, c as IEffort, d as EffortResolutionOptions, R as ResolvedEffort, e as EffortRegistrySource, f as IRuntimeActionable, g as IEventBus, h as IRuntimeStack, i as IRuntimeClock, j as IJitCompiler, k as INowProvider, l as RuntimeStackOptions, m as RuntimeError, A as AnalyticsContext, n as IEvent, o as IRuntimeAction, O as OutputListener, U as Unsubscribe, p as IAnalyticsEngine, q as IRuntimeBlock, r as IBehaviorContext, B as BehaviorEventType, s as BehaviorEventListener, S as SubscribeOptions, t as Unsubscribe$1, u as OutputOptions, M as MemoryTag, v as IMemoryLocation, w as IRuntimeBehavior, x as IBlockContext, y as MetricVisibility, z as BlockLifecycleOptions, C as CompletionDecision, T as TimeSpan, D as IRuntimeMemory, F as TypedMemoryReference, G as MemorySearchCriteria, H as IMemoryReference, J as StackObserver, K as StackSnapshot, L as StackListener, N as MemoryTypeEnum, P as IAnchorValue, Q as IEventHandler, V as HandlerScope, X as IScriptRuntime, Y as TestableBlockConfig, Z as IRuntimeOptions, _ as ScriptRuntime, $ as EventHandlerOptions, a0 as EventCallback, a1 as TimerState, a2 as IRealtimeProcessor, a3 as ISummaryProcessor, a4 as ProjectionResult } from './browserRuntimeNow-SxxdmtB7.js';
5
+ export { a5 as ButtonConfig, a6 as ButtonVariant, a7 as ButtonsState, a8 as ChildrenStatusState, a9 as CompletionState, aa as DEFAULT_RESOLVER_OPTIONS, ab as DISCIPLINE_FACTORS, ac as DisplayState, ad as EFFORT_DISCIPLINES, ae as EFFORT_REGISTRY_ORIGINS, af as EffortBaseAttributes, ag as EffortDerivation, ah as EffortDiscipline, ai as EffortResolvedFrom, aj as FenceDialect, ak as FragmentDisplayState, al as FragmentState, am as IAnalyticsProcessorDescriptor, an as INTENSITY_TIERS, ao as IntensityTier, ap as MemoryLocation, aq as MemoryType, ar as MemoryTypeMap, as as MemoryValueOf, at as RoundState, au as RuntimeState, av as ScriptRuntimeDependencies, aw as TimerDirection, ax as VISIBILITY_ICONS, ay as VISIBILITY_LABELS, az as createBrowserRuntimeNow, aA as disciplineFactorFor, aB as filterTagsByVisibility, aC as frozenNow, aD as getMetricVisibility, aE as getRuntimeNowMs, aF as isEffortDiscipline, aG as isFragmentTag, aH as wallClockNow } from './browserRuntimeNow-SxxdmtB7.js';
6
+ import { LanguageSupport, LRLanguage } from '@codemirror/language';
7
+ import { LRParser } from '@lezer/lr';
8
+
9
+ /**
10
+ * Registry — one registration story for every consumer-facing extension point.
11
+ *
12
+ * Used for dialects, compiler strategies, realtime analytics processors,
13
+ * and summary analytics processors. Replaces the previous hardcoded
14
+ * `createDialectStack(overrides)`, `PRODUCTION_STRATEGIES`, and
15
+ * `StandardAnalyticsProfile.allRealtime`/`allSummary` flat arrays with
16
+ * one shape: a priority-ordered map keyed by `id`, with consumer-facing
17
+ * `register` / `unregister` / `list` so a third party can extend the
18
+ * library without forking the repository.
19
+ *
20
+ * The constructor seeds the registry with the built-in defaults. Built-ins
21
+ * can still be overridden (or removed) by registering an entry with the
22
+ * same `id` (or calling `unregister(id)`).
23
+ *
24
+ * @typeParam T Items must expose an `id: string` and an optional
25
+ * `priority: number`; `list()` returns them sorted by descending
26
+ * priority, matching the contract strategies, dialects, and
27
+ * processors already follow.
28
+ */
29
+ declare class Registry<T extends {
30
+ id: string;
31
+ priority?: number;
32
+ }> {
33
+ private items;
34
+ constructor(defaults?: readonly T[]);
35
+ /**
36
+ * Register an item. If an item with the same `id` already exists it is
37
+ * replaced (mirrors "last registration wins" already used for dialect
38
+ * metric merges).
39
+ *
40
+ * Use {@link unregister} first if you need strict rejection of duplicates.
41
+ */
42
+ register(item: T): void;
43
+ /** Remove the item registered under `id`. No-op if not present. */
44
+ unregister(id: string): void;
45
+ /** Whether an item is currently registered under `id`. */
46
+ has(id: string): boolean;
47
+ /**
48
+ * Get the item registered under `id`, or `undefined` if absent.
49
+ */
50
+ get(id: string): T | undefined;
51
+ /**
52
+ * Items in descending `priority` order (highest first). Items without
53
+ * an explicit priority are treated as priority 0.
54
+ */
55
+ list(): T[];
56
+ /** Number of currently registered items. */
57
+ get size(): number;
58
+ }
59
+
60
+ /**
61
+ * Hint helpers + canonical hint vocabulary contract.
62
+ *
63
+ * A "hint" is a dot-namespaced string (e.g. `workout.amrap`,
64
+ * `behavior.required_timer`) that dialects, effort markdown files, and the
65
+ * parser attach to a statement so that compiler strategies and the label
66
+ * composer can make decisions. Hints used to live in a parallel `Set<string>`
67
+ * channel on the statement; they now flow through the single metric channel
68
+ * as {@link MetricType.Hint} metrics.
69
+ *
70
+ * Hint metrics are deliberately excluded from display resolution
71
+ * (see ownership `legacyAdapters`) and from runtime block fragments
72
+ * (see `BlockBuilder.setFragments`), so they never surface as visible metrics.
73
+ *
74
+ * ─────────────────────────────────────────────────────────────────────────────
75
+ * CANONICAL HINT VOCABULARY (Tier 3 §3.2)
76
+ * ─────────────────────────────────────────────────────────────────────────────
77
+ *
78
+ * A new dialect or effort author can self-serve the answer to "which
79
+ * hints are actually consumed by the runtime?" by reading
80
+ * {@link CONSUMED_HINTS}. Any other hint key is **analytics-only** today:
81
+ * it reaches the output stream and processors, but does not affect
82
+ * compilation, label generation, or exit ordering. There is no hidden
83
+ * "the strategy that consumes this hint" to discover.
84
+ *
85
+ * Categories:
86
+ *
87
+ * - **compiler-consumed** — `BEHAVIOR_REPEATING_INTERVAL`,
88
+ * `BEHAVIOR_REQUIRED_TIMER`, `BEHAVIOR_INJECT_REST`. Setting one of these
89
+ * from a dialect (or an effort — see Tier 3 §3.3) flips a strategy-level
90
+ * decision at compile time (e.g. IntervalLogicStrategy matches on
91
+ * `BEHAVIOR_REPEATING_INTERVAL`).
92
+ * - **label-consumed** — `LABEL_AMRAP`, `LABEL_EMOM`, `LABEL_TABATA`,
93
+ * `LABEL_FOR_TIME`. These override the generated label in
94
+ * `LabelComposer.getLogicKeyword` (the only consumer).
95
+ * - **analytics-only / sport-domain** — every other hint a sport dialect
96
+ * emits (e.g. `workout.run`, `ClimbMetricType.Grade`, `domain.climb.*`)
97
+ * is intentionally analytics-only: it surfaces on the output stream
98
+ * for processors and review-grid consumers, and never affects
99
+ * compilation. If you write a custom analytics processor, gate it via
100
+ * `requiredMetrics` (see `IAnalyticsProcessorDescriptor`) — not by
101
+ * inventing a new hint.
102
+ *
103
+ * If you need a new compiler-consumed or label-consumed hint, add it
104
+ * here and update the consumer(s) in the same PR. If you only need
105
+ * analytics-only data, emit a regular `MetricType.Metric` (or a
106
+ * domain-specific MetricType) — no new hint key required.
107
+ */
108
+ /** Hint keys currently consumed by compiler strategies or the label composer. */
109
+ declare const CONSUMED_HINTS: {
110
+ /** IntervalLogicStrategy: treats the block as an EMOM-style repeating interval. */
111
+ readonly REPEATING_INTERVAL: "behavior.repeating_interval";
112
+ /** GenericTimerStrategy: block cannot be skipped past until timer:complete fires. */
113
+ readonly REQUIRED_TIMER: "behavior.required_timer";
114
+ /** GenericTimerStrategy: injects a rest block between repeating intervals. */
115
+ readonly INJECT_REST: "behavior.inject_rest";
116
+ /** LabelComposer: overrides the generated label with a workout-type name. */
117
+ readonly LABEL_AMRAP: "workout.amrap";
118
+ readonly LABEL_EMOM: "workout.emom";
119
+ readonly LABEL_TABATA: "workout.tabata";
120
+ readonly LABEL_FOR_TIME: "workout.for_time";
121
+ };
122
+ /** Derived from CONSUMED_HINTS — useful for runtime validation ("is this hint consumed?"). */
123
+ declare const CONSUMED_HINT_KEYS: readonly string[];
124
+ /** A read surface that exposes metrics, via `rawMetrics` or a `metrics` field. */
125
+ interface MetricBearing {
126
+ rawMetrics?: IMetric[];
127
+ metrics?: MetricContainer | IMetric[];
128
+ }
129
+ /** Create a single hint metric. */
130
+ declare function hintMetric(hint: string, origin?: MetricOrigin): IMetric;
131
+ /**
132
+ * Build (or extend) a {@link MetricContainer} of hint metrics from a list of
133
+ * hint strings. Duplicate hint strings are collapsed.
134
+ */
135
+ declare function hintsToContainer(hints: readonly string[], existing?: MetricContainer, origin?: MetricOrigin): MetricContainer;
136
+ /** All hint strings carried by a metric-bearing source. */
137
+ declare function getHints(source: MetricBearing | undefined): string[];
138
+ /** Whether a metric-bearing source carries a given hint. */
139
+ declare function hasHint(source: MetricBearing | undefined, hint: string): boolean;
140
+
141
+ /**
142
+ * Unit Registry — the core catalog of measurement units.
143
+ *
144
+ * Units are NOT a parser concept. The parser emits bare Number and Text; a
145
+ * {@link UnitSet} (imported by a Dialect) identifies which text tokens are units
146
+ * and what {@link Dimension} they measure. See CONTEXT.md → "Units of measurement".
147
+ *
148
+ * This module is pure data + lookup. It has no dependency on metric classes,
149
+ * the parser, or dialects — those import from here, never the reverse.
150
+ */
151
+ /** The physical quantity a {@link UnitDef} measures. */
152
+ type Dimension = 'length' | 'mass' | 'energy' | 'count' | 'time';
153
+ /** A single recognized unit: its canonical spelling, dimension, and aliases. */
154
+ interface UnitDef$1 {
155
+ /** Canonical, normalized spelling (e.g. `m`, `kg`, `cal`). */
156
+ readonly canonical: string;
157
+ /** What the unit measures. */
158
+ readonly dimension: Dimension;
159
+ /** Alternate spellings / acronyms (e.g. `lb` ← `lbs`, `pound`, `pounds`). */
160
+ readonly aliases: readonly string[];
161
+ }
162
+ /** Result of peeling a leading unit token off raw text. */
163
+ interface UnitMatch {
164
+ /** The matched canonical unit definition. */
165
+ readonly unit: UnitDef$1;
166
+ /** The exact token as it appeared in the source (e.g. `miles`). */
167
+ readonly token: string;
168
+ /** Remaining text after the consumed unit token (whitespace-trimmed). */
169
+ readonly rest: string;
170
+ }
171
+ /**
172
+ * An importable, composable set of {@link UnitDef}s.
173
+ *
174
+ * Lookups are case-insensitive across canonical spellings and aliases. Sets
175
+ * compose with {@link extend}: on a token collision the later definition wins,
176
+ * which is how a Dialect overrides a base unit.
177
+ */
178
+ declare class UnitSet {
179
+ /** Definitions in priority order (later entries override earlier on token collision). */
180
+ private readonly defs;
181
+ private readonly byToken;
182
+ constructor(defs?: readonly UnitDef$1[]);
183
+ /** Look up a unit by canonical spelling or alias (case-insensitive). */
184
+ get(token: string): UnitDef$1 | undefined;
185
+ /** Whether the token is a recognized unit (canonical or alias). */
186
+ has(token: string): boolean;
187
+ /**
188
+ * Peel a leading unit token off the front of `text`.
189
+ *
190
+ * Returns `null` when the first whitespace/letter-delimited word is not a
191
+ * recognized unit — this is what keeps `5 Burpees` from ever fusing while
192
+ * `4 mile Run` → `{ unit: mile, rest: "Run" }`.
193
+ */
194
+ consumeLeading(text: string): UnitMatch | null;
195
+ /**
196
+ * Compose into a new set. Later units override earlier ones on token
197
+ * collision — the mechanism a Dialect uses to add or redefine units.
198
+ */
199
+ extend(...units: UnitDef$1[]): UnitSet;
200
+ /** All definitions in priority order. */
201
+ toArray(): UnitDef$1[];
202
+ }
203
+ /**
204
+ * The base catalog applied at the bottom of the Dialect Stack. Sport and
205
+ * personal dialects extend or override these via {@link UnitSet.extend}.
206
+ */
207
+ declare const STANDARD_UNITS: readonly UnitDef$1[];
208
+ /**
209
+ * Factory for {@link UnitSet}s. `standard()` is the base catalog; `of()` builds
210
+ * a custom set a Dialect can stack on top.
211
+ */
212
+ declare const UnitRegistry: {
213
+ /** The base catalog used by the base Units Dialect. */
214
+ standard(): UnitSet;
215
+ /** Build a set from arbitrary definitions. */
216
+ of(units: readonly UnitDef$1[]): UnitSet;
217
+ };
218
+
219
+ /**
220
+ * Named display surface — each has distinct visibility and formatting rules.
221
+ */
222
+ type MetricPresentationSurface = 'runtime-badge' | 'timer-subtitle' | 'history-label' | 'review-grid-cell' | 'review-grid-column' | 'label-composer' | 'debug';
223
+ type MetricRenderKind = 'badge' | 'comment' | 'plain-text' | 'column-heading' | 'hidden';
224
+ type MetricTone = 'time' | 'rep' | 'effort' | 'distance' | 'rounds' | 'action' | 'resistance' | 'rest' | 'muted' | 'system' | 'unknown';
225
+ interface MetricPresentationToken {
226
+ readonly metric: IMetric;
227
+ /** Computed display text (e.g., "3 Rounds", "1:30", "Pushups") */
228
+ readonly label: string;
229
+ /** Hover tooltip text */
230
+ readonly tooltip: string;
231
+ readonly renderKind: MetricRenderKind;
232
+ readonly tone: MetricTone;
233
+ /** Icon key (maps to getMetricIcon — null if no icon) */
234
+ readonly iconKey: string | null;
235
+ readonly visible: boolean;
236
+ /** True for group(+/-) and lap structural markers */
237
+ readonly structural: boolean;
238
+ /** True for effort metrics whose value is "Rest" */
239
+ readonly rest: boolean;
240
+ /** True for parser-origin text metrics (coach annotations) */
241
+ readonly comment: boolean;
242
+ /** True for user-origin metrics (user override in review grid) */
243
+ readonly userEntered: boolean;
244
+ /** Column label for review-grid-column surface */
245
+ readonly columnLabel?: string;
246
+ }
247
+ interface IMetricPresentationPolicy {
248
+ present(metric: IMetric, surface: MetricPresentationSurface): MetricPresentationToken;
249
+ presentGroup(metrics: readonly IMetric[], surface: MetricPresentationSurface): MetricPresentationToken[];
250
+ columnLabel(type: MetricType | string): string;
251
+ isStructural(metric: IMetric): boolean;
252
+ isHidden(metric: IMetric, surface: MetricPresentationSurface): boolean;
253
+ }
254
+
255
+ declare function createPolicy(): IMetricPresentationPolicy;
256
+ /**
257
+ * Singleton policy instance for app code.
258
+ * Use `createMetricPresentationPolicy()` in tests for isolated instances.
259
+ */
260
+ declare const metricPresentation: IMetricPresentationPolicy;
261
+
262
+ declare function isTimeLikeMetric(type: MetricType | string): boolean;
263
+ /**
264
+ * Compute the display label for a metric on a given surface.
265
+ * All per-type formatting rules live here — nowhere else.
266
+ */
267
+ declare function computeLabel(metric: IMetric, _surface: MetricPresentationSurface): string;
268
+ /**
269
+ * Build a tooltip string for a metric — identical to the existing buildTooltip
270
+ * in MetricPill so callers can migrate without losing fidelity.
271
+ */
272
+ declare function buildTooltip(metric: IMetric): string;
273
+ declare function computeColumnLabel(type: MetricType | string): string;
274
+
275
+ /**
276
+ * Base Units Dialect — the bottom of the Dialect Stack.
277
+ *
278
+ * The parser is unit-agnostic: it emits bare Number and Text. This dialect's
279
+ * {@link transform} fuses adjacent Number + unit-word metrics into dimensioned
280
+ * metrics (Distance / Resistance / energy …) using the core {@link UnitRegistry}.
281
+ *
282
+ * It contributes no hints — units are a rewrite, not a marker — so {@link analyze}
283
+ * returns nothing. Sport dialects stack on top and may extend the unit set; a
284
+ * personal-overrides dialect can sit last. Because fusion is idempotent, this
285
+ * dialect is safe to run in both the parse pipeline and the compile-time stack.
286
+ */
287
+ declare class UnitsDialect implements IDialect {
288
+ id: string;
289
+ name: string;
290
+ private readonly units;
291
+ /**
292
+ * @param units the unit set to recognize. Defaults to the standard catalog;
293
+ * pass an extended set (e.g. `UnitRegistry.standard().extend(...)`) to add
294
+ * or override units.
295
+ */
296
+ constructor(units?: UnitSet);
297
+ transform(statement: ICodeStatement): void;
298
+ analyze(_statement: ICodeStatement): DialectAnalysis;
299
+ }
300
+
301
+ /**
302
+ * CrossFit dialect for recognizing CrossFit-specific workout patterns.
303
+ *
304
+ * Recognizes:
305
+ * - AMRAP: "As Many Rounds As Possible" - time-bound workout
306
+ * - EMOM: "Every Minute On the Minute" - repeating interval workout
307
+ * - FOR TIME: Time-capped workout with completion goal
308
+ * - TABATA: High-intensity interval protocol (20s work / 10s rest)
309
+ */
310
+ declare class CrossFitDialect implements IDialect {
311
+ id: string;
312
+ name: string;
313
+ /**
314
+ * Check if any Action or Effort metrics contains a keyword (case-insensitive)
315
+ */
316
+ private hasKeyword;
317
+ /**
318
+ * Check if statement has children (is a parent block)
319
+ */
320
+ private hasChildren;
321
+ analyze(statement: ICodeStatement): DialectAnalysis;
322
+ }
323
+
324
+ /**
325
+ * WOD (Workout of the Day) dialect for recognizing general structured workout patterns.
326
+ *
327
+ * Recognizes:
328
+ * - STRENGTH: Heavy barbell / loaded movement blocks
329
+ * - METCON: Metabolic conditioning blocks
330
+ * - SKILLS: Skill/technique practice blocks
331
+ * - WOD: Explicit "WOD" keyword
332
+ * - SUPERSET: Multiple exercises grouped with no rest
333
+ *
334
+ * Emitted hints (dot-namespaced):
335
+ * domain.wod - any statement processed by this dialect
336
+ * workout.strength - heavy loaded-movement block
337
+ * workout.metcon - metabolic conditioning block
338
+ * workout.skills - skill/technique block
339
+ * workout.superset - superset / paired-set block
340
+ * behavior.load_bearing - statement involves significant external load
341
+ */
342
+ declare class WodDialect implements IDialect {
343
+ id: string;
344
+ name: string;
345
+ /** Case-insensitive keyword check on Action/Effort metrics */
346
+ private hasKeyword;
347
+ analyze(statement: ICodeStatement): DialectAnalysis;
348
+ }
349
+
350
+ /**
351
+ * Cardio dialect for recognizing endurance/aerobic workout patterns.
352
+ *
353
+ * Recognizes:
354
+ * - RUN / JOG / SPRINT: Running activities
355
+ * - ROW: Rowing (erg, water)
356
+ * - BIKE / CYCLE: Cycling activities
357
+ * - SWIM: Swimming
358
+ * - WALK: Walking / recovery activity
359
+ * - Distance-only statements (e.g. "5km", "400m") without explicit activity
360
+ *
361
+ * Emitted hints (dot-namespaced):
362
+ * domain.cardio - any statement processed by this dialect
363
+ * workout.run - running activity
364
+ * workout.row - rowing activity
365
+ * workout.bike - cycling activity
366
+ * workout.swim - swimming activity
367
+ * workout.walk - walking activity
368
+ * behavior.distance_based - primary metric is distance
369
+ * behavior.pace_based - pacing / splits are relevant
370
+ * behavior.aerobic - sustained aerobic effort
371
+ */
372
+ declare class CardioDialect implements IDialect {
373
+ id: string;
374
+ name: string;
375
+ private hasKeyword;
376
+ private hasDistance;
377
+ analyze(statement: ICodeStatement): DialectAnalysis;
378
+ }
379
+
380
+ /**
381
+ * Yoga dialect for recognizing yoga and mindfulness practice patterns.
382
+ *
383
+ * Recognizes:
384
+ * - Pose holds: duration-based single poses (e.g. "Warrior II :60")
385
+ * - Flows: sequences of poses (e.g. "Sun Salutation x5")
386
+ * - Breathing exercises: pranayama patterns
387
+ * - Meditation: seated/silent practice blocks
388
+ * - Explicit pose names (a curated vocabulary of common poses)
389
+ *
390
+ * Emitted hints (dot-namespaced):
391
+ * domain.yoga - any statement processed by this dialect
392
+ * workout.pose - single pose hold
393
+ * workout.flow - sequence / vinyasa flow
394
+ * workout.breathing - pranayama / breathing exercise
395
+ * workout.meditation - seated meditation block
396
+ * behavior.hold - timed static hold
397
+ * behavior.mindful - mindfulness / attention cue
398
+ */
399
+ declare class YogaDialect implements IDialect {
400
+ id: string;
401
+ name: string;
402
+ /**
403
+ * Common yoga pose keywords.
404
+ * TODO: Expand this list as the dialect matures.
405
+ */
406
+ private static readonly POSE_KEYWORDS;
407
+ private static readonly FLOW_KEYWORDS;
408
+ private static readonly BREATHING_KEYWORDS;
409
+ private static readonly MEDITATION_KEYWORDS;
410
+ private hasAnyKeyword;
411
+ analyze(statement: ICodeStatement): DialectAnalysis;
412
+ }
413
+
414
+ /**
415
+ * Habits dialect for tracking daily/recurring habit completions.
416
+ *
417
+ * Recognizes:
418
+ * - Daily habits: single-completion items logged once per day
419
+ * - Recurring habits: timed or rep-counted recurring actions
420
+ * - Streaks: consecutive day tracking
421
+ * - Check-off items: boolean "did I do this?" entries
422
+ *
423
+ * Emitted hints (dot-namespaced):
424
+ * domain.habits - any statement processed by this dialect
425
+ * behavior.daily - once-a-day completion
426
+ * behavior.completable - boolean done/not-done state
427
+ * behavior.streak - streak-relevant (consecutive occurrences matter)
428
+ * behavior.recurring - repeats on a schedule
429
+ * workout.habit_check - single check-off habit
430
+ * workout.habit_timed - habit with a target duration (e.g. "Meditate :10:00")
431
+ * workout.habit_rep - habit with a target count (e.g. "10 Pushups daily")
432
+ */
433
+ declare class HabitsDialect implements IDialect {
434
+ id: string;
435
+ name: string;
436
+ /**
437
+ * Keywords that indicate a habit/daily-tracking context.
438
+ * TODO: Expand as habit vocabulary is refined.
439
+ */
440
+ private static readonly HABIT_KEYWORDS;
441
+ private static readonly STREAK_KEYWORDS;
442
+ private hasAnyKeyword;
443
+ analyze(statement: ICodeStatement): DialectAnalysis;
444
+ }
445
+
446
+ declare const ClimbMetricType: {
447
+ readonly Discipline: "climb-discipline";
448
+ readonly Grade: "climb-grade";
449
+ readonly SendType: "climb-send-type";
450
+ readonly AttemptCount: "climb-attempt-count";
451
+ readonly RouteName: "climb-route-name";
452
+ readonly HighPoint: "climb-high-point";
453
+ };
454
+ type ClimbGradeSystem = 'v-scale' | 'font' | 'yds' | 'french' | 'uiaa' | 'ewbank' | 'british' | 'dankyu' | 'unknown';
455
+ interface ClimbGradeMetricValue {
456
+ raw: string;
457
+ system: ClimbGradeSystem;
458
+ normalizedRank?: number;
459
+ }
460
+ /**
461
+ * Climbing dialect for recognizing route/problem logs and climbing training.
462
+ *
463
+ * Recognizes:
464
+ * - route/problem names from Action metrics
465
+ * - V-scale, YDS, Fontainebleau, and French-style grades
466
+ * - send type aliases such as OS, flash, RP, TR, and DNF
467
+ * - attempt counts from climb-only `@N` shorthand parsed as empty-unit resistance
468
+ * - high-point notes such as `bolt 6` and `move 9`
469
+ */
470
+ declare class ClimbDialect implements IDialect {
471
+ id: string;
472
+ name: string;
473
+ analyze(statement: ICodeStatement): DialectAnalysis;
474
+ private findRouteName;
475
+ private findGrade;
476
+ private ydsRank;
477
+ private findSendType;
478
+ private findAttemptCount;
479
+ private findHighPoint;
480
+ private findDisciplines;
481
+ }
482
+
483
+ /**
484
+ * DialectStack — the single ordered place where the base Units Dialect and
485
+ * the sport/personal Dialects are composed and run.
486
+ *
487
+ * Replaces both:
488
+ * - The parse-time `baseUnits` singleton in `lezer-mapper.ts` (hardcoded to
489
+ * one Dialect with no extension hook).
490
+ * - The empty compile-time `DialectRegistry` that was removed from `JitCompiler`
491
+ * in S5a (no production path ever registered a Dialect).
492
+ *
493
+ * Ordering: base Units Dialect runs first (fuses bare Number + unit-word into
494
+ * dimensioned metrics); sport Dialects run after (they observe fused units);
495
+ * personal-overrides run last (a `CONTEXT.md` concept that was previously
496
+ * impossible). Later-wins metric merge and hint accumulation are handled by
497
+ * the per-statement `transform` + `analyze` + append loop.
498
+ */
499
+ declare class DialectStack {
500
+ private readonly dialects?;
501
+ private readonly registry?;
502
+ /**
503
+ * Accepts either a frozen array (the `createDialectStack(overrides)` /
504
+ * test-fixture case) or a live `Registry<IDialect>` (the production
505
+ * singleton below). When constructed from a registry, every `process()`
506
+ * call re-reads `registry.list()`, so a `dialectRegistry.register(...)`
507
+ * made by a consumer after import is honored on the next parse — a
508
+ * frozen array snapshot taken once at module-load time would not see it.
509
+ */
510
+ constructor(dialects: readonly IDialect[] | Registry<IDialect>);
511
+ private get effectiveDialects();
512
+ /**
513
+ * Resolve the effective Dialect list for a block's `:sport` suffix.
514
+ *
515
+ * - No suffix → the full stack (a bare `time`/`log` block — unchanged).
516
+ * - Known suffix (registry id or {@link SPORT_ALIASES} entry) → the base
517
+ * Units Dialect plus the named sport Dialect, in registration order.
518
+ * - Unknown suffix → warn once per suffix and fall back to the full stack.
519
+ */
520
+ dialectsFor(sport?: string): readonly IDialect[];
521
+ /**
522
+ * Process a single statement through the Dialects selected by `sport`
523
+ * (full stack when omitted). Each Dialect's `transform` runs before its
524
+ * `analyze`, and the emitted metrics (hint markers + domain values) are
525
+ * appended onto the statement.
526
+ */
527
+ process(statement: ICodeStatement, sport?: string): void;
528
+ /** Process a batch of statements through the Dialects selected by `sport`. */
529
+ processAll(statements: ICodeStatement[], sport?: string): void;
530
+ private applyDialects;
531
+ /** The ordered Dialect list (for inspection / testing). Live when registry-backed. */
532
+ get list(): readonly IDialect[];
533
+ }
534
+ /**
535
+ * Consumer-facing dialect registry, pre-seeded with the built-in dialects.
536
+ *
537
+ * Replaces the previous `createDialectStack(overrides)` parameter as the way
538
+ * to extend the dialect set. Consumer code can call
539
+ * `dialectRegistry.register(new MyDialect())` to add a personal-overrides
540
+ * dialect, and the production `dialectStack` below reads its ordered list
541
+ * from this registry.
542
+ *
543
+ * Built-ins can be removed or overridden by `id`.
544
+ */
545
+ declare const dialectRegistry: Registry<IDialect>;
546
+ /**
547
+ * Build a DialectStack from an explicit list — preserved signature for
548
+ * tests and the `personal-overrides` pattern. Production uses
549
+ * {@link dialectStack} (which reads the registry) via `extractStatements`.
550
+ */
551
+ declare function createDialectStack(overrides?: IDialect[]): DialectStack;
552
+ /**
553
+ * Module singleton — the default Dialect Stack used by the parse pipeline.
554
+ *
555
+ * Registry-backed (not a frozen array snapshot): every `process()`/`processAll()`
556
+ * call re-reads {@link dialectRegistry} live, so a consumer `dialectRegistry.register(...)`
557
+ * call is honored on the very next parse, no matter when it happens relative to
558
+ * module load. Equivalent to the old `const baseUnits = new UnitsDialect()` but
559
+ * with the full sport-Dialect set wired, and genuinely extensible.
560
+ */
561
+ declare const dialectStack: DialectStack;
562
+
563
+ /**
564
+ * Pure fusion over a metric array (no source metadata). Used by unit tests and
565
+ * any caller that doesn't track `metricMeta`.
566
+ *
567
+ * Rep(100) + Effort("m Run") → Distance(100, "m") + Effort("Run")
568
+ * Rep(24) + Effort("kg") → Resistance(24, "kg")
569
+ * Rep(5) + Effort("Burpees")→ unchanged (not a unit)
570
+ */
571
+ declare function fuseUnitsInMetrics(metrics: IMetric[], units: UnitSet): IMetric[];
572
+ /**
573
+ * Rewrite a statement's metric list in place, fusing number + unit pairs and
574
+ * keeping `metricMeta` source locations in sync (the fused metric spans the
575
+ * number through the unit token; the residual effort keeps the remainder).
576
+ * No-op when nothing fuses. Safe to call repeatedly (idempotent).
577
+ */
578
+ declare function fuseUnits(statement: ICodeStatement, units: UnitSet): void;
579
+
580
+ interface SyntaxMeta {
581
+ line: number;
582
+ startOffset: number;
583
+ endOffset: number;
584
+ columnStart: number;
585
+ columnEnd: number;
586
+ length: number;
587
+ raw: string;
588
+ }
589
+ interface BasePrimitive {
590
+ kind: 'lap' | 'duration' | 'rounds' | 'action' | 'text' | 'heading' | 'quantity' | 'effort' | 'property' | 'metric_object';
591
+ raw: string;
592
+ meta: SyntaxMeta;
593
+ }
594
+ interface MetricObjectPrimitive extends BasePrimitive {
595
+ kind: 'metric_object';
596
+ pairs: Array<{
597
+ key: string;
598
+ value: string | number | boolean | null;
599
+ }>;
600
+ }
601
+ interface LapPrimitive extends BasePrimitive {
602
+ kind: 'lap';
603
+ lapType: 'round' | 'compose';
604
+ }
605
+ interface DurationPrimitive extends BasePrimitive {
606
+ kind: 'duration';
607
+ timerRaw?: string;
608
+ hasTrend: boolean;
609
+ isRequired: boolean;
610
+ }
611
+ interface RoundsPrimitive extends BasePrimitive {
612
+ kind: 'rounds';
613
+ sequence?: number[];
614
+ label?: string;
615
+ }
616
+ interface ActionPrimitive extends BasePrimitive {
617
+ kind: 'action';
618
+ hasColonPrefix: boolean;
619
+ }
620
+ interface TextPrimitive extends BasePrimitive {
621
+ kind: 'text';
622
+ }
623
+ interface HeadingPrimitive extends BasePrimitive {
624
+ kind: 'heading';
625
+ }
626
+ interface QuantityPrimitive extends BasePrimitive {
627
+ kind: 'quantity';
628
+ value?: number;
629
+ hasAtSign: boolean;
630
+ }
631
+ /**
632
+ * Effort primitive — emitted for any free text including the bare "/" and "|"
633
+ * separator tokens. Slash/Pipe were grammar-level primitives consumed only by
634
+ * fuseUnits; they now ride as `EffortPrimitive` with raw '/' or '|' and the
635
+ * dialect matches the raw string:
636
+ * 1/4 mile → 0.25 mile (slash converts to a single decimal)
637
+ * Run | Walk → ChoiceGroupMetric([Effort('Run'), Effort('Walk')]) (pipe groups)
638
+ */
639
+ interface EffortPrimitive extends BasePrimitive {
640
+ kind: 'effort';
641
+ }
642
+ interface PropertyPrimitive extends BasePrimitive {
643
+ kind: 'property';
644
+ key: string;
645
+ valueRaw: string;
646
+ value: string | number | boolean | null;
647
+ }
648
+ type SyntaxPrimitive = LapPrimitive | DurationPrimitive | RoundsPrimitive | ActionPrimitive | TextPrimitive | HeadingPrimitive | QuantityPrimitive | EffortPrimitive | PropertyPrimitive | MetricObjectPrimitive;
649
+ interface SyntaxStatement {
650
+ id: number;
651
+ line: number;
652
+ meta: SyntaxMeta;
653
+ primitives: SyntaxPrimitive[];
654
+ children: number[][];
655
+ parentId?: number;
656
+ isLeaf: boolean;
657
+ }
658
+ interface SyntaxFacts {
659
+ statements: SyntaxStatement[];
660
+ }
661
+
662
+ declare function classifyStatements(facts: SyntaxFacts): ParsedCodeStatement[];
663
+
664
+ /** Helper to compute line and column numbers from offset without EditorState */
665
+ interface LineLocator {
666
+ (offset: number): {
667
+ lineNumber: number;
668
+ lineStart: number;
669
+ };
670
+ }
671
+ declare function createLineLocator(source: string): LineLocator;
672
+ declare function extractSyntaxFacts(sourceOrState: string | {
673
+ doc: {
674
+ toString(): string;
675
+ };
676
+ }, treeArg?: Tree): SyntaxFacts;
677
+
678
+ type SourceInput = string | {
679
+ tree?: Tree;
680
+ source: string;
681
+ doc?: {
682
+ toString(): string;
683
+ };
684
+ } | {
685
+ doc: {
686
+ toString(): string;
687
+ };
688
+ };
689
+ /**
690
+ * Extract statements WITHOUT running the Dialect Stack.
691
+ *
692
+ * Used by the parser test harness (which applies its own Dialect set) and by
693
+ * any consumer that needs the raw classified statements before Dialect
694
+ * processing. Production consumers should use {@link extractStatements}.
695
+ */
696
+ declare function extractStatementsRaw(sourceOrTree: SourceInput, treeArg?: Tree): ICodeStatement[];
697
+ /**
698
+ * Extracts WhiteboardScript statements from source or Lezer tree.
699
+ *
700
+ * The Dialect Stack (base Units + sport Dialects + personal-overrides) runs on
701
+ * every statement here, so every parse consumer gets fused units and sport
702
+ * hints uniformly. See `DialectStack.ts`.
703
+ *
704
+ * @param sport - The block's `:sport` fence suffix (` ```log:climbing `).
705
+ * Omitted → the full registry stack runs. See {@link DialectStack.dialectsFor}.
706
+ */
707
+ declare function extractStatements(sourceOrTree: SourceInput, sportOrTree?: string | Tree, sportArg?: string): ICodeStatement[];
708
+
709
+ interface ParseOptions {
710
+ dialect?: string;
711
+ sport?: string;
712
+ strict?: boolean;
713
+ withoutDialects?: boolean;
714
+ }
715
+ /**
716
+ * Parses Whiteboard Script source directly into a WhiteboardScript AST
717
+ * with 0 DOM / EditorState dependencies.
718
+ *
719
+ * @param inputText Raw Whiteboard Script text.
720
+ * @param options Parse options including dialect/sport fence suffix and strictness.
721
+ */
722
+ declare function parseScript(inputText: string, options?: ParseOptions): WhiteboardScript;
723
+
724
+ /**
725
+ * Headless parser facade using the Lezer parser with 0 DOM / EditorState overhead.
726
+ */
727
+ declare class MdTimerRuntime {
728
+ /**
729
+ * Parse WhiteboardScript source into a {@link WhiteboardScript}, running the
730
+ * Dialect Stack on every statement.
731
+ *
732
+ * @param sport - The block's `:sport` fence suffix (` ```log:climbing `).
733
+ */
734
+ read(inputText: string, sport?: string): IScript;
735
+ /**
736
+ * Parse without running the Dialect Stack. Used by the parser test harness
737
+ * which applies its own Dialect set. Production consumers should use {@link read}.
738
+ */
739
+ readWithoutDialects(inputText: string): IScript;
740
+ }
741
+
742
+ declare function createParser(): MdTimerRuntime;
743
+ /**
744
+ * @deprecated Use createParser() instead.
745
+ */
746
+ declare const sharedParser: MdTimerRuntime;
747
+
748
+ declare const whiteboardScriptLanguage: LRLanguage;
749
+ declare function whiteboardScript(): LanguageSupport;
750
+
751
+ declare const parser: LRParser;
752
+
753
+ /**
754
+ * Effort Resolver — Analytics Boundary
755
+ *
756
+ * Implements IEffortResolver with exact and fuzzy alias matching.
757
+ * Creates synthetic unresolved efforts for unmatched labels.
758
+ */
759
+
760
+ declare class EffortResolver implements IEffortResolver {
761
+ private readonly registry;
762
+ private options;
763
+ constructor(registry: IEffortRegistry, options?: EffortResolverOptions);
764
+ resolveBySlug(slug: string): IEffort | null;
765
+ resolveByAlias(label: string): IEffort | null;
766
+ resolveFuzzy(label: string, options?: {
767
+ threshold?: number;
768
+ }): IEffort;
769
+ resolveEffort(label: string, options?: EffortResolutionOptions): ResolvedEffort;
770
+ resolveDefinition(effort: IEffort, options?: EffortResolutionOptions): ResolvedEffort;
771
+ list(): readonly IEffort[];
772
+ private materialize;
773
+ private shouldApplyCoefficient;
774
+ private resolveDisciplineFactor;
775
+ /**
776
+ * Create a synthetic unresolved effort for analytics continuity.
777
+ * Not persisted to the registry.
778
+ */
779
+ private createSyntheticEffort;
780
+ }
781
+
782
+ /**
783
+ * In-Memory Effort Registry
784
+ *
785
+ * Stores all efforts in a Map indexed by slug.
786
+ * Used for:
787
+ * - Unit tests (deterministic, no I/O)
788
+ * - Storybook / static builds (no IndexedDB)
789
+ * - Bundled seed fallback
790
+ */
791
+ declare class InMemoryEffortRegistry implements IEffortRegistry {
792
+ private efforts;
793
+ private sourceFilter;
794
+ constructor(sourceFilter?: EffortRegistrySource);
795
+ /** Seed with bundled efforts */
796
+ loadBundled(): Promise<void>;
797
+ /** Seed with arbitrary efforts (useful for tests) */
798
+ seed(efforts: readonly IEffort[]): void;
799
+ /** Clear all efforts */
800
+ clear(): void;
801
+ resolve(slug: string): IEffort | null;
802
+ list(): readonly IEffort[];
803
+ listByOrigin(origin: EffortRegistrySource): readonly IEffort[];
804
+ upsert(effort: IEffort): Promise<void>;
805
+ delete(slug: string): Promise<void>;
806
+ }
807
+
808
+ /**
809
+ * Fuzzy Alias Matching — Levenshtein Distance
810
+ *
811
+ * Edit-distance based fuzzy matching for effort alias resolution.
812
+ * Default threshold: ≤ 2 (configurable per instance).
813
+ */
814
+ /**
815
+ * Compute the Levenshtein edit distance between two strings.
816
+ * Time: O(m × n), Space: O(min(m, n))
817
+ */
818
+ declare function levenshteinDistance(a: string, b: string): number;
819
+ /**
820
+ * Normalise a string for fuzzy comparison:
821
+ * - lower-case
822
+ * - trim whitespace
823
+ * - strip punctuation
824
+ * - collapse multiple spaces
825
+ */
826
+ declare function normalizeForFuzzy(input: string): string;
827
+ /**
828
+ * Check whether two strings are within a given edit-distance threshold.
829
+ */
830
+ declare function isWithinThreshold(a: string, b: string, threshold: number): boolean;
831
+ /**
832
+ * Score a fuzzy match candidate. Lower is better (0 = exact).
833
+ */
834
+ interface FuzzyCandidate {
835
+ readonly target: string;
836
+ readonly distance: number;
837
+ }
838
+ /**
839
+ * Find the best fuzzy match for `query` among `candidates`.
840
+ * Returns `null` if no candidate is within `threshold`.
841
+ */
842
+ declare function findBestFuzzyMatch(query: string, candidates: readonly string[], threshold: number): FuzzyCandidate | null;
843
+
844
+ /**
845
+ * Effort Registry — Test Fixtures
846
+ *
847
+ * Deterministic mock effort sets for unit and integration tests.
848
+ * Covers common test scenarios: running, strength, low MET, high MET,
849
+ * user-derived efforts, and synthetic unresolved.
850
+ */
851
+
852
+ declare const fixtureRunning: IEffort;
853
+ declare const fixtureRowing: IEffort;
854
+ declare const fixtureBackSquat: IEffort;
855
+ declare const fixtureBurpee: IEffort;
856
+ declare const fixturePlank: IEffort;
857
+ declare const fixtureUserCustom: IEffort;
858
+ /** A user effort that overrides a bundled effort by slug collision */
859
+ declare const fixtureUserOverride: IEffort;
860
+ /** Common bundled fixture set — covers running, rowing, strength, bodyweight, low MET */
861
+ declare const commonFixtureSet: readonly IEffort[];
862
+ /** Full fixture set including user efforts */
863
+ declare const fullFixtureSet: readonly IEffort[];
864
+
865
+ /** All bundled efforts — seeded with common fixture set for headless/standalone runtime */
866
+ declare const bundledEfforts: readonly IEffort[];
867
+ /** Number of bundled efforts */
868
+ declare const BUNDLED_EFFORT_COUNT: number;
869
+
870
+ /**
871
+ * IRuntimeContext — what the execution world (actions, blocks, behaviors)
872
+ * receives. The runtime's collaborators (the "bag") plus the verbs to enqueue
873
+ * work and emit output — and nothing else.
874
+ *
875
+ * This is the named seam that replaces reaching the bag through the full
876
+ * {@link IScriptRuntime} god-interface. `handle` is included here because the
877
+ * execution world emits events via it (BehaviorContext.emitEvent); the remaining
878
+ * control verbs (`pushBlock`/`popBlock`/`dispose`) and the observation surface
879
+ * (`subscribeTo*`) stay on `IScriptRuntime` for external drivers.
880
+ *
881
+ * `IRuntimeContext extends IRuntimeActionable`, so it remains a valid narrowing
882
+ * of `IRuntimeAction.do` / `IRuntimeBlock.mount` (which declare the
883
+ * `IRuntimeActionable` primitive to break the `IRuntimeAction ↔ IScriptRuntime`
884
+ * cycle). Concrete actions/blocks narrow their parameter from `IScriptRuntime`
885
+ * to `IRuntimeContext` — bivariance allows it, same trick they use today.
886
+ *
887
+ * `ScriptRuntime` and `ExecutionContext` already satisfy this structurally, so
888
+ * narrowing an action's `do(runtime)` parameter type to `IRuntimeContext`
889
+ * compiles with no runtime change. See docs/adr/runtime-context-seam.md.
890
+ */
891
+ interface IRuntimeContext extends IRuntimeActionable {
892
+ /** The parsed script being executed. */
893
+ script: WhiteboardScript;
894
+ /** The event bus (subscribe/emit within the execution world). */
895
+ eventBus: IEventBus;
896
+ /** The block execution stack. */
897
+ stack: IRuntimeStack;
898
+ /** The time source (frozen within a turn by `ExecutionContext`). */
899
+ clock: IRuntimeClock;
900
+ /** The JIT compiler (statement → block). */
901
+ jit: IJitCompiler;
902
+ /** Wall-clock provider. */
903
+ nowProvider: INowProvider;
904
+ /** Runtime options (hooks, tracker, wrapper, logger). */
905
+ options: RuntimeStackOptions;
906
+ /** Errors collected during execution. */
907
+ errors?: RuntimeError[];
908
+ /** Optional analytics context for compile-time effort enrichment. */
909
+ analyticsContext?: AnalyticsContext;
910
+ /**
911
+ * Add an output statement and notify subscribers. Used by `BehaviorContext`,
912
+ * `RuntimeBlock`, and `EmitSystemOutputAction` to emit outputs at any
913
+ * lifecycle point. (Phase 2 of the seam renames this to `ctx.output.add`.)
914
+ */
915
+ addOutput(output: IOutputStatement): void;
916
+ /**
917
+ * Route an event back through the runtime's dispatch flow. Included because
918
+ * behaviors emit events via `BehaviorContext.emitEvent` → `runtime.handle`.
919
+ * (The remaining control verbs — pushBlock/popBlock/dispose — stay on
920
+ * `IScriptRuntime` for external drivers.)
921
+ */
922
+ handle(event: IEvent): void;
923
+ }
924
+
925
+ /**
926
+ * Action for pushing errors to the runtime error list.
927
+ *
928
+ * This action provides centralized error handling by collecting errors
929
+ * in the runtime instead of using EventHandlerResponse.abort flags.
930
+ *
931
+ * Actions can check `runtime.errors.length > 0` to abort processing.
932
+ *
933
+ * @example
934
+ * ```typescript
935
+ * // Push an error during event handling
936
+ * try {
937
+ * // ... some operation
938
+ * } catch (error) {
939
+ * return [new ErrorAction(error as Error, 'TimerBehavior')];
940
+ * }
941
+ *
942
+ * // Check for errors before proceeding
943
+ * if (runtime.errors && runtime.errors.length > 0) {
944
+ * return []; // Abort further processing
945
+ * }
946
+ * ```
947
+ */
948
+ declare class ErrorAction implements IRuntimeAction {
949
+ /** The error that occurred */
950
+ readonly error: Error;
951
+ /** Source of the error (block ID, handler ID, etc.) */
952
+ readonly source: string;
953
+ /** Additional context about the error */
954
+ readonly context?: unknown | undefined;
955
+ readonly type = "error";
956
+ constructor(
957
+ /** The error that occurred */
958
+ error: Error,
959
+ /** Source of the error (block ID, handler ID, etc.) */
960
+ source: string,
961
+ /** Additional context about the error */
962
+ context?: unknown | undefined);
963
+ do(runtime: IRuntimeContext): void;
964
+ }
965
+
966
+ /**
967
+ * OutputEmitter — the single module responsible for everything that flows into
968
+ * the output log: buffering, subscriber notification, analytics enrichment,
969
+ * and all runtime emission helpers.
970
+ *
971
+ * ## Why this exists
972
+ *
973
+ * Previously ScriptRuntime owned the output buffer, 5 private emission methods,
974
+ * analytics integration, and subscriber management — 8 separate emission sites
975
+ * scattered across the runtime. OutputEmitter concentrates all of that behind a
976
+ * narrow interface so that:
977
+ *
978
+ * - **Locality**: every output invariant (GC guard for system outputs, analytics
979
+ * enrichment, listener error isolation) lives in one place.
980
+ * - **Leverage**: callers call `add()` and the whole policy runs automatically.
981
+ * - **Testability**: the output pipeline can be tested without constructing a
982
+ * full ScriptRuntime.
983
+ *
984
+ * ## Interface
985
+ *
986
+ * ```
987
+ * add(output) — buffer + notify (called by runtime, behaviors, blocks)
988
+ * subscribe(listener) — reactive subscription with deferred catch-up
989
+ * getAll() — snapshot of accumulated statements
990
+ * setAnalyticsEngine(engine) — attach enrichment pipeline
991
+ * finalizeAnalytics() — flush summary statements after workout ends
992
+ * dispose() — release buffer and listeners
993
+ * ```
994
+ *
995
+ * ## Emission helpers
996
+ *
997
+ * The five helpers that were private methods of ScriptRuntime are now methods
998
+ * here. They read their clock/stack/script from {@link OutputEmitter.attach}
999
+ * (wired once by ScriptRuntime) rather than taking runtime-shaped args at every
1000
+ * call — closing the "carve-out moved code without moving the bugs" defect.
1001
+ *
1002
+ * - emitLoad()
1003
+ * - emitStackEvent(event)
1004
+ * - emitSegmentFromResultMemory(block, stackDepth)
1005
+ * - emitRuntimeEvent(event)
1006
+ * - emitCompilerBlock(block)
1007
+ */
1008
+ declare class OutputEmitter {
1009
+ private _outputStatements;
1010
+ private _outputListeners;
1011
+ private _analyticsEngine;
1012
+ /** Session-scoped latch: summary outputs are appended once per session. */
1013
+ private _analyticsFinalized;
1014
+ /**
1015
+ * Ephemeral Tier-2 (running-total) snapshot — display-only. Replaced
1016
+ * wholesale on each segment; NEVER buffered into _outputStatements, so it
1017
+ * never reaches the persisted log. Only finalizeAnalytics() materializes
1018
+ * the final values into the records.
1019
+ */
1020
+ private _liveAnalytics;
1021
+ private _clock;
1022
+ private _stack;
1023
+ private _script;
1024
+ /**
1025
+ * Wire the runtime dependencies the emission helpers read. Called once by
1026
+ * ScriptRuntime's constructor after its own stack/clock/script are set.
1027
+ * Replaces the per-call clock/stack/script parameters the helpers used to take.
1028
+ */
1029
+ attach(deps: {
1030
+ clock: IRuntimeClock;
1031
+ stack: IRuntimeStack;
1032
+ script: WhiteboardScript;
1033
+ }): void;
1034
+ private _requireClock;
1035
+ /**
1036
+ * Add an output statement, run it through analytics enrichment, buffer it,
1037
+ * and notify all subscribers.
1038
+ *
1039
+ * GC guard: system-typed outputs are skipped entirely when no listener is
1040
+ * present to prevent object allocation pressure during high-iteration
1041
+ * workloads (e.g. 10 000-round performance tests).
1042
+ */
1043
+ add(output: IOutputStatement): void;
1044
+ /**
1045
+ * Subscribe to output statements.
1046
+ *
1047
+ * New subscribers receive a deferred catch-up of all statements emitted so
1048
+ * far (setTimeout 0) to avoid synchronous React render warnings.
1049
+ */
1050
+ subscribe(listener: OutputListener): Unsubscribe;
1051
+ /** Returns a copy of all output statements accumulated so far. */
1052
+ getAll(): IOutputStatement[];
1053
+ /** Returns true when at least one subscriber is listening. */
1054
+ hasListeners(): boolean;
1055
+ setAnalyticsEngine(engine: IAnalyticsEngine): void;
1056
+ /**
1057
+ * Flush the analytics engine's final summary outputs after a workout ends.
1058
+ * Summary statements bypass the enrichment chain (already fully processed)
1059
+ * and are the ONLY Tier-2 outputs that reach the persisted buffer — live
1060
+ * running-total snapshots stay ephemeral ({@link emitLiveAnalytics}).
1061
+ */
1062
+ finalizeAnalytics(): IOutputStatement[];
1063
+ /**
1064
+ * Replace the ephemeral live-analytics snapshot and notify subscribers,
1065
+ * WITHOUT buffering into the persisted output stream. Live Tier-2 totals
1066
+ * (running totals per segment) are display-only; only {@link finalizeAnalytics}
1067
+ * writes the final values into the records.
1068
+ *
1069
+ * Each projection output is still fanned out to listeners individually so
1070
+ * reactive consumers (session store, cast bridge) update per-segment.
1071
+ */
1072
+ private emitLiveAnalytics;
1073
+ /** Ephemeral Tier-2 (running-total) snapshot for live display; never persisted. */
1074
+ getLiveAnalytics(): IOutputStatement[];
1075
+ dispose(): void;
1076
+ /**
1077
+ * Emit a `'load'` output for every statement in the script.
1078
+ * Called once during ScriptRuntime construction. Reads the attached script
1079
+ * and clock (see {@link attach}); no runtime-shaped args cross the seam.
1080
+ */
1081
+ emitLoad(): void;
1082
+ /**
1083
+ * Emit a `'system'` output for a push or pop stack event.
1084
+ * Reads the attached clock and stack blocks; only the event (which carries
1085
+ * the affected block + depth) crosses the seam.
1086
+ */
1087
+ emitStackEvent(event: {
1088
+ type: 'push' | 'pop';
1089
+ block: IRuntimeBlock;
1090
+ depth: number;
1091
+ }): void;
1092
+ /**
1093
+ * Emit `'segment'` outputs from a block's `metric:result` memory.
1094
+ * Called when a block is popped from the stack. `stackDepth` is the
1095
+ * event-specific depth at pop time; clock is read from the attached deps.
1096
+ */
1097
+ emitSegmentFromResultMemory(block: IRuntimeBlock, stackDepth: number): void;
1098
+ /**
1099
+ * Emit an `'event'` output for a dispatched runtime event.
1100
+ * Reads the attached clock and stack; only the event crosses the seam.
1101
+ */
1102
+ emitRuntimeEvent(event: IEvent): void;
1103
+ /**
1104
+ * Emit a `'compiler'` output recording the behaviors attached to a block.
1105
+ * Reads the attached clock and stack count; only the block crosses the seam.
1106
+ */
1107
+ emitCompilerBlock(block: IRuntimeBlock): void;
1108
+ }
1109
+
1110
+ /**
1111
+ * BehaviorContext is the concrete implementation of IBehaviorContext.
1112
+ *
1113
+ * It is created by RuntimeBlock when a block is mounted and provides
1114
+ * behaviors with a unified API for:
1115
+ * - Subscribing to events (tick, next, etc.)
1116
+ * - Emitting events (for coordination)
1117
+ * - Emitting output statements (for reporting)
1118
+ * - Reading and writing block memory
1119
+ * - Marking the block as complete
1120
+ *
1121
+ * ## Lifecycle
1122
+ *
1123
+ * 1. Created during block.mount()
1124
+ * 2. Passed to behavior.onMount(ctx)
1125
+ * 3. Same context passed to behavior.onNext(ctx)
1126
+ * 4. Same context passed to behavior.onUnmount(ctx)
1127
+ * 5. dispose() called during block.unmount() to clean up subscriptions
1128
+ */
1129
+ declare class BehaviorContext implements IBehaviorContext {
1130
+ readonly block: IRuntimeBlock;
1131
+ readonly clock: IRuntimeClock;
1132
+ readonly stackLevel: number;
1133
+ private runtime;
1134
+ private subscriptions;
1135
+ /**
1136
+ * Capabilities registered on this context (or shared from the persistent
1137
+ * per-block context if one was injected).
1138
+ *
1139
+ * Capability state is block-scoped, not call-scoped: the `inspectNext`
1140
+ * path in `RuntimeBlock` constructs a fresh `BehaviorContext` per
1141
+ * invocation so it can run the onNext chain without side effects, but
1142
+ * capabilities declared during `onMount` must still be visible to
1143
+ * peer behaviors in any subsequent call. We therefore let an external
1144
+ * owner (the persistent `_behaviorContext` on `RuntimeBlock`) inject
1145
+ * the shared Set so all contexts for the same block observe the same
1146
+ * capabilities.
1147
+ */
1148
+ private _capabilities;
1149
+ constructor(block: IRuntimeBlock, clock: IRuntimeClock, stackLevel: number, runtime: IRuntimeContext, sharedCapabilities?: Set<string>);
1150
+ subscribe(eventType: BehaviorEventType, listener: BehaviorEventListener, options?: SubscribeOptions): Unsubscribe$1;
1151
+ emitEvent(event: IEvent): void;
1152
+ emitOutput(type: OutputStatementType, metrics: MetricContainer | IMetric[], _options?: OutputOptions): void;
1153
+ /**
1154
+ * Add output to the runtime's collection and notify subscribers.
1155
+ */
1156
+ private addOutputToRuntime;
1157
+ pushMemory(tag: MemoryTag, metrics: MetricContainer | IMetric[]): IMemoryLocation;
1158
+ getMemoryByTag(tag: MemoryTag): IMemoryLocation[];
1159
+ updateMemory(tag: MemoryTag, metrics: MetricContainer | IMetric[]): void;
1160
+ declareCapability(cap: string): void;
1161
+ hasCapability(cap: string): boolean;
1162
+ /**
1163
+ * Returns the Set used to store declared capabilities. Internal callers
1164
+ * (e.g. `RuntimeBlock.inspectNext`) pass this back to a fresh
1165
+ * `BehaviorContext` so that all contexts for the same block observe the
1166
+ * same capabilities without coupling the inspection context to a
1167
+ * specific owner.
1168
+ */
1169
+ getCapabilities(): Set<string>;
1170
+ markComplete(reason?: string): void;
1171
+ /**
1172
+ * Dispose of all subscriptions.
1173
+ * Called by RuntimeBlock during unmount.
1174
+ */
1175
+ dispose(): void;
1176
+ }
1177
+
1178
+ /**
1179
+ * Options for constructing a {@link RuntimeBlock}.
1180
+ *
1181
+ * Construction goes through a single options object — there is no positional
1182
+ * overload. The sanctioned way to build a block is {@link BlockBuilder.build},
1183
+ * and the four block subclasses (RestBlock, EffortBlock, SessionRootBlock,
1184
+ * WaitingToStartBlock) forward their own config into this shape.
1185
+ */
1186
+ interface RuntimeBlockOptions {
1187
+ /** The owning runtime. */
1188
+ runtime: IRuntimeContext;
1189
+ /** Source statement ids this block was compiled from. Defaults to `[]`. */
1190
+ sourceIds?: number[];
1191
+ /** Behaviors composed onto this block. Defaults to `[]`. */
1192
+ behaviors?: IRuntimeBehavior[];
1193
+ /** Block context. A {@link BlockContext} is created from the key when omitted. */
1194
+ context?: IBlockContext;
1195
+ /** Block key. A fresh {@link BlockKey} is created when omitted. */
1196
+ key?: BlockKey;
1197
+ /** Stable type tag (e.g. 'Rest', 'SessionRoot'). */
1198
+ blockType?: string;
1199
+ /** Optional label, stored as a `metric:label` memory location. */
1200
+ label?: string;
1201
+ /** Optional display metric groups, each stored as a `metric:display` location. */
1202
+ metrics?: MetricContainer[] | IMetric[][];
1203
+ }
1204
+ /**
1205
+ * RuntimeBlock represents an executable unit in the workout runtime.
1206
+ *
1207
+ * Blocks are composed of behaviors that handle specific aspects (time, iteration, completion, etc.)
1208
+ * Behaviors receive an IBehaviorContext that provides access to memory, events, and output emission.
1209
+ *
1210
+ * ## Lifecycle
1211
+ *
1212
+ * 1. Block is created by a strategy with behaviors attached
1213
+ * 2. mount() is called when pushed onto stack - behaviors receive onMount(ctx)
1214
+ * 3. next() is called when child completes or user advances - behaviors receive onNext(ctx)
1215
+ * 4. unmount() is called when popped from stack - behaviors receive onUnmount(ctx)
1216
+ * 5. dispose() is called for cleanup - behaviors receive onDispose(ctx)
1217
+ */
1218
+ declare class RuntimeBlock implements IRuntimeBlock {
1219
+ readonly behaviors: IRuntimeBehavior[];
1220
+ readonly key: BlockKey;
1221
+ readonly blockType?: string;
1222
+ readonly context: IBlockContext;
1223
+ readonly sourceIds: number[];
1224
+ private _memory;
1225
+ /**
1226
+ * Computed label derived from the block's Label metrics.
1227
+ * Priority: Label metric in memory → blockType → 'Block'
1228
+ */
1229
+ get label(): string;
1230
+ protected _behaviorContext?: BehaviorContext;
1231
+ readonly executionTiming: {
1232
+ startTime?: Date;
1233
+ endTime?: Date;
1234
+ };
1235
+ private _isComplete;
1236
+ private _completionReason?;
1237
+ private _eventUnsubscribers;
1238
+ protected _runtime?: IRuntimeContext;
1239
+ constructor(options: RuntimeBlockOptions);
1240
+ /**
1241
+ * Push a new memory location onto the block's memory list.
1242
+ * Multiple locations with the same tag can coexist.
1243
+ */
1244
+ pushMemory(location: IMemoryLocation): void;
1245
+ /**
1246
+ * Merge a set of compiler hints (e.g. from an effort markdown file) into
1247
+ * a `metric:hint` memory location. Hints are surfaced to strategies
1248
+ * exactly the same way dialect-emitted hints are. Duplicate keys are
1249
+ * overwritten (the effort hint wins, matching the doc's
1250
+ * "more specific wins" precedence).
1251
+ */
1252
+ mergeHints(hints: Readonly<Record<string, unknown>>): void;
1253
+ /**
1254
+ * Get all memory locations matching the given tag.
1255
+ * Returns an empty array if no locations match.
1256
+ */
1257
+ getMemoryByTag(tag: MemoryTag): IMemoryLocation[];
1258
+ /**
1259
+ * Get all memory locations owned by this block.
1260
+ * Returns the full memory list in insertion order.
1261
+ */
1262
+ getAllMemory(): IMemoryLocation[];
1263
+ /**
1264
+ * Get all metrics memory locations matching a given visibility tier.
1265
+ *
1266
+ * @param visibility The visibility tier: 'display' | 'promote' | 'private'
1267
+ * @returns Memory locations whose tags belong to the requested tier
1268
+ */
1269
+ getMetricMemoryByVisibility(visibility: MetricVisibility): IMemoryLocation[];
1270
+ /**
1271
+ * Register default event handlers for block-level events.
1272
+ *
1273
+ * Note: 'next' events are handled globally by NextEventHandler
1274
+ * registered in ScriptRuntime. Per-block 'next' handlers are not
1275
+ * needed and were removed to avoid unnecessary handler registrations.
1276
+ */
1277
+ protected registerDefaultHandler(): void;
1278
+ /**
1279
+ * Called when this block is pushed onto the runtime stack.
1280
+ */
1281
+ mount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
1282
+ /**
1283
+ * Inspect what the next() call would do, WITHOUT dispatching.
1284
+ *
1285
+ * PURE READ for completion state. Runs the behavior onNext chain against
1286
+ * a temporary BehaviorContext, captures the decision, and restores the
1287
+ * block's completion flag so the caller can inspect without side effects.
1288
+ *
1289
+ * The decision is DETERMINISTIC given the same runtime state: the same
1290
+ * behaviors, the same block memory, and the same clock always produce
1291
+ * the exact same decision (behaviors are assumed to be pure functions
1292
+ * of their context).
1293
+ *
1294
+ * @param runtime The script runtime context
1295
+ * @param options Lifecycle options including optional clock for timing consistency
1296
+ */
1297
+ inspectNext(runtime: IRuntimeContext, options?: BlockLifecycleOptions): CompletionDecision;
1298
+ /**
1299
+ * Called when a child block completes or user advances.
1300
+ *
1301
+ * @param runtime The script runtime context
1302
+ * @param options Lifecycle options including optional clock for timing consistency
1303
+ */
1304
+ next(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
1305
+ /**
1306
+ * Called when this block is popped from the runtime stack.
1307
+ *
1308
+ * @param runtime The script runtime context
1309
+ * @param options Lifecycle options including optional clock for timing consistency
1310
+ */
1311
+ unmount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
1312
+ /**
1313
+ * Called for final cleanup.
1314
+ */
1315
+ dispose(runtime: IRuntimeContext): void;
1316
+ get isComplete(): boolean;
1317
+ get completionReason(): string | undefined;
1318
+ markComplete(reason?: string): void;
1319
+ /**
1320
+ * Gets a specific behavior by type from the behaviors array.
1321
+ */
1322
+ getBehavior<T extends IRuntimeBehavior>(behaviorType: new (...args: any[]) => T): T | undefined;
1323
+ /**
1324
+ * Emit a system output for next lifecycle event.
1325
+ * Called directly from next() to ensure accurate timing.
1326
+ */
1327
+ private emitNextSystemOutput;
1328
+ }
1329
+
1330
+ /**
1331
+ * SnapshotClock wraps an IRuntimeClock and freezes `currentDate` at a specific timestamp.
1332
+ * Used to ensure consistent timing during execution chains (pop → next → push).
1333
+ *
1334
+ * All other properties and methods delegate to the underlying clock.
1335
+ *
1336
+ * @example
1337
+ * ```typescript
1338
+ * // Freeze time at completion moment
1339
+ * const createdAt = new Date();
1340
+ * const snapshot = SnapshotClock.at(runtime.clock, createdAt);
1341
+ *
1342
+ * // All operations see the same frozen time
1343
+ * parent.next(runtime, { clock: snapshot });
1344
+ * // child pushed with startTime = snapshot.currentDate = createdAt
1345
+ * ```
1346
+ */
1347
+ declare class SnapshotClock implements IRuntimeClock {
1348
+ private readonly _underlying;
1349
+ private readonly _frozenTime;
1350
+ constructor(_underlying: IRuntimeClock, _frozenTime: Date);
1351
+ /** Always returns the frozen timestamp */
1352
+ get currentDate(): Date;
1353
+ now(): Date;
1354
+ nowMs(): number;
1355
+ /** Delegate to underlying clock */
1356
+ get elapsed(): number;
1357
+ get isRunning(): boolean;
1358
+ get spans(): ReadonlyArray<TimeSpan>;
1359
+ start(): Date;
1360
+ stop(): Date;
1361
+ /**
1362
+ * Factory method for cleaner creation.
1363
+ *
1364
+ * @param clock The underlying clock to wrap
1365
+ * @param time The frozen timestamp to return from `currentDate`
1366
+ */
1367
+ static at(clock: IRuntimeClock, time: Date): SnapshotClock;
1368
+ /**
1369
+ * Create a snapshot at the clock's current time.
1370
+ * Useful for freezing `currentDate` before starting an execution chain.
1371
+ *
1372
+ * @param clock The clock to snapshot
1373
+ */
1374
+ static now(clock: IRuntimeClock): SnapshotClock;
1375
+ }
1376
+ /**
1377
+ * RuntimeClock tracks time using spans created by start/stop calls.
1378
+ *
1379
+ * Provides:
1380
+ * - `now`: Current wall-clock time as a Date
1381
+ * - `elapsed`: Total milliseconds of running time (sum of all spans)
1382
+ * - `start()`/`stop()`: Control methods that return timestamps for event creation
1383
+ */
1384
+ declare class RuntimeClock implements IRuntimeClock {
1385
+ private _spans;
1386
+ /**
1387
+ * Current wall-clock time as a Date.
1388
+ */
1389
+ get currentDate(): Date;
1390
+ now(): Date;
1391
+ nowMs(): number;
1392
+ /**
1393
+ * Whether the clock is currently running (has an open span without a stop time).
1394
+ */
1395
+ get isRunning(): boolean;
1396
+ /**
1397
+ * All time spans tracked by this clock.
1398
+ */
1399
+ get spans(): ReadonlyArray<TimeSpan>;
1400
+ /**
1401
+ * Total elapsed milliseconds while running.
1402
+ * Sums all completed spans plus the current running span (if any).
1403
+ */
1404
+ get elapsed(): number;
1405
+ /**
1406
+ * Start the clock. Creates a new span with the current time.
1407
+ * Returns the start timestamp for event creation.
1408
+ *
1409
+ * If the clock is already running, returns the current span's start time.
1410
+ */
1411
+ start(): Date;
1412
+ /**
1413
+ * Stop the clock. Closes the current span with the current time.
1414
+ * Returns the stop timestamp for event creation.
1415
+ *
1416
+ * If the clock is not running, returns the current time.
1417
+ */
1418
+ stop(): Date;
1419
+ /**
1420
+ * Reset the clock. Clears all spans.
1421
+ */
1422
+ reset(): void;
1423
+ }
1424
+ /**
1425
+ * Type for the mock clock returned by createMockClock.
1426
+ */
1427
+ type MockClock = IRuntimeClock & {
1428
+ /** Advance the mock time by the specified milliseconds */
1429
+ advance: (ms: number) => void;
1430
+ /** Set the mock time to a specific Date */
1431
+ setTime: (time: Date) => void;
1432
+ };
1433
+ /**
1434
+ * Creates a mock clock for testing with controllable time.
1435
+ */
1436
+ declare function createMockClock(initialTime?: Date): MockClock;
1437
+
1438
+ /**
1439
+ * RuntimeController — stable object passed via context to let bridge components
1440
+ * trigger runtime lifecycle without storing React hook closures in Zustand.
1441
+ */
1442
+ interface RuntimeController {
1443
+ initialize(block: ScriptBlock): void;
1444
+ dispose(): void;
1445
+ }
1446
+
1447
+ /**
1448
+ * RuntimeLogger - Centralized debug logging for the runtime engine.
1449
+ *
1450
+ * Enable/disable via:
1451
+ * - `RuntimeLogger.enable()` / `RuntimeLogger.disable()`
1452
+ * - `window.__WOD_DEBUG__ = true` in browser console
1453
+ *
1454
+ * Logs:
1455
+ * - Block push/pop with behaviors
1456
+ * - `next()` calls
1457
+ * - Events that produce actions
1458
+ * - Memory updates
1459
+ */
1460
+
1461
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
1462
+ interface RuntimeLogEntry {
1463
+ timestamp: Date;
1464
+ level: LogLevel;
1465
+ category: string;
1466
+ message: string;
1467
+ data?: Record<string, unknown>;
1468
+ }
1469
+ declare class RuntimeLoggerImpl {
1470
+ private _enabled;
1471
+ private _history;
1472
+ private _maxHistory;
1473
+ private readonly STYLES;
1474
+ constructor();
1475
+ get enabled(): boolean;
1476
+ enable(): void;
1477
+ disable(): void;
1478
+ getHistory(): RuntimeLogEntry[];
1479
+ clearHistory(): void;
1480
+ /**
1481
+ * Log when a block is pushed onto the stack
1482
+ */
1483
+ logPush(block: IRuntimeBlock, parentKey?: string): void;
1484
+ /**
1485
+ * Log when a block is popped from the stack
1486
+ */
1487
+ logPop(block: IRuntimeBlock, reason?: string): void;
1488
+ /**
1489
+ * Log when next() is called on a block
1490
+ */
1491
+ logNext(block: IRuntimeBlock, actions: IRuntimeAction[]): void;
1492
+ /**
1493
+ * Log when an event produces actions
1494
+ */
1495
+ logEvent(event: IEvent, actions: IRuntimeAction[]): void;
1496
+ /**
1497
+ * Log when memory is updated on a block
1498
+ */
1499
+ logMemoryUpdate(blockKey: string, memoryType: string, value: unknown): void;
1500
+ /**
1501
+ * Log action execution (verbose, under a group)
1502
+ */
1503
+ logAction(action: IRuntimeAction, depth: number): void;
1504
+ private getBehaviorNames;
1505
+ private summarizeObject;
1506
+ private addToHistory;
1507
+ }
1508
+ declare const RuntimeLogger: RuntimeLoggerImpl;
1509
+
1510
+ type RuntimeMemoryLocation = {
1511
+ ref: IMemoryReference;
1512
+ data: unknown;
1513
+ };
1514
+ /**
1515
+ * RuntimeMemory is a pure storage system for runtime state.
1516
+ *
1517
+ * Note: This class does not dispatch events. Memory events (allocate, set, release)
1518
+ * are dispatched by BlockContext, which wraps memory operations and dispatches
1519
+ * events to the EventBus. This keeps RuntimeMemory decoupled from the event system.
1520
+ */
1521
+ declare class RuntimeMemory implements IRuntimeMemory {
1522
+ private _references;
1523
+ allocate<T>(type: string, ownerId: string, initialValue?: T, visibility?: 'public' | 'private' | 'inherited'): TypedMemoryReference<T>;
1524
+ get<T>(reference: TypedMemoryReference<T>): T | undefined;
1525
+ set<T>(reference: TypedMemoryReference<T>, value: T): void;
1526
+ search(criteria: MemorySearchCriteria): IMemoryReference[];
1527
+ release(reference: IMemoryReference): void;
1528
+ }
1529
+
1530
+ /**
1531
+ * RuntimeObservers — the shared observer collaborator for IScriptRuntime adapters.
1532
+ *
1533
+ * Owns the **subscriber side** of the stack-snapshot reactive seam
1534
+ * (`IScriptRuntime.subscribeToStack`). Both `ScriptRuntime` and
1535
+ * `ChromecastProxyRuntime` compose one of these.
1536
+ *
1537
+ * ## Post-mount contract
1538
+ *
1539
+ * Stack snapshots emitted via `emitStack` reflect **post-mount state** —
1540
+ * the calling adapter is responsible for invoking `emitStack` only with
1541
+ * snapshots taken from a stack where every visible block has completed
1542
+ * `onMount`. `ScriptRuntime` enforces this by calling `emitStack` from
1543
+ * two points only:
1544
+ *
1545
+ * 1. The bridge from `IRuntimeStack` push/pop events (post-mount because
1546
+ * `RuntimeStack.push` mounts the block before notifying listeners).
1547
+ * 2. The post-turn settle re-emit in `do()` (post-mount by construction —
1548
+ * the turn completed, so every action including `onMount` has run).
1549
+ *
1550
+ * The collaborator itself does not check this invariant; the **adapter's
1551
+ * emit sites** are the contract. The regression test
1552
+ * `tests/runtime-compliance/post-mount-snapshot-invariant.compliance.test.ts`
1553
+ * pins the outcome.
1554
+ *
1555
+ * ## What this collaborator is NOT
1556
+ *
1557
+ * - Not a producer of snapshots. The adapter decides *when* to call
1558
+ * `emitStack(snapshot)`. The collaborator only fans out.
1559
+ * - Not a replacement for `OutputEmitter` (output buffer, analytics
1560
+ * enrichment, GC guard, deferred catch-up). Output emission semantics
1561
+ * live there.
1562
+ */
1563
+
1564
+ /**
1565
+ * Subscriber collaborator for stack snapshots. (The former tracker-listener
1566
+ * seam was removed when session-totals moved to the live 'analytics' output
1567
+ * stream — see AnalyticsEngine.setLiveOutputEmitter.)
1568
+ */
1569
+ declare class RuntimeObservers {
1570
+ private readonly _stackObservers;
1571
+ /**
1572
+ * Subscribe to stack snapshots. The collaborator does NOT emit an
1573
+ * `'initial'` snapshot automatically — the calling adapter is
1574
+ * responsible for constructing the snapshot and calling `emitStack`
1575
+ * right after subscribe, so the adapter owns its own initial-snapshot
1576
+ * timing (ScriptRuntime defers via setTimeout to dodge React render
1577
+ * warnings; the Chromecast proxy fires synchronously per its existing
1578
+ * contract — see `ChromecastProxyRuntime.test.ts`).
1579
+ */
1580
+ subscribeToStack(observer: StackObserver): Unsubscribe;
1581
+ /**
1582
+ * Fan-out a stack snapshot to all subscribers. Errors are isolated
1583
+ * per-observer so a misbehaving listener cannot stop the others.
1584
+ */
1585
+ emitStack(snapshot: StackSnapshot): void;
1586
+ /**
1587
+ * Membership check used by adapters that defer their initial-snapshot
1588
+ * emit (e.g. ScriptRuntime via setTimeout) and need to drop the
1589
+ * emit if the observer unsubscribed in the meantime.
1590
+ */
1591
+ hasStackObserver(observer: StackObserver): boolean;
1592
+ /**
1593
+ * Build the `initial`-type snapshot the collaborator itself cannot
1594
+ * build (it doesn't know the adapter's stack). Used by the settle
1595
+ * path in `ScriptRuntime.do()`'s `finally` to re-emit a
1596
+ * post-mount snapshot after the turn completes.
1597
+ */
1598
+ emitSettled(blocks: readonly IRuntimeBlock[], depth: number, clockTime: Date): void;
1599
+ /**
1600
+ * Drop all subscribers. After `dispose`, `emit*` calls become no-ops
1601
+ * (empty Set checks), and any subscription returned previously
1602
+ * continues to function as an unsubscribe.
1603
+ */
1604
+ dispose(): void;
1605
+ /** Introspection (testing / diagnostics). */
1606
+ get stackObserverCount(): number;
1607
+ }
1608
+
1609
+ /**
1610
+ * Lightweight runtime stack that only maintains state.
1611
+ * All complex logic (lifecycles, wrapping, hooks) has been moved to ScriptRuntime.
1612
+ * Stack events are dispatched via the EventBus in ScriptRuntime and via
1613
+ * independent subscribers on this stack.
1614
+ */
1615
+ declare class RuntimeStack implements IRuntimeStack {
1616
+ private readonly _blocks;
1617
+ private readonly _listeners;
1618
+ /** Top-first view of blocks (copy) */
1619
+ get blocks(): readonly IRuntimeBlock[];
1620
+ get count(): number;
1621
+ get current(): IRuntimeBlock | undefined;
1622
+ get keys(): BlockKey[];
1623
+ push(block: IRuntimeBlock): void;
1624
+ pop(): IRuntimeBlock | undefined;
1625
+ clear(): void;
1626
+ /**
1627
+ * Subscribe to stack changes.
1628
+ * New subscribers will immediately receive an 'initial' event with the current stack state.
1629
+ */
1630
+ subscribe(listener: StackListener): () => void;
1631
+ private notify;
1632
+ }
1633
+
1634
+ /**
1635
+ * BlockContext implementation for runtime block memory management.
1636
+ *
1637
+ * Responsibilities:
1638
+ * - Allocate and track memory references for a single block
1639
+ * - Provide type-safe access to allocated memory
1640
+ * - Clean up all memory on release
1641
+ *
1642
+ * Lifecycle:
1643
+ * 1. Created by strategy with runtime and ownerId
1644
+ * 2. Strategy allocates memory via allocate()
1645
+ * 3. Memory references injected into behaviors
1646
+ * 4. Consumer calls release() after block disposal
1647
+ *
1648
+ * @example
1649
+ * ```typescript
1650
+ * const context = new BlockContext(runtime, 'block-123');
1651
+ * const ref = context.allocate<number>('counter', 0, 'private');
1652
+ * // ... use ref in behaviors ...
1653
+ * context.release(); // Clean up
1654
+ * ```
1655
+ */
1656
+ declare class BlockContext implements IBlockContext {
1657
+ private readonly runtime;
1658
+ readonly ownerId: string;
1659
+ readonly exerciseId: string;
1660
+ private _references;
1661
+ private _released;
1662
+ private _memory;
1663
+ constructor(runtime: IRuntimeContext, ownerId: string, exerciseId?: string, initialReferences?: IMemoryReference[], memory?: IRuntimeMemory);
1664
+ get references(): ReadonlyArray<IMemoryReference>;
1665
+ allocate<T>(type: MemoryTypeEnum | string, initialValue?: T, visibility?: 'public' | 'private' | 'inherited'): TypedMemoryReference<T>;
1666
+ get<T>(type: MemoryTypeEnum | string): TypedMemoryReference<T> | undefined;
1667
+ getAll<T>(type: MemoryTypeEnum | string): TypedMemoryReference<T>[];
1668
+ set<T>(reference: TypedMemoryReference<T>, value: T): void;
1669
+ release(): void;
1670
+ isReleased(): boolean;
1671
+ getOrCreateAnchor(anchorId: string): TypedMemoryReference<IAnchorValue>;
1672
+ }
1673
+
1674
+ declare class NextAction implements IRuntimeAction {
1675
+ private readonly options?;
1676
+ private readonly now;
1677
+ readonly type = "next";
1678
+ /**
1679
+ * @param options Optional lifecycle options to pass to the block's next() method.
1680
+ * When provided (e.g., from PopBlockAction carrying createdAt), these options
1681
+ * are merged with a snapshot clock. When omitted (e.g., user-triggered next),
1682
+ * a fresh snapshot clock is created.
1683
+ */
1684
+ constructor(options?: BlockLifecycleOptions | undefined, now?: INowProvider);
1685
+ do(runtime: IRuntimeContext): IRuntimeAction[];
1686
+ private validateRuntimeState;
1687
+ }
1688
+
1689
+ type ChildSelectionLoopCondition = 'always' | 'timer-active' | 'rounds-remaining';
1690
+ interface ChildSelectionConfig {
1691
+ /** Groups of statement indices to compile and dispatch */
1692
+ childGroups: number[][];
1693
+ /** Enable looping when all children complete */
1694
+ loop?: boolean | {
1695
+ /** Loop condition: always, while timer running, or while rounds remain */
1696
+ condition?: ChildSelectionLoopCondition;
1697
+ };
1698
+ /** Inject rest blocks between loop iterations */
1699
+ injectRest?: boolean;
1700
+ /** Skip first child dispatch on mount (e.g., for deferred start) */
1701
+ skipOnMount?: boolean;
1702
+ /**
1703
+ * If set, initializes round state on mount (absorbs ReEntryBehavior).
1704
+ * Use 1 for the first round. When provided, round memory is written
1705
+ * at mount time so display and loop logic can read it immediately.
1706
+ */
1707
+ startRound?: number;
1708
+ /**
1709
+ * Total rounds for completion checking (undefined = unbounded).
1710
+ * Only used when startRound is also provided.
1711
+ */
1712
+ totalRounds?: number;
1713
+ }
1714
+
1715
+ /** Legacy timer config shape — kept for call-site backward compatibility */
1716
+ interface TimerConfig {
1717
+ direction: 'up' | 'down';
1718
+ durationMs?: number;
1719
+ label?: string;
1720
+ role?: 'primary' | 'secondary' | 'hidden' | 'auto';
1721
+ }
1722
+ interface TimerCompletionConfig {
1723
+ completesBlock?: boolean;
1724
+ }
1725
+ /** Round configuration stored by asRepeater() and forwarded by asContainer() */
1726
+ interface RepeaterConfig {
1727
+ totalRounds?: number;
1728
+ startRound?: number;
1729
+ addCompletion?: boolean;
1730
+ }
1731
+
1732
+ declare class BlockBuilder {
1733
+ private runtime;
1734
+ private behaviors;
1735
+ private context;
1736
+ private key;
1737
+ private label;
1738
+ private blockType;
1739
+ private metrics;
1740
+ private sourceIds;
1741
+ /** Pending round config stored by asRepeater(), consumed by asContainer() */
1742
+ private pendingRoundConfig;
1743
+ /** Declared exit intent — resolved into a single ExitBehavior in build(). */
1744
+ private declaredExitMode;
1745
+ constructor(runtime: IRuntimeContext);
1746
+ addBehavior(behavior: IRuntimeBehavior): BlockBuilder;
1747
+ /**
1748
+ * Adds a behavior only if one of that type doesn't already exist.
1749
+ */
1750
+ addBehaviorIfMissing(behavior: IRuntimeBehavior): BlockBuilder;
1751
+ /**
1752
+ * Remove a behavior by its constructor type.
1753
+ *
1754
+ * Private: this is a mechanic `build()` uses to resolve a declared exit
1755
+ * intent (see `declareExit`) into exactly one `ExitBehavior`. Strategies
1756
+ * declare intent instead of calling this directly — see §2.3 of
1757
+ * docs/architectural-cleanup-tier-2-consolidations.md for why strategies
1758
+ * used to mutate each other's contributions this way, and why that
1759
+ * coupling was removed.
1760
+ */
1761
+ private removeBehavior;
1762
+ /**
1763
+ * Move an already-added behavior to the LAST position in the behavior list.
1764
+ *
1765
+ * Behavior order = Map insertion order = RuntimeBlock.onNext execution order.
1766
+ *
1767
+ * Private: `build()` calls this once to canonicalize ordering (e.g.
1768
+ * MetricPromotionBehavior must run after ChildSelectionBehavior so it sees
1769
+ * the round ChildSelectionBehavior just advanced). No-op when the behavior
1770
+ * is not present (or already last). Strategies no longer call this
1771
+ * directly — see §2.3 of docs/architectural-cleanup-tier-2-consolidations.md.
1772
+ */
1773
+ private moveBehaviorLast;
1774
+ hasBehavior<T extends IRuntimeBehavior>(type: new (...args: any[]) => T): boolean;
1775
+ /**
1776
+ * Declare the block's exit intent. Strategies call this instead of adding
1777
+ * or removing ExitBehavior across each other. 'deferred' supersedes
1778
+ * 'immediate' (a container strategy overrides a leaf strategy's
1779
+ * immediate-exit guess). Resolved into a single ExitBehavior in build().
1780
+ */
1781
+ declareExit(mode: 'immediate' | 'deferred'): BlockBuilder;
1782
+ /** The declared exit intent (resolved into a single ExitBehavior in build). */
1783
+ get exitMode(): 'immediate' | 'deferred' | undefined;
1784
+ getBehavior<T extends IRuntimeBehavior>(type: new (...args: any[]) => T): T | undefined;
1785
+ setContext(context: IBlockContext): BlockBuilder;
1786
+ setKey(key: BlockKey): BlockBuilder;
1787
+ setLabel(label: string): BlockBuilder;
1788
+ setBlockType(type: string): BlockBuilder;
1789
+ setFragments(metrics: IMetric[][] | MetricContainer[]): BlockBuilder;
1790
+ setSourceIds(ids: number[]): BlockBuilder;
1791
+ getLabel(): string;
1792
+ getBlockType(): string;
1793
+ getFragments(): MetricContainer[] | undefined;
1794
+ getSourceIds(): number[];
1795
+ getContext(): IBlockContext | undefined;
1796
+ getKey(): BlockKey | undefined;
1797
+ /**
1798
+ * Timer Aspect Composer — assigns exactly ONE self-contained timer behavior.
1799
+ *
1800
+ * - `direction: 'down'` + `durationMs` → `CountdownTimerBehavior`
1801
+ * (subscribes to tick, fires timer:complete, handles pause/resume)
1802
+ * - `direction: 'up'` or no duration → `CountupTimerBehavior`
1803
+ * (tracks elapsed via spans, handles pause/resume, no completion signal)
1804
+ *
1805
+ * @param config Timer configuration
1806
+ * @returns This builder for chaining
1807
+ */
1808
+ asTimer(config: TimerConfig & {
1809
+ addCompletion?: boolean;
1810
+ completionConfig?: TimerCompletionConfig;
1811
+ injectRest?: boolean;
1812
+ required?: boolean;
1813
+ }): BlockBuilder;
1814
+ /**
1815
+ * Returns true if round config was stored via asRepeater().
1816
+ * Use this guard in strategies that must skip their iteration logic when rounds are already set.
1817
+ */
1818
+ hasRoundConfig(): boolean;
1819
+ /**
1820
+ * Returns true if any timer behavior (countdown, countup, or span-only) is present.
1821
+ * Use this guard in strategies that must skip their timer logic when timer is already set.
1822
+ */
1823
+ hasTimerBehavior(): boolean;
1824
+ /**
1825
+ * Repeater Aspect Composer — stores round/iteration config to be forwarded
1826
+ * when asContainer() is called.
1827
+ *
1828
+ * Previously created ReEntryBehavior + RoundsEndBehavior directly; now the
1829
+ * config is absorbed into ChildSelectionBehavior via asContainer(), which
1830
+ * owns round state initialization and the overflow safety net.
1831
+ *
1832
+ * @param config Round configuration
1833
+ * @returns This builder for chaining
1834
+ */
1835
+ asRepeater(config: RepeaterConfig): BlockBuilder;
1836
+ /**
1837
+ * Container Aspect Composer — adds ChildSelectionBehavior, incorporating any
1838
+ * round config stored by a prior asRepeater() call.
1839
+ *
1840
+ * @param config Container configuration
1841
+ * @returns This builder for chaining
1842
+ */
1843
+ asContainer(config: ChildSelectionConfig & {
1844
+ addLoop?: boolean;
1845
+ loopConfig?: {
1846
+ condition?: ChildSelectionLoopCondition;
1847
+ };
1848
+ }): BlockBuilder;
1849
+ build(): IRuntimeBlock;
1850
+ }
1851
+
1852
+ /**
1853
+ * Contract for a JIT compiler strategy.
1854
+ *
1855
+ * Each strategy must carry a stable `id` (used by the consumer-facing
1856
+ * {@link strategyRegistry}) and a `priority` (ordering). `id` is the lookup
1857
+ * key for `register(id)` / override; `priority` is the registration-order
1858
+ * tiebreaker (higher runs first).
1859
+ */
1860
+ interface IRuntimeBlockStrategy {
1861
+ /** Stable, unique identifier (used by strategyRegistry for registration). */
1862
+ readonly id: string;
1863
+ /**
1864
+ * Execution priority. Higher values run first.
1865
+ * Suggested ranges:
1866
+ * - Logic/Drivers: 90-100 (e.g. Interval, AMRAP)
1867
+ * - Components: 50-80 (e.g. Timer, Loop)
1868
+ * - Enhancements: 20-40 (e.g. Sound, History)
1869
+ * - Fallback: 0 (e.g. Effort)
1870
+ */
1871
+ priority: number;
1872
+ /**
1873
+ * Checks if this strategy applies to the given statement.
1874
+ */
1875
+ match(statements: ICodeStatement[], runtime: IRuntimeContext): boolean;
1876
+ /**
1877
+ * Applies this strategy to the block builder.
1878
+ * Note: This replaces `compile`. The builder accumulates state.
1879
+ */
1880
+ apply(builder: BlockBuilder, statements: ICodeStatement[], runtime: IRuntimeContext): void;
1881
+ }
1882
+
1883
+ /**
1884
+ * Action that pushes a compiled block onto the runtime stack.
1885
+ * Used by behaviors to add child blocks for execution.
1886
+ */
1887
+ declare class PushBlockAction implements IRuntimeAction {
1888
+ readonly block: IRuntimeBlock;
1889
+ private readonly options;
1890
+ private _type;
1891
+ constructor(block: IRuntimeBlock, options?: BlockLifecycleOptions);
1892
+ get type(): string;
1893
+ set type(_value: string);
1894
+ do(runtime: IRuntimeContext): IRuntimeAction[];
1895
+ }
1896
+
1897
+ /**
1898
+ * Action that pops the current block from the runtime stack.
1899
+ * Handles the full lifecycle: unmount, pop, dispose, emit output.
1900
+ *
1901
+ * Returns unmount actions followed by a NextAction for the parent,
1902
+ * decoupling the pop and next phases into distinct actions processed
1903
+ * by the ExecutionContext.
1904
+ */
1905
+ declare class PopBlockAction implements IRuntimeAction {
1906
+ private readonly options;
1907
+ readonly type = "pop-block";
1908
+ constructor(options?: BlockLifecycleOptions);
1909
+ do(runtime: IRuntimeContext): IRuntimeAction[];
1910
+ }
1911
+
1912
+ /**
1913
+ * Action that aborts the current session by force-popping all blocks from the stack.
1914
+ *
1915
+ * Unlike PopBlockAction (which pops one block and calls parent.next()), AbortSessionAction
1916
+ * drains the entire stack without triggering parent advancement or pushing new children.
1917
+ *
1918
+ * Each block receives the full lifecycle:
1919
+ * 1. markComplete('user-abort') — marks the block as intentionally terminated
1920
+ * 2. unmount() — runs behaviors (e.g. ReportOutputBehavior) which write result memory
1921
+ * 3. stack.pop() — triggers emitSegmentFromResultMemory in ScriptRuntime, the sole
1922
+ * site that turns a pop into a 'segment' OutputStatement
1923
+ * 4. dispose() — releases block resources
1924
+ *
1925
+ * This ensures every aborted block still gets a well-formed 'segment' output
1926
+ * (checked by assertPairedOutputs() in tests).
1927
+ */
1928
+ declare class AbortSessionAction implements IRuntimeAction {
1929
+ readonly type = "abort-session";
1930
+ do(runtime: IRuntimeContext): IRuntimeAction[];
1931
+ }
1932
+
1933
+ /**
1934
+ * ClearChildrenAction pops all blocks above a target block on the stack.
1935
+ *
1936
+ * Used when a parent timer expires (AMRAP/EMOM) and active children must
1937
+ * be force-completed and removed. Each child is popped via PopBlockAction
1938
+ * so the full lifecycle runs:
1939
+ *
1940
+ * 1. PopBlockAction checks isComplete → marks 'forced-pop' if not yet complete
1941
+ * 2. Unmount fires → ReportOutputBehavior writes result memory (elapsed/spans),
1942
+ * then the pop emits a single 'segment' output carrying those fragments
1943
+ * 3. Block is disposed and cleaned up
1944
+ *
1945
+ * After all children are cleared, a NextAction is queued so the parent
1946
+ * block (now on top) receives its next() call, where the container's
1947
+ * ExitBehavior (deferred) can pop it cleanly.
1948
+ *
1949
+ * ## Example: AMRAP timer expires mid-exercise
1950
+ *
1951
+ * Stack: [Session, AMRAP, Pullups]
1952
+ * Timer fires → ClearChildrenAction(AMRAP.key)
1953
+ * → PopBlockAction pops Pullups (forced-pop, fragments reported)
1954
+ * → NextAction on AMRAP → ExitBehavior (deferred) → PopBlockAction
1955
+ * → Session receives next(), advances to review
1956
+ */
1957
+ declare class ClearChildrenAction implements IRuntimeAction {
1958
+ private readonly targetBlockKey;
1959
+ readonly type = "clear-children";
1960
+ constructor(targetBlockKey: string);
1961
+ do(runtime: IRuntimeContext): IRuntimeAction[];
1962
+ }
1963
+
1964
+ /**
1965
+ * UpdateNextPreviewAction resolves the next child statement(s) and stores
1966
+ * their metrics in the block's `metric:next` memory location.
1967
+ *
1968
+ * ## Purpose
1969
+ *
1970
+ * Behaviors (ChildRunnerBehavior) cannot access `runtime.script` directly —
1971
+ * it's only available in `IRuntimeAction.do(runtime)`. This action bridges
1972
+ * the gap: the behavior returns this action with statement IDs, and the
1973
+ * action looks up the metrics and pushes them into block memory.
1974
+ *
1975
+ * ## Memory Contract
1976
+ *
1977
+ * - Tag: `metric:next`
1978
+ * - Value: `MetricContainer` from the next statement(s) to be compiled
1979
+ * - When `nextStatementIds` is empty, any existing `metric:next` memory
1980
+ * is cleared (updated to empty metric) to signal "no more children".
1981
+ *
1982
+ * ## Consumed By
1983
+ *
1984
+ * - `useNextPreview` hook → `LookaheadView` component
1985
+ * - The UI walks the stack leaf-to-root and shows the deepest block's
1986
+ * `metric:next` as the "Up Next" preview.
1987
+ */
1988
+ declare class UpdateNextPreviewAction implements IRuntimeAction {
1989
+ private readonly blockKey;
1990
+ private readonly nextStatementIds;
1991
+ readonly type = "update-next-preview";
1992
+ readonly target?: string;
1993
+ readonly payload?: unknown;
1994
+ constructor(blockKey: string, nextStatementIds: number[]);
1995
+ do(runtime: IRuntimeContext): IRuntimeAction[];
1996
+ }
1997
+
1998
+ /**
1999
+ * Configuration for starting a workout.
2000
+ */
2001
+ interface StartWorkoutOptions {
2002
+ /** Display label for the session */
2003
+ label?: string;
2004
+ /** Total rounds for the workout (default: 1) */
2005
+ totalRounds?: number;
2006
+ /** Optional lifecycle options for the root block */
2007
+ lifecycle?: BlockLifecycleOptions;
2008
+ }
2009
+ /**
2010
+ * Action that initializes a workout by wrapping the script's statements
2011
+ * in a root block and pushing it onto the stack.
2012
+ *
2013
+ * This action:
2014
+ * 1. Reads all top-level statements from runtime.script
2015
+ * 2. Creates child groups (each top-level statement in its own group)
2016
+ * 3. Builds a SessionRootBlock using SessionRootStrategy
2017
+ * 4. Pushes the root block to start the workout
2018
+ *
2019
+ * @example
2020
+ * ```typescript
2021
+ * runtime.do(new StartWorkoutAction());
2022
+ * runtime.do(new StartWorkoutAction({ label: 'Grace', totalRounds: 3 }));
2023
+ * ```
2024
+ */
2025
+ declare class StartWorkoutAction implements IRuntimeAction {
2026
+ private readonly options;
2027
+ readonly type = "start-workout";
2028
+ constructor(options?: StartWorkoutOptions);
2029
+ do(runtime: IRuntimeContext): IRuntimeAction[];
2030
+ }
2031
+
2032
+ /**
2033
+ * Configuration for starting a session-based workout.
2034
+ */
2035
+ interface StartSessionOptions {
2036
+ /** Display label for the session (e.g., "Grace", "Cindy") */
2037
+ label?: string;
2038
+ /** Total rounds for the session (default: 1) */
2039
+ totalRounds?: number;
2040
+ /** Optional lifecycle options for the root block */
2041
+ lifecycle?: BlockLifecycleOptions;
2042
+ }
2043
+ /**
2044
+ * Action that initializes a workout using the SessionRootBlock.
2045
+ *
2046
+ * Uses SessionRootStrategy to create a SessionRootBlock with proper behavior composition:
2047
+ *
2048
+ * - ReportOutputBehavior (emits segment/completion outputs — source of truth for history)
2049
+ * - CountupTimerBehavior (countup for total elapsed time)
2050
+ * - ChildSelectionBehavior (sequences WaitingToStart + children)
2051
+ *
2052
+ * History persistence is the responsibility of the workbench layer,
2053
+ * which subscribes to the output stream.
2054
+ *
2055
+ * @see SessionRootStrategy
2056
+ * @see SessionRootBlock
2057
+ */
2058
+ declare class StartSessionAction implements IRuntimeAction {
2059
+ private readonly options;
2060
+ readonly type = "start-session";
2061
+ constructor(options?: StartSessionOptions);
2062
+ do(runtime: IRuntimeContext): IRuntimeAction[];
2063
+ }
2064
+
2065
+ /**
2066
+ * Action that compiles a set of statement IDs and pushes the resulting block.
2067
+ * Allows behaviors to request JIT compilation without holding a reference to the runtime/JIT compiler.
2068
+ */
2069
+ declare class CompileAndPushBlockAction implements IRuntimeAction {
2070
+ readonly statementIds: number[];
2071
+ readonly options: BlockLifecycleOptions;
2072
+ readonly type = "compile-and-push-block";
2073
+ constructor(statementIds: number[], options?: BlockLifecycleOptions);
2074
+ do(runtime: IRuntimeContext): IRuntimeAction[];
2075
+ }
2076
+
2077
+ /**
2078
+ * Action that constructs a RestBlock with the given duration and pushes it
2079
+ * onto the runtime stack.
2080
+ *
2081
+ * Previously a private class inside ChildSelectionBehavior. Elevated to a
2082
+ * named action so any behavior or strategy that needs to inject a rest period
2083
+ * can do so without re-implementing the RestBlock construction pattern.
2084
+ *
2085
+ * @example
2086
+ * ```typescript
2087
+ * // Inside a behavior that needs a rest break between intervals
2088
+ * return [new PushRestBlockAction(remainingMs, 'Rest')];
2089
+ * ```
2090
+ */
2091
+ declare class PushRestBlockAction implements IRuntimeAction {
2092
+ readonly durationMs: number;
2093
+ readonly label: string;
2094
+ readonly type = "push-rest-block";
2095
+ constructor(durationMs: number, label?: string);
2096
+ do(runtime: IRuntimeContext): IRuntimeAction[];
2097
+ }
2098
+
2099
+ /**
2100
+ * Action that emits a system-level output statement for lifecycle tracing.
2101
+ *
2102
+ * System outputs are diagnostic records emitted on block push/pop/next
2103
+ * and when event handlers produce actions. They flow through the normal
2104
+ * output pipeline (runtime.addOutput) and appear alongside segment/completion
2105
+ * outputs in the output log.
2106
+ *
2107
+ * Consumers that don't want system output can filter:
2108
+ * outputs.filter(o => o.outputType !== 'system')
2109
+ *
2110
+ * ## Fragment Encoding
2111
+ *
2112
+ * Each system output carries a single metrics:
2113
+ * - metricType: MetricType.System
2114
+ * - type: 'lifecycle' | 'event-action'
2115
+ * - image: human-readable message
2116
+ * - value: SystemOutputValue with structured data
2117
+ * - origin: 'runtime'
2118
+ */
2119
+ declare class EmitSystemOutputAction implements IRuntimeAction {
2120
+ private readonly message;
2121
+ private readonly event;
2122
+ private readonly blockKey;
2123
+ private readonly blockLabel?;
2124
+ private readonly stackLevel?;
2125
+ private readonly extra?;
2126
+ readonly type = "emit-system-output";
2127
+ /**
2128
+ * @param message Human-readable description (becomes metrics.image)
2129
+ * @param event Which lifecycle event triggered this output
2130
+ * @param blockKey The block key involved
2131
+ * @param blockLabel Optional human-readable block label
2132
+ * @param stackLevel Stack depth at time of emission
2133
+ * @param extra Additional key-value data to include in the metrics value
2134
+ */
2135
+ constructor(message: string, event: 'push' | 'pop' | 'next' | 'event-action', blockKey: string, blockLabel?: string | undefined, stackLevel?: number | undefined, extra?: Record<string, unknown> | undefined);
2136
+ do(runtime: IRuntimeContext): IRuntimeAction[];
2137
+ }
2138
+
2139
+ /**
2140
+ * ActionDescriptor - Describes a user-facing action/button in the workout UI.
2141
+ *
2142
+ * Used by both the runtime hooks layer (useActiveControls) and UI components
2143
+ * (TimerStackView) to represent interactive buttons.
2144
+ */
2145
+ interface ActionDescriptor {
2146
+ id: string;
2147
+ name: string;
2148
+ eventName: string;
2149
+ ownerId: string;
2150
+ sourceId?: string;
2151
+ raw?: string;
2152
+ displayLabel?: string;
2153
+ payload?: Record<string, unknown>;
2154
+ isPinned?: boolean;
2155
+ }
2156
+
2157
+ /**
2158
+ * Action for declarative event emission from behaviors.
2159
+ *
2160
+ * This action allows behaviors to emit events through the action system
2161
+ * using eventBus.emit(), maintaining declarative patterns.
2162
+ *
2163
+ * @example
2164
+ * ```typescript
2165
+ * // Emit a timer start event
2166
+ * return [new EmitEventAction('timer:started')];
2167
+ *
2168
+ * // Emit event with custom data
2169
+ * return [new EmitEventAction('rounds:completed', { round: 5 })];
2170
+ *
2171
+ * // Emit event with custom timestamp
2172
+ * return [new EmitEventAction('workout:finished', undefined, new Date())];
2173
+ * ```
2174
+ */
2175
+ declare class EmitEventAction implements IRuntimeAction {
2176
+ /** Name of the event to emit */
2177
+ readonly eventName: string;
2178
+ /** Optional data to include with the event */
2179
+ readonly data?: unknown | undefined;
2180
+ readonly type = "emit-event";
2181
+ readonly timestamp: Date;
2182
+ constructor(
2183
+ /** Name of the event to emit */
2184
+ eventName: string,
2185
+ /** Optional data to include with the event */
2186
+ data?: unknown | undefined,
2187
+ /** Optional timestamp (defaults to now) */
2188
+ timestamp?: Date, now?: INowProvider);
2189
+ do(runtime: IRuntimeContext): IRuntimeAction[];
2190
+ }
2191
+
2192
+ /**
2193
+ * Action for declaratively registering an event handler.
2194
+ *
2195
+ * This action manages event handler registration by storing the handler
2196
+ * in runtime memory, making it available for event processing.
2197
+ *
2198
+ * @example
2199
+ * ```typescript
2200
+ * // Register a handler for block completion (default 'active' scope)
2201
+ * const handler = new BlockCompleteEventHandler(blockId);
2202
+ * return [new RegisterEventHandlerAction(handler, blockId)];
2203
+ *
2204
+ * // Register a handler that listens to child events ('bubble' scope)
2205
+ * return [new RegisterEventHandlerAction(handler, blockId, 'bubble')];
2206
+ * ```
2207
+ */
2208
+ declare class RegisterEventHandlerAction implements IRuntimeAction {
2209
+ /** The event handler to register */
2210
+ readonly handler: IEventHandler;
2211
+ /** The block ID that owns this handler (for cleanup) */
2212
+ readonly ownerId: string;
2213
+ /** Handler scope. Default: 'active' (only fires when owner is current block) */
2214
+ readonly scope: HandlerScope;
2215
+ readonly type = "register-event-handler";
2216
+ constructor(
2217
+ /** The event handler to register */
2218
+ handler: IEventHandler,
2219
+ /** The block ID that owns this handler (for cleanup) */
2220
+ ownerId: string,
2221
+ /** Handler scope. Default: 'active' (only fires when owner is current block) */
2222
+ scope?: HandlerScope);
2223
+ do(runtime: IRuntimeContext): void;
2224
+ }
2225
+
2226
+ /**
2227
+ * Action for declaratively unregistering an event handler.
2228
+ *
2229
+ * This action manages event handler cleanup by removing the handler
2230
+ * from runtime memory, preventing further event processing.
2231
+ *
2232
+ * @example
2233
+ * ```typescript
2234
+ * // Unregister a handler during block disposal
2235
+ * return [new UnregisterEventHandlerAction(handlerId)];
2236
+ * ```
2237
+ */
2238
+ declare class UnregisterEventHandlerAction implements IRuntimeAction {
2239
+ /** The ID of the handler to unregister */
2240
+ readonly handlerId: string;
2241
+ readonly type = "unregister-event-handler";
2242
+ constructor(
2243
+ /** The ID of the handler to unregister */
2244
+ handlerId: string);
2245
+ do(runtime: IRuntimeContext): void;
2246
+ }
2247
+
2248
+ /**
2249
+ * Action for playing sounds during workout execution.
2250
+ *
2251
+ * This action emits a 'sound:play' event that can be consumed by UI components
2252
+ * to trigger audio playback. The runtime itself does not handle audio playback
2253
+ * directly - it delegates to registered sound handlers.
2254
+ *
2255
+ * @example
2256
+ * ```typescript
2257
+ * // Play a predefined beep sound
2258
+ * return [new PlaySoundAction('beep')];
2259
+ *
2260
+ * // Play a sound at 50% volume
2261
+ * return [new PlaySoundAction('buzzer', 0.5)];
2262
+ *
2263
+ * // Play a custom sound URL
2264
+ * return [new PlaySoundAction('https://example.com/sound.mp3')];
2265
+ * ```
2266
+ */
2267
+ declare class PlaySoundAction implements IRuntimeAction {
2268
+ /**
2269
+ * Sound identifier. Can be:
2270
+ * - Predefined sound name: 'beep', 'buzzer', 'chime', 'tick'
2271
+ * - URL to audio file
2272
+ */
2273
+ readonly sound: string;
2274
+ /** Volume level (0.0 to 1.0). Default is 1.0 */
2275
+ readonly volume: number;
2276
+ /** Optional cue ID for tracking which cue triggered this sound */
2277
+ readonly cueId?: string | undefined;
2278
+ private readonly now;
2279
+ readonly type = "play-sound";
2280
+ constructor(
2281
+ /**
2282
+ * Sound identifier. Can be:
2283
+ * - Predefined sound name: 'beep', 'buzzer', 'chime', 'tick'
2284
+ * - URL to audio file
2285
+ */
2286
+ sound: string,
2287
+ /** Volume level (0.0 to 1.0). Default is 1.0 */
2288
+ volume?: number,
2289
+ /** Optional cue ID for tracking which cue triggered this sound */
2290
+ cueId?: string | undefined, now?: INowProvider);
2291
+ /**
2292
+ * Executes the sound playback by emitting a 'sound:play' event.
2293
+ * UI components should register handlers for this event to play audio.
2294
+ */
2295
+ do(runtime: IRuntimeContext): IRuntimeAction[];
2296
+ }
2297
+
2298
+ /**
2299
+ * Action that registers a runtime error and transitions the workout to an error state.
2300
+ */
2301
+ declare class ThrowErrorAction implements IRuntimeAction {
2302
+ private readonly error;
2303
+ private readonly source;
2304
+ readonly type = "throw-error";
2305
+ constructor(error: Error, source: string);
2306
+ do(runtime: IRuntimeContext): void;
2307
+ }
2308
+ /**
2309
+ * Factory function to create a ThrowErrorAction.
2310
+ */
2311
+ declare function ThrowError(error: Error, source: string): IRuntimeAction;
2312
+
2313
+ /**
2314
+ * ExecutionContext manages a "turn" of execution.
2315
+ *
2316
+ * It wraps a runtime and ensures that all actions pushed during the turn
2317
+ * see a consistent frozen clock and share an execution depth limit.
2318
+ *
2319
+ * This class declares only the narrow {@link IRuntimeContext} surface — the
2320
+ * collaborators and verbs the execution world (actions, blocks, behaviors)
2321
+ * actually uses mid-turn. The wide `IScriptRuntime` members (subscribeTo*,
2322
+ * finalizeAnalytics, setAnalyticsEngine, pushBlock/popBlock, dispose) stay on
2323
+ * `ScriptRuntime` for external drivers; they are NOT re-declared here, so
2324
+ * adding a member to `IScriptRuntime` no longer forces a matching pass-through.
2325
+ *
2326
+ * ## Stack Semantics (LIFO)
2327
+ *
2328
+ * Actions are processed using a **stack** (Last-In-First-Out). When an action
2329
+ * produces child actions via `runtime.do()`, those children are pushed onto the
2330
+ * stack and executed **before** returning to any remaining sibling actions.
2331
+ *
2332
+ * This ensures that nested operations (e.g., a pop that triggers a parent.next()
2333
+ * which pushes a new child) complete their full depth before processing continues
2334
+ * at the parent level — mirroring the natural call-stack behavior of block
2335
+ * lifecycle operations.
2336
+ *
2337
+ * ## Lifecycle
2338
+ *
2339
+ * 1. Created by `ScriptRuntime.do()` or `ScriptRuntime.handle()` when no active context exists
2340
+ * 2. Clock is frozen at creation time (SnapshotClock)
2341
+ * 3. Actions are executed from the stack until empty
2342
+ * 4. Context is discarded — all actions in the turn shared the same frozen timestamp
2343
+ */
2344
+ declare class ExecutionContext implements IRuntimeContext {
2345
+ private readonly _runtime;
2346
+ private _workList;
2347
+ private _iteration;
2348
+ private readonly _maxIterations;
2349
+ private readonly _snapshotClock;
2350
+ constructor(_runtime: IScriptRuntime, maxIterations?: number);
2351
+ get script(): WhiteboardScript;
2352
+ get eventBus(): IEventBus;
2353
+ get stack(): IRuntimeStack;
2354
+ get options(): RuntimeStackOptions;
2355
+ get jit(): IJitCompiler;
2356
+ get errors(): RuntimeError[] | undefined;
2357
+ get analyticsContext(): AnalyticsContext | undefined;
2358
+ get clock(): SnapshotClock;
2359
+ get nowProvider(): INowProvider;
2360
+ addOutput(output: IOutputStatement): void;
2361
+ /**
2362
+ * Pushes an action onto the execution work list to be processed in this turn.
2363
+ * Actions pushed during another action's execution land on top of the stack
2364
+ * (LIFO / Depth-First semantics).
2365
+ */
2366
+ do(action: IRuntimeAction): void;
2367
+ /**
2368
+ * Pushes multiple actions onto the work list. Land on top of the stack
2369
+ * in the order provided (first element on very top).
2370
+ */
2371
+ doAll(actions: IRuntimeAction[]): void;
2372
+ /**
2373
+ * Dispatches an event and pushes all resulting actions onto the work list.
2374
+ */
2375
+ handle(event: IEvent): void;
2376
+ /**
2377
+ * Executes an initial action and then all subsequent actions on the work list until empty.
2378
+ *
2379
+ * Uses an 'Ordered Stack' model:
2380
+ * 1. **Queued actions (do)** are appended to the back (FIFO).
2381
+ * 2. **Returned actions (children)** are prepended to the front (Depth-First).
2382
+ * 3. **Execution** always takes from the front.
2383
+ *
2384
+ * This ensures that nested operations (e.g., a pop that triggers a parent.next())
2385
+ * complete their full depth before processing continues at the sibling level,
2386
+ * while explicitly queued side-effects run in the order they were triggered.
2387
+ *
2388
+ * @throws Error if max iterations reached
2389
+ */
2390
+ execute(initialAction: IRuntimeAction): void;
2391
+ }
2392
+
2393
+ /**
2394
+ * IPromotionResolver — the seam that extracts parent-block promotion
2395
+ * logic from `JitCompiler`.
2396
+ *
2397
+ * Phase D of plan-candidates-2-3-4-5 removes a layer violation in
2398
+ * `JitCompiler.compile()` where the compiler reaches into
2399
+ * `parentBlock.getMetricMemoryByVisibility('promote')` and
2400
+ * `behavior.getPromotedFragments(runtime, parentBlock)`. That code is
2401
+ * a runtime concern (it inspects live block state); it does not belong
2402
+ * in a `Statement[] → Block` transform.
2403
+ *
2404
+ * The resolver owns the parent-side projection and returns the metrics
2405
+ * that should be injected into the child block's statements. The
2406
+ * `JitCompiler` is left with a one-liner call to `resolvePromotions`.
2407
+ */
2408
+
2409
+ interface PromotionResolution {
2410
+ /**
2411
+ * Promoted metrics derived from the parent block's memory and any
2412
+ * `IMetricPromoter` behaviors attached to it. Empty when the parent
2413
+ * has nothing to promote.
2414
+ */
2415
+ readonly promotedFragments: readonly IMetric[];
2416
+ /**
2417
+ * `true` when the resolver computed a non-empty promotion set; the
2418
+ * compiler uses this bit to invalidate its strategy-match cache.
2419
+ */
2420
+ readonly hasPromotions: boolean;
2421
+ }
2422
+ interface IPromotionResolver {
2423
+ /**
2424
+ * Inspect the parent block (if any) on the runtime stack and return
2425
+ * the metrics it wants to inject into the child being compiled.
2426
+ */
2427
+ resolvePromotions(parentBlock: IRuntimeBlock | undefined, runtime: IRuntimeContext): PromotionResolution;
2428
+ }
2429
+
2430
+ /**
2431
+ * Just-In-Time Compiler for Runtime Blocks.
2432
+ * Coordinates strategy application to build composed RuntimeBlocks.
2433
+ */
2434
+ declare class JitCompiler implements IJitCompiler {
2435
+ private strategies;
2436
+ private _promotionResolver;
2437
+ constructor(strategies?: IRuntimeBlockStrategy[], promotionResolver?: IPromotionResolver);
2438
+ registerStrategy(strategy: IRuntimeBlockStrategy): void;
2439
+ compile(nodes: ICodeStatement[], runtime: IRuntimeContext): IRuntimeBlock | undefined;
2440
+ }
2441
+
2442
+ /**
2443
+ * RuntimeFactory - Service for creating ScriptRuntime instances
2444
+ *
2445
+ * This factory encapsulates the runtime creation logic, decoupling it from
2446
+ * UI components like WorkbenchContext. It handles:
2447
+ * - WhiteboardScript creation from block content
2448
+ * - ScriptRuntime instantiation with JIT compiler
2449
+ * - Root block compilation and mounting
2450
+ * - Optional debug mode enabling
2451
+ *
2452
+ * @example
2453
+ * ```typescript
2454
+ * const factory = new RuntimeFactory(createCompiler());
2455
+ * const runtime = factory.createRuntime(scriptBlock);
2456
+ *
2457
+ * // With debug mode
2458
+ * const debugRuntime = factory.createRuntime(scriptBlock, { debugMode: true });
2459
+ * ```
2460
+ */
2461
+
2462
+ /**
2463
+ * Interface for runtime factory implementations
2464
+ * Allows for dependency injection and testing
2465
+ */
2466
+ interface IRuntimeFactory {
2467
+ /**
2468
+ * Creates a new ScriptRuntime from a ScriptBlock
2469
+ * @param block - The WOD block containing workout script and parsed statements
2470
+ * @param options - Optional runtime options (debug mode, logging, tracker, etc.)
2471
+ * @returns A fully initialized ScriptRuntime, or null if block has no statements
2472
+ */
2473
+ createRuntime(block: ScriptBlock, options?: RuntimeStackOptions): IScriptRuntime | null;
2474
+ /**
2475
+ * Disposes of a runtime and cleans up resources
2476
+ * @param runtime - The runtime to dispose
2477
+ */
2478
+ disposeRuntime(runtime: IScriptRuntime): void;
2479
+ }
2480
+ /**
2481
+ * Default implementation of IRuntimeFactory
2482
+ */
2483
+ declare class RuntimeFactory implements IRuntimeFactory {
2484
+ private readonly compiler;
2485
+ private readonly nowProvider;
2486
+ constructor(compiler: JitCompiler, nowProvider?: INowProvider);
2487
+ /**
2488
+ * Creates a new ScriptRuntime from a ScriptBlock
2489
+ *
2490
+ * Process:
2491
+ * 1. Validates block has statements
2492
+ * 2. Creates WhiteboardScript from content + statements
2493
+ * 3. Instantiates ScriptRuntime with JIT compiler and options
2494
+ * 4. Dispatches StartSessionAction to wrap script in session root block and push it
2495
+ *
2496
+ * @param block - The WOD block to create runtime for
2497
+ * @param options - Optional runtime options (debug mode, logging, tracker, etc.)
2498
+ * @returns Initialized ScriptRuntime or null if invalid block
2499
+ */
2500
+ createRuntime(block: ScriptBlock, options?: RuntimeStackOptions): IScriptRuntime | null;
2501
+ /**
2502
+ * Disposes of a runtime, cleaning up all blocks and resources
2503
+ *
2504
+ * @param runtime - The runtime to dispose
2505
+ */
2506
+ disposeRuntime(runtime: IScriptRuntime): void;
2507
+ }
2508
+
2509
+ /**
2510
+ * RuntimeBuilder provides a fluent API for constructing ScriptRuntime instances
2511
+ * with configurable options including debug mode, logging, and custom block wrappers.
2512
+ *
2513
+ * @example
2514
+ * ```typescript
2515
+ * // Basic usage
2516
+ * const runtime = new RuntimeBuilder(script, compiler).build();
2517
+ *
2518
+ * // With debug mode
2519
+ * const debugRuntime = new RuntimeBuilder(script, compiler)
2520
+ * .withDebugMode(true)
2521
+ * .build();
2522
+ *
2523
+ * // With custom configuration
2524
+ * const customRuntime = new RuntimeBuilder(script, compiler)
2525
+ * .withDebugMode(true)
2526
+ * .withLogging(true)
2527
+ * .withDefaultTestableConfig({ mountMode: 'spy', nextMode: 'spy' })
2528
+ * .withDebugLogHandler((event) => {
2529
+ * console.log('[Debug]', event);
2530
+ * })
2531
+ * .build();
2532
+ * ```
2533
+ */
2534
+ declare class RuntimeBuilder {
2535
+ private readonly script;
2536
+ private readonly compiler;
2537
+ private options;
2538
+ private nowProvider;
2539
+ constructor(script: WhiteboardScript, compiler: JitCompiler);
2540
+ /**
2541
+ * Enable or disable debug mode.
2542
+ * When enabled:
2543
+ * - All blocks are wrapped with TestableBlock for inspection
2544
+ * - Lifecycle events are tracked and can be inspected
2545
+ */
2546
+ withDebugMode(enabled: boolean): this;
2547
+ /**
2548
+ * Enable or disable debug logging.
2549
+ * Can be enabled independently of debug mode for lighter-weight logging.
2550
+ */
2551
+ withLogging(enabled: boolean): this;
2552
+ /**
2553
+ * Set default TestableBlock configuration.
2554
+ * Applied to all blocks wrapped in debug mode.
2555
+ */
2556
+ withDefaultTestableConfig(config: Partial<TestableBlockConfig>): this;
2557
+ /**
2558
+ * Set a custom block wrapper factory.
2559
+ * Use this to customize how blocks are wrapped in debug mode.
2560
+ */
2561
+ withBlockWrapperFactory(factory: IRuntimeOptions['blockWrapperFactory']): this;
2562
+ /**
2563
+ * Set a custom debug log handler.
2564
+ * Called for every debug event in addition to console logging.
2565
+ */
2566
+ withDebugLogHandler(handler: IRuntimeOptions['onDebugLog']): this;
2567
+ /**
2568
+ * Set a custom INowProvider (time seam — see docs/adr/time-seam.md).
2569
+ * Defaults to wall-clock.
2570
+ */
2571
+ withNowProvider(provider: INowProvider): this;
2572
+ /**
2573
+ * Get the current options (for inspection/debugging)
2574
+ */
2575
+ getOptions(): Readonly<IRuntimeOptions>;
2576
+ /**
2577
+ * Build the ScriptRuntime with the configured options.
2578
+ */
2579
+ build(): ScriptRuntime;
2580
+ /**
2581
+ * Build the runtime and also return the options used.
2582
+ * Useful for debugging the builder configuration.
2583
+ */
2584
+ buildWithOptions(): {
2585
+ runtime: ScriptRuntime;
2586
+ options: IRuntimeOptions;
2587
+ };
2588
+ }
2589
+
2590
+ type EventHandlerRegistration = {
2591
+ handler: IEventHandler;
2592
+ ownerId: string;
2593
+ priority: number;
2594
+ scope: HandlerScope;
2595
+ };
2596
+ /**
2597
+ * Simple in-memory event bus for runtime events.
2598
+ *
2599
+ * ## Handler Scope Filtering
2600
+ *
2601
+ * Handlers registered via `register()` are filtered by scope before dispatch:
2602
+ * - `'active'` (default): Only fires when ownerId matches the current/active block on the stack
2603
+ * - `'bubble'`: Fires when ownerId is anywhere on the stack (parent can listen to child events)
2604
+ * - `'global'`: Always fires regardless of stack state (for runtime-level handlers)
2605
+ *
2606
+ * ## Callbacks vs Handlers
2607
+ *
2608
+ * - **Handlers** (via `register()`): Produce `IRuntimeAction[]`, are scope-filtered
2609
+ * - **Callbacks** (via `on()`): Simple notifications for UI, are NOT scope-filtered (always fire)
2610
+ *
2611
+ * Callbacks are intended for UI components that need to react to all events regardless
2612
+ * of which block is active. They do not produce actions and cannot affect runtime state.
2613
+ */
2614
+ declare class EventBus implements IEventBus {
2615
+ private handlersByEvent;
2616
+ private callbacksByEvent;
2617
+ register(eventName: string, handler: IEventHandler, ownerId: string, options?: EventHandlerOptions): () => void;
2618
+ on(eventName: string, callback: EventCallback, ownerId: string): () => void;
2619
+ unregisterById(handlerId: string): void;
2620
+ unregisterByOwner(ownerId: string): void;
2621
+ dispatch(event: IEvent, runtime: IScriptRuntime): IRuntimeAction[];
2622
+ emit(event: IEvent, runtime: IScriptRuntime): void;
2623
+ /**
2624
+ * Dispose of all handlers and callbacks, releasing all references.
2625
+ */
2626
+ dispose(): void;
2627
+ }
2628
+
2629
+ declare class NextEvent implements IEvent {
2630
+ private readonly now;
2631
+ readonly name: string;
2632
+ readonly timestamp: Date;
2633
+ private _data?;
2634
+ private static _counter;
2635
+ constructor(data?: unknown, now?: INowProvider);
2636
+ get data(): unknown;
2637
+ set data(value: unknown);
2638
+ toJSON(): {
2639
+ name: string;
2640
+ timestamp: Date;
2641
+ data: unknown;
2642
+ };
2643
+ }
2644
+
2645
+ declare class NextEventHandler implements IEventHandler {
2646
+ private _id;
2647
+ private _name;
2648
+ constructor(id: string);
2649
+ get id(): string;
2650
+ set id(_value: string);
2651
+ get name(): string;
2652
+ set name(_value: string);
2653
+ handler(event: IEvent, runtime: IScriptRuntime): IRuntimeAction[];
2654
+ }
2655
+
2656
+ interface RuntimeButton {
2657
+ id: string;
2658
+ label?: string;
2659
+ icon?: 'play' | 'pause' | 'stop' | 'next' | 'check' | 'analytics' | 'x';
2660
+ action: string;
2661
+ variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link';
2662
+ color?: string;
2663
+ disabled?: boolean;
2664
+ size?: 'default' | 'sm' | 'lg' | 'icon';
2665
+ }
2666
+ interface RuntimeControls {
2667
+ buttons: RuntimeButton[];
2668
+ displayMode?: 'timer' | 'clock';
2669
+ }
2670
+
2671
+ /**
2672
+ * Sound-related type definitions for the runtime.
2673
+ */
2674
+ /**
2675
+ * Configuration for a single sound cue.
2676
+ * Sound cues are triggered when a timer reaches a specific threshold.
2677
+ */
2678
+ interface SoundCue {
2679
+ /** Unique identifier for this cue */
2680
+ id: string;
2681
+ /**
2682
+ * Time threshold in milliseconds when this cue should trigger.
2683
+ * For countdown timers: triggers when remainingMs <= threshold
2684
+ * For count-up timers: triggers when elapsedMs >= threshold
2685
+ */
2686
+ threshold: number;
2687
+ /**
2688
+ * Sound to play. Can be:
2689
+ * - Predefined sound name: 'beep', 'buzzer', 'chime', 'tick'
2690
+ * - URL to audio file
2691
+ */
2692
+ sound: string;
2693
+ /** Volume level (0.0 to 1.0). Default is 1.0 */
2694
+ volume?: number;
2695
+ }
2696
+ /**
2697
+ * State tracking for a single sound cue.
2698
+ * Used to prevent duplicate triggers.
2699
+ */
2700
+ interface SoundCueState {
2701
+ /** Reference to the cue configuration ID */
2702
+ cueId: string;
2703
+ /** Whether this cue has been triggered */
2704
+ triggered: boolean;
2705
+ /** Timestamp when the cue was triggered (ms since epoch) */
2706
+ triggeredAt?: number;
2707
+ }
2708
+ /**
2709
+ * Configuration for SoundBehavior.
2710
+ */
2711
+ interface SoundBehaviorConfig {
2712
+ /** Direction of the timer ('up' for count-up, 'down' for countdown) */
2713
+ direction: 'up' | 'down';
2714
+ /** Total duration for countdown timers (required for 'down' direction) */
2715
+ durationMs?: number;
2716
+ /** Array of sound cues to trigger at specific time thresholds */
2717
+ cues: SoundCue[];
2718
+ }
2719
+ /**
2720
+ * Runtime state for sound behavior stored in memory.
2721
+ */
2722
+ interface SoundState {
2723
+ /** Block ID that owns this sound state */
2724
+ blockId: string;
2725
+ /** State of each configured cue */
2726
+ cues: SoundCueState[];
2727
+ }
2728
+ /**
2729
+ * Predefined sound names that can be used in SoundCue.sound
2730
+ */
2731
+ declare const PREDEFINED_SOUNDS: {
2732
+ /** Short single beep - good for countdown ticks */
2733
+ readonly BEEP: "beep";
2734
+ /** Long buzz - good for timer completion */
2735
+ readonly BUZZER: "buzzer";
2736
+ /** Pleasant chime - good for milestones */
2737
+ readonly CHIME: "chime";
2738
+ /** Clock tick - good for second-by-second countdown */
2739
+ readonly TICK: "tick";
2740
+ /** Ascending sequence - good for timer start */
2741
+ readonly START: "start";
2742
+ };
2743
+ type PredefinedSoundName = typeof PREDEFINED_SOUNDS[keyof typeof PREDEFINED_SOUNDS];
2744
+
2745
+ /**
2746
+ * Calculates **Elapsed** time from timer spans.
2747
+ *
2748
+ * Iterates the span list, summing (end − start) for each span.
2749
+ * Open spans (no `ended`) use `now` as the end timestamp.
2750
+ *
2751
+ * This is the canonical Elapsed computation — all behaviors
2752
+ * that need pause-aware active time should use this function.
2753
+ *
2754
+ * ## Glossary (docs/architecture/time-terminology.md)
2755
+ * - **Elapsed** = Σ(end − start) per span (this function)
2756
+ * - **Total** = lastEnd − firstStart (computed elsewhere)
2757
+ *
2758
+ * @param timer The timer state containing the Time spans
2759
+ * @param now Current timestamp in milliseconds for open spans
2760
+ * @returns Elapsed time in milliseconds (active only, excludes pauses)
2761
+ */
2762
+ declare function calculateElapsed(timer: TimerState, now: number): number;
2763
+ /**
2764
+ * Formats milliseconds as mm:ss for display.
2765
+ *
2766
+ * @param ms Duration or Elapsed value in milliseconds
2767
+ * @returns Formatted string like "5:03"
2768
+ */
2769
+ declare function formatDuration(ms: number): string;
2770
+
2771
+ /** A list of execution spans. The trailing span may be open (`ended === undefined`). */
2772
+ type SpanList = readonly TimeSpan[];
2773
+ /**
2774
+ * Start a fresh span list with a single open span.
2775
+ * Used for the initial mount span and for interval/timer resets.
2776
+ */
2777
+ declare function startSpan(nowMs: number): TimeSpan[];
2778
+ /**
2779
+ * Close the trailing open span at `nowMs` (pause / unmount).
2780
+ * No-op for spans that are already closed.
2781
+ */
2782
+ declare function closeCurrentSpan(spans: SpanList, nowMs: number): TimeSpan[];
2783
+ /** Append a new open span at `nowMs` (resume). */
2784
+ declare function openSpan(spans: SpanList, nowMs: number): TimeSpan[];
2785
+ /**
2786
+ * Read the `TimerState` from a block's `time` memory, or `undefined` if absent.
2787
+ * This is the canonical accessor — prefer it over reaching into
2788
+ * `getMemoryByTag('time')[0]?.metrics[0]?.value` by hand.
2789
+ */
2790
+ declare function readTimer(ctx: IBehaviorContext): TimerState | undefined;
2791
+ /**
2792
+ * Apply a span transform to the block's `time` memory, preserving the metric
2793
+ * fragment (image, direction, durationMs, label, role) and swapping only the
2794
+ * `spans` array.
2795
+ *
2796
+ * @returns `true` when the timer existed and was updated; `false` otherwise.
2797
+ */
2798
+ declare function mutateTimerSpans(ctx: IBehaviorContext, transform: (spans: SpanList, nowMs: number) => TimeSpan[]): boolean;
2799
+
2800
+ /**
2801
+ * Shared utility for managing IMetric arrays and labels.
2802
+ *
2803
+ * This utility is used by multiple UI components:
2804
+ * - RuntimeEventLog (sectioned log view)
2805
+ * - RuntimeHistoryPanel (tree view)
2806
+ * - AnalyticsLayout (timeline/graphs)
2807
+ */
2808
+
2809
+ /**
2810
+ * Extract a human-readable label from an array of metric.
2811
+ */
2812
+ declare function metricsToLabel(metrics: MetricContainer | IMetric[] | IMetric[][]): string;
2813
+
2814
+ /**
2815
+ * Configuration for the session root block.
2816
+ */
2817
+ interface SessionRootConfig {
2818
+ /** Session label (e.g., "Morning WOD") */
2819
+ label?: string;
2820
+ /** Child statement ID groups to execute */
2821
+ childGroups: number[][];
2822
+ /** Total rounds for the workout (default: 1) */
2823
+ totalRounds?: number;
2824
+ }
2825
+ /**
2826
+ * SessionRootBlock is the top-level container for a workout session.
2827
+ *
2828
+ * ## Lifecycle
2829
+ *
2830
+ * 1. Mount: Emits 'segment' output with session label, starts elapsed timer,
2831
+ * pushes WaitingToStart block as first child (index 0)
2832
+ * 2. First next(): WaitingToStart pops → ChildRunner pushes first workout block
2833
+ * 3. Subsequent next(): ChildRunner pushes remaining workout blocks in sequence
2834
+ * 4. Final next(): All children done → marks complete and pops
2835
+ * 5. Pop: emits a 'segment' output carrying the completion reason, records history
2836
+ *
2837
+ * ## Behavior Chain Order
2838
+ *
2839
+ * Behaviors are order-independent for correctness. ChildSelectionBehavior
2840
+ * handles round advancement internally, and RuntimeBlock.next() auto-pops
2841
+ * when any behavior marks the block complete.
2842
+ *
2843
+ * - CountupTimerBehavior (elapsed workout timer)
2844
+ * - ChildSelectionBehavior (child dispatch + round init + advancement + safety net)
2845
+ * - WaitingToStartInjectorBehavior (pushes WaitingToStart gate on mount)
2846
+ * - ReportOutputBehavior (output on mount/unmount)
2847
+ * - LabelingBehavior (display)
2848
+ * - ButtonBehavior (controls)
2849
+ *
2850
+ * History persistence is the responsibility of the workbench — all data
2851
+ * needed for history is available in the output stream (ReportOutputBehavior).
2852
+ */
2853
+ declare class SessionRootBlock extends RuntimeBlock {
2854
+ constructor(runtime: IRuntimeContext, config: SessionRootConfig);
2855
+ /**
2856
+ * Builds the behavior list for a session root block.
2857
+ * @param config Session configuration
2858
+ * @param runtime Script runtime (needed for WaitingToStartInjectorBehavior)
2859
+ */
2860
+ static buildBehaviors(config: SessionRootConfig, runtime: IRuntimeContext): IRuntimeBehavior[];
2861
+ mount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
2862
+ unmount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
2863
+ dispose(runtime: IRuntimeContext): void;
2864
+ }
2865
+
2866
+ /**
2867
+ * EffortBlock Configuration
2868
+ */
2869
+ interface EffortBlockConfig {
2870
+ exerciseName: string;
2871
+ targetReps: number;
2872
+ }
2873
+ /**
2874
+ * EffortBlock tracks individual exercise/rep completion.
2875
+ */
2876
+ declare class EffortBlock extends RuntimeBlock {
2877
+ private readonly config;
2878
+ private currentReps;
2879
+ private lastCompletionMode;
2880
+ constructor(runtime: IRuntimeContext, sourceIds: number[], config: EffortBlockConfig, metrics?: MetricContainer[]);
2881
+ mount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
2882
+ unmount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
2883
+ dispose(runtime: IRuntimeContext): void;
2884
+ /**
2885
+ * Check if the effort target has been met.
2886
+ * Note: This is distinct from the block's isComplete flag which is used by the stack.
2887
+ */
2888
+ isTargetComplete(): boolean;
2889
+ getCurrentReps(): number;
2890
+ getTargetReps(): number;
2891
+ getExerciseName(): string;
2892
+ incrementRep(): IRuntimeAction[];
2893
+ setReps(count: number): IRuntimeAction[];
2894
+ markComplete(): IRuntimeAction[];
2895
+ private getMetricActions;
2896
+ }
2897
+
2898
+ /**
2899
+ * Configuration for the rest block.
2900
+ */
2901
+ interface RestBlockConfig {
2902
+ /** Duration of the rest period in milliseconds */
2903
+ durationMs: number;
2904
+ /** Label for the rest period (default: "Rest") */
2905
+ label?: string;
2906
+ }
2907
+ /**
2908
+ * RestBlock is a timer-based block for rest periods between exercises.
2909
+ *
2910
+ * Timer-based parents (AMRAP, EMOM) can auto-generate Rest blocks between exercises.
2911
+ * The rest block counts down from the configured duration and auto-pops when the timer expires.
2912
+ *
2913
+ * ## Lifecycle
2914
+ *
2915
+ * 1. Mount: Emits 'segment' output with rest label and duration, starts countdown timer
2916
+ * 2. Timer counts down via CountdownTimerBehavior span tracking
2917
+ * 3. CountdownTimerBehavior marks complete when elapsed >= durationMs
2918
+ * 4. Pop: emits a 'segment' output carrying the completion reason, plays rest-over sound cue
2919
+ *
2920
+ * ## Behavior Chain
2921
+ *
2922
+ * - ReportOutputBehavior (output on mount/unmount)
2923
+ * - CountdownTimerBehavior (countdown timer + pause/resume + auto-complete)
2924
+ * - LabelingBehavior (show rest countdown)
2925
+ * - SoundCueBehavior (beep on unmount for rest-over signal)
2926
+ */
2927
+ declare class RestBlock extends RuntimeBlock {
2928
+ constructor(runtime: IRuntimeContext, config: RestBlockConfig);
2929
+ /**
2930
+ * Builds the behavior list for a rest block.
2931
+ */
2932
+ static buildBehaviors(config: RestBlockConfig): IRuntimeBehavior[];
2933
+ mount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
2934
+ unmount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
2935
+ dispose(runtime: IRuntimeContext): void;
2936
+ }
2937
+
2938
+ /**
2939
+ * WaitingToStartBlock is an idle gate that pauses before workout execution.
2940
+ *
2941
+ * The user must click 'next()' (i.e., the "Start" button) to advance.
2942
+ * When next() is called, WaitingToStart pops immediately via ExitBehavior.
2943
+ * The parent SessionRoot then detects the pop and pushes the first workout block.
2944
+ *
2945
+ * ## Lifecycle
2946
+ *
2947
+ * 1. Mount: Emits 'segment' output with "Ready to Start" message
2948
+ * 2. User clicks next → ExitBehavior marks complete, returns PopBlockAction
2949
+ * 3. Pop: emits a 'segment' output carrying the completion reason
2950
+ * 4. Parent receives control, pushes next child
2951
+ *
2952
+ * ## Behavior Chain
2953
+ *
2954
+ * - ReportOutputBehavior (output on mount/unmount)
2955
+ * - LabelingBehavior (show "Ready to Start")
2956
+ * - ButtonBehavior (show "Start" button)
2957
+ * - ExitBehavior (pop on user advance)
2958
+ */
2959
+ declare class WaitingToStartBlock extends RuntimeBlock {
2960
+ constructor(runtime: IRuntimeContext);
2961
+ /**
2962
+ * Builds the behavior list for the WaitingToStart block.
2963
+ */
2964
+ static buildBehaviors(): IRuntimeBehavior[];
2965
+ mount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
2966
+ unmount(runtime: IRuntimeContext, options?: BlockLifecycleOptions): IRuntimeAction[];
2967
+ dispose(runtime: IRuntimeContext): void;
2968
+ }
2969
+
2970
+ /**
2971
+ * Consumer-facing strategy registry, pre-seeded with the built-in strategies.
2972
+ *
2973
+ * Replaces the previous hardcoded `PRODUCTION_STRATEGIES` flat array. Consumer
2974
+ * code can call `strategyRegistry.register(new MyStrategy())` to add their
2975
+ * own strategies without editing the library.
2976
+ *
2977
+ * @example
2978
+ * ```typescript
2979
+ * import { strategyRegistry } from 'wod-wiki/core';
2980
+ * strategyRegistry.register(new MySportStrategy());
2981
+ * ```
2982
+ *
2983
+ * Note: rest blocks are NOT produced by a JIT strategy. They are built
2984
+ * directly at runtime (PushRestBlockAction / BlockBuilder) and pushed onto
2985
+ * the stack, so no rest strategy appears in this registry.
2986
+ */
2987
+ declare const strategyRegistry: Registry<IRuntimeBlockStrategy>;
2988
+ /**
2989
+ * Legacy array form of the production strategy set, kept for backward
2990
+ * compatibility. Derived from {@link strategyRegistry} (same instances, same
2991
+ * priority-sorted order `JitCompiler` itself would apply) rather than
2992
+ * re-constructing a separate set of strategy instances — a previous version
2993
+ * of this export built its own `new XxxStrategy()` list independently,
2994
+ * which meant `strategyRegistry.register()`/`unregister()` calls silently
2995
+ * had no effect on anything reading `PRODUCTION_STRATEGIES` directly. Use
2996
+ * `strategyRegistry` (or `createCompiler()`, which reads it live) for new code.
2997
+ */
2998
+ declare const PRODUCTION_STRATEGIES: readonly IRuntimeBlockStrategy[];
2999
+ /**
3000
+ * Create a JitCompiler with the given strategies (or the registered set).
3001
+ *
3002
+ * The default argument now reads from the consumer-facing
3003
+ * {@link strategyRegistry} so consumer registrations are honored. Pass an
3004
+ * explicit array to override (e.g. in tests).
3005
+ */
3006
+ declare function createCompiler(strategies?: IRuntimeBlockStrategy[]): JitCompiler;
3007
+
3008
+ /**
3009
+ * AST for the composed-calculation expression language (spec §2).
3010
+ *
3011
+ * One expression language across all three scopes; scope only changes how
3012
+ * atoms resolve. `wql` atoms are legal in store scope only (executed by
3013
+ * QueryService); stream aggregates (`sum(reps)`) are builtin calls resolved
3014
+ * by the engine, not by this core.
3015
+ */
3016
+ type BinaryOp = '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or';
3017
+ type ExprNode = {
3018
+ kind: 'literal';
3019
+ value: number;
3020
+ } | {
3021
+ kind: 'period';
3022
+ days: number;
3023
+ } | {
3024
+ kind: 'string';
3025
+ value: string;
3026
+ }
3027
+ /** Metric ref, context node (`session.duration`), or library node ref. */
3028
+ | {
3029
+ kind: 'ref';
3030
+ name: string;
3031
+ } | {
3032
+ kind: 'call';
3033
+ name: string;
3034
+ args: ExprNode[];
3035
+ }
3036
+ /** Effort-exclusion filter argument: `without: rest|pause|rest-*`. */
3037
+ | {
3038
+ kind: 'filter';
3039
+ value: string;
3040
+ } | {
3041
+ kind: 'unary';
3042
+ op: '-' | 'not';
3043
+ arg: ExprNode;
3044
+ } | {
3045
+ kind: 'binary';
3046
+ op: BinaryOp;
3047
+ left: ExprNode;
3048
+ right: ExprNode;
3049
+ }
3050
+ /** Store-scope WQL selection, e.g. `sum:sessionLoad{} by {day}`. */
3051
+ | {
3052
+ kind: 'wql';
3053
+ aggregator: string;
3054
+ metric: string;
3055
+ filters?: string;
3056
+ groupBy?: string[];
3057
+ };
3058
+ /** A parsed calc line: `name = expr -> unit when predicate`. */
3059
+ interface CalcLine {
3060
+ name: string;
3061
+ expr: ExprNode;
3062
+ unit?: string;
3063
+ when?: ExprNode;
3064
+ }
3065
+
3066
+ /**
3067
+ * Exponent-vector dimension model for the composed calculations layer.
3068
+ *
3069
+ * Dimensions are 5-element exponent vectors over base physical quantities:
3070
+ * [L, M, T, C, E] = [length, mass, time, count(reps), energy(cal)]
3071
+ *
3072
+ * Algebra (docs/composed-calculations-spec.md §5.1):
3073
+ * Dim(a * b) = Dim(a) + Dim(b)
3074
+ * Dim(a / b) = Dim(a) − Dim(b)
3075
+ * Dim(a ± b) requires Dim(a) = Dim(b) (static registration failure otherwise)
3076
+ */
3077
+ type DimVector = readonly [number, number, number, number, number];
3078
+ declare const DIM_ZERO: DimVector;
3079
+ declare const DIM_LENGTH: DimVector;
3080
+ declare const DIM_MASS: DimVector;
3081
+ declare const DIM_TIME: DimVector;
3082
+ declare const DIM_COUNT: DimVector;
3083
+ declare const DIM_ENERGY: DimVector;
3084
+ declare function addDim(a: DimVector, b: DimVector): DimVector;
3085
+ declare function subDim(a: DimVector, b: DimVector): DimVector;
3086
+ declare function dimEquals(a: DimVector, b: DimVector): boolean;
3087
+ declare function isDimensionless(d: DimVector): boolean;
3088
+ /** Human-readable vector, e.g. "T·L⁻¹" for pace. */
3089
+ declare function formatDim(d: DimVector): string;
3090
+ /**
3091
+ * Named compound dimension registry (spec §5.2). Used for diagnostics and
3092
+ * dimension-filtered unit suggestions — not required for evaluation.
3093
+ */
3094
+ declare const NAMED_COMPOUNDS: Record<string, {
3095
+ dim: DimVector;
3096
+ preferredUnits: string[];
3097
+ }>;
3098
+ /** Reverse lookup: find the compound name for a vector, if one exists. */
3099
+ declare function compoundName(d: DimVector): string | undefined;
3100
+
3101
+ /**
3102
+ * Canonical Node DAG records (spec §2.2) — the storage/registration form for
3103
+ * calculations. Seeds are TS object literals (#849); the line-form parser
3104
+ * (#863) must round-trip these losslessly.
3105
+ */
3106
+
3107
+ type CalcOrigin = 'analyzed' | 'analyzed-estimated' | 'user';
3108
+ type CalcScope = 'segment' | 'workout' | 'store';
3109
+ interface CalcNode {
3110
+ id: string;
3111
+ kind: 'metric' | 'lookup' | 'builtin' | 'wql' | 'expr';
3112
+ expression?: string;
3113
+ ast?: ExprNode;
3114
+ /**
3115
+ * Declared node unit. Named zero-vector casts (AU, pts, MET-min, ratio)
3116
+ * override the computed vector (§5.3 applied to intermediate nodes —
3117
+ * required by TIS-shaped formulas that fold time into a score); any other
3118
+ * unit must match the computed vector.
3119
+ */
3120
+ unit?: string;
3121
+ }
3122
+ interface CalcVariant {
3123
+ id: string;
3124
+ priority: number;
3125
+ when?: string;
3126
+ whenAst?: ExprNode;
3127
+ origin: CalcOrigin;
3128
+ nodes: Record<string, CalcNode>;
3129
+ /** Node dimensions computed at registration (static check output). */
3130
+ nodeDims?: Record<string, DimVector>;
3131
+ }
3132
+ interface CalculationDefinition$1 {
3133
+ id: string;
3134
+ kind: 'output' | 'library';
3135
+ scope: CalcScope;
3136
+ fences?: string[];
3137
+ when?: string;
3138
+ whenAst?: ExprNode;
3139
+ variants: CalcVariant[];
3140
+ output?: {
3141
+ nodeId: string | string[];
3142
+ key?: string;
3143
+ emitType?: string;
3144
+ unit?: string;
3145
+ isGrouped?: boolean;
3146
+ /** Group dimensions for grouped emission (e.g. ['effort']). */
3147
+ groupBy?: string[];
3148
+ publishMetadataNodes?: string[];
3149
+ /** Display label for published facts (store-scope rollups). */
3150
+ label?: string;
3151
+ };
3152
+ }
3153
+
3154
+ /**
3155
+ * Context used to select processors for a given workout.
3156
+ */
3157
+ interface AnalyticsProfileContext {
3158
+ dialect: FenceDialect;
3159
+ scriptMetricTypes: ReadonlySet<MetricType | string>;
3160
+ /**
3161
+ * Optional user physiological profile. Used by calculations that personalize
3162
+ * output (the TIS composed calc uses vo2max to compute METmax).
3163
+ */
3164
+ userProfile?: {
3165
+ /** VO2max in mL/kg/min — for personalized MET-Score normalization */
3166
+ vo2max?: number;
3167
+ };
3168
+ /**
3169
+ * Analytics context with injected services (e.g. effort resolver).
3170
+ * When provided, the profile wires two-pass effort resolution and
3171
+ * processors consume resolved effort data instead of hardcoded lookups.
3172
+ */
3173
+ analyticsContext?: AnalyticsContext;
3174
+ /**
3175
+ * User-authored composed calcs (#880), registered after the built-ins so
3176
+ * they override same-id built-ins and evaluate at high priority.
3177
+ */
3178
+ calcs?: CalculationDefinition$1[];
3179
+ }
3180
+ /**
3181
+ * Profile that assembles the realtime and summary processor lists
3182
+ * for a given workout context.
3183
+ */
3184
+ interface IAnalyticsProfile {
3185
+ build(context: AnalyticsProfileContext): {
3186
+ realtime: IRealtimeProcessor[];
3187
+ summary: ISummaryProcessor[];
3188
+ };
3189
+ }
3190
+
3191
+ declare class AnalyticsEngine implements IAnalyticsEngine {
3192
+ private realtimeProcessors;
3193
+ private summaryProcessors;
3194
+ private outputHistory;
3195
+ /** Emits a live 'analytics' snapshot per segment so the UI updates in real time. */
3196
+ private _onLiveOutput?;
3197
+ /**
3198
+ * Signature of the last live projection emission. Used by finalize() to avoid
3199
+ * re-emitting the same projections at session end. Timestamps are ignored.
3200
+ */
3201
+ private _lastLiveProjectionSignature?;
3202
+ /**
3203
+ * Wire a sink for live analytics outputs. The sink receives the FULL
3204
+ * projection snapshot (a replaceable, ephemeral set) each time projections
3205
+ * change — not appended one-at-a-time. The snapshot is display-only; it must
3206
+ * not be persisted. Persistence happens once via {@link finalize}.
3207
+ */
3208
+ setLiveOutputEmitter(emit: (outputs: IOutputStatement[]) => void): void;
3209
+ addRealtimeProcessor(processor: IRealtimeProcessor): void;
3210
+ addSummaryProcessor(processor: ISummaryProcessor): void;
3211
+ run(output: IOutputStatement): IOutputStatement;
3212
+ finalize(): IOutputStatement[];
3213
+ /**
3214
+ * Identity of a projection set for dedupe — which fields make two emission
3215
+ * sets 'the same'. Timestamps are intentionally ignored; display/fact
3216
+ * layers have their own keys (see getAnalyticsFromLogs and
3217
+ * normalizeSummaryFacts).
3218
+ */
3219
+ private _projectionSignature;
3220
+ /** Build one 'analytics' OutputStatement per summary projection. */
3221
+ private _buildProjectionOutputs;
3222
+ /** Run all summary processors over current output history. */
3223
+ private _runSummaries;
3224
+ }
3225
+
3226
+ /**
3227
+ * Union accepted by the transformer — either a live OutputStatement (from a
3228
+ * running runtime) or a plain StoredOutputStatement (loaded from IndexedDB).
3229
+ */
3230
+ type OutputLike = IOutputStatement | StoredOutputStatement;
3231
+ /**
3232
+ * UI-ready segment format.
3233
+ */
3234
+ interface SegmentWithMetadata extends Segment {
3235
+ metrics?: MetricContainer;
3236
+ tags?: string[];
3237
+ spanType?: string;
3238
+ context?: Record<string, any>;
3239
+ }
3240
+ /**
3241
+ * AnalyticsTransformer - Transforms OutputStatements into UI-ready analytics data.
3242
+ */
3243
+ declare class AnalyticsTransformer {
3244
+ /**
3245
+ * Transform OutputStatements into UI-ready Segments.
3246
+ * This is the primary API for the new runtime architecture.
3247
+ */
3248
+ fromOutputStatements(outputs: OutputLike[], workoutStartTime?: number, now?: INowProvider): SegmentWithMetadata[];
3249
+ /**
3250
+ * Group segments by analytics categories.
3251
+ */
3252
+ toAnalyticsGroup(segments: SegmentWithMetadata[]): AnalyticsGroup[];
3253
+ /**
3254
+ * Filter segments by tag.
3255
+ */
3256
+ filterByTags(segments: SegmentWithMetadata[], tags: string[]): SegmentWithMetadata[];
3257
+ /**
3258
+ * Filter segments by workout type.
3259
+ */
3260
+ filterByType(segments: SegmentWithMetadata[], type: string): SegmentWithMetadata[];
3261
+ /**
3262
+ * Get debug context from a segment.
3263
+ */
3264
+ getDebugContext(segment: SegmentWithMetadata): Record<string, unknown>;
3265
+ /**
3266
+ * Check if a segment was created by a specific strategy.
3267
+ */
3268
+ isFromStrategy(segment: SegmentWithMetadata, strategyName: string): boolean;
3269
+ }
3270
+ /**
3271
+ * Analytics result from transforming runtime output.
3272
+ */
3273
+ interface AnalyticsResult {
3274
+ segments: SegmentWithMetadata[];
3275
+ groups: AnalyticsGroup[];
3276
+ }
3277
+ /**
3278
+ * Transform runtime output statements into analytics-ready data.
3279
+ * This is the primary entry point for analytics visualization.
3280
+ *
3281
+ * @param runtime The script runtime to extract output from
3282
+ * @returns Analytics data including time-series data, segments, and metric groups
3283
+ */
3284
+ declare function getAnalyticsFromRuntime(runtime: IScriptRuntime | null): AnalyticsResult;
3285
+ /**
3286
+ * Derive analytics segments from a stored workout log.
3287
+ *
3288
+ * This is the **canonical read path** for all analytics derived from a
3289
+ * completed workout. WorkoutResult.data.logs (StoredOutputStatement[]) is
3290
+ * the source of truth; call this function to obtain Segment[] for display,
3291
+ * review grids, or trend summaries.
3292
+ *
3293
+ * Relationship to the analytics IDB store:
3294
+ * The `analytics` IndexedDB store holds AnalyticsDataPoint[] summary fact
3295
+ * rows written by normalizeSummaryFacts() from Tier-2 outputs in data.logs.
3296
+ * They are NOT required for any current display feature. If they disagree
3297
+ * with logs, logs win. Use this function — not the analytics store — to
3298
+ * obtain segment data for display.
3299
+ *
3300
+ * @param outputs - StoredOutputStatement[] from WorkoutResult.data.logs
3301
+ * @param workoutStartTime - Optional workout start timestamp (ms). Used to
3302
+ * anchor relative timing in the segment timeline.
3303
+ * @param now - Optional clock provider; defaults to wall-clock.
3304
+ */
3305
+ declare function getAnalyticsFromLogs(outputs: StoredOutputStatement[], workoutStartTime?: number, now?: INowProvider): AnalyticsResult;
3306
+
3307
+ /**
3308
+ * Consumer-facing registries for analytics processors — extension points
3309
+ * for custom (e.g. sport-specific) processors. The built-in calculations
3310
+ * are no longer processors: they are registered composed calcs evaluated
3311
+ * by the CalcEngine (#879 cutover). Register custom processors with the
3312
+ * standard `register`/`unregister` shape.
3313
+ *
3314
+ * @example
3315
+ * ```typescript
3316
+ * import { summaryProcessorRegistry } from 'wod-wiki/core';
3317
+ * summaryProcessorRegistry.register(ClimbGradeProgressionProcess);
3318
+ * ```
3319
+ */
3320
+ declare const realtimeProcessorRegistry: Registry<IRealtimeProcessor>;
3321
+ declare const summaryProcessorRegistry: Registry<ISummaryProcessor>;
3322
+ /**
3323
+ * Standard built-in analytics profile.
3324
+ *
3325
+ * Chain: TwoPassEffortResolutionProcess (effort-data infrastructure) →
3326
+ * CalcEngine (the composed calculation layer: phase-1 segment annotations
3327
+ * as a realtime processor, phase-2 workout running totals as a summary
3328
+ * processor) → any custom registry processors. Applicability of individual
3329
+ * calcs is dynamic (`when` predicates, #848); the fence/requiredMetrics
3330
+ * filter below still applies to custom registry processors.
3331
+ */
3332
+ declare class StandardAnalyticsProfile implements IAnalyticsProfile {
3333
+ build(context: AnalyticsProfileContext): {
3334
+ realtime: IRealtimeProcessor[];
3335
+ summary: ISummaryProcessor[];
3336
+ };
3337
+ private isApplicable;
3338
+ }
3339
+
3340
+ interface CreateAnalyticsEngineResult {
3341
+ engine: AnalyticsEngine;
3342
+ analyticsContext: AnalyticsContext;
3343
+ }
3344
+ interface CreateAnalyticsEngineOptions {
3345
+ effortResolver?: IEffortResolver;
3346
+ userProfile?: {
3347
+ vo2max?: number;
3348
+ };
3349
+ /** User-authored composed calcs (#880), registered after the built-ins. */
3350
+ calcs?: CalculationDefinition$1[];
3351
+ }
3352
+ /**
3353
+ * Creates a fully-configured AnalyticsEngine for a workout block.
3354
+ *
3355
+ * Extracts the dialect and metric types from the block, builds a
3356
+ * StandardAnalyticsProfile, and assembles the engine with all
3357
+ * applicable realtime and summary processors.
3358
+ *
3359
+ * When no effortResolver is provided, creates a default one backed
3360
+ * by the bundled effort catalog (synchronously seeded).
3361
+ */
3362
+ declare function createAnalyticsEngineForBlock(block: ScriptBlock, options?: CreateAnalyticsEngineOptions): CreateAnalyticsEngineResult;
3363
+
3364
+ /**
3365
+ * Two-Pass Effort Resolution — Realtime Processor
3366
+ *
3367
+ * Pass 1 (compile-time recovery):
3368
+ * Output statements that carry an effort metric with `origin: 'compiler'`
3369
+ * are treated as already-canonical. The processor looks them up by
3370
+ * slug/alias and attaches the full {@link IEffort} record.
3371
+ *
3372
+ * Pass 2 (analytics-time fuzzy recovery):
3373
+ * Output statements with free-form effort or action text get resolved
3374
+ * through fuzzy alias matching. A successful match attaches the effort
3375
+ * with `origin: 'analyzed'`; a synthetic fallback attaches it with
3376
+ * `origin: 'analyzed-estimated'`.
3377
+ *
3378
+ * The resolved effort is stored as a metric of type `'effort-data'` so
3379
+ * downstream summary processors can consume it without importing the
3380
+ * registry directly.
3381
+ *
3382
+ * @see docs/adr/effort-resolution-compile-time.md
3383
+ */
3384
+ declare class TwoPassEffortResolutionProcess implements IRealtimeProcessor {
3385
+ private readonly resolver;
3386
+ readonly id = "two-pass-effort-resolution";
3387
+ readonly fenceTypes: readonly ["time", "log"];
3388
+ constructor(resolver: IEffortResolver);
3389
+ process(output: IOutputStatement): IOutputStatement;
3390
+ private resolve;
3391
+ }
3392
+
3393
+ /**
3394
+ * Custom metric type for resolved effort data attached by
3395
+ * TwoPassEffortResolutionProcess.
3396
+ */
3397
+ declare const EFFORT_DATA_METRIC_TYPE: "effort-data";
3398
+ /**
3399
+ * Resolved effort data extracted from an enriched metrics stream.
3400
+ */
3401
+ declare const DEFAULT_UNRESOLVED_EFFORT_MET: number;
3402
+ interface ResolvedEffortData {
3403
+ /** Effective resolved effort instance. */
3404
+ resolved: ResolvedEffort;
3405
+ /** Effective effort copy retained for legacy callers. */
3406
+ effort: IEffort;
3407
+ origin: MetricOrigin;
3408
+ }
3409
+ /**
3410
+ * Extract the most recent resolved effort data from a metric stream.
3411
+ *
3412
+ * Iterates in order and returns the last `effort-data` metric found,
3413
+ * since metrics are processed sequentially and later entries override
3414
+ * earlier ones within a segment.
3415
+ */
3416
+ declare function extractEffortData(metrics: IMetric[]): ResolvedEffortData | undefined;
3417
+ /**
3418
+ * Determine the dominant origin across a collection of effort data.
3419
+ *
3420
+ * Priority: analyzed-estimated (most conservative) > analyzed > compiler.
3421
+ * If any effort was unresolved synthetic, the whole projection is flagged
3422
+ * as estimated so downstream consumers know to treat it with caution.
3423
+ */
3424
+ declare function resolveDominantOrigin(origins: MetricOrigin[]): MetricOrigin;
3425
+
3426
+ /**
3427
+ * Flatten all metrics from a collection of output statements into a single array.
3428
+ * Preserves order. Used by projection stages to replicate the metric stream
3429
+ * that AnalysisService previously received from ProjectionSyncProcess.
3430
+ */
3431
+ declare function extractMetrics(outputs: IOutputStatement[]): IMetric[];
3432
+
3433
+ interface CalculationDefinition {
3434
+ readonly target: string;
3435
+ readonly expression: string;
3436
+ readonly raw: string;
3437
+ readonly line: number;
3438
+ readonly ast: ExpressionNode;
3439
+ readonly sources: string[];
3440
+ }
3441
+ interface CalculateBlockParseError {
3442
+ readonly line: number;
3443
+ readonly raw: string;
3444
+ readonly message: string;
3445
+ }
3446
+ interface CalculateBlockParseResult {
3447
+ readonly definitions: CalculationDefinition[];
3448
+ readonly errors: CalculateBlockParseError[];
3449
+ }
3450
+ type ExpressionNode = NumberNode | IdentifierNode | UnaryNode | BinaryNode | CallNode;
3451
+ interface NumberNode {
3452
+ readonly kind: 'number';
3453
+ readonly value: number;
3454
+ }
3455
+ interface IdentifierNode {
3456
+ readonly kind: 'identifier';
3457
+ readonly name: string;
3458
+ }
3459
+ interface UnaryNode {
3460
+ readonly kind: 'unary';
3461
+ readonly operator: '+' | '-';
3462
+ readonly argument: ExpressionNode;
3463
+ }
3464
+ interface BinaryNode {
3465
+ readonly kind: 'binary';
3466
+ readonly operator: '+' | '-' | '*' | '/' | '^';
3467
+ readonly left: ExpressionNode;
3468
+ readonly right: ExpressionNode;
3469
+ }
3470
+ interface CallNode {
3471
+ readonly kind: 'call';
3472
+ readonly name: string;
3473
+ readonly args: ExpressionNode[];
3474
+ }
3475
+ declare class CalculateBlockProcessor implements ISummaryProcessor {
3476
+ private readonly definitions;
3477
+ readonly id = "calculate-block";
3478
+ readonly name = "CalculateBlockProcessor";
3479
+ constructor(definitions: readonly CalculationDefinition[]);
3480
+ summarize(outputs: IOutputStatement[]): ProjectionResult[];
3481
+ }
3482
+ /**
3483
+ * Parse a `calculate` block into assignment definitions.
3484
+ */
3485
+ declare function parseCalculateBlock(content: string): CalculateBlockParseResult;
3486
+ /**
3487
+ * Evaluate calculate definitions against runtime output statements.
3488
+ */
3489
+ declare function evaluateCalculateDefinitions(definitions: readonly CalculationDefinition[], outputs: readonly IOutputStatement[]): ProjectionResult[];
3490
+
3491
+ /**
3492
+ * Unit registry for the composed calculations layer.
3493
+ *
3494
+ * Every unit maps to a dimension vector plus a conversion factor to the
3495
+ * vector's canonical base unit (ms, m, kg, reps, cal, and products thereof).
3496
+ * `convert(x, u)` is vector-checked: source and target vectors must match,
3497
+ * then the scalar rescales by `factor(from) / factor(to)`.
3498
+ *
3499
+ * Named zero-vector units (AU, pts, MET-min, ratio) are **authoritative
3500
+ * casts** (spec §5.3): declaring them as an output unit overrides the
3501
+ * computed vector instead of failing validation.
3502
+ */
3503
+
3504
+ interface UnitDef {
3505
+ readonly dim: DimVector;
3506
+ /** Multiply a value in this unit by `factor` to get the base-unit value. */
3507
+ readonly factor: number;
3508
+ }
3509
+ declare const AUTHORITATIVE_CASTS: Record<string, true>;
3510
+ declare const UNITS: Record<string, UnitDef>;
3511
+ /** Authoritative casts are also valid output units (zero-vector override). */
3512
+ declare function getUnit(name: string): UnitDef | undefined;
3513
+ /**
3514
+ * Convert a scalar between units. Vectors must match (static checking catches
3515
+ * mismatches at registration; this is the runtime guard). A value with no
3516
+ * recorded unit is treated as already being in the vector's base unit.
3517
+ */
3518
+ declare function convertScalar(value: number, fromUnit: string | undefined, toUnit: string): number;
3519
+ declare class CalcUnitError extends Error {
3520
+ constructor(message: string);
3521
+ }
3522
+ /**
3523
+ * Compose units through `*`. Dimensionless sides contribute nothing; count
3524
+ * units (`reps`) drop when any non-count unit is present — the domain
3525
+ * convention behind `kg` (volume) and `kg/s` (power) rather than `kg·reps`.
3526
+ */
3527
+ declare function composeMulUnit(a: string | undefined, aDim: DimVector, b: string | undefined, bDim: DimVector, resultDim: DimVector): string | undefined;
3528
+ /** Compose units through `/` (`m` ÷ `s` → `m/s`). */
3529
+ declare function composeDivUnit(a: string | undefined, b: string | undefined, bDim: DimVector, resultDim: DimVector): string | undefined;
3530
+
3531
+ /**
3532
+ * Dimension-aware value model for calc evaluation.
3533
+ *
3534
+ * Numbers carry a dimension vector and (optionally) the unit they were
3535
+ * measured in; arithmetic tracks vectors per spec §5.1. `absent` is the
3536
+ * missing-data value: it propagates through arithmetic, reads as `false` in
3537
+ * predicates (applicability decision, #848), and is what `has()` probes.
3538
+ */
3539
+
3540
+ type Val = {
3541
+ kind: 'number';
3542
+ value: number;
3543
+ dim: DimVector;
3544
+ unit?: string;
3545
+ } | {
3546
+ kind: 'string';
3547
+ value: string;
3548
+ } | {
3549
+ kind: 'series';
3550
+ points: Map<number, number>;
3551
+ dim: DimVector;
3552
+ unit?: string;
3553
+ } | {
3554
+ kind: 'period';
3555
+ days: number;
3556
+ } | {
3557
+ kind: 'absent';
3558
+ };
3559
+ declare const ABSENT: Val;
3560
+ declare function num(value: number, dim?: DimVector, unit?: string): Val;
3561
+ declare function str(value: string): Val;
3562
+ /** Predicate truthiness: absent → false; numbers → ≠ 0; strings → non-empty. */
3563
+ declare function truthy(v: Val): boolean;
3564
+
3565
+ /**
3566
+ * Hand-rolled parser for the calc expression language.
3567
+ *
3568
+ * Line-oriented and small by design (review doc §11.4): expressions use a
3569
+ * Pratt parser; WQL atoms (`sum:reps{...} by {effort}`) lex as single atoms
3570
+ * and stay opaque here — the QueryService layer executes them. The full
3571
+ * authoring surface (sections, `|` variant sugar, `where` bindings) belongs
3572
+ * to the line-form compiler (#863/#880); seeds are DAG records (#849), so
3573
+ * only the expression/predicate core and the single-line form live here.
3574
+ */
3575
+
3576
+ declare class CalcParseError extends Error {
3577
+ readonly pos: number;
3578
+ constructor(message: string, pos: number);
3579
+ }
3580
+ /** Parse a standalone expression or predicate. */
3581
+ declare function parseExpression(src: string): ExprNode;
3582
+ /**
3583
+ * Parse one calc line: `name = expr -> unit when predicate`.
3584
+ * Unit may be `auto`; `key`/`grouped` output declarations are registry
3585
+ * concerns (record form), not parsed here.
3586
+ */
3587
+ declare function parseCalcLine(src: string): CalcLine;
3588
+
3589
+ /**
3590
+ * Lossless line-form compiler for the composed calculations layer (spec §2).
3591
+ *
3592
+ * Round-trips between the author-facing line syntax and canonical DAG
3593
+ * records (`CalculationDefinition`, spec §2.2). The line syntax is a superset
3594
+ * of `parseCalcLine`: it adds scope headers, `(library)` markers, `key`,
3595
+ * `grouped`, `label`, `emit`, `meta` output clauses, `|` variant sugar (with
3596
+ * optional `estimated` origin and per-variant `when`), and `where`
3597
+ * intermediate bindings.
3598
+ *
3599
+ * Guarantees (verified by tests):
3600
+ * - `compile(print(def))` deep-equals `def` for any def this module emits.
3601
+ * - Output defs register cleanly into `CalculationRegistry` (no
3602
+ * `CalcRegistrationError`), so diagnostics reuse registration-time
3603
+ * checking.
3604
+ *
3605
+ * Only the atom/expression grammar lives in `parser.ts`; line structure,
3606
+ * sections, variants, and output clauses live here.
3607
+ */
3608
+
3609
+ interface LineFormScope {
3610
+ scope: CalcScope;
3611
+ fences?: string[];
3612
+ }
3613
+ interface CompiledLineForm {
3614
+ defs: CalculationDefinition$1[];
3615
+ warnings: string[];
3616
+ }
3617
+ /**
3618
+ * Compile calc-line source into DAG records.
3619
+ *
3620
+ * Two document styles are accepted:
3621
+ * - A scope header line per calc: `segment on [time, log] when P: name = …`
3622
+ * - A bare line: `name = …` (scope from `defaultScope`, default 'segment').
3623
+ *
3624
+ * One `CalculationDefinition` per line; `where` bindings fold into the line's
3625
+ * node graph.
3626
+ */
3627
+ declare function compileLineForm(src: string, defaultScope?: LineFormScope): CompiledLineForm;
3628
+ /**
3629
+ * Render one `CalculationDefinition` back to canonical line form, losslessly
3630
+ * preserving everything the parser reads (scope, fences, when, variants,
3631
+ * origins, units, output clauses, intermediate nodes).
3632
+ */
3633
+ /** First output node id (output.nodeId may be a single id or an array). */
3634
+ declare function outputNodeId(def: CalculationDefinition$1): string | undefined;
3635
+ declare function printCalculation(def: CalculationDefinition$1): string;
3636
+
3637
+ /**
3638
+ * Expression evaluator for the composed calculations layer.
3639
+ *
3640
+ * The core owns arithmetic, comparison/logic, and the scalar function table
3641
+ * (min, max, abs, round, floor, ceil, clamp, convert, has). Everything
3642
+ * scope-dependent — metric refs, context nodes, stream aggregates, lookup(),
3643
+ * WQL atoms — resolves through the EvalContext, so the same evaluator serves
3644
+ * segment, workout, and store scopes (spec §3.1).
3645
+ */
3646
+
3647
+ declare class CalcEvalError extends Error {
3648
+ constructor(message: string);
3649
+ }
3650
+ interface EvalContext {
3651
+ /** Resolve a ref atom (metric, context node, library node). ABSENT when missing. */
3652
+ resolveRef(name: string): Val;
3653
+ /**
3654
+ * Extension functions (stream aggregates, lookup). Receives evaluated args
3655
+ * plus the raw arg nodes (aggregates need the filter node unevaluated).
3656
+ * Return undefined to signal "not mine" → CalcEvalError.
3657
+ */
3658
+ callFunction?(name: string, args: Val[], rawArgs: ExprNode[]): Val | undefined;
3659
+ /** Store-scope WQL selections (QueryService). Store scope only. */
3660
+ resolveWql?(node: Extract<ExprNode, {
3661
+ kind: 'wql';
3662
+ }>): Val;
3663
+ }
3664
+ declare function evaluate(node: ExprNode, ctx: EvalContext): Val;
3665
+
3666
+ /**
3667
+ * Static dimension checking, run at calc registration time (spec §5).
3668
+ *
3669
+ * Walks an expression computing dimension vectors: `*` adds, `/` subtracts,
3670
+ * `+`/`-`/comparisons require equal vectors (numeric literal 0 is polymorphic
3671
+ * so `elapsed > 0` passes), `convert()` targets must match the source
3672
+ * vector. Declared output units must match the computed vector unless the
3673
+ * unit is `auto` or a named zero-vector authoritative cast (AU, pts,
3674
+ * MET-min, ratio). Any mismatch or unknown symbol is a registration error —
3675
+ * bad calcs never reach the evaluator.
3676
+ */
3677
+
3678
+ declare class CalcRegistrationError extends Error {
3679
+ constructor(message: string);
3680
+ }
3681
+ interface StaticEnv {
3682
+ /** Dimension of a ref atom; undefined → unknown symbol error. */
3683
+ refDim(name: string): DimVector | undefined;
3684
+ /** Dimension of an extension function result (aggregates, lookup fields). */
3685
+ callDim?(name: string, argDims: (DimVector | undefined)[]): DimVector | undefined;
3686
+ /** Dimension of a lookup() table field; undefined → unknown table/field. */
3687
+ lookupDim?(table: string, field: string): DimVector | undefined;
3688
+ /** Dimension of a WQL selection result; defaults to dimensionless. */
3689
+ wqlDim?(node: Extract<ExprNode, {
3690
+ kind: 'wql';
3691
+ }>): DimVector | undefined;
3692
+ }
3693
+ /** Infer the dimension vector of an expression, throwing on any violation. */
3694
+ declare function inferDim(node: ExprNode, env: StaticEnv): DimVector;
3695
+ /**
3696
+ * Validate a parsed calc line's declared output unit against the computed
3697
+ * vector. `auto` always passes; authoritative casts override; anything else
3698
+ * must match exactly.
3699
+ */
3700
+ declare function validateOutputUnit(exprDim: DimVector, unit: string | undefined): void;
3701
+ /** Full registration-time check for one calc line. Returns the computed vector. */
3702
+ declare function checkCalcLine(line: CalcLine, env: StaticEnv): DimVector;
3703
+
3704
+ /**
3705
+ * Lookup table registry (spec §4).
3706
+ *
3707
+ * Tables are adapters over heterogeneous sources (effort resolver, static
3708
+ * maps, user profile) exposing a uniform single-row lookup contract:
3709
+ * `lookup("table", keyExpr, "field")`. Registration is layered —
3710
+ * bundled → dialect → user — with later layers replacing earlier ones
3711
+ * (mirrors the Dialect Stack's personal-overrides-last rule). Miss behavior
3712
+ * is per-table: `absent` yields the absent value; `default-row` yields a
3713
+ * synthetic row so calcs never see a miss.
3714
+ */
3715
+
3716
+ interface LookupFieldDef {
3717
+ dimension: DimVector;
3718
+ type: 'number' | 'string';
3719
+ }
3720
+ interface ILookupTable {
3721
+ readonly id: string;
3722
+ readonly fields: Record<string, LookupFieldDef>;
3723
+ readonly missPolicy: 'absent' | 'default-row';
3724
+ /** Single-row lookup. `absent` tables return undefined on a miss. */
3725
+ get(key: string, field: string): Val | undefined;
3726
+ }
3727
+ type LookupLayer = 'bundled' | 'dialect' | 'user';
3728
+ declare class LookupRegistry {
3729
+ private readonly tables;
3730
+ /** Register a table; a later-or-equal layer replaces the existing entry. */
3731
+ register(table: ILookupTable, layer?: LookupLayer): void;
3732
+ get(tableId: string): ILookupTable | undefined;
3733
+ /** Field dimension for static checking; undefined when table/field unknown. */
3734
+ fieldDim(tableId: string, field: string): DimVector | undefined;
3735
+ /** Runtime lookup honoring the table's miss policy. */
3736
+ lookup(tableId: string, key: string, field: string): Val;
3737
+ /**
3738
+ * EvalContext `callFunction` implementation for `lookup("table", key, "field")`.
3739
+ * Compose with other extension functions (aggregates) at the engine layer.
3740
+ */
3741
+ readonly callFunction: EvalContext['callFunction'];
3742
+ }
3743
+
3744
+ /**
3745
+ * Built-in lookup table adapters (spec §4.1).
3746
+ *
3747
+ * - `effort` — IEffortResolver-backed; preserves fuzzy matching, aliases,
3748
+ * derivations, modifiers. Miss policy `default-row`: the resolver's
3749
+ * synthetic unresolved effort (default MET 5.0, flagged isEstimated)
3750
+ * guarantees a row for any key.
3751
+ * - `rpe-labels` — effort label → RPE map shared by SessionLoad and TIS
3752
+ * (replaces the duplicated effortToRpe maps, review doc §7 note b).
3753
+ * - `profile` — user profile attributes (vo2max). Miss policy `absent`.
3754
+ * - `disciplines` — canonical discipline factor table. Miss policy
3755
+ * `default-row` (1.0).
3756
+ */
3757
+
3758
+ /** Label → RPE. Canonical map (SessionLoadProjectionEngine / TISProcessor). */
3759
+ declare const RPE_LABELS: Record<string, number>;
3760
+ declare function createEffortTable(resolver: IEffortResolver): ILookupTable;
3761
+ declare function createRpeLabelsTable(): ILookupTable;
3762
+ interface IProfileSource {
3763
+ readonly vo2max?: number;
3764
+ }
3765
+ declare function createProfileTable(profile: IProfileSource | undefined): ILookupTable;
3766
+ declare function createDisciplinesTable(): ILookupTable;
3767
+
3768
+ /**
3769
+ * Known atom vocabulary for the composed calculations layer.
3770
+ *
3771
+ * Stream atoms name metrics on the log stream that aggregates scan
3772
+ * (`sum(metMinutes)` reads `metMinutes` segment annotations). Context atoms
3773
+ * are engine-provided nodes (`session.duration`, `effort`, …). Registration
3774
+ * uses these tables for static dimension checking; evaluation uses them to
3775
+ * resolve refs and stamp units.
3776
+ */
3777
+
3778
+ interface StreamAtomDef {
3779
+ dim: DimVector;
3780
+ /** Metric type scanned on the log stream. */
3781
+ metricType: string;
3782
+ /** Unit stamped on aggregate values (last-seen source unit wins for distance/volume). */
3783
+ unit?: string;
3784
+ }
3785
+ declare const STREAM_ATOMS: Record<string, StreamAtomDef>;
3786
+ /** Engine-provided context nodes (both scopes, where meaningful). */
3787
+ declare const CONTEXT_ATOMS: Record<string, DimVector>;
3788
+ /** Stream aggregate builtins (workout scope). `count` is dimensionless. */
3789
+ declare const AGGREGATE_BUILTINS: Record<string, true>;
3790
+
3791
+ /**
3792
+ * Calculation registry — registration and static validation of composed
3793
+ * calculation DAG records (spec §2.2).
3794
+ *
3795
+ * Every expression and predicate is parsed and dimension-checked at
3796
+ * registration (spec §5): unknown symbols, dimension mismatches, bad
3797
+ * convert targets, and invalid output units fail here, never at runtime.
3798
+ * Library calcs register their node dimensions under qualified names
3799
+ * (`calcId.nodeId`) so dependent calcs can reference them statically.
3800
+ */
3801
+
3802
+ declare class CalculationRegistry {
3803
+ private readonly lookups;
3804
+ private readonly defs;
3805
+ /** Qualified library node dimensions: `calcId.nodeId` → vector. */
3806
+ private readonly libraryDims;
3807
+ constructor(lookups: LookupRegistry);
3808
+ /** All registered calcs for a scope, registration order preserved. */
3809
+ byScope(scope: CalcScope): CalculationDefinition$1[];
3810
+ get(id: string): CalculationDefinition$1 | undefined;
3811
+ /**
3812
+ * Register a calculation. Compiles every node expression and predicate,
3813
+ * runs static dimension checking, and (for library calcs) publishes node
3814
+ * dimensions for dependent calcs. Throws CalcRegistrationError on any
3815
+ * violation.
3816
+ */
3817
+ register(def: CalculationDefinition$1): void;
3818
+ /** Parse + dimension-check every node and predicate in a variant. */
3819
+ private compileVariant;
3820
+ /** Static environment for a calc: siblings → library → stream → context. */
3821
+ private staticEnv;
3822
+ }
3823
+ /** Output node ids as an array regardless of the single/multi form. */
3824
+ declare function outputNodeIds(def: CalculationDefinition$1): string[];
3825
+
3826
+ /**
3827
+ * Composed calculation engine — Phases 1 & 2 of the evaluation pipeline
3828
+ * (spec §1.1).
3829
+ *
3830
+ * Implements both legacy processor contracts so it drops into the existing
3831
+ * AnalyticsEngine chain:
3832
+ *
3833
+ * - `process(output)` — **Phase 1, segment scope.** On each leaf segment,
3834
+ * evaluates segment calcs against the segment's metrics + context and
3835
+ * appends `analyzed` / `analyzed-estimated` annotations to the segment's
3836
+ * own OutputStatement (pace, power, and the library nodes segmentVolume,
3837
+ * effortRpe, metMinutes + the metMinutesEstimated companion count).
3838
+ *
3839
+ * - `summarize(outputs)` — **Phase 2, workout scope.** Recompute-all over
3840
+ * the accumulated log-stream history: stream aggregates, context nodes,
3841
+ * variant resolution, grouped emission. Emits ProjectionResults which the
3842
+ * AnalyticsEngine turns into replacement `analytics` OutputStatements
3843
+ * with signature dedupe (live running totals).
3844
+ *
3845
+ * Store scope (Phase 3) lives in the rollup driver replacement (#877).
3846
+ */
3847
+
3848
+ interface CalcEngineDeps {
3849
+ /** Fence dialect of the block being analyzed; calcs filter on it. */
3850
+ dialect: string;
3851
+ lookups: LookupRegistry;
3852
+ userProfile?: IProfileSource;
3853
+ }
3854
+ declare class CalcEngine implements IRealtimeProcessor, ISummaryProcessor {
3855
+ private readonly registry;
3856
+ private readonly deps;
3857
+ readonly id = "composed-calculations";
3858
+ readonly fenceTypes: readonly ["time", "log"];
3859
+ private readonly segmentCalcs;
3860
+ private readonly workoutCalcs;
3861
+ /**
3862
+ * Last resolved effort slug seen this workout — the "last-seen effort"
3863
+ * scan state from MetMinuteProjectionEngine, carried across process()
3864
+ * calls (segments arrive in stream order, live and in replay).
3865
+ */
3866
+ private lastSegmentEffort?;
3867
+ constructor(registry: CalculationRegistry, deps: CalcEngineDeps);
3868
+ process(output: IOutputStatement): IOutputStatement;
3869
+ private appendAnnotation;
3870
+ /** A frozen prediction of this type already rides the segment (replay). */
3871
+ private hasFrozen;
3872
+ summarize(outputs: IOutputStatement[]): ProjectionResult[];
3873
+ private summarizeUngrouped;
3874
+ private summarizeGrouped;
3875
+ private lookupString;
3876
+ private toProjection;
3877
+ private applicable;
3878
+ /** First applicable variant wins (variants are pre-sorted by priority). */
3879
+ private selectVariant;
3880
+ /** Evaluate all nodes in a variant, resolving sibling refs lazily. */
3881
+ private evaluateVariant;
3882
+ private makeContext;
3883
+ /** Workout-scope atoms over a segment list (full history or one group). */
3884
+ private buildWorkoutAtoms;
3885
+ }
3886
+
3887
+ /**
3888
+ * Built-in calculation suite as DAG seed records (#849 resolution 4: seeds
3889
+ * are TS object literals; no parser on the critical path — the line-form
3890
+ * parser's acceptance test is round-tripping these exact records).
3891
+ *
3892
+ * This is spec §6 compiled to record form, with the migration decisions
3893
+ * baked in:
3894
+ * - rest exclusion pinned once as the shared filter `rest|pause|rest-*` (§10.6);
3895
+ * - origin provenance via the metMinutesEstimated companion count (§10.3);
3896
+ * - TIS intermediate nodes published as fact metadata (§10.4);
3897
+ * - durationScore carries a `pts` cast — §5.3 applied to intermediates so
3898
+ * the score algebra passes static dimension checking;
3899
+ * - rounding lives in `round()` where today rounds, nowhere else (§10.9).
3900
+ */
3901
+
3902
+ /** Shared rest/pause exclusion filter constant (§10.6). */
3903
+ declare const REST_EXCLUSION = "rest|pause|rest-*";
3904
+ declare const BUILTIN_CALCS: CalculationDefinition$1[];
3905
+ declare const STORE_CALCS: CalculationDefinition$1[];
3906
+
3907
+ /**
3908
+ * Factory for a fully-seeded composed calculation stack: lookup registry
3909
+ * with the four built-in adapters, calculation registry with the built-in
3910
+ * suite statically validated, and the engine ready to join an
3911
+ * AnalyticsEngine processor chain.
3912
+ */
3913
+
3914
+ interface CalcStackOptions {
3915
+ effortResolver: IEffortResolver;
3916
+ userProfile?: IProfileSource;
3917
+ /** Extra/override calcs (dialect, user) registered after the built-ins. */
3918
+ calcs?: CalculationDefinition$1[];
3919
+ }
3920
+ interface CalcStack {
3921
+ lookups: LookupRegistry;
3922
+ registry: CalculationRegistry;
3923
+ }
3924
+ declare function createCalcStack(options: CalcStackOptions): CalcStack;
3925
+ declare function createCalcEngine(dialect: string, options: CalcStackOptions): CalcEngine;
3926
+
3927
+ interface CalcDiagnostic {
3928
+ severity: 'error' | 'warning';
3929
+ message: string;
3930
+ }
3931
+ interface CalcAnalysis {
3932
+ defs: CalculationDefinition$1[];
3933
+ diagnostics: CalcDiagnostic[];
3934
+ dim: DimVector | undefined;
3935
+ compound: string | undefined;
3936
+ }
3937
+ declare function buildStaticLookups(): LookupRegistry;
3938
+
3939
+ declare const index_ABSENT: typeof ABSENT;
3940
+ declare const index_AGGREGATE_BUILTINS: typeof AGGREGATE_BUILTINS;
3941
+ declare const index_AUTHORITATIVE_CASTS: typeof AUTHORITATIVE_CASTS;
3942
+ declare const index_BUILTIN_CALCS: typeof BUILTIN_CALCS;
3943
+ type index_BinaryOp = BinaryOp;
3944
+ declare const index_CONTEXT_ATOMS: typeof CONTEXT_ATOMS;
3945
+ type index_CalcAnalysis = CalcAnalysis;
3946
+ type index_CalcDiagnostic = CalcDiagnostic;
3947
+ type index_CalcEngine = CalcEngine;
3948
+ declare const index_CalcEngine: typeof CalcEngine;
3949
+ type index_CalcEngineDeps = CalcEngineDeps;
3950
+ type index_CalcEvalError = CalcEvalError;
3951
+ declare const index_CalcEvalError: typeof CalcEvalError;
3952
+ type index_CalcLine = CalcLine;
3953
+ type index_CalcNode = CalcNode;
3954
+ type index_CalcOrigin = CalcOrigin;
3955
+ type index_CalcParseError = CalcParseError;
3956
+ declare const index_CalcParseError: typeof CalcParseError;
3957
+ type index_CalcRegistrationError = CalcRegistrationError;
3958
+ declare const index_CalcRegistrationError: typeof CalcRegistrationError;
3959
+ type index_CalcScope = CalcScope;
3960
+ type index_CalcStack = CalcStack;
3961
+ type index_CalcStackOptions = CalcStackOptions;
3962
+ type index_CalcUnitError = CalcUnitError;
3963
+ declare const index_CalcUnitError: typeof CalcUnitError;
3964
+ type index_CalcVariant = CalcVariant;
3965
+ type index_CalculationRegistry = CalculationRegistry;
3966
+ declare const index_CalculationRegistry: typeof CalculationRegistry;
3967
+ type index_CompiledLineForm = CompiledLineForm;
3968
+ declare const index_DIM_COUNT: typeof DIM_COUNT;
3969
+ declare const index_DIM_ENERGY: typeof DIM_ENERGY;
3970
+ declare const index_DIM_LENGTH: typeof DIM_LENGTH;
3971
+ declare const index_DIM_MASS: typeof DIM_MASS;
3972
+ declare const index_DIM_TIME: typeof DIM_TIME;
3973
+ declare const index_DIM_ZERO: typeof DIM_ZERO;
3974
+ type index_DimVector = DimVector;
3975
+ type index_EvalContext = EvalContext;
3976
+ type index_ExprNode = ExprNode;
3977
+ type index_ILookupTable = ILookupTable;
3978
+ type index_IProfileSource = IProfileSource;
3979
+ type index_LineFormScope = LineFormScope;
3980
+ type index_LookupFieldDef = LookupFieldDef;
3981
+ type index_LookupLayer = LookupLayer;
3982
+ type index_LookupRegistry = LookupRegistry;
3983
+ declare const index_LookupRegistry: typeof LookupRegistry;
3984
+ declare const index_NAMED_COMPOUNDS: typeof NAMED_COMPOUNDS;
3985
+ declare const index_REST_EXCLUSION: typeof REST_EXCLUSION;
3986
+ declare const index_RPE_LABELS: typeof RPE_LABELS;
3987
+ declare const index_STORE_CALCS: typeof STORE_CALCS;
3988
+ declare const index_STREAM_ATOMS: typeof STREAM_ATOMS;
3989
+ type index_StaticEnv = StaticEnv;
3990
+ type index_StreamAtomDef = StreamAtomDef;
3991
+ declare const index_UNITS: typeof UNITS;
3992
+ type index_UnitDef = UnitDef;
3993
+ type index_Val = Val;
3994
+ declare const index_addDim: typeof addDim;
3995
+ declare const index_buildStaticLookups: typeof buildStaticLookups;
3996
+ declare const index_checkCalcLine: typeof checkCalcLine;
3997
+ declare const index_compileLineForm: typeof compileLineForm;
3998
+ declare const index_composeDivUnit: typeof composeDivUnit;
3999
+ declare const index_composeMulUnit: typeof composeMulUnit;
4000
+ declare const index_compoundName: typeof compoundName;
4001
+ declare const index_convertScalar: typeof convertScalar;
4002
+ declare const index_createCalcEngine: typeof createCalcEngine;
4003
+ declare const index_createCalcStack: typeof createCalcStack;
4004
+ declare const index_createDisciplinesTable: typeof createDisciplinesTable;
4005
+ declare const index_createEffortTable: typeof createEffortTable;
4006
+ declare const index_createProfileTable: typeof createProfileTable;
4007
+ declare const index_createRpeLabelsTable: typeof createRpeLabelsTable;
4008
+ declare const index_dimEquals: typeof dimEquals;
4009
+ declare const index_evaluate: typeof evaluate;
4010
+ declare const index_formatDim: typeof formatDim;
4011
+ declare const index_getUnit: typeof getUnit;
4012
+ declare const index_inferDim: typeof inferDim;
4013
+ declare const index_isDimensionless: typeof isDimensionless;
4014
+ declare const index_num: typeof num;
4015
+ declare const index_outputNodeId: typeof outputNodeId;
4016
+ declare const index_outputNodeIds: typeof outputNodeIds;
4017
+ declare const index_parseCalcLine: typeof parseCalcLine;
4018
+ declare const index_parseExpression: typeof parseExpression;
4019
+ declare const index_printCalculation: typeof printCalculation;
4020
+ declare const index_str: typeof str;
4021
+ declare const index_subDim: typeof subDim;
4022
+ declare const index_truthy: typeof truthy;
4023
+ declare const index_validateOutputUnit: typeof validateOutputUnit;
4024
+ declare namespace index {
4025
+ export { index_ABSENT as ABSENT, index_AGGREGATE_BUILTINS as AGGREGATE_BUILTINS, index_AUTHORITATIVE_CASTS as AUTHORITATIVE_CASTS, index_BUILTIN_CALCS as BUILTIN_CALCS, type index_BinaryOp as BinaryOp, index_CONTEXT_ATOMS as CONTEXT_ATOMS, type index_CalcAnalysis as CalcAnalysis, type index_CalcDiagnostic as CalcDiagnostic, index_CalcEngine as CalcEngine, type index_CalcEngineDeps as CalcEngineDeps, index_CalcEvalError as CalcEvalError, type index_CalcLine as CalcLine, type index_CalcNode as CalcNode, type index_CalcOrigin as CalcOrigin, index_CalcParseError as CalcParseError, index_CalcRegistrationError as CalcRegistrationError, type index_CalcScope as CalcScope, type index_CalcStack as CalcStack, type index_CalcStackOptions as CalcStackOptions, index_CalcUnitError as CalcUnitError, type index_CalcVariant as CalcVariant, type CalculationDefinition$1 as CalculationDefinition, index_CalculationRegistry as CalculationRegistry, type index_CompiledLineForm as CompiledLineForm, index_DIM_COUNT as DIM_COUNT, index_DIM_ENERGY as DIM_ENERGY, index_DIM_LENGTH as DIM_LENGTH, index_DIM_MASS as DIM_MASS, index_DIM_TIME as DIM_TIME, index_DIM_ZERO as DIM_ZERO, type index_DimVector as DimVector, type index_EvalContext as EvalContext, type index_ExprNode as ExprNode, type index_ILookupTable as ILookupTable, type index_IProfileSource as IProfileSource, type index_LineFormScope as LineFormScope, type index_LookupFieldDef as LookupFieldDef, type index_LookupLayer as LookupLayer, index_LookupRegistry as LookupRegistry, index_NAMED_COMPOUNDS as NAMED_COMPOUNDS, index_REST_EXCLUSION as REST_EXCLUSION, index_RPE_LABELS as RPE_LABELS, index_STORE_CALCS as STORE_CALCS, index_STREAM_ATOMS as STREAM_ATOMS, type index_StaticEnv as StaticEnv, type index_StreamAtomDef as StreamAtomDef, index_UNITS as UNITS, type index_UnitDef as UnitDef, type index_Val as Val, index_addDim as addDim, index_buildStaticLookups as buildStaticLookups, index_checkCalcLine as checkCalcLine, index_compileLineForm as compileLineForm, index_composeDivUnit as composeDivUnit, index_composeMulUnit as composeMulUnit, index_compoundName as compoundName, index_convertScalar as convertScalar, index_createCalcEngine as createCalcEngine, index_createCalcStack as createCalcStack, index_createDisciplinesTable as createDisciplinesTable, index_createEffortTable as createEffortTable, index_createProfileTable as createProfileTable, index_createRpeLabelsTable as createRpeLabelsTable, index_dimEquals as dimEquals, index_evaluate as evaluate, index_formatDim as formatDim, index_getUnit as getUnit, index_inferDim as inferDim, index_isDimensionless as isDimensionless, index_num as num, index_outputNodeId as outputNodeId, index_outputNodeIds as outputNodeIds, index_parseCalcLine as parseCalcLine, index_parseExpression as parseExpression, index_printCalculation as printCalculation, index_str as str, index_subDim as subDim, index_truthy as truthy, index_validateOutputUnit as validateOutputUnit };
4026
+ }
4027
+
4028
+ /**
4029
+ * workloadRollup — pure workload-window math behind Rollup Facts
4030
+ * (CONTEXT.md §Analytics). Foster sRPE periodization over daily SessionLoad:
4031
+ *
4032
+ * dailyLoad[d] = Σ sessionLoad fact values in UTC day d
4033
+ * acute = mean daily load over the trailing 7 days (D-6 … D)
4034
+ * chronic = mean daily load over the trailing 28 days (D-27 … D)
4035
+ * ACWR = acute / chronic — emitted when chronic > 0
4036
+ * monotony = mean7 / sd7 — population SD (÷7) over the 7
4037
+ * daily values; emitted when sd7 > 0 (a flat week has no
4038
+ * meaningful monotony)
4039
+ * strain = monotony × (Σ of the 7 daily loads) — emitted with monotony
4040
+ *
4041
+ * Day buckets are UTC (`Math.floor(ts / DAY)`) — the same bucketing the
4042
+ * Query Service uses for the `day` dimension, so rollup rows land in the
4043
+ * buckets widgets already group by.
4044
+ */
4045
+ declare const DAY = 86400000;
4046
+ /** Local day bucket for a canonical timestamp. */
4047
+ declare function dayBucket(ts: number): number;
4048
+ /** One UTC day's computed windows; absent fields were suppressed (see header). */
4049
+ interface DayRollup {
4050
+ day: number;
4051
+ acwr?: number;
4052
+ monotony?: number;
4053
+ strain?: number;
4054
+ }
4055
+ /** Sum fact values into UTC day buckets. */
4056
+ declare function dailySessionLoads(facts: readonly {
4057
+ timestamp: number;
4058
+ value: number;
4059
+ }[]): Map<number, number>;
4060
+ /**
4061
+ * Compute the Foster windows for every day that can produce a value: from
4062
+ * the first day with load through `throughDay` (inclusive, UTC day bucket).
4063
+ * Earlier days have empty chronic windows and zero-variance acute windows,
4064
+ * so every value would be suppressed anyway.
4065
+ */
4066
+ declare function computeWorkloadRollups(loads: ReadonlyMap<number, number>, throughDay: number): DayRollup[];
4067
+
4068
+ /**
4069
+ * Convert a live IOutputStatement into a StoredOutputStatement.
4070
+ *
4071
+ * Normalises non-serialisable types (`Set` → `string[]`, `MetricContainer` → `IMetric[]`)
4072
+ * so the result survives a JSON / IndexedDB round-trip without data loss.
4073
+ */
4074
+ declare function toStoredOutputStatement(output: IOutputStatement | OutputStatement): StoredOutputStatement;
4075
+
4076
+ export { AbortSessionAction, type ActionDescriptor, type ActionPrimitive, AnalyticsContext, AnalyticsEngine, type AnalyticsProfileContext, type AnalyticsResult, AnalyticsTransformer, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BehaviorContext, BlockContext, BlockLifecycleOptions, CONSUMED_HINTS, CONSUMED_HINT_KEYS, type CalculationDefinition$1 as CalcDefinition, CalcEngine, type CalcOrigin, type CalcScope, type CalculationDefinition as CalculateBlockDefinition, type CalculateBlockParseError, type CalculateBlockParseResult, CalculateBlockProcessor, CalculationRegistry, CardioDialect, ClearChildrenAction, ClimbDialect, type ClimbGradeMetricValue, type ClimbGradeSystem, ClimbMetricType, CompileAndPushBlockAction, CompletionDecision, type CreateAnalyticsEngineOptions, type CreateAnalyticsEngineResult, CrossFitDialect, DAY, DEFAULT_UNRESOLVED_EFFORT_MET, DIM_LENGTH, DIM_MASS, DIM_TIME, DIM_ZERO, type DayRollup, DialectStack, type Dimension, type DurationPrimitive, EFFORT_DATA_METRIC_TYPE, EffortBlock, type EffortBlockConfig, type EffortPrimitive, EffortRegistrySource, EffortResolutionOptions, EffortResolver, EffortResolverOptions, EmitEventAction, EmitSystemOutputAction, ErrorAction, EventBus, type EventHandlerRegistration, ExecutionContext, type FuzzyCandidate, HabitsDialect, type HeadingPrimitive, IAnalyticsEngine, type IAnalyticsProfile, IBlockContext, IEffort, IEffortRegistry, IEffortResolver, IEvent, IEventBus, IJitCompiler, type ILookupTable, IMemoryLocation, IMemoryReference, type IMetricPresentationPolicy, INowProvider, IRealtimeProcessor, IRuntimeAction, IRuntimeBehavior, IRuntimeBlock, type IRuntimeBlockStrategy, IRuntimeClock, type IRuntimeFactory, IRuntimeStack, IScript, IScriptRuntime, ISummaryProcessor, InMemoryEffortRegistry, JitCompiler, type LapPrimitive, type LineLocator, type LogLevel, LookupRegistry, MdTimerRuntime, MemoryTag, MemoryTypeEnum, type MetricObjectPrimitive, type MetricPresentationSurface, type MetricPresentationToken, type MetricRenderKind, type MetricTone, MetricVisibility, type MockClock, NextAction, NextEvent, NextEventHandler, OutputEmitter, PREDEFINED_SOUNDS, PRODUCTION_STRATEGIES, type ParseOptions, PlaySoundAction, PopBlockAction, type PredefinedSoundName, ProjectionResult, type PropertyPrimitive, PushBlockAction, PushRestBlockAction, type QuantityPrimitive, RegisterEventHandlerAction, Registry, ResolvedEffort, type ResolvedEffortData, RestBlock, type RestBlockConfig, type RoundsPrimitive, RuntimeBlock, type RuntimeBlockOptions, RuntimeBuilder, type RuntimeButton, RuntimeClock, type RuntimeController, type RuntimeControls, RuntimeError, RuntimeFactory, type RuntimeLogEntry, RuntimeLogger, RuntimeMemory, type RuntimeMemoryLocation, RuntimeObservers, RuntimeStack, STANDARD_UNITS, STORE_CALCS, ScriptRuntime, type SegmentWithMetadata, SessionRootBlock, type SessionRootConfig, SnapshotClock, type SoundBehaviorConfig, type SoundCue, type SoundCueState, type SoundState, type SourceInput, type SpanList, StackObserver, StackSnapshot, StandardAnalyticsProfile, StartSessionAction, type StartSessionOptions, StartWorkoutAction, type StartWorkoutOptions, type SyntaxFacts, type SyntaxMeta, type SyntaxPrimitive, type SyntaxStatement, type TextPrimitive, ThrowError, ThrowErrorAction, TimeSpan, TimerState, TwoPassEffortResolutionProcess, TypedMemoryReference, type UnitDef$1 as UnitDef, type UnitMatch, UnitRegistry, UnitSet, UnitsDialect, UnregisterEventHandlerAction, Unsubscribe, UpdateNextPreviewAction, WaitingToStartBlock, WhiteboardScript, WodDialect, YogaDialect, buildTooltip, bundledEfforts, index as calc, calculateElapsed, classifyStatements, closeCurrentSpan, commonFixtureSet, computeColumnLabel, computeLabel, computeWorkloadRollups, createAnalyticsEngineForBlock, createCalcEngine, createCompiler, createDialectStack, createLineLocator, createPolicy as createMetricPresentationPolicy, createMockClock, createParser, dailySessionLoads, dayBucket, dialectRegistry, dialectStack, evaluate, evaluateCalculateDefinitions, extractEffortData, extractMetrics, extractStatements, extractStatementsRaw, extractSyntaxFacts, findBestFuzzyMatch, fixtureBackSquat, fixtureBurpee, fixturePlank, fixtureRowing, fixtureRunning, fixtureUserCustom, fixtureUserOverride, formatDuration, metricsToLabel as fragmentsToLabel, fullFixtureSet, fuseUnits, fuseUnitsInMetrics, getAnalyticsFromLogs, getAnalyticsFromRuntime, getHints, hasHint, hintMetric, hintsToContainer, isTimeLikeMetric, isWithinThreshold, levenshteinDistance, metricPresentation, mutateTimerSpans, normalizeForFuzzy, openSpan, parseCalcLine, parseCalculateBlock, parseExpression, parseScript, readTimer, realtimeProcessorRegistry, resolveDominantOrigin, sharedParser, startSpan, strategyRegistry, summaryProcessorRegistry, toStoredOutputStatement, parser as whiteboardParser, whiteboardScript, whiteboardScriptLanguage };