@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.
- package/dist/browserRuntimeNow-SxxdmtB7.d.cts +2350 -0
- package/dist/browserRuntimeNow-SxxdmtB7.d.ts +2350 -0
- package/dist/chunk-F2TOS4AF.mjs +79 -0
- package/dist/chunk-F2TOS4AF.mjs.map +1 -0
- package/dist/index.cjs +12938 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4076 -0
- package/dist/index.d.ts +4076 -0
- package/dist/index.mjs +12692 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.cjs +842 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +640 -0
- package/dist/react.d.ts +640 -0
- package/dist/react.mjs +741 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1,2350 @@
|
|
|
1
|
+
import { ICodeStatement, ParseError, MetricContainer, IMetric, BlockKey, TimeSpan as TimeSpan$1, OutputStatementType, MetricType, IOutputStatement, MetricOrigin } from '@bitcobblers/wod-wiki-core';
|
|
2
|
+
|
|
3
|
+
interface IScript {
|
|
4
|
+
source: string;
|
|
5
|
+
statements: ICodeStatement[];
|
|
6
|
+
errors?: ParseError[] | undefined;
|
|
7
|
+
getIds(ids: number[]): ICodeStatement[];
|
|
8
|
+
getId(id: number): ICodeStatement | undefined;
|
|
9
|
+
getAt(index: number): ICodeStatement | undefined;
|
|
10
|
+
}
|
|
11
|
+
declare class WhiteboardScript implements IScript {
|
|
12
|
+
source: string;
|
|
13
|
+
statements: ICodeStatement[];
|
|
14
|
+
errors: ParseError[] | undefined;
|
|
15
|
+
private _idMap?;
|
|
16
|
+
constructor(source: string, statements: ICodeStatement[], errors?: ParseError[]);
|
|
17
|
+
/**
|
|
18
|
+
* Gets the ID-to-statement Map, building it lazily on first access.
|
|
19
|
+
* Provides O(1) lookups instead of O(n) find() calls.
|
|
20
|
+
*/
|
|
21
|
+
private getIdMap;
|
|
22
|
+
/**
|
|
23
|
+
* Resolves multiple statement IDs to statements.
|
|
24
|
+
* Uses cached Map for O(n) performance instead of O(n²).
|
|
25
|
+
*/
|
|
26
|
+
getIds(ids: number[]): ICodeStatement[];
|
|
27
|
+
/**
|
|
28
|
+
* Resolves a single statement ID to a statement.
|
|
29
|
+
* Uses cached Map for O(1) performance instead of O(n).
|
|
30
|
+
*/
|
|
31
|
+
getId(id: number): ICodeStatement | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Gets a statement by its array index.
|
|
34
|
+
*/
|
|
35
|
+
getAt(index: number): ICodeStatement | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Flattens grouped children (number[][]) and resolves to statements.
|
|
38
|
+
* Convenience wrapper for getIds(children.flat()).
|
|
39
|
+
*/
|
|
40
|
+
getFlatChildren(children: number[][]): ICodeStatement[];
|
|
41
|
+
/**
|
|
42
|
+
* Resolves grouped children while preserving group structure.
|
|
43
|
+
* Maps each number[] group to ICodeStatement[] group.
|
|
44
|
+
*/
|
|
45
|
+
getChildGroups(children: number[][]): ICodeStatement[][];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Canonical discipline vocabulary — the single source of truth for effort
|
|
50
|
+
* disciplines across the resolver, the TIS factor table, fact-row tags, the
|
|
51
|
+
* WQL tag dictionary, and UI filters.
|
|
52
|
+
*
|
|
53
|
+
* The vocabulary is the 10 values used by the bundled catalog
|
|
54
|
+
* (markdown/efforts/<discipline>/<slug>.md, 53 efforts). Earlier factor-table
|
|
55
|
+
* keys ('resistance', 'yoga', 'cardio', 'hiit') and the 'weightlifting' /
|
|
56
|
+
* 'monostructural' examples in storage docs were stale text — no live effort
|
|
57
|
+
* ever carried them.
|
|
58
|
+
*/
|
|
59
|
+
declare const EFFORT_DISCIPLINES: readonly ["bodyweight", "cycling", "gymnastics", "kettlebell", "recovery", "rowing", "running", "strength", "swimming", "walking"];
|
|
60
|
+
type EffortDiscipline = (typeof EFFORT_DISCIPLINES)[number];
|
|
61
|
+
declare function isEffortDiscipline(value: unknown): value is EffortDiscipline;
|
|
62
|
+
/**
|
|
63
|
+
* TIS discipline multiplier per canonical discipline. Loaded modalities
|
|
64
|
+
* (strength + the loaded-skill pair kettlebell/gymnastics) score 1.2;
|
|
65
|
+
* recovery absorbs the retired 'yoga' case at 0.9; the monostructural five
|
|
66
|
+
* and bodyweight take the 1.0 default. Unknown/absent disciplines fall back
|
|
67
|
+
* to 1.0 — the historical behavior for every non-'strength' value.
|
|
68
|
+
*/
|
|
69
|
+
declare const DISCIPLINE_FACTORS: Record<EffortDiscipline, number>;
|
|
70
|
+
/** Factor for an arbitrary discipline string; 1.0 when absent or unknown. */
|
|
71
|
+
declare function disciplineFactorFor(discipline: string | undefined): number;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Effort Registry — Core Types
|
|
75
|
+
*
|
|
76
|
+
* First-class domain entity system for exercise effort definitions.
|
|
77
|
+
* Centralizes effort metadata (MET, discipline, aliases) and supports
|
|
78
|
+
* user-defined derivations with coefficient chains and hard overrides.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
type EffortRegistrySource = 'bundled' | 'user' | 'synthetic-unresolved';
|
|
82
|
+
/** Origins that hold real registry records (synthetic-unresolved entries are
|
|
83
|
+
* resolver phantoms, never listed) — the WQL `origin:` filter vocabulary. */
|
|
84
|
+
declare const EFFORT_REGISTRY_ORIGINS: readonly ["bundled", "user"];
|
|
85
|
+
/** Intensity tiers, low → high — the WQL `intensity:` filter vocabulary. */
|
|
86
|
+
declare const INTENSITY_TIERS: readonly ["low", "moderate", "high"];
|
|
87
|
+
type IntensityTier = (typeof INTENSITY_TIERS)[number];
|
|
88
|
+
interface EffortBaseAttributes {
|
|
89
|
+
/** Metabolic equivalent of task */
|
|
90
|
+
met: number;
|
|
91
|
+
/** Broad discipline category — canonical vocabulary in `./disciplines`. */
|
|
92
|
+
discipline?: EffortDiscipline;
|
|
93
|
+
/** TIS discipline multiplier. When omitted, resolver derives it from discipline. */
|
|
94
|
+
disciplineFactor?: number;
|
|
95
|
+
/** Qualitative intensity bucket */
|
|
96
|
+
intensityTier?: IntensityTier;
|
|
97
|
+
}
|
|
98
|
+
interface EffortDerivation {
|
|
99
|
+
/** Slug of the parent effort this was cloned from */
|
|
100
|
+
parentSlug?: string;
|
|
101
|
+
/** Multiplicative modifiers applied to numeric base attributes */
|
|
102
|
+
coefficients?: Record<string, number>;
|
|
103
|
+
/** Explicit replacement values for specific attributes */
|
|
104
|
+
hardOverrides?: Record<string, unknown>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Canonical effort entity.
|
|
108
|
+
*
|
|
109
|
+
* The slug is the durable identity boundary. Display labels and aliases
|
|
110
|
+
* may vary, but references should point at the slug.
|
|
111
|
+
*/
|
|
112
|
+
interface IEffort {
|
|
113
|
+
/** UUID */
|
|
114
|
+
id: string;
|
|
115
|
+
/** Canonical identifier — unique, lowercase, hyphenated */
|
|
116
|
+
slug: string;
|
|
117
|
+
/** Primary human-readable name */
|
|
118
|
+
label: string;
|
|
119
|
+
/** Alternative names for fuzzy matching */
|
|
120
|
+
aliases: string[];
|
|
121
|
+
/** Canonical effort attributes */
|
|
122
|
+
baseAttributes: EffortBaseAttributes;
|
|
123
|
+
/** Where this record came from */
|
|
124
|
+
registrySource: EffortRegistrySource;
|
|
125
|
+
/** Clone-based derivation metadata (user efforts only) */
|
|
126
|
+
derivation?: EffortDerivation;
|
|
127
|
+
/** ISO timestamp */
|
|
128
|
+
createdAt?: string;
|
|
129
|
+
/** ISO timestamp */
|
|
130
|
+
updatedAt?: string;
|
|
131
|
+
/** Free-form markdown description / notes */
|
|
132
|
+
body?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Compiler hints attached to any block resolved to this effort. Consumed by
|
|
135
|
+
* strategies exactly like dialect-emitted hints — see `CONSUMED_HINTS` in
|
|
136
|
+
* `core/metrics/hints.ts`. Use sparingly: any key not in `CONSUMED_HINTS`
|
|
137
|
+
* is currently inert (analytics-only). For effort-specific metadata, prefer
|
|
138
|
+
* a domain metric.
|
|
139
|
+
*/
|
|
140
|
+
hints?: Record<string, unknown>;
|
|
141
|
+
}
|
|
142
|
+
interface IEffortRegistry {
|
|
143
|
+
resolve(slug: string): IEffort | null;
|
|
144
|
+
list(): readonly IEffort[];
|
|
145
|
+
listByOrigin(origin: EffortRegistrySource): readonly IEffort[];
|
|
146
|
+
upsert(effort: IEffort): Promise<void>;
|
|
147
|
+
delete(slug: string): Promise<void>;
|
|
148
|
+
loadBundled(): Promise<void>;
|
|
149
|
+
}
|
|
150
|
+
interface EffortResolutionOptions {
|
|
151
|
+
/** Attribute metrics from /effort/:slug query params or logged execution metrics. */
|
|
152
|
+
modifiers?: Record<string, string>;
|
|
153
|
+
/** Fuzzy-match threshold override. */
|
|
154
|
+
threshold?: number;
|
|
155
|
+
}
|
|
156
|
+
type EffortResolvedFrom = 'user' | 'bundled' | 'default';
|
|
157
|
+
/**
|
|
158
|
+
* Effective effort instance after alias lookup, derivation, modifiers,
|
|
159
|
+
* hard overrides, fallback policy, and discipline-factor calculation.
|
|
160
|
+
*/
|
|
161
|
+
interface ResolvedEffort {
|
|
162
|
+
/** Effective effort copy; baseAttributes reflect the resolved values. */
|
|
163
|
+
effort: IEffort;
|
|
164
|
+
/** Original registry definition before effective attributes were materialized. */
|
|
165
|
+
definition: IEffort;
|
|
166
|
+
slug: string;
|
|
167
|
+
label: string;
|
|
168
|
+
met: number;
|
|
169
|
+
/** Effective attributes mirrored for legacy metric consumers. */
|
|
170
|
+
baseAttributes: EffortBaseAttributes;
|
|
171
|
+
discipline?: EffortDiscipline;
|
|
172
|
+
disciplineFactor: number;
|
|
173
|
+
intensityTier?: IntensityTier;
|
|
174
|
+
modifiers: Record<string, string>;
|
|
175
|
+
registrySource: EffortRegistrySource;
|
|
176
|
+
resolvedFrom: EffortResolvedFrom;
|
|
177
|
+
isEstimated: boolean;
|
|
178
|
+
}
|
|
179
|
+
interface IEffortResolver {
|
|
180
|
+
/** Exact slug lookup */
|
|
181
|
+
resolveBySlug(slug: string): IEffort | null;
|
|
182
|
+
/** Exact alias lookup */
|
|
183
|
+
resolveByAlias(label: string): IEffort | null;
|
|
184
|
+
/**
|
|
185
|
+
* Fuzzy alias matching with configurable threshold.
|
|
186
|
+
* Returns a synthetic unresolved effort when no match is found
|
|
187
|
+
* (analytics continuity — never returns null).
|
|
188
|
+
*/
|
|
189
|
+
resolveFuzzy(label: string, options?: {
|
|
190
|
+
threshold?: number;
|
|
191
|
+
}): IEffort;
|
|
192
|
+
/** Resolve a label/slug into an effective effort instance. */
|
|
193
|
+
resolveEffort(label: string, options?: EffortResolutionOptions): ResolvedEffort;
|
|
194
|
+
/** Materialize an already-found effort definition into an effective instance. */
|
|
195
|
+
resolveDefinition(effort: IEffort, options?: EffortResolutionOptions): ResolvedEffort;
|
|
196
|
+
/** All registered efforts (bundled + user) */
|
|
197
|
+
list(): readonly IEffort[];
|
|
198
|
+
}
|
|
199
|
+
interface EffortResolverOptions {
|
|
200
|
+
/** Default MET for synthetic unresolved efforts */
|
|
201
|
+
defaultMet?: number;
|
|
202
|
+
/** Default fuzzy-match threshold (Levenshtein distance) */
|
|
203
|
+
defaultThreshold?: number;
|
|
204
|
+
}
|
|
205
|
+
declare const DEFAULT_RESOLVER_OPTIONS: Required<EffortResolverOptions>;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Minimal executable runtime action shape.
|
|
209
|
+
*
|
|
210
|
+
* Describes what {@link IRuntimeActionable.do}/{@link IRuntimeActionable.doAll}
|
|
211
|
+
* actually require — an object with a string `type` discriminator AND a
|
|
212
|
+
* `do()` method that the executor can invoke. Typing the queue surface
|
|
213
|
+
* through this primitive ensures callers cannot enqueue a non-executable
|
|
214
|
+
* object that would crash the action loop.
|
|
215
|
+
*
|
|
216
|
+
* `IRuntimeAction` extends this primitive (adding the optional `target`
|
|
217
|
+
* and `payload` fields, plus a richer return type for chained child
|
|
218
|
+
* actions).
|
|
219
|
+
*
|
|
220
|
+
* Lives in the same file as {@link IRuntimeActionable} (rather than its
|
|
221
|
+
* own module) so neither primitive needs to import the other — that would
|
|
222
|
+
* create an internal cycle inside the primitives layer itself.
|
|
223
|
+
*/
|
|
224
|
+
interface IRuntimeActionLike {
|
|
225
|
+
/** Type discriminator for the action. */
|
|
226
|
+
readonly type: string;
|
|
227
|
+
/**
|
|
228
|
+
* Execute the action against the runtime context. Implementations may
|
|
229
|
+
* return additional actions (see `IRuntimeAction.do`) — the primitive
|
|
230
|
+
* shape only requires that the method exists.
|
|
231
|
+
*/
|
|
232
|
+
do(runtime: IRuntimeActionable): unknown;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Minimal primitive abstraction representing the runtime context that can
|
|
236
|
+
* queue runtime actions.
|
|
237
|
+
*
|
|
238
|
+
* Both {@link IScriptRuntime} and {@link IEventBus} depend on this primitive
|
|
239
|
+
* (or a stronger primitive that extends it) instead of each other. This
|
|
240
|
+
* breaks the cycles between `IRuntimeAction ↔ IScriptRuntime` and
|
|
241
|
+
* `IEventBus ↔ IScriptRuntime`.
|
|
242
|
+
*
|
|
243
|
+
* Actions are typed structurally through {@link IRuntimeActionLike} — an
|
|
244
|
+
* executable shape requiring both a `type` discriminator and a `do()`
|
|
245
|
+
* method. This keeps the primitives layer free of back-references into the
|
|
246
|
+
* broader contracts (it does NOT import `IRuntimeAction`) while still
|
|
247
|
+
* forcing queued actions to be executable so the action loop cannot crash
|
|
248
|
+
* on a structurally-typed but inert object.
|
|
249
|
+
*
|
|
250
|
+
* Concretely the runtime is always an `IScriptRuntime`, but typing through
|
|
251
|
+
* this primitive prevents the contract-layer cycle.
|
|
252
|
+
*/
|
|
253
|
+
interface IRuntimeActionable {
|
|
254
|
+
/**
|
|
255
|
+
* Execute a single action at the next available opportunity.
|
|
256
|
+
*/
|
|
257
|
+
do(action: IRuntimeActionLike): void;
|
|
258
|
+
/**
|
|
259
|
+
* Execute multiple actions in order, internalizing reverse-push for LIFO
|
|
260
|
+
* processing. The first action in the array will execute first.
|
|
261
|
+
*/
|
|
262
|
+
doAll(actions: IRuntimeActionLike[]): void;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Interface for actions that can be performed by the runtime in response to events.
|
|
267
|
+
*
|
|
268
|
+
* Actions return child actions instead of pushing them onto the runtime directly.
|
|
269
|
+
* The ExecutionContext collects returned actions and pushes them onto its internal
|
|
270
|
+
* LIFO stack, ensuring depth-first processing without actions needing to know
|
|
271
|
+
* about stack mechanics.
|
|
272
|
+
*
|
|
273
|
+
* Extends {@link IRuntimeActionLike} so the queue surface
|
|
274
|
+
* ({@link IRuntimeActionable.do}/{@link IRuntimeActionable.doAll}) accepts
|
|
275
|
+
* `IRuntimeAction` instances. The `do()` method receives the runtime via the
|
|
276
|
+
* {@link IRuntimeActionable} primitive instead of `IScriptRuntime`. This
|
|
277
|
+
* breaks the cycle `IRuntimeAction ↔ IScriptRuntime`. Concrete
|
|
278
|
+
* implementations are free to narrow their parameter type to `IScriptRuntime`
|
|
279
|
+
* (method bivariance allows this), and callers always pass an
|
|
280
|
+
* `IScriptRuntime` since `IScriptRuntime extends IRuntimeActionable`.
|
|
281
|
+
*/
|
|
282
|
+
interface IRuntimeAction extends IRuntimeActionLike {
|
|
283
|
+
/** Type of action to perform */
|
|
284
|
+
type: string;
|
|
285
|
+
/** Target of the action (optional) */
|
|
286
|
+
target?: string;
|
|
287
|
+
/** Action payload/data */
|
|
288
|
+
payload?: unknown;
|
|
289
|
+
/**
|
|
290
|
+
* Executes the action within the given runtime context.
|
|
291
|
+
*
|
|
292
|
+
* Returns child actions that should be executed next. The ExecutionContext
|
|
293
|
+
* pushes returned actions onto its LIFO stack for depth-first processing.
|
|
294
|
+
* Actions should NOT call runtime.do() or runtime.doAll() — just return
|
|
295
|
+
* the actions they want executed.
|
|
296
|
+
*
|
|
297
|
+
* @returns Array of child actions to execute, or void/empty array for none
|
|
298
|
+
*/
|
|
299
|
+
do(runtime: IRuntimeActionable): IRuntimeAction[] | void;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* A reference to a memory location that can be passed between components.
|
|
304
|
+
* This allows processes to reference memory without directly coupling to it.
|
|
305
|
+
*
|
|
306
|
+
* Note: The concrete `TypedMemoryReference` implementation lives in
|
|
307
|
+
* `src/runtime/impl/TypedMemoryReference.ts` to keep this contracts file
|
|
308
|
+
* free of implementation code and to break the
|
|
309
|
+
* `IMemoryReference ↔ IRuntimeMemory` cycle.
|
|
310
|
+
*/
|
|
311
|
+
interface IMemoryReference {
|
|
312
|
+
readonly id: string;
|
|
313
|
+
readonly ownerId: string;
|
|
314
|
+
readonly type: string;
|
|
315
|
+
readonly visibility: 'public' | 'private' | 'inherited';
|
|
316
|
+
/**
|
|
317
|
+
* Get the current value in an untyped manner.
|
|
318
|
+
* Useful for debugging and generic operations.
|
|
319
|
+
* @returns The current value or undefined
|
|
320
|
+
*/
|
|
321
|
+
value(): unknown;
|
|
322
|
+
/**
|
|
323
|
+
* List of active subscriptions for this memory reference.
|
|
324
|
+
* Readonly access for debugging and inspection.
|
|
325
|
+
*/
|
|
326
|
+
readonly subscriptions: ReadonlyArray<IMemorySubscription<unknown>>;
|
|
327
|
+
}
|
|
328
|
+
interface IMemorySubscription<T> {
|
|
329
|
+
readonly id: string;
|
|
330
|
+
readonly callback: (newValue: T | undefined, oldValue: T | undefined) => void;
|
|
331
|
+
readonly memoryId: string;
|
|
332
|
+
readonly createdAt: Date;
|
|
333
|
+
}
|
|
334
|
+
interface SubscriptionOptions {
|
|
335
|
+
immediate?: boolean;
|
|
336
|
+
throttle?: number;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Search criteria for finding memory references.
|
|
341
|
+
* Uses only the basic properties, not methods.
|
|
342
|
+
*/
|
|
343
|
+
type MemorySearchCriteria = {
|
|
344
|
+
id: string | null;
|
|
345
|
+
ownerId: string | null;
|
|
346
|
+
type: string | null;
|
|
347
|
+
visibility: 'public' | 'private' | 'inherited' | null;
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* The main runtime memory interface that manages separate memory from the execution stack.
|
|
351
|
+
* This enables debugging and state inspection independently of program execution flow.
|
|
352
|
+
*
|
|
353
|
+
* Note: Memory events (allocate, set, release) are dispatched by BlockContext,
|
|
354
|
+
* not by RuntimeMemory directly. This keeps the memory system pure and decoupled
|
|
355
|
+
* from the event system.
|
|
356
|
+
*/
|
|
357
|
+
interface IRuntimeMemory {
|
|
358
|
+
/**
|
|
359
|
+
* Allocates a new memory location and returns a reference to it.
|
|
360
|
+
* This memory will be automatically cleaned up when the associated stack item is removed.
|
|
361
|
+
*/
|
|
362
|
+
allocate<T>(type: string, ownerId: string, initialValue?: T, visibility?: 'public' | 'private' | 'inherited'): TypedMemoryReference<T>;
|
|
363
|
+
/**
|
|
364
|
+
* Gets a memory reference by its ID.
|
|
365
|
+
* Returns undefined if the reference doesn't exist or has been cleaned up.
|
|
366
|
+
*/
|
|
367
|
+
get<T>(reference: TypedMemoryReference<T>): T | undefined;
|
|
368
|
+
/**
|
|
369
|
+
* Sets the value of a memory reference.
|
|
370
|
+
* Throws an error if the reference is invalid.
|
|
371
|
+
**/
|
|
372
|
+
set<T>(reference: TypedMemoryReference<T>, value: T): void;
|
|
373
|
+
/**
|
|
374
|
+
* Searches for memory references that match the given criteria.
|
|
375
|
+
* Returns an array of matching references (empty if none found).
|
|
376
|
+
* the search is based on any non-null fields in the criteria object.
|
|
377
|
+
*/
|
|
378
|
+
search(criteria: MemorySearchCriteria): IMemoryReference[];
|
|
379
|
+
/**
|
|
380
|
+
* Manually releases a memory reference and all its children.
|
|
381
|
+
* This is automatically called when stack items are removed.
|
|
382
|
+
*/
|
|
383
|
+
release(reference: IMemoryReference): void;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
declare class TypedMemoryReference<T> implements IMemoryReference {
|
|
387
|
+
private readonly _memory;
|
|
388
|
+
readonly ownerId: string;
|
|
389
|
+
readonly type: string;
|
|
390
|
+
visibility: 'public' | 'private' | 'inherited';
|
|
391
|
+
id: string;
|
|
392
|
+
private _subscriptions;
|
|
393
|
+
constructor(_memory: IRuntimeMemory, ownerId: string, type: string, visibility?: 'public' | 'private' | 'inherited');
|
|
394
|
+
/**
|
|
395
|
+
* Get the current value in an untyped manner.
|
|
396
|
+
* Implements IMemoryReference.value() for debugging and inspection.
|
|
397
|
+
*/
|
|
398
|
+
value(): unknown;
|
|
399
|
+
/**
|
|
400
|
+
* Readonly access to subscriptions list.
|
|
401
|
+
* Implements IMemoryReference.subscriptions for debugging and inspection.
|
|
402
|
+
*/
|
|
403
|
+
get subscriptions(): ReadonlyArray<IMemorySubscription<unknown>>;
|
|
404
|
+
get(): T | undefined;
|
|
405
|
+
set(value: T): void;
|
|
406
|
+
/**
|
|
407
|
+
* Subscribe to changes in this memory reference.
|
|
408
|
+
* @param callback Function to call when the value changes
|
|
409
|
+
* @param options Subscription options (immediate, throttle)
|
|
410
|
+
* @returns Unsubscribe function
|
|
411
|
+
*/
|
|
412
|
+
subscribe(callback: (newValue: T | undefined, oldValue: T | undefined) => void, options?: SubscriptionOptions): () => void;
|
|
413
|
+
/**
|
|
414
|
+
* Unsubscribe from changes by subscription ID.
|
|
415
|
+
* @param subscriptionId The ID of the subscription to remove
|
|
416
|
+
*/
|
|
417
|
+
unsubscribe(subscriptionId: string): void;
|
|
418
|
+
/**
|
|
419
|
+
* Check if this reference has any active subscribers.
|
|
420
|
+
* @returns true if there are active subscribers
|
|
421
|
+
*/
|
|
422
|
+
hasSubscribers(): boolean;
|
|
423
|
+
/**
|
|
424
|
+
* Notify all subscribers of a value change.
|
|
425
|
+
* This is called internally by RuntimeMemory.
|
|
426
|
+
* @internal
|
|
427
|
+
*/
|
|
428
|
+
notifySubscribers(newValue: T | undefined, oldValue: T | undefined): void;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Enum for type-safe memory allocation in the runtime system.
|
|
433
|
+
* Prevents typos and provides IDE autocomplete support.
|
|
434
|
+
*/
|
|
435
|
+
declare enum MemoryTypeEnum {
|
|
436
|
+
/**
|
|
437
|
+
* Display stack state - Full UI display hierarchy state
|
|
438
|
+
* Type: IDisplayStackState (timer stack, card stack, workout state)
|
|
439
|
+
*/
|
|
440
|
+
DISPLAY_STACK_STATE = "displaystack",
|
|
441
|
+
/**
|
|
442
|
+
* Action stack state - Available actions surfaced to UI
|
|
443
|
+
* Type: ActionStackState
|
|
444
|
+
*/
|
|
445
|
+
ACTION_STACK_STATE = "actions",
|
|
446
|
+
/**
|
|
447
|
+
* Timer state prefix - use as `timer:${blockId}`
|
|
448
|
+
* Type: TimerState
|
|
449
|
+
*/
|
|
450
|
+
TIMER_PREFIX = "timer:",
|
|
451
|
+
/**
|
|
452
|
+
* Handler prefix - use as `handler:${id}`
|
|
453
|
+
* Type: IEventHandler
|
|
454
|
+
*/
|
|
455
|
+
HANDLER_PREFIX = "handler:",
|
|
456
|
+
/**
|
|
457
|
+
* Target reps for the current round/block
|
|
458
|
+
* Type: number
|
|
459
|
+
*/
|
|
460
|
+
METRIC_REPS = "metric:reps",
|
|
461
|
+
/**
|
|
462
|
+
* Anchor for cross-block references
|
|
463
|
+
* Type: IAnchorValue
|
|
464
|
+
*/
|
|
465
|
+
ANCHOR = "anchor"
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* The structure of the value stored within an anchor reference.
|
|
470
|
+
* An anchor's value is a meta-entry that describes how to find the actual data,
|
|
471
|
+
* rather than being the data itself.
|
|
472
|
+
*
|
|
473
|
+
* This enables dynamic data source resolution and decouples UI components
|
|
474
|
+
* from specific memory reference implementations.
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* ```typescript
|
|
478
|
+
* // Anchor pointing to a timer's time spans
|
|
479
|
+
* const anchorValue: IAnchorValue = {
|
|
480
|
+
* searchCriteria: {
|
|
481
|
+
* ownerId: 'stopwatch-block-id',
|
|
482
|
+
* type: MemoryTypeEnum.TIMER_TIME_SPANS,
|
|
483
|
+
* id: null,
|
|
484
|
+
* visibility: null
|
|
485
|
+
* }
|
|
486
|
+
* };
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
interface IAnchorValue {
|
|
490
|
+
/**
|
|
491
|
+
* Search criteria used to find the target memory reference(s).
|
|
492
|
+
* This is passed to IRuntimeMemory.search() to resolve the anchor.
|
|
493
|
+
*
|
|
494
|
+
* Typically specifies ownerId and type to locate the data source.
|
|
495
|
+
* Can be updated at runtime to dynamically change what the anchor points to.
|
|
496
|
+
*/
|
|
497
|
+
searchCriteria: Partial<MemorySearchCriteria>;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* BlockContext provides memory allocation and access for a runtime block.
|
|
502
|
+
*
|
|
503
|
+
* Design Principles:
|
|
504
|
+
* - Strategy-owned: Created by strategies before block creation
|
|
505
|
+
* - Constructor-injected: Passed to behaviors during instantiation
|
|
506
|
+
* - Explicit cleanup: Consumer must call release() after block disposal
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* ```typescript
|
|
510
|
+
* // Strategy allocates memory
|
|
511
|
+
* const context = new BlockContext(runtime, blockId);
|
|
512
|
+
* const timerRef = context.allocate<TimeSpan[]>('timer-time-spans', [], 'public');
|
|
513
|
+
*
|
|
514
|
+
* // Behavior receives memory via constructor
|
|
515
|
+
* const behavior = new TimerBehavior(timerRef);
|
|
516
|
+
*
|
|
517
|
+
* // Consumer handles cleanup
|
|
518
|
+
* block.dispose();
|
|
519
|
+
* block.context.release();
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
interface IBlockContext {
|
|
523
|
+
/**
|
|
524
|
+
* Unique identifier for the owning block.
|
|
525
|
+
* Used to track memory ownership in RuntimeMemory.
|
|
526
|
+
*/
|
|
527
|
+
readonly ownerId: string;
|
|
528
|
+
/**
|
|
529
|
+
* The ID of the exercise this block is executing.
|
|
530
|
+
* Used for metric collection and exercise definition lookup.
|
|
531
|
+
*/
|
|
532
|
+
readonly exerciseId: string;
|
|
533
|
+
/**
|
|
534
|
+
* All memory references allocated by this context.
|
|
535
|
+
* Readonly array to prevent external modification.
|
|
536
|
+
*/
|
|
537
|
+
readonly references: ReadonlyArray<IMemoryReference>;
|
|
538
|
+
/**
|
|
539
|
+
* Allocate memory for this block's state.
|
|
540
|
+
*
|
|
541
|
+
* @param type Memory type identifier from MemoryTypeEnum or custom string
|
|
542
|
+
* @param initialValue Optional initial value for the memory
|
|
543
|
+
* @param visibility 'public' for cross-block access, 'private' for internal use
|
|
544
|
+
* @returns Typed memory reference for accessing the allocated memory
|
|
545
|
+
* @throws Error if context has been released
|
|
546
|
+
*/
|
|
547
|
+
allocate<T>(type: MemoryTypeEnum | string, initialValue?: T, visibility?: 'public' | 'private'): TypedMemoryReference<T>;
|
|
548
|
+
/**
|
|
549
|
+
* Get the first memory reference of a specific type.
|
|
550
|
+
*
|
|
551
|
+
* @param type Memory type identifier from MemoryTypeEnum or custom string
|
|
552
|
+
* @returns Memory reference or undefined if not found
|
|
553
|
+
*/
|
|
554
|
+
get<T>(type: MemoryTypeEnum | string): TypedMemoryReference<T> | undefined;
|
|
555
|
+
/**
|
|
556
|
+
* Get all memory references of a specific type.
|
|
557
|
+
* Useful when multiple memory entries of the same type exist.
|
|
558
|
+
*
|
|
559
|
+
* @param type Memory type identifier to search for
|
|
560
|
+
* @returns Array of matching memory references (empty if none found)
|
|
561
|
+
*/
|
|
562
|
+
getAll<T>(type: string): TypedMemoryReference<T>[];
|
|
563
|
+
/**
|
|
564
|
+
* Set the value of a memory reference and dispatch a memory:set event.
|
|
565
|
+
* Use this method instead of ref.set() to ensure events are dispatched.
|
|
566
|
+
*
|
|
567
|
+
* @param reference The memory reference to update
|
|
568
|
+
* @param value The new value to set
|
|
569
|
+
* @throws Error if context has been released
|
|
570
|
+
*/
|
|
571
|
+
set<T>(reference: TypedMemoryReference<T>, value: T): void;
|
|
572
|
+
/**
|
|
573
|
+
* Release all allocated memory references.
|
|
574
|
+
* Must be called by consumer after block disposal.
|
|
575
|
+
*
|
|
576
|
+
* This method is idempotent - calling multiple times is safe.
|
|
577
|
+
* After release, allocate() will throw an error.
|
|
578
|
+
*/
|
|
579
|
+
release(): void;
|
|
580
|
+
/**
|
|
581
|
+
* Check if this context has been released.
|
|
582
|
+
*
|
|
583
|
+
* @returns true if release() has been called
|
|
584
|
+
*/
|
|
585
|
+
isReleased(): boolean;
|
|
586
|
+
/**
|
|
587
|
+
* Get or create an anchor reference with a stable ID.
|
|
588
|
+
*
|
|
589
|
+
* Anchors are special memory references that act as named pointers
|
|
590
|
+
* to other memory references. They enable UI components to subscribe
|
|
591
|
+
* to data without knowing the specific data source.
|
|
592
|
+
*
|
|
593
|
+
* If an anchor with the given ID already exists (from any owner),
|
|
594
|
+
* it returns the existing reference. Otherwise, it creates a new one.
|
|
595
|
+
*
|
|
596
|
+
* Anchors are always allocated with 'public' visibility so they can
|
|
597
|
+
* be found by UI components across the application.
|
|
598
|
+
*
|
|
599
|
+
* @param anchorId Stable, semantic ID for the anchor (e.g., 'anchor-main-workout-clock')
|
|
600
|
+
* @returns Typed memory reference for the anchor
|
|
601
|
+
* @throws Error if context has been released
|
|
602
|
+
*
|
|
603
|
+
* @example
|
|
604
|
+
* ```typescript
|
|
605
|
+
* const anchor = context.getOrCreateAnchor('anchor-main-clock');
|
|
606
|
+
* anchor.set({
|
|
607
|
+
* searchCriteria: {
|
|
608
|
+
* ownerId: timerBlockId,
|
|
609
|
+
* type: MemoryTypeEnum.TIMER_TIME_SPANS
|
|
610
|
+
* }
|
|
611
|
+
* });
|
|
612
|
+
* ```
|
|
613
|
+
*/
|
|
614
|
+
getOrCreateAnchor(anchorId: string): TypedMemoryReference<IAnchorValue>;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Base interface for runtime events that can be handled by the system.
|
|
619
|
+
*/
|
|
620
|
+
interface IEvent {
|
|
621
|
+
/** Name/type of the event */
|
|
622
|
+
name: string;
|
|
623
|
+
/** Timestamp when the event occurred */
|
|
624
|
+
timestamp: Date;
|
|
625
|
+
/** Additional event data */
|
|
626
|
+
data?: unknown;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Tags for memory locations. A block can have multiple locations with the same tag.
|
|
631
|
+
* Tags replace the previous MemoryType keys but are no longer unique per block.
|
|
632
|
+
*
|
|
633
|
+
* Fragment tags follow the `metrics:*` namespace convention and are classified
|
|
634
|
+
* into three visibility tiers:
|
|
635
|
+
*
|
|
636
|
+
* | Tier | Tags | Purpose |
|
|
637
|
+
* |-----------|------------------------------------------|----------------------------------|
|
|
638
|
+
* | display | `metrics:display` | Shown on UI cards |
|
|
639
|
+
* | result | `metric:result` | Block output collected on pop |
|
|
640
|
+
* | promote | `metric:promote`, `metric:rep-target` | Inherited by child blocks |
|
|
641
|
+
* | private | `metric:tracked`, `metric:label` | Internal behavior state |
|
|
642
|
+
*
|
|
643
|
+
* @see MetricVisibility for classification helpers.
|
|
644
|
+
*/
|
|
645
|
+
type MemoryTag = 'time' | 'round' | 'children:status' | 'completion' | 'display' | 'controls' | 'metrics' | 'metric:display' | 'metric:promote' | 'metric:rep-target' | 'metric:result' | 'metric:tracked' | 'metric:label' | 'metric:hint' | 'metric:next';
|
|
646
|
+
/**
|
|
647
|
+
* A single memory location in a block's memory list.
|
|
648
|
+
*
|
|
649
|
+
* Memory locations are the fundamental unit of state storage in the runtime.
|
|
650
|
+
* Multiple locations with the same tag can coexist, enabling:
|
|
651
|
+
* - Multiple timers per block
|
|
652
|
+
* - Multiple display rows per block (metrics:display)
|
|
653
|
+
* - Multiple round counters
|
|
654
|
+
*
|
|
655
|
+
* All memory values are MetricContainer instances — metrics are the universal currency.
|
|
656
|
+
*/
|
|
657
|
+
interface IMemoryLocation {
|
|
658
|
+
/** Discriminator tag — multiple locations can share the same tag */
|
|
659
|
+
readonly tag: MemoryTag;
|
|
660
|
+
/** The metrics data stored at this location */
|
|
661
|
+
readonly metrics: MetricContainer;
|
|
662
|
+
/** Subscribe to changes at this location */
|
|
663
|
+
subscribe(listener: (newValue: MetricContainer, oldValue: MetricContainer) => void): () => void;
|
|
664
|
+
/** Update the metrics at this location */
|
|
665
|
+
update(metrics: MetricContainer | IMetric[]): void;
|
|
666
|
+
/** Dispose of this location and notify subscribers */
|
|
667
|
+
dispose(): void;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Concrete implementation of a memory location.
|
|
671
|
+
*
|
|
672
|
+
* Provides observable state storage with subscription management.
|
|
673
|
+
* When disposed, sends empty array to all subscribers for cleanup.
|
|
674
|
+
*/
|
|
675
|
+
declare class MemoryLocation implements IMemoryLocation {
|
|
676
|
+
readonly tag: MemoryTag;
|
|
677
|
+
private _listeners;
|
|
678
|
+
private _metrics;
|
|
679
|
+
private _isDisposed;
|
|
680
|
+
constructor(tag: MemoryTag, initialFragments?: MetricContainer | IMetric[]);
|
|
681
|
+
get metrics(): MetricContainer;
|
|
682
|
+
update(metrics: MetricContainer | IMetric[]): void;
|
|
683
|
+
subscribe(listener: (nv: MetricContainer, ov: MetricContainer) => void): () => void;
|
|
684
|
+
dispose(): void;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* MetricVisibility.ts - Categorizes metrics memory tags by visibility level.
|
|
689
|
+
*
|
|
690
|
+
* All `metrics:*` memory tags carry typed metrics on runtime blocks.
|
|
691
|
+
* This module introduces a three-tier visibility classification so the UI,
|
|
692
|
+
* JIT compiler, and runtime can treat metrics memory consistently:
|
|
693
|
+
*
|
|
694
|
+
* | Visibility | Purpose | Example tags |
|
|
695
|
+
* |------------|---------|-------------|
|
|
696
|
+
* | `display` | Shown on the workout card in the UI (one row per location) | `metrics:display` |
|
|
697
|
+
* | `result` | Block output metric collected when the block is popped | `metric:result` |
|
|
698
|
+
* | `promote` | Promoted/inherited by child blocks during compilation | `metric:promote`, `metric:rep-target` |
|
|
699
|
+
* | `private` | Internal behavior state; hidden from UI in normal mode | `metric:tracked`, `metric:label` |
|
|
700
|
+
*
|
|
701
|
+
* In **debug mode** the UI shows all three tiers, each with a distinguishing
|
|
702
|
+
* icon so developers can inspect the full memory surface of every block.
|
|
703
|
+
*/
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* The four visibility tiers for metrics memory tags.
|
|
707
|
+
*/
|
|
708
|
+
type MetricVisibility = 'display' | 'result' | 'promote' | 'private';
|
|
709
|
+
/**
|
|
710
|
+
* Returns the visibility tier for a given memory tag.
|
|
711
|
+
*
|
|
712
|
+
* - Known `metrics:*` tags are mapped via the static sets above.
|
|
713
|
+
* - Any unknown `metric:*` tag defaults to `'private'`.
|
|
714
|
+
* - Non-metric tags (e.g., `'timer'`, `'round'`) return `undefined`.
|
|
715
|
+
*/
|
|
716
|
+
declare function getMetricVisibility(tag: MemoryTag): MetricVisibility | undefined;
|
|
717
|
+
/**
|
|
718
|
+
* Returns `true` if the tag belongs to a metrics namespace.
|
|
719
|
+
*/
|
|
720
|
+
declare function isFragmentTag(tag: MemoryTag): boolean;
|
|
721
|
+
/**
|
|
722
|
+
* Filter memory tags by visibility tier.
|
|
723
|
+
*/
|
|
724
|
+
declare function filterTagsByVisibility(tags: readonly MemoryTag[], visibility: MetricVisibility): MemoryTag[];
|
|
725
|
+
/**
|
|
726
|
+
* Unicode icon for each visibility tier.
|
|
727
|
+
* Used in debug mode UI to label each metrics row.
|
|
728
|
+
*/
|
|
729
|
+
declare const VISIBILITY_ICONS: Record<MetricVisibility, string>;
|
|
730
|
+
/**
|
|
731
|
+
* Short label for each visibility tier (used in debug badges).
|
|
732
|
+
*/
|
|
733
|
+
declare const VISIBILITY_LABELS: Record<MetricVisibility, string>;
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Minimal block reference primitive consumed by {@link IBehaviorContext}.
|
|
737
|
+
*
|
|
738
|
+
* `IRuntimeBlock` extends this primitive with runtime-side lifecycle methods
|
|
739
|
+
* (`mount`, `next`, `unmount`, `dispose`). Defining the consumer-facing
|
|
740
|
+
* surface here lets `IBehaviorContext` reference a block without importing
|
|
741
|
+
* `IRuntimeBlock`, which in turn breaks the cycle:
|
|
742
|
+
*
|
|
743
|
+
* IBehaviorContext → IRuntimeBlock → IRuntimeBehavior → IBehaviorContext
|
|
744
|
+
*
|
|
745
|
+
* The shape includes the full surface that behavior implementations use
|
|
746
|
+
* through `ctx.block.*`, but intentionally avoids importing
|
|
747
|
+
* `IRuntimeBehavior` (which depends on `IBehaviorContext`). The `behaviors`
|
|
748
|
+
* array is therefore typed as opaque objects here and refined to
|
|
749
|
+
* `readonly IRuntimeBehavior[]` on `IRuntimeBlock` itself.
|
|
750
|
+
*/
|
|
751
|
+
interface IBlockRef {
|
|
752
|
+
/** Unique identifier for this block instance. */
|
|
753
|
+
readonly key: BlockKey;
|
|
754
|
+
/** Source code location identifiers. */
|
|
755
|
+
readonly sourceIds: number[];
|
|
756
|
+
/** Type discriminator for UI display and logging. */
|
|
757
|
+
readonly blockType?: string;
|
|
758
|
+
/** Human-readable label for the block. */
|
|
759
|
+
readonly label: string;
|
|
760
|
+
/** The execution context for this block (memory allocation/cleanup). */
|
|
761
|
+
readonly context: IBlockContext;
|
|
762
|
+
/** Whether this block has completed execution. */
|
|
763
|
+
readonly isComplete: boolean;
|
|
764
|
+
/** Reason the block completed, if any. */
|
|
765
|
+
readonly completionReason?: string;
|
|
766
|
+
/**
|
|
767
|
+
* Read-only view of the behaviors attached to the block.
|
|
768
|
+
*
|
|
769
|
+
* Intentionally typed as opaque objects in the primitive layer to avoid
|
|
770
|
+
* importing `IRuntimeBehavior` (which would re-introduce a cycle through
|
|
771
|
+
* `IBehaviorContext`). `IRuntimeBlock` refines this to
|
|
772
|
+
* `readonly IRuntimeBehavior[]`.
|
|
773
|
+
*/
|
|
774
|
+
readonly behaviors: ReadonlyArray<{
|
|
775
|
+
readonly constructor: Function;
|
|
776
|
+
}>;
|
|
777
|
+
/** Push a memory location onto the block's memory list. */
|
|
778
|
+
pushMemory(location: IMemoryLocation): void;
|
|
779
|
+
/** Get all memory locations matching the given tag. */
|
|
780
|
+
getMemoryByTag(tag: MemoryTag): IMemoryLocation[];
|
|
781
|
+
/** Get all memory locations owned by this block. */
|
|
782
|
+
getAllMemory(): IMemoryLocation[];
|
|
783
|
+
/** Get all metrics memory locations matching a given visibility tier. */
|
|
784
|
+
getMetricMemoryByVisibility(visibility: MetricVisibility): IMemoryLocation[];
|
|
785
|
+
/** Mark the block as complete (idempotent). */
|
|
786
|
+
markComplete(reason?: string): void;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* TimeSpan represents a discrete segment of a block's **Time** — a single
|
|
791
|
+
* start/stop recording within the spans collection.
|
|
792
|
+
*
|
|
793
|
+
* Multiple TimeSpans allow for pause/resume tracking.
|
|
794
|
+
*
|
|
795
|
+
* Note: The `.duration` getter computes the length of this individual span
|
|
796
|
+
* (end − start). This differs from the glossary term **Duration**, which
|
|
797
|
+
* refers to the parser-defined planned target. To avoid confusion, prefer
|
|
798
|
+
* the term "span length" when discussing individual span measurements.
|
|
799
|
+
*
|
|
800
|
+
* @see docs/architecture/time-terminology.md
|
|
801
|
+
*/
|
|
802
|
+
|
|
803
|
+
declare class TimeSpan implements TimeSpan$1 {
|
|
804
|
+
started: number;
|
|
805
|
+
ended?: number | undefined;
|
|
806
|
+
constructor(started: number, ended?: number | undefined);
|
|
807
|
+
static fromJSON(json: any): TimeSpan;
|
|
808
|
+
/**
|
|
809
|
+
* Create a TimeSpan from Date objects (legacy support/compatibility)
|
|
810
|
+
*/
|
|
811
|
+
static fromDates(start: Date, end?: Date): TimeSpan;
|
|
812
|
+
/**
|
|
813
|
+
* Length of this individual span in milliseconds.
|
|
814
|
+
* Uses current time if the span is still open.
|
|
815
|
+
*
|
|
816
|
+
* Note: Not to be confused with the glossary term "Duration" which
|
|
817
|
+
* refers to the parser-defined planned target (DurationMetric).
|
|
818
|
+
*/
|
|
819
|
+
get duration(): number;
|
|
820
|
+
/**
|
|
821
|
+
* Whether the span is currently open (no end time).
|
|
822
|
+
*/
|
|
823
|
+
get isOpen(): boolean;
|
|
824
|
+
get startDate(): Date;
|
|
825
|
+
get endDate(): Date | undefined;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* INowProvider — the only seam for "what time is it" in the runtime.
|
|
830
|
+
* Events, actions, and behaviors that need a timestamp take a now-fn
|
|
831
|
+
* in their constructor; the runtime passes its single provider down.
|
|
832
|
+
* Tests pass a frozen clock so the timeline is deterministic.
|
|
833
|
+
*/
|
|
834
|
+
interface INowProvider {
|
|
835
|
+
now(): Date;
|
|
836
|
+
nowMs(): number;
|
|
837
|
+
}
|
|
838
|
+
/** Default provider: reads wall clock. */
|
|
839
|
+
declare const wallClockNow: INowProvider;
|
|
840
|
+
/** A provider that always returns the same Date (for tests). */
|
|
841
|
+
declare function frozenNow(at: Date): INowProvider;
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* IRuntimeClock provides time tracking for the runtime.
|
|
845
|
+
*
|
|
846
|
+
* It maintains a list of time spans created by start/stop calls,
|
|
847
|
+
* and provides:
|
|
848
|
+
* - `currentDate`: Current wall-clock time as a Date
|
|
849
|
+
* - `elapsed`: Total milliseconds of running time (sum of all spans)
|
|
850
|
+
* - `start()`/`stop()`: Control methods that return timestamps for event creation
|
|
851
|
+
*
|
|
852
|
+
* Renamed from `now` → `currentDate` to free the `now()` method for the INowProvider seam.
|
|
853
|
+
* Callers should use `clock.now()` for the provider contract, `clock.currentDate` for the raw Date.
|
|
854
|
+
*/
|
|
855
|
+
interface IRuntimeClock extends INowProvider {
|
|
856
|
+
/** Current wall-clock time as a Date */
|
|
857
|
+
readonly currentDate: Date;
|
|
858
|
+
/** Total elapsed milliseconds while running (sum of all completed spans + current running span) */
|
|
859
|
+
readonly elapsed: number;
|
|
860
|
+
/** Whether the clock is currently running (has an open span) */
|
|
861
|
+
readonly isRunning: boolean;
|
|
862
|
+
/** All time spans tracked by this clock */
|
|
863
|
+
readonly spans: ReadonlyArray<TimeSpan>;
|
|
864
|
+
/** Start the clock, returns the start timestamp for event creation */
|
|
865
|
+
start(): Date;
|
|
866
|
+
/** Stop the clock, returns the stop timestamp for event creation */
|
|
867
|
+
stop(): Date;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Runtime context surface required by {@link IEventBus} dispatchers,
|
|
872
|
+
* handlers, and callbacks.
|
|
873
|
+
*
|
|
874
|
+
* Extends {@link IRuntimeActionable} (so callers can queue follow-up
|
|
875
|
+
* actions) with the minimal accessors the event bus actually consumes:
|
|
876
|
+
*
|
|
877
|
+
* - `stack` — used for handler scope filtering ('active' vs 'bubble' vs
|
|
878
|
+
* 'global'). Only the keys are needed, so the shape is intentionally
|
|
879
|
+
* minimal and does not import `IRuntimeStack` / `IRuntimeBlock`.
|
|
880
|
+
* - `errors` — used by `dispatch()` to short-circuit handler chains when
|
|
881
|
+
* a previous handler pushed a `RuntimeError`.
|
|
882
|
+
* - `handle(event)` — used by `emit()` to route the event back through
|
|
883
|
+
* the runtime's own dispatch flow.
|
|
884
|
+
*
|
|
885
|
+
* Defining this primitive (instead of typing the bus parameters as the
|
|
886
|
+
* looser {@link IRuntimeActionable}) prevents the
|
|
887
|
+
* `IEventBus ↔ IScriptRuntime` cycle while keeping the contract
|
|
888
|
+
* type-accurate so callers cannot pass a stub that only provides
|
|
889
|
+
* `do()`/`doAll()` and crash at runtime when the bus reaches for
|
|
890
|
+
* `runtime.stack`.
|
|
891
|
+
*/
|
|
892
|
+
|
|
893
|
+
/** Minimal stack snapshot accessor used by event-scope filtering. */
|
|
894
|
+
interface IEventDispatchStack {
|
|
895
|
+
/** The current (top-of-stack) block reference, if any. */
|
|
896
|
+
readonly current: {
|
|
897
|
+
readonly key: {
|
|
898
|
+
toString(): string;
|
|
899
|
+
};
|
|
900
|
+
} | undefined;
|
|
901
|
+
/** Block keys for every block on the stack, bottom-to-top. */
|
|
902
|
+
readonly keys: ReadonlyArray<{
|
|
903
|
+
toString(): string;
|
|
904
|
+
}>;
|
|
905
|
+
}
|
|
906
|
+
interface IEventDispatchContext extends IRuntimeActionable {
|
|
907
|
+
/** Stack accessor used for handler scope filtering. */
|
|
908
|
+
readonly stack: IEventDispatchStack;
|
|
909
|
+
/**
|
|
910
|
+
* Optional list of accumulated runtime errors. The bus uses
|
|
911
|
+
* `errors.length` to short-circuit handler chains.
|
|
912
|
+
*/
|
|
913
|
+
readonly errors?: ReadonlyArray<unknown>;
|
|
914
|
+
/**
|
|
915
|
+
* Route an event back through the runtime's own dispatch flow.
|
|
916
|
+
* Used by `IEventBus.emit()`.
|
|
917
|
+
*/
|
|
918
|
+
handle(event: {
|
|
919
|
+
readonly name: string;
|
|
920
|
+
readonly timestamp: Date;
|
|
921
|
+
readonly data?: unknown;
|
|
922
|
+
}): void;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Interface for handling runtime events and producing actions.
|
|
927
|
+
* Each handler is responsible for a specific type of event processing.
|
|
928
|
+
*
|
|
929
|
+
* Handlers return an array of actions to execute. An empty array means
|
|
930
|
+
* the handler did not handle the event or has no actions to perform.
|
|
931
|
+
*
|
|
932
|
+
* For error handling, use the ErrorAction to push errors to runtime.errors.
|
|
933
|
+
*
|
|
934
|
+
* The runtime is typed as the {@link IEventDispatchContext} primitive so
|
|
935
|
+
* handlers can rely on the stack accessors / `do()` / `handle()` surface
|
|
936
|
+
* the bus already requires, without importing `IScriptRuntime` and
|
|
937
|
+
* re-introducing the contract-layer cycle. Concrete handler implementations
|
|
938
|
+
* are free to narrow their parameter type to `IScriptRuntime`.
|
|
939
|
+
*/
|
|
940
|
+
interface IEventHandler {
|
|
941
|
+
/** Unique identifier for this handler */
|
|
942
|
+
readonly id: string;
|
|
943
|
+
/** Name of the handler for logging/debugging */
|
|
944
|
+
readonly name: string;
|
|
945
|
+
/**
|
|
946
|
+
* Handles the event and returns an array of actions to execute.
|
|
947
|
+
* @param event The event to handle
|
|
948
|
+
* @param runtime Runtime context for event processing (primitive form)
|
|
949
|
+
* @returns Array of actions to execute (empty if event not handled)
|
|
950
|
+
*/
|
|
951
|
+
handler(event: IEvent, runtime: IEventDispatchContext): IRuntimeAction[];
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Simple callback type for UI event listeners that don't produce actions.
|
|
956
|
+
*
|
|
957
|
+
* Receives an {@link IEventDispatchContext} — the same primitive used by
|
|
958
|
+
* {@link IEventBus.dispatch} — so callbacks can rely on the stack
|
|
959
|
+
* accessors and `handle()` / `do()` surface the bus already requires.
|
|
960
|
+
* This avoids importing `IScriptRuntime` (which would re-introduce the
|
|
961
|
+
* `IEventBus ↔ IScriptRuntime` cycle) while keeping the contract
|
|
962
|
+
* type-accurate. Concrete callbacks are free to narrow the parameter type
|
|
963
|
+
* to `IScriptRuntime`.
|
|
964
|
+
*/
|
|
965
|
+
type EventCallback = (event: IEvent, runtime: IEventDispatchContext) => void;
|
|
966
|
+
/**
|
|
967
|
+
* Handler scope determines when a handler receives events.
|
|
968
|
+
*
|
|
969
|
+
* - 'active' (default): Only fires when the owner block is the current/active block on the stack.
|
|
970
|
+
* - 'bubble': Fires when the owner block is anywhere on the stack (allows parent listening).
|
|
971
|
+
* - 'global': Always fires regardless of stack state (for runtime-level handlers).
|
|
972
|
+
*/
|
|
973
|
+
type HandlerScope = 'active' | 'bubble' | 'global';
|
|
974
|
+
/**
|
|
975
|
+
* Options for registering an event handler.
|
|
976
|
+
*/
|
|
977
|
+
interface EventHandlerOptions {
|
|
978
|
+
/** Handler priority (higher priority handlers execute first). Default: 0 */
|
|
979
|
+
priority?: number;
|
|
980
|
+
/** Handler scope determining when it receives events. Default: 'active' */
|
|
981
|
+
scope?: HandlerScope;
|
|
982
|
+
}
|
|
983
|
+
interface IEventBus {
|
|
984
|
+
/**
|
|
985
|
+
* Register a full event handler that can produce runtime actions.
|
|
986
|
+
* Used for runtime-internal event processing.
|
|
987
|
+
*
|
|
988
|
+
* @param eventName - The event name to listen for ('*' for all events)
|
|
989
|
+
* @param handler - The event handler
|
|
990
|
+
* @param ownerId - The owner block ID (used for scope filtering and cleanup)
|
|
991
|
+
* @param options - Handler options including priority and scope
|
|
992
|
+
* @returns Unsubscribe function
|
|
993
|
+
*/
|
|
994
|
+
register(eventName: string, handler: IEventHandler, ownerId: string, options?: EventHandlerOptions): () => void;
|
|
995
|
+
/**
|
|
996
|
+
* Register a simple callback for event notifications.
|
|
997
|
+
* Used for UI components that need to react to events without producing actions.
|
|
998
|
+
* Returns an unsubscribe function.
|
|
999
|
+
*/
|
|
1000
|
+
on(eventName: string, callback: EventCallback, ownerId: string): () => void;
|
|
1001
|
+
unregisterById(handlerId: string): void;
|
|
1002
|
+
unregisterByOwner(ownerId: string): void;
|
|
1003
|
+
/**
|
|
1004
|
+
* Dispatch an event and return the resulting actions without executing them.
|
|
1005
|
+
* Used internally when actions need special handling.
|
|
1006
|
+
*
|
|
1007
|
+
* @param runtime - The runtime context. Typed as the
|
|
1008
|
+
* {@link IEventDispatchContext} primitive so the implementation can rely
|
|
1009
|
+
* on `stack` (for scope filtering) and `errors` (for early-exit) without
|
|
1010
|
+
* importing `IScriptRuntime` and re-introducing the
|
|
1011
|
+
* `IEventBus ↔ IScriptRuntime` cycle.
|
|
1012
|
+
*/
|
|
1013
|
+
dispatch(event: IEvent, runtime: IEventDispatchContext): IRuntimeAction[];
|
|
1014
|
+
/**
|
|
1015
|
+
* Dispatch an event and execute all resulting actions immediately.
|
|
1016
|
+
* This is the primary entry point for event handling.
|
|
1017
|
+
*
|
|
1018
|
+
* @param event - The event to dispatch
|
|
1019
|
+
* @param runtime - The runtime context. Typed as the
|
|
1020
|
+
* {@link IEventDispatchContext} primitive so the implementation can call
|
|
1021
|
+
* `runtime.handle(event)` without importing `IScriptRuntime`.
|
|
1022
|
+
*/
|
|
1023
|
+
emit(event: IEvent, runtime: IEventDispatchContext): void;
|
|
1024
|
+
/**
|
|
1025
|
+
* Dispose of all handlers and callbacks, releasing all references.
|
|
1026
|
+
* Call this during runtime cleanup to prevent memory leaks.
|
|
1027
|
+
*/
|
|
1028
|
+
dispose(): void;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Unsubscribe function returned by subscribe().
|
|
1033
|
+
*/
|
|
1034
|
+
type Unsubscribe$1 = () => void;
|
|
1035
|
+
/**
|
|
1036
|
+
* Event types that behaviors can subscribe to.
|
|
1037
|
+
*/
|
|
1038
|
+
type BehaviorEventType = 'tick' | 'next' | 'timer:complete' | 'pause' | 'resume' | '*';
|
|
1039
|
+
/**
|
|
1040
|
+
* Listener function for behavior event subscriptions.
|
|
1041
|
+
*/
|
|
1042
|
+
type BehaviorEventListener = (event: IEvent, ctx: IBehaviorContext) => IRuntimeAction[];
|
|
1043
|
+
/**
|
|
1044
|
+
* Options for subscribing to events.
|
|
1045
|
+
*/
|
|
1046
|
+
interface SubscribeOptions {
|
|
1047
|
+
/**
|
|
1048
|
+
* Handler scope for this subscription.
|
|
1049
|
+
* - `'active'` (default): Only fires when this block is the active (top-of-stack) block
|
|
1050
|
+
* - `'bubble'`: Fires when this block is anywhere on the stack (parent hears child events)
|
|
1051
|
+
* - `'global'`: Always fires regardless of stack state
|
|
1052
|
+
*/
|
|
1053
|
+
scope?: HandlerScope;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Options for emitting an output statement.
|
|
1057
|
+
*/
|
|
1058
|
+
interface OutputOptions {
|
|
1059
|
+
/** Human-readable label for the output */
|
|
1060
|
+
label?: string;
|
|
1061
|
+
/**
|
|
1062
|
+
* Reason the block completed.
|
|
1063
|
+
*
|
|
1064
|
+
* Propagated to OutputStatement so downstream consumers can distinguish:
|
|
1065
|
+
* - `'user-advance'` — self-pop (user clicked next)
|
|
1066
|
+
* - `'forced-pop'` — parent-pop (parent timer expired, etc.)
|
|
1067
|
+
* - `'timer-expired'` — block's own timer completed
|
|
1068
|
+
*/
|
|
1069
|
+
completionReason?: string;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* IBehaviorContext is the unified context object passed to behavior lifecycle hooks.
|
|
1073
|
+
*
|
|
1074
|
+
* It provides everything a behavior needs to interact with the runtime:
|
|
1075
|
+
* - Access to the block and clock
|
|
1076
|
+
* - Event subscription during mount (for tick, next, etc.)
|
|
1077
|
+
* - Output emission at any lifecycle point
|
|
1078
|
+
* - Typed memory access
|
|
1079
|
+
* - Block completion control
|
|
1080
|
+
*
|
|
1081
|
+
* ## Design Philosophy
|
|
1082
|
+
*
|
|
1083
|
+
* This context replaces the old `(block, clock)` tuple with a richer API that:
|
|
1084
|
+
* 1. **Enables event subscription**: Behaviors can register for tick/next events during mount
|
|
1085
|
+
* 2. **Supports output emission**: Behaviors emit IOutputStatements at any lifecycle point
|
|
1086
|
+
* 3. **Provides typed memory**: Read and write to block-owned memory with type safety
|
|
1087
|
+
* 4. **Separates events from outputs**: Events are coordination, outputs are reporting
|
|
1088
|
+
*
|
|
1089
|
+
* @example
|
|
1090
|
+
* ```typescript
|
|
1091
|
+
* class TimerBehavior implements IRuntimeBehavior {
|
|
1092
|
+
* onMount(ctx: IBehaviorContext) {
|
|
1093
|
+
* // Subscribe to tick events
|
|
1094
|
+
* ctx.subscribe('tick', (event, ctx) => {
|
|
1095
|
+
* const [timerLoc] = ctx.getMemoryByTag('time');
|
|
1096
|
+
* if (timer?.elapsed >= timer?.duration) {
|
|
1097
|
+
* ctx.markComplete('timer:complete');
|
|
1098
|
+
* }
|
|
1099
|
+
* return [];
|
|
1100
|
+
* });
|
|
1101
|
+
*
|
|
1102
|
+
* // Emit segment started output
|
|
1103
|
+
* ctx.emitOutput('segment', [timerFragment], { label: '5:00 countdown' });
|
|
1104
|
+
*
|
|
1105
|
+
* return [];
|
|
1106
|
+
* }
|
|
1107
|
+
*
|
|
1108
|
+
* onUnmount(ctx: IBehaviorContext) {
|
|
1109
|
+
* // Emit a milestone marking this behavior's own completion. The block's
|
|
1110
|
+
* // final 'segment' output (carrying completionReason) is emitted separately
|
|
1111
|
+
* // by the runtime when the block pops — behaviors should not emit their own
|
|
1112
|
+
* // 'segment'/'completion'-shaped output on unmount; see ReportOutputBehavior
|
|
1113
|
+
* // for the canonical pattern (write result memory, let the pop emit the segment).
|
|
1114
|
+
* ctx.emitOutput('milestone', [elapsedFragment], { label: 'Timer Complete' });
|
|
1115
|
+
* return [];
|
|
1116
|
+
* }
|
|
1117
|
+
* }
|
|
1118
|
+
* ```
|
|
1119
|
+
*/
|
|
1120
|
+
interface IBehaviorContext {
|
|
1121
|
+
/** The block this behavior is attached to */
|
|
1122
|
+
readonly block: IBlockRef;
|
|
1123
|
+
/** Current runtime clock */
|
|
1124
|
+
readonly clock: IRuntimeClock;
|
|
1125
|
+
/**
|
|
1126
|
+
* Current stack level (depth) of the block.
|
|
1127
|
+
* 0 = root block, 1 = first child, etc.
|
|
1128
|
+
*/
|
|
1129
|
+
readonly stackLevel: number;
|
|
1130
|
+
/**
|
|
1131
|
+
* Subscribe to runtime events.
|
|
1132
|
+
*
|
|
1133
|
+
* Subscriptions are automatically cleaned up when the block unmounts.
|
|
1134
|
+
* By default, the listener only fires when this block is the active block.
|
|
1135
|
+
* Use `{ scope: 'bubble' }` to receive events when a child block is active
|
|
1136
|
+
* (e.g., parent timer tracking ticks while children execute).
|
|
1137
|
+
*
|
|
1138
|
+
* @param eventType The event type to subscribe to ('tick', 'next', etc.)
|
|
1139
|
+
* @param listener The callback to invoke when the event fires
|
|
1140
|
+
* @param options Optional settings including handler scope
|
|
1141
|
+
* @returns Unsubscribe function (usually not needed as cleanup is automatic)
|
|
1142
|
+
*
|
|
1143
|
+
* @example
|
|
1144
|
+
* ```typescript
|
|
1145
|
+
* onMount(ctx) {
|
|
1146
|
+
* // Active scope (default) — only fires when this block is on top
|
|
1147
|
+
* ctx.subscribe('next', (event, ctx) => {
|
|
1148
|
+
* return [];
|
|
1149
|
+
* });
|
|
1150
|
+
*
|
|
1151
|
+
* // Bubble scope — fires even when a child block is active
|
|
1152
|
+
* ctx.subscribe('tick', (event, ctx) => {
|
|
1153
|
+
* // Track timer even when child effort blocks are executing
|
|
1154
|
+
* return [];
|
|
1155
|
+
* }, { scope: 'bubble' });
|
|
1156
|
+
* }
|
|
1157
|
+
* ```
|
|
1158
|
+
*/
|
|
1159
|
+
subscribe(eventType: BehaviorEventType, listener: BehaviorEventListener, options?: SubscribeOptions): Unsubscribe$1;
|
|
1160
|
+
/**
|
|
1161
|
+
* Emit an event to the runtime event bus.
|
|
1162
|
+
*
|
|
1163
|
+
* Use this for **coordination** — triggering reactions in other parts of the system.
|
|
1164
|
+
* For example: 'timer:started', 'timer:complete', 'pause', 'resume'.
|
|
1165
|
+
*
|
|
1166
|
+
* @param event The event to emit
|
|
1167
|
+
*/
|
|
1168
|
+
emitEvent(event: IEvent): void;
|
|
1169
|
+
/**
|
|
1170
|
+
* Emit an output statement.
|
|
1171
|
+
*
|
|
1172
|
+
* Use this for **reporting** — recording what happened during execution.
|
|
1173
|
+
* Output statements are collected by the runtime and made available to UI/history.
|
|
1174
|
+
*
|
|
1175
|
+
* Output can be emitted at any lifecycle point:
|
|
1176
|
+
* - onMount: 'segment' started
|
|
1177
|
+
* - onNext: 'segment' per iteration, 'milestone' events
|
|
1178
|
+
* - onUnmount: write final results to memory (see ReportOutputBehavior) rather
|
|
1179
|
+
* than emitting directly — the runtime emits exactly one 'segment' output per
|
|
1180
|
+
* block pop, carrying those results plus a `completionReason` field
|
|
1181
|
+
*
|
|
1182
|
+
* @param type The type of output ('segment', 'milestone', 'metric', 'label')
|
|
1183
|
+
* @param metrics The data metrics that make up this output
|
|
1184
|
+
* @param options Optional metadata (label)
|
|
1185
|
+
*
|
|
1186
|
+
* @example
|
|
1187
|
+
* ```typescript
|
|
1188
|
+
* // Emit a segment for round 3
|
|
1189
|
+
* ctx.emitOutput('segment', [
|
|
1190
|
+
* { metricType: MetricType.Rounds, value: 3, origin: 'runtime' }
|
|
1191
|
+
* ], { label: 'Round 3 of 5' });
|
|
1192
|
+
*
|
|
1193
|
+
* // Emit a milestone marking a notable event mid-execution
|
|
1194
|
+
* ctx.emitOutput('milestone', [
|
|
1195
|
+
* { metricType: MetricType.Duration, value: elapsed, origin: 'runtime' }
|
|
1196
|
+
* ], { label: 'Halfway' });
|
|
1197
|
+
* ```
|
|
1198
|
+
*/
|
|
1199
|
+
emitOutput(type: OutputStatementType, metrics: MetricContainer | IMetric[], options?: OutputOptions): void;
|
|
1200
|
+
/**
|
|
1201
|
+
* Push a new memory location with metrics data onto the block's memory list.
|
|
1202
|
+
*
|
|
1203
|
+
* This creates a new MemoryLocation internally and returns it for potential updates.
|
|
1204
|
+
* Multiple locations with the same tag can coexist on the same block.
|
|
1205
|
+
*
|
|
1206
|
+
* @param tag The memory tag (e.g., 'timer', 'metric:display')
|
|
1207
|
+
* @param metric The metric array to store at this location
|
|
1208
|
+
* @returns The created memory location for optional further manipulation
|
|
1209
|
+
*
|
|
1210
|
+
* @example
|
|
1211
|
+
* ```typescript
|
|
1212
|
+
* // Push timer metrics
|
|
1213
|
+
* ctx.pushMemory('time', [timerFragment]);
|
|
1214
|
+
*
|
|
1215
|
+
* // Push display row (metrics:display)
|
|
1216
|
+
* ctx.pushMemory('metric:display', [timerFrag, actionFrag, effortFrag]);
|
|
1217
|
+
* ```
|
|
1218
|
+
*/
|
|
1219
|
+
pushMemory(tag: MemoryTag, metrics: MetricContainer | IMetric[]): IMemoryLocation;
|
|
1220
|
+
/**
|
|
1221
|
+
* Get all memory locations matching the given tag.
|
|
1222
|
+
* Delegates to block.getMemoryByTag — use this inside behaviors instead of
|
|
1223
|
+
* the deprecated getMemory() shim.
|
|
1224
|
+
*/
|
|
1225
|
+
getMemoryByTag(tag: MemoryTag): IMemoryLocation[];
|
|
1226
|
+
/**
|
|
1227
|
+
* Update the first matching memory location with new metrics data.
|
|
1228
|
+
*
|
|
1229
|
+
* Convenience method for updating existing memory without managing references.
|
|
1230
|
+
* If no location with the given tag exists, this is a no-op.
|
|
1231
|
+
*
|
|
1232
|
+
* @param tag The memory tag to update
|
|
1233
|
+
* @param metrics The new metrics array
|
|
1234
|
+
*
|
|
1235
|
+
* @example
|
|
1236
|
+
* ```typescript
|
|
1237
|
+
* // Update timer metrics with new elapsed time
|
|
1238
|
+
* ctx.updateMemory('time', [updatedTimerMetric]);
|
|
1239
|
+
* ```
|
|
1240
|
+
*/
|
|
1241
|
+
updateMemory(tag: MemoryTag, metrics: MetricContainer | IMetric[]): void;
|
|
1242
|
+
/**
|
|
1243
|
+
* Declare that this block (via one of its behaviors) provides the named
|
|
1244
|
+
* capability. Other behaviors on the same block can then coordinate with
|
|
1245
|
+
* the declarer via `hasCapability` instead of string-inspecting
|
|
1246
|
+
* `behaviors[i].constructor.name`.
|
|
1247
|
+
*
|
|
1248
|
+
* Capabilities are block-scoped: they are declared during `onMount`
|
|
1249
|
+
* and consulted at any lifecycle hook. Declaring an unknown capability
|
|
1250
|
+
* is a no-op (capabilities are loose strings, not an enum).
|
|
1251
|
+
*
|
|
1252
|
+
* @param cap Capability identifier (e.g. `'childSelection'`)
|
|
1253
|
+
*/
|
|
1254
|
+
declareCapability(cap: string): void;
|
|
1255
|
+
/**
|
|
1256
|
+
* Returns true if the named capability was declared on this block.
|
|
1257
|
+
*
|
|
1258
|
+
* Use this to coordinate between behaviors that share a block without
|
|
1259
|
+
* importing each other or relying on `constructor.name`.
|
|
1260
|
+
*
|
|
1261
|
+
* @param cap Capability identifier (e.g. `'childSelection'`)
|
|
1262
|
+
*/
|
|
1263
|
+
hasCapability(cap: string): boolean;
|
|
1264
|
+
/**
|
|
1265
|
+
* Returns the Set used to store declared capabilities. Internal callers
|
|
1266
|
+
* (e.g. `RuntimeBlock.inspectNext`) pass this back to a fresh
|
|
1267
|
+
* `BehaviorContext` so that all contexts for the same block observe the
|
|
1268
|
+
* same capabilities without coupling the inspection context to a
|
|
1269
|
+
* specific owner.
|
|
1270
|
+
*/
|
|
1271
|
+
getCapabilities(): Set<string>;
|
|
1272
|
+
/**
|
|
1273
|
+
* Mark the block as complete.
|
|
1274
|
+
*
|
|
1275
|
+
* When marked complete, the block will be popped from the stack
|
|
1276
|
+
*
|
|
1277
|
+
* @param reason Optional reason for completion (for debugging/history)
|
|
1278
|
+
*/
|
|
1279
|
+
markComplete(reason?: string): void;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* Generic, composable behavior contract applied to runtime blocks.
|
|
1284
|
+
*
|
|
1285
|
+
* New Design (Phase 2+): behaviors interact purely via IBehaviorContext.
|
|
1286
|
+
*
|
|
1287
|
+
* Design goals:
|
|
1288
|
+
* - Constructor-configurable: concrete behaviors can accept arbitrary args in their constructors.
|
|
1289
|
+
* - Context-driven: every hook receives IBehaviorContext.
|
|
1290
|
+
* - Optional hooks: implement only what you need.
|
|
1291
|
+
*/
|
|
1292
|
+
interface IRuntimeBehavior {
|
|
1293
|
+
/**
|
|
1294
|
+
* Called when the owning block is mounted.
|
|
1295
|
+
* Use ctx.subscribe() to listen for events and ctx.emitOutput() for initial reports.
|
|
1296
|
+
* Use ctx.pushMemory() to initialize block state.
|
|
1297
|
+
*/
|
|
1298
|
+
onMount(ctx: IBehaviorContext): IRuntimeAction[];
|
|
1299
|
+
/**
|
|
1300
|
+
* Called when parent.next() is invoked (child completed or manual advance).
|
|
1301
|
+
*
|
|
1302
|
+
* This is the signal to advance the block's internal state (e.g., next round).
|
|
1303
|
+
* If the block is finished, call ctx.markComplete().
|
|
1304
|
+
*/
|
|
1305
|
+
onNext(ctx: IBehaviorContext): IRuntimeAction[];
|
|
1306
|
+
/**
|
|
1307
|
+
* Called when the owning block is about to be unmounted.
|
|
1308
|
+
* Use ctx.emitOutput() to report final completion fragments.
|
|
1309
|
+
* Subscriptions are automatically cleaned up *after* this method returns.
|
|
1310
|
+
*/
|
|
1311
|
+
onUnmount(ctx: IBehaviorContext): IRuntimeAction[];
|
|
1312
|
+
/**
|
|
1313
|
+
* Called when the block is being disposed.
|
|
1314
|
+
* Final cleanup hook.
|
|
1315
|
+
*/
|
|
1316
|
+
onDispose(ctx: IBehaviorContext): void;
|
|
1317
|
+
/**
|
|
1318
|
+
* Legacy API: called when a block is pushed onto the stack.
|
|
1319
|
+
* Superseded by onMount. Retained for backward-compatible mock behaviors.
|
|
1320
|
+
*/
|
|
1321
|
+
onPush?(block: any, clock: any): IRuntimeAction[];
|
|
1322
|
+
/**
|
|
1323
|
+
* Legacy API: called when a block is popped from the stack.
|
|
1324
|
+
* Superseded by onUnmount. Retained for backward-compatible mock behaviors.
|
|
1325
|
+
*/
|
|
1326
|
+
onPop?(block: any, clock: any): IRuntimeAction[];
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* Lifecycle options propagated through block mount / next / unmount calls.
|
|
1331
|
+
*
|
|
1332
|
+
* Lives in the primitives layer (rather than `IRuntimeBlock.ts`) so that
|
|
1333
|
+
* `IScriptRuntime` can reference it without importing the full
|
|
1334
|
+
* `IRuntimeBlock` interface — breaking the cycle
|
|
1335
|
+
* `IRuntimeBlock ↔ IScriptRuntime`.
|
|
1336
|
+
*/
|
|
1337
|
+
interface BlockLifecycleOptions$1 {
|
|
1338
|
+
/** Start timestamp when the block was pushed onto the stack. */
|
|
1339
|
+
startTime?: Date;
|
|
1340
|
+
/** Completion timestamp when the block was popped from the stack. */
|
|
1341
|
+
createdAt?: Date;
|
|
1342
|
+
/** Current timestamp for the operation (onNext, etc). */
|
|
1343
|
+
now?: Date;
|
|
1344
|
+
/**
|
|
1345
|
+
* Clock to use for this lifecycle operation.
|
|
1346
|
+
*
|
|
1347
|
+
* If provided, this clock is passed to behaviors and child operations,
|
|
1348
|
+
* allowing consistent timing during execution chains (pop → next → push).
|
|
1349
|
+
*
|
|
1350
|
+
* Use `SnapshotClock.at(clock, time)` to freeze time during an execution chain:
|
|
1351
|
+
* - When a timer expires at T₁, create a snapshot at T₁
|
|
1352
|
+
* - Pass the snapshot through pop → parent.next → child push
|
|
1353
|
+
* - All operations see T₁ as `clock.now`, ensuring no timing gaps
|
|
1354
|
+
*
|
|
1355
|
+
* If not provided, defaults to `runtime.clock`.
|
|
1356
|
+
*/
|
|
1357
|
+
clock?: IRuntimeClock;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Re-export of the primitives `BlockLifecycleOptions` for backward
|
|
1362
|
+
* compatibility with existing import sites that pull this type from
|
|
1363
|
+
* `IRuntimeBlock`.
|
|
1364
|
+
*/
|
|
1365
|
+
type BlockLifecycleOptions = BlockLifecycleOptions$1;
|
|
1366
|
+
/**
|
|
1367
|
+
* The decision reached by an inspectNext() call.
|
|
1368
|
+
*
|
|
1369
|
+
* `complete` is true if the behavior chain marked the block complete.
|
|
1370
|
+
* `reason` is the human-readable reason (mirrors CompletionReason).
|
|
1371
|
+
* `actions` is the action list that a follow-up next() call would dispatch.
|
|
1372
|
+
* The actions are exactly the ones the auto-pop in next() would queue.
|
|
1373
|
+
*/
|
|
1374
|
+
interface CompletionDecision {
|
|
1375
|
+
readonly complete: boolean;
|
|
1376
|
+
readonly reason?: string;
|
|
1377
|
+
readonly actions: readonly IRuntimeAction[];
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Represents a runtime block that can be executed within the WOD runtime stack.
|
|
1381
|
+
*
|
|
1382
|
+
* ## Lifecycle Management
|
|
1383
|
+
*
|
|
1384
|
+
* Lifecycle pattern:
|
|
1385
|
+
* - Blocks initialize in constructors and register resources
|
|
1386
|
+
* - RuntimeStack.push() validates and stores blocks
|
|
1387
|
+
* - RuntimeStack.pop() orchestrates unmount → dispose → context.release → parent.next
|
|
1388
|
+
* - Blocks must implement dispose/unmount, but callers no longer manually dispose popped blocks
|
|
1389
|
+
*
|
|
1390
|
+
* ## Performance Requirements
|
|
1391
|
+
* - All lifecycle methods should complete within 50ms
|
|
1392
|
+
* - Stack operations should be optimized for frequent push/pop cycles
|
|
1393
|
+
* - Memory usage should be minimized during execution
|
|
1394
|
+
*/
|
|
1395
|
+
interface IRuntimeBlock extends IBlockRef {
|
|
1396
|
+
/**
|
|
1397
|
+
* Execution timing metadata propagated by the runtime (start/completion timestamps).
|
|
1398
|
+
* Implementations can rely on RuntimeStack/PushBlockAction to set this.
|
|
1399
|
+
*/
|
|
1400
|
+
executionTiming?: BlockLifecycleOptions;
|
|
1401
|
+
/**
|
|
1402
|
+
* Unique identifier for this block instance.
|
|
1403
|
+
* Used for tracking and debugging purposes.
|
|
1404
|
+
*/
|
|
1405
|
+
readonly key: BlockKey;
|
|
1406
|
+
/**
|
|
1407
|
+
* Source code location identifiers.
|
|
1408
|
+
* Links block to original workout script positions.
|
|
1409
|
+
*/
|
|
1410
|
+
readonly sourceIds: number[];
|
|
1411
|
+
/**
|
|
1412
|
+
* Type discriminator for UI display and logging.
|
|
1413
|
+
* Enables identification of block types (Timer, Rounds, Effort, etc.).
|
|
1414
|
+
*/
|
|
1415
|
+
readonly blockType?: string;
|
|
1416
|
+
/**
|
|
1417
|
+
* Human-readable label for the block.
|
|
1418
|
+
* Computed from the block's Label metrics in metrics:display memory.
|
|
1419
|
+
* Falls back to blockType or 'Block' if no Label metric exists.
|
|
1420
|
+
* e.g., "Round 1 of 3", "21 Reps", "For Time"
|
|
1421
|
+
*/
|
|
1422
|
+
readonly label: string;
|
|
1423
|
+
/**
|
|
1424
|
+
* The execution context for this block.
|
|
1425
|
+
* Manages memory allocation and cleanup.
|
|
1426
|
+
*/
|
|
1427
|
+
readonly context: IBlockContext;
|
|
1428
|
+
/**
|
|
1429
|
+
* Called when this block is pushed onto the runtime stack.
|
|
1430
|
+
* Sets up initial state and registers event listeners.
|
|
1431
|
+
*
|
|
1432
|
+
* Note: Event registration and resource allocation should happen here,
|
|
1433
|
+
* not in the constructor, to ensure they are active only while mounted.
|
|
1434
|
+
*
|
|
1435
|
+
* @param runtime The runtime context (typed as the IRuntimeActionable primitive
|
|
1436
|
+
* to break the cycle through IScriptRuntime)
|
|
1437
|
+
* @param options Lifecycle timing data (start time)
|
|
1438
|
+
* @returns Array of runtime actions to execute after mount
|
|
1439
|
+
*/
|
|
1440
|
+
mount(runtime: IRuntimeActionable, options?: BlockLifecycleOptions): IRuntimeAction[];
|
|
1441
|
+
/**
|
|
1442
|
+
* Called to advance the block's execution state.
|
|
1443
|
+
*
|
|
1444
|
+
* Triggers:
|
|
1445
|
+
* 1. **Automatic**: Called by RuntimeStack when a child block is popped (child completion).
|
|
1446
|
+
* 2. **Manual**: Called by NextAction when user manually acts (e.g., skips block).
|
|
1447
|
+
* 3. **Timer**: Called by TimerBehavior when a timer completes (implicit next).
|
|
1448
|
+
*
|
|
1449
|
+
* Responsibilities:
|
|
1450
|
+
* - Determine the next step in the block's execution flow.
|
|
1451
|
+
* - If block has children (e.g., loops), push the next child.
|
|
1452
|
+
* - If block is simple (e.g., single movement), mark itself complete.
|
|
1453
|
+
* - If block is a timer, handle timer completion logic.
|
|
1454
|
+
*
|
|
1455
|
+
* @param runtime The runtime context (typed as the IRuntimeActionable primitive
|
|
1456
|
+
* to break the cycle through IScriptRuntime)
|
|
1457
|
+
* @param options Lifecycle timing data (completion timestamp if triggered by child pop)
|
|
1458
|
+
* @returns Array of runtime actions representing next execution steps (e.g., PushBlock, MarkComplete)
|
|
1459
|
+
*/
|
|
1460
|
+
next(runtime: IRuntimeActionable, options?: BlockLifecycleOptions): IRuntimeAction[];
|
|
1461
|
+
/**
|
|
1462
|
+
* Inspect what the next() call would do, WITHOUT dispatching.
|
|
1463
|
+
*
|
|
1464
|
+
* PURE READ. Does not mutate _isComplete, does not register any
|
|
1465
|
+
* side effects on the runtime, does not emit any events. Runs the
|
|
1466
|
+
* behavior onNext chain against a temporary BehaviorContext and
|
|
1467
|
+
* returns the decision the chain reached.
|
|
1468
|
+
*
|
|
1469
|
+
* Use this to ask "what would happen if I called next() right now?"
|
|
1470
|
+
* without paying for the side effects. The cast sender can pre-emptively
|
|
1471
|
+
* dim the next block; analytics can explain "why did this block end?"
|
|
1472
|
+
* from the decision trail; tests can assert on the decision without
|
|
1473
|
+
* touching state.
|
|
1474
|
+
*
|
|
1475
|
+
* @param runtime The runtime context (typed as the IRuntimeActionable primitive
|
|
1476
|
+
* to break the cycle through IScriptRuntime)
|
|
1477
|
+
* @param options Lifecycle timing data (completion timestamp if triggered by child pop)
|
|
1478
|
+
* @returns The completion decision: { complete, reason?, actions }
|
|
1479
|
+
*/
|
|
1480
|
+
inspectNext(runtime: IRuntimeActionable, options?: BlockLifecycleOptions): CompletionDecision;
|
|
1481
|
+
/**
|
|
1482
|
+
* Called when this block is popped from the runtime stack.
|
|
1483
|
+
* Handles completion logic, cleanup, and resource disposal.
|
|
1484
|
+
*
|
|
1485
|
+
* Note: This method MUST unsubscribe from events and dispose
|
|
1486
|
+
* memory entries to complete any active observables.
|
|
1487
|
+
*
|
|
1488
|
+
* @param runtime The runtime context (typed as the IRuntimeActionable primitive
|
|
1489
|
+
* to break the cycle through IScriptRuntime)
|
|
1490
|
+
* @param options Lifecycle timing data (completion timestamp)
|
|
1491
|
+
* @returns Array of runtime actions to execute after unmount
|
|
1492
|
+
*/
|
|
1493
|
+
unmount(runtime: IRuntimeActionable, options?: BlockLifecycleOptions): IRuntimeAction[];
|
|
1494
|
+
/**
|
|
1495
|
+
* Cleans up any resources held by this block.
|
|
1496
|
+
*
|
|
1497
|
+
* ## Critical Requirements
|
|
1498
|
+
* - MUST be called by consumer after popping from stack
|
|
1499
|
+
* - Should complete within 50ms for performance
|
|
1500
|
+
* - Must handle being called multiple times safely
|
|
1501
|
+
* - Should not throw exceptions during cleanup
|
|
1502
|
+
*
|
|
1503
|
+
* ## Resource Management
|
|
1504
|
+
* - Close file handles, network connections
|
|
1505
|
+
* - Unregister event listeners and callbacks
|
|
1506
|
+
* - Release memory references and clear caches
|
|
1507
|
+
* - Dispose of child objects and dependencies
|
|
1508
|
+
*
|
|
1509
|
+
* @param runtime The runtime context (typed as the IRuntimeActionable primitive
|
|
1510
|
+
* to break the cycle through IScriptRuntime)
|
|
1511
|
+
* @throws Never - Should handle all cleanup errors internally
|
|
1512
|
+
*/
|
|
1513
|
+
dispose(runtime: IRuntimeActionable): void;
|
|
1514
|
+
/**
|
|
1515
|
+
* Gets a specific behavior by type from the behaviors array.
|
|
1516
|
+
* @param behaviorType Constructor/class of the behavior to find
|
|
1517
|
+
* @returns The behavior instance or undefined if not found
|
|
1518
|
+
*/
|
|
1519
|
+
getBehavior<T extends IRuntimeBehavior>(behaviorType: new (...args: any[]) => T): T | undefined;
|
|
1520
|
+
/**
|
|
1521
|
+
* Read-only list of all behaviors attached to this block.
|
|
1522
|
+
* Useful for debug/inspection UIs.
|
|
1523
|
+
*/
|
|
1524
|
+
readonly behaviors: readonly IRuntimeBehavior[];
|
|
1525
|
+
/**
|
|
1526
|
+
* Push a new memory location onto the block's memory list.
|
|
1527
|
+
* Multiple locations with the same tag can coexist.
|
|
1528
|
+
*/
|
|
1529
|
+
pushMemory(location: IMemoryLocation): void;
|
|
1530
|
+
/**
|
|
1531
|
+
* Merge a set of compiler hints (e.g. from an effort markdown file) into
|
|
1532
|
+
* the block. Hints are stored as `MetricType.Hint` metrics in a
|
|
1533
|
+
* `metric:hint` memory location.
|
|
1534
|
+
*
|
|
1535
|
+
* IMPORTANT: unlike dialect-emitted hints (which are attached to the
|
|
1536
|
+
* *statement* before `JitCompiler.compile()` runs, and so are visible to
|
|
1537
|
+
* `hasHint`/`getHints` calls inside strategy `match()`/`apply()`), hints
|
|
1538
|
+
* merged here land on the *block* — which only exists after compilation.
|
|
1539
|
+
* They currently do NOT reach any strategy decision; `hasHint`/`getHints`
|
|
1540
|
+
* never read from block memory. See
|
|
1541
|
+
* docs/architectural-cleanup-tier-3-extensibility.md §3.3 verification
|
|
1542
|
+
* notes for the full explanation. Use sparingly: any hint key not in
|
|
1543
|
+
* `CONSUMED_HINTS` (see `core/metrics/hints.ts`) is analytics-only even
|
|
1544
|
+
* when the read-path gap above is fixed. For effort-specific metrics,
|
|
1545
|
+
* prefer a domain metric.
|
|
1546
|
+
*/
|
|
1547
|
+
mergeHints(hints: Readonly<Record<string, unknown>>): void;
|
|
1548
|
+
/**
|
|
1549
|
+
* Get all memory locations matching the given tag.
|
|
1550
|
+
* Returns an empty array if no locations match.
|
|
1551
|
+
*
|
|
1552
|
+
* @param tag The memory tag to filter by
|
|
1553
|
+
* @returns Array of memory locations with the given tag
|
|
1554
|
+
*/
|
|
1555
|
+
getMemoryByTag(tag: MemoryTag): IMemoryLocation[];
|
|
1556
|
+
/**
|
|
1557
|
+
* Get all memory locations owned by this block.
|
|
1558
|
+
* Returns the full memory list in insertion order.
|
|
1559
|
+
*
|
|
1560
|
+
* @returns Array of all memory locations
|
|
1561
|
+
*/
|
|
1562
|
+
getAllMemory(): IMemoryLocation[];
|
|
1563
|
+
/**
|
|
1564
|
+
* Get all metrics memory locations matching a given visibility tier.
|
|
1565
|
+
*
|
|
1566
|
+
* Fragment tags follow the `metrics:*` namespace convention and are
|
|
1567
|
+
* classified into three visibility tiers: `'display'`, `'promote'`,
|
|
1568
|
+
* and `'private'`. This method filters all memory locations to those
|
|
1569
|
+
* with metrics tags belonging to the specified tier.
|
|
1570
|
+
*
|
|
1571
|
+
* @param visibility The visibility tier to filter by
|
|
1572
|
+
* @returns Array of memory locations in the requested tier
|
|
1573
|
+
*
|
|
1574
|
+
* @example
|
|
1575
|
+
* ```typescript
|
|
1576
|
+
* // Get metrics visible in the UI card
|
|
1577
|
+
* const displayLocs = block.getMetricMemoryByVisibility('display');
|
|
1578
|
+
*
|
|
1579
|
+
* // Get all tiers for debug view
|
|
1580
|
+
* const all = (['display', 'promote', 'private'] as const)
|
|
1581
|
+
* .flatMap(v => block.getMetricMemoryByVisibility(v));
|
|
1582
|
+
* ```
|
|
1583
|
+
*/
|
|
1584
|
+
getMetricMemoryByVisibility(visibility: MetricVisibility): IMemoryLocation[];
|
|
1585
|
+
/**
|
|
1586
|
+
* Indicates whether this block has completed execution.
|
|
1587
|
+
* When true, the stack will pop this block during its next completion sweep.
|
|
1588
|
+
*
|
|
1589
|
+
* @remarks
|
|
1590
|
+
* - Set by behaviors when their completion condition is met
|
|
1591
|
+
* - Read by the stack after action/event processing
|
|
1592
|
+
* - Once true, should not be reset to false
|
|
1593
|
+
*/
|
|
1594
|
+
readonly isComplete: boolean;
|
|
1595
|
+
/**
|
|
1596
|
+
* The reason this block was completed, if any.
|
|
1597
|
+
*
|
|
1598
|
+
* Set by `markComplete(reason)`. Common values:
|
|
1599
|
+
* - `'user-advance'` — user clicked next (self-pop)
|
|
1600
|
+
* - `'forced-pop'` — parent popped this block (e.g., timer expiry)
|
|
1601
|
+
* - `'timer-expired'` — block's own timer completed
|
|
1602
|
+
* - `'rounds-complete'` — all rounds finished
|
|
1603
|
+
*
|
|
1604
|
+
* Available during `onUnmount` so output behaviors can include
|
|
1605
|
+
* completion context in the emitted output.
|
|
1606
|
+
*/
|
|
1607
|
+
readonly completionReason?: string;
|
|
1608
|
+
/**
|
|
1609
|
+
* Marks the block as complete. Idempotent - subsequent calls have no effect.
|
|
1610
|
+
* Called by behaviors when their completion condition is met.
|
|
1611
|
+
*
|
|
1612
|
+
* @param reason Optional reason for completion (for debugging/history)
|
|
1613
|
+
*/
|
|
1614
|
+
markComplete(reason?: string): void;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* Runtime error information.
|
|
1619
|
+
* Captured errors are stored in the runtime's error list for centralized handling.
|
|
1620
|
+
*/
|
|
1621
|
+
interface RuntimeError {
|
|
1622
|
+
/** The error that occurred */
|
|
1623
|
+
error: Error;
|
|
1624
|
+
/** Where the error occurred (block ID, handler ID, etc.) */
|
|
1625
|
+
source: string;
|
|
1626
|
+
/** When the error occurred */
|
|
1627
|
+
timestamp: Date;
|
|
1628
|
+
/** Additional context about the error */
|
|
1629
|
+
context?: unknown;
|
|
1630
|
+
blockKey?: string;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
type StackEvent = {
|
|
1634
|
+
type: 'push';
|
|
1635
|
+
block: IRuntimeBlock;
|
|
1636
|
+
depth: number;
|
|
1637
|
+
blocks: readonly IRuntimeBlock[];
|
|
1638
|
+
} | {
|
|
1639
|
+
type: 'pop';
|
|
1640
|
+
block: IRuntimeBlock;
|
|
1641
|
+
depth: number;
|
|
1642
|
+
blocks: readonly IRuntimeBlock[];
|
|
1643
|
+
} | {
|
|
1644
|
+
type: 'initial';
|
|
1645
|
+
blocks: readonly IRuntimeBlock[];
|
|
1646
|
+
};
|
|
1647
|
+
type StackListener = (event: StackEvent) => void;
|
|
1648
|
+
/**
|
|
1649
|
+
* Unsubscribe function returned by subscription methods.
|
|
1650
|
+
*/
|
|
1651
|
+
type Unsubscribe = () => void;
|
|
1652
|
+
/**
|
|
1653
|
+
* Snapshot of the runtime stack state at the time of a stack mutation.
|
|
1654
|
+
*
|
|
1655
|
+
* Pushed to UI observers via `IScriptRuntime.subscribeToStack()` on every
|
|
1656
|
+
* push/pop/clear operation. Contains everything the UI needs to render
|
|
1657
|
+
* the current state in a single object.
|
|
1658
|
+
*/
|
|
1659
|
+
interface StackSnapshot {
|
|
1660
|
+
/** The event that caused this snapshot */
|
|
1661
|
+
type: 'push' | 'pop' | 'clear' | 'initial';
|
|
1662
|
+
/** Current stack blocks (bottom-to-top), frozen at event time */
|
|
1663
|
+
blocks: readonly IRuntimeBlock[];
|
|
1664
|
+
/** The block that was pushed/popped (undefined on 'clear'/'initial') */
|
|
1665
|
+
affectedBlock?: IRuntimeBlock;
|
|
1666
|
+
/** Stack depth after the event */
|
|
1667
|
+
depth: number;
|
|
1668
|
+
/** Timestamp from the runtime clock */
|
|
1669
|
+
clockTime: Date;
|
|
1670
|
+
}
|
|
1671
|
+
type StackObserver = (snapshot: StackSnapshot) => void;
|
|
1672
|
+
interface IRuntimeStack {
|
|
1673
|
+
readonly blocks: readonly IRuntimeBlock[];
|
|
1674
|
+
readonly count: number;
|
|
1675
|
+
readonly current: IRuntimeBlock | undefined;
|
|
1676
|
+
readonly keys: BlockKey[];
|
|
1677
|
+
push(block: IRuntimeBlock): void;
|
|
1678
|
+
pop(): IRuntimeBlock | undefined;
|
|
1679
|
+
clear(): void;
|
|
1680
|
+
/**
|
|
1681
|
+
* Subscribe to stack changes.
|
|
1682
|
+
* New subscribers will immediately receive an 'initial' event with the current stack state.
|
|
1683
|
+
*/
|
|
1684
|
+
subscribe(listener: StackListener): () => void;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
type FenceDialect = string;
|
|
1688
|
+
|
|
1689
|
+
/**
|
|
1690
|
+
* Shared descriptor for all analytics processors.
|
|
1691
|
+
*
|
|
1692
|
+
* Defines applicability rules: which fence dialects (the `wod`/`log`/`plan`
|
|
1693
|
+
* code-fence language a block was written in) the processor is eligible for,
|
|
1694
|
+
* and which metrics must be present in the authored script for the processor
|
|
1695
|
+
* to activate.
|
|
1696
|
+
*
|
|
1697
|
+
* Note: the descriptor field is named `fenceTypes` (not `dialects`) to make
|
|
1698
|
+
* clear this is the **fence** axis only. Sport-specific filtering is
|
|
1699
|
+
* expressed via `requiredMetrics` (e.g. `ClimbMetricType.Grade` for
|
|
1700
|
+
* Climb-only processors). See Tier 3 §3.1.
|
|
1701
|
+
*/
|
|
1702
|
+
interface IAnalyticsProcessorDescriptor {
|
|
1703
|
+
readonly id: string;
|
|
1704
|
+
/**
|
|
1705
|
+
* Allow-list of fence dialects this processor is eligible for.
|
|
1706
|
+
* Omit or leave undefined to allow all fence dialects.
|
|
1707
|
+
*/
|
|
1708
|
+
readonly fenceTypes?: readonly FenceDialect[];
|
|
1709
|
+
/**
|
|
1710
|
+
* Metrics that must ALL be present in the authored script for this
|
|
1711
|
+
* processor to activate (AND semantics).
|
|
1712
|
+
* Omit or leave undefined when applicability cannot be expressed
|
|
1713
|
+
* with a simple metric list (e.g. disjunctive conditions).
|
|
1714
|
+
*
|
|
1715
|
+
* Typed `MetricType | string` (matching `IMetric.type`'s own type) so a
|
|
1716
|
+
* custom dialect's domain metric type (e.g. `ClimbMetricType.Grade`,
|
|
1717
|
+
* a plain string constant, not a `MetricType` enum member) can be used
|
|
1718
|
+
* here directly — this was already the documented example above before
|
|
1719
|
+
* the type was widened to actually allow it.
|
|
1720
|
+
*/
|
|
1721
|
+
readonly requiredMetrics?: readonly (MetricType | string)[];
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* Realtime processor contract.
|
|
1726
|
+
*
|
|
1727
|
+
* Applied to every output statement as it is emitted. Used for
|
|
1728
|
+
* per-segment metric derivation (e.g. pace, power enrichment).
|
|
1729
|
+
*/
|
|
1730
|
+
interface IRealtimeProcessor extends IAnalyticsProcessorDescriptor {
|
|
1731
|
+
process(output: IOutputStatement): IOutputStatement;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* Generic result structure for projection engine calculations.
|
|
1736
|
+
*
|
|
1737
|
+
* Provides a standardized format for all analytical projections,
|
|
1738
|
+
* making results self-describing and easy to consume by UI components.
|
|
1739
|
+
*/
|
|
1740
|
+
interface ProjectionResult {
|
|
1741
|
+
/** Human-readable name of the projection (e.g., "Total Volume", "Average Power") */
|
|
1742
|
+
name: string;
|
|
1743
|
+
/** Calculated value */
|
|
1744
|
+
value: number;
|
|
1745
|
+
/** Unit of measurement (e.g., "kg", "watts", "calories") */
|
|
1746
|
+
unit: string;
|
|
1747
|
+
/** Associated metric type for UI categorization (optional) */
|
|
1748
|
+
metricType?: MetricType | string;
|
|
1749
|
+
/** Time span over which this projection was calculated */
|
|
1750
|
+
timeSpan: TimeSpan$1;
|
|
1751
|
+
/**
|
|
1752
|
+
* Origin of the projection value.
|
|
1753
|
+
* Defaults to 'analyzed' when not specified.
|
|
1754
|
+
* Use 'analyzed-estimated' when the value was computed with fallback
|
|
1755
|
+
* assumptions (e.g. population-average METmax when VO2max is unknown).
|
|
1756
|
+
*/
|
|
1757
|
+
origin?: MetricOrigin;
|
|
1758
|
+
/** Optional additional metadata for the projection */
|
|
1759
|
+
metadata?: Record<string, any>;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
/**
|
|
1763
|
+
* Summary processor contract.
|
|
1764
|
+
*
|
|
1765
|
+
* Applied to the accumulated output history. Used for multi-segment
|
|
1766
|
+
* aggregation and projection (e.g. total reps, total distance, volume).
|
|
1767
|
+
*/
|
|
1768
|
+
interface ISummaryProcessor extends IAnalyticsProcessorDescriptor {
|
|
1769
|
+
summarize(outputs: IOutputStatement[]): ProjectionResult[];
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
interface IAnalyticsEngine {
|
|
1773
|
+
/** Register a realtime processor. */
|
|
1774
|
+
addRealtimeProcessor(processor: IRealtimeProcessor): void;
|
|
1775
|
+
/** Register a summary processor. */
|
|
1776
|
+
addSummaryProcessor(processor: ISummaryProcessor): void;
|
|
1777
|
+
/**
|
|
1778
|
+
* Wire a sink for live analytics outputs. The sink receives the full
|
|
1779
|
+
* projection snapshot (ephemeral, display-only) each time projections
|
|
1780
|
+
* change — never appended one-at-a-time, and never persisted. Persistence
|
|
1781
|
+
* of the final values happens once via {@link finalize}.
|
|
1782
|
+
*/
|
|
1783
|
+
setLiveOutputEmitter(emit: (outputs: IOutputStatement[]) => void): void;
|
|
1784
|
+
/** Run enrichment on a single output, then fire live projection update */
|
|
1785
|
+
run(output: IOutputStatement): IOutputStatement;
|
|
1786
|
+
/** Final projection pass — returns analytics output statements for summary */
|
|
1787
|
+
finalize(): IOutputStatement[];
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
/**
|
|
1791
|
+
* Configuration for method interception behavior.
|
|
1792
|
+
* Defined here (in runtime/contracts) so that both production runtime
|
|
1793
|
+
* code and the testing layer can reference it without creating a
|
|
1794
|
+
* runtime → testing circular dependency.
|
|
1795
|
+
*/
|
|
1796
|
+
type InterceptMode = 'passthrough' | 'spy' | 'override' | 'ignore';
|
|
1797
|
+
/**
|
|
1798
|
+
* Configuration for TestableBlock behavior.
|
|
1799
|
+
* Used by IRuntimeOptions to allow debug-mode block wrapping.
|
|
1800
|
+
*/
|
|
1801
|
+
interface TestableBlockConfig {
|
|
1802
|
+
/** Custom ID for easy identification in visualizations */
|
|
1803
|
+
testId?: string;
|
|
1804
|
+
/** Custom label override for display purposes */
|
|
1805
|
+
labelOverride?: string;
|
|
1806
|
+
/** Mode for mount() interception */
|
|
1807
|
+
mountMode?: InterceptMode;
|
|
1808
|
+
/** Custom mount implementation when mode is 'override' */
|
|
1809
|
+
mountOverride?: (runtime: IRuntimeActionable) => IRuntimeAction[];
|
|
1810
|
+
/** Mode for next() interception */
|
|
1811
|
+
nextMode?: InterceptMode;
|
|
1812
|
+
/** Custom next implementation when mode is 'override' */
|
|
1813
|
+
nextOverride?: (runtime: IRuntimeActionable) => IRuntimeAction[];
|
|
1814
|
+
/** Mode for inspectNext() interception */
|
|
1815
|
+
inspectNextMode?: InterceptMode;
|
|
1816
|
+
/** Custom inspectNext implementation when mode is 'override' */
|
|
1817
|
+
inspectNextOverride?: (runtime: IRuntimeActionable) => CompletionDecision;
|
|
1818
|
+
/** Mode for unmount() interception */
|
|
1819
|
+
unmountMode?: InterceptMode;
|
|
1820
|
+
/** Custom unmount implementation when mode is 'override' */
|
|
1821
|
+
unmountOverride?: (runtime: IRuntimeActionable) => IRuntimeAction[];
|
|
1822
|
+
/** Mode for dispose() interception */
|
|
1823
|
+
disposeMode?: InterceptMode;
|
|
1824
|
+
/** Custom dispose implementation when mode is 'override' */
|
|
1825
|
+
disposeOverride?: (runtime: IRuntimeActionable) => void;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* Factory function type for wrapping blocks in debug mode.
|
|
1830
|
+
* Receives the original block and returns a wrapped version.
|
|
1831
|
+
*/
|
|
1832
|
+
type BlockWrapperFactory = (block: IRuntimeBlock, config?: TestableBlockConfig) => IRuntimeBlock;
|
|
1833
|
+
/**
|
|
1834
|
+
* Configuration options for ScriptRuntime initialization.
|
|
1835
|
+
* These options control debugging, logging, and testing capabilities.
|
|
1836
|
+
*/
|
|
1837
|
+
interface IRuntimeOptions {
|
|
1838
|
+
/**
|
|
1839
|
+
* Enable debug mode for the runtime.
|
|
1840
|
+
* When true:
|
|
1841
|
+
* - All blocks pushed to the stack are automatically wrapped with TestableBlock
|
|
1842
|
+
* - Additional lifecycle events are logged
|
|
1843
|
+
*/
|
|
1844
|
+
debugMode?: boolean;
|
|
1845
|
+
/**
|
|
1846
|
+
* Custom factory for wrapping blocks when debugMode is true.
|
|
1847
|
+
* If not provided, uses the default TestableBlock wrapper with spy mode.
|
|
1848
|
+
*/
|
|
1849
|
+
blockWrapperFactory?: BlockWrapperFactory;
|
|
1850
|
+
/**
|
|
1851
|
+
* Default configuration for TestableBlock wrappers.
|
|
1852
|
+
* Applied to all blocks wrapped in debug mode unless overridden.
|
|
1853
|
+
*/
|
|
1854
|
+
defaultTestableConfig?: Partial<TestableBlockConfig>;
|
|
1855
|
+
/**
|
|
1856
|
+
* Enable console logging for runtime debug output.
|
|
1857
|
+
* Automatically set to true when debugMode is true.
|
|
1858
|
+
*/
|
|
1859
|
+
enableLogging?: boolean;
|
|
1860
|
+
/**
|
|
1861
|
+
* Custom log handler for debug events.
|
|
1862
|
+
* If provided, called in addition to console logging.
|
|
1863
|
+
*/
|
|
1864
|
+
onDebugLog?: (event: DebugLogEvent) => void;
|
|
1865
|
+
/**
|
|
1866
|
+
* Maximum depth for nested actions within a single execution turn.
|
|
1867
|
+
* Defaults to 20 if not specified.
|
|
1868
|
+
*/
|
|
1869
|
+
maxActionDepth?: number;
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Types of debug log events
|
|
1873
|
+
*/
|
|
1874
|
+
type DebugLogEventType = 'block-wrapped' | 'block-mount' | 'block-next' | 'block-unmount' | 'block-dispose' | 'stack-push' | 'stack-pop' | 'memory-allocate' | 'memory-release' | 'event-handled';
|
|
1875
|
+
/**
|
|
1876
|
+
* Debug log event structure
|
|
1877
|
+
*/
|
|
1878
|
+
interface DebugLogEvent {
|
|
1879
|
+
type: DebugLogEventType;
|
|
1880
|
+
timestamp: number;
|
|
1881
|
+
blockKey?: string;
|
|
1882
|
+
blockType?: string;
|
|
1883
|
+
details?: Record<string, any>;
|
|
1884
|
+
}
|
|
1885
|
+
interface RuntimeStackWrapper {
|
|
1886
|
+
wrap?: (block: IRuntimeBlock, parent?: IRuntimeBlock) => IRuntimeBlock;
|
|
1887
|
+
cleanup?: (block: IRuntimeBlock) => void;
|
|
1888
|
+
}
|
|
1889
|
+
interface RuntimeStackLogger {
|
|
1890
|
+
debug?: (message: string, details?: Record<string, unknown>) => void;
|
|
1891
|
+
error?: (message: string, error: unknown, details?: Record<string, unknown>) => void;
|
|
1892
|
+
}
|
|
1893
|
+
interface RuntimeStackHooks {
|
|
1894
|
+
onBeforePush?: (block: IRuntimeBlock, parent?: IRuntimeBlock) => void;
|
|
1895
|
+
onAfterPush?: (block: IRuntimeBlock, parent?: IRuntimeBlock) => void;
|
|
1896
|
+
onBeforePop?: (block: IRuntimeBlock | undefined) => void;
|
|
1897
|
+
onAfterPop?: (block: IRuntimeBlock | undefined) => void;
|
|
1898
|
+
unregisterByOwner?: (ownerKey: string) => void;
|
|
1899
|
+
}
|
|
1900
|
+
interface RuntimeStackOptions extends IRuntimeOptions {
|
|
1901
|
+
wrapper?: RuntimeStackWrapper;
|
|
1902
|
+
logger?: RuntimeStackLogger;
|
|
1903
|
+
hooks?: RuntimeStackHooks;
|
|
1904
|
+
analyticsOptions?: {
|
|
1905
|
+
effortResolver?: IEffortResolver;
|
|
1906
|
+
userProfile?: {
|
|
1907
|
+
vo2max?: number;
|
|
1908
|
+
};
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/**
|
|
1913
|
+
* AnalyticsContext — shared services injected into the analytics pipeline.
|
|
1914
|
+
*
|
|
1915
|
+
* Provides processors with domain-level resolution capabilities without
|
|
1916
|
+
* direct coupling to registry implementations.
|
|
1917
|
+
*
|
|
1918
|
+
* @see docs/adr/effort-resolution-compile-time.md
|
|
1919
|
+
*/
|
|
1920
|
+
interface AnalyticsContext {
|
|
1921
|
+
/** Effort resolver for canonical slug and fuzzy alias matching. */
|
|
1922
|
+
effortResolver: IEffortResolver;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
interface IJitCompiler {
|
|
1926
|
+
compile(nodes: ICodeStatement[], runtime: IRuntimeActionable): IRuntimeBlock | undefined;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
/**
|
|
1930
|
+
* Listener callback for output statement events.
|
|
1931
|
+
*/
|
|
1932
|
+
type OutputListener = (output: IOutputStatement) => void;
|
|
1933
|
+
interface IScriptRuntime extends IRuntimeActionable {
|
|
1934
|
+
options: RuntimeStackOptions;
|
|
1935
|
+
script: WhiteboardScript;
|
|
1936
|
+
eventBus: IEventBus;
|
|
1937
|
+
stack: IRuntimeStack;
|
|
1938
|
+
jit: IJitCompiler;
|
|
1939
|
+
clock: IRuntimeClock;
|
|
1940
|
+
nowProvider: INowProvider;
|
|
1941
|
+
/** Errors collected during runtime execution */
|
|
1942
|
+
errors?: RuntimeError[];
|
|
1943
|
+
/** Optional analytics context for compile-time effort enrichment. */
|
|
1944
|
+
analyticsContext?: AnalyticsContext;
|
|
1945
|
+
/**
|
|
1946
|
+
* Executes an action at the next available opportunity.
|
|
1947
|
+
* Actions are processed in "turns" where time is frozen and
|
|
1948
|
+
* nested actions are allowed up to a recursion limit.
|
|
1949
|
+
*/
|
|
1950
|
+
do(action: IRuntimeAction): void;
|
|
1951
|
+
/**
|
|
1952
|
+
* Pushes multiple actions onto the execution stack in the correct order
|
|
1953
|
+
* for LIFO processing. The first action in the array will execute first.
|
|
1954
|
+
*
|
|
1955
|
+
* This method internalizes the reverse-push pattern so callers don't need
|
|
1956
|
+
* to know about LIFO stack mechanics — they just pass actions in the order
|
|
1957
|
+
* they want them executed.
|
|
1958
|
+
*
|
|
1959
|
+
* @param actions Actions to execute, in desired execution order
|
|
1960
|
+
*/
|
|
1961
|
+
doAll(actions: IRuntimeAction[]): void;
|
|
1962
|
+
/**
|
|
1963
|
+
* Dispatches an event and executes all resulting actions in a single turn.
|
|
1964
|
+
*/
|
|
1965
|
+
handle(event: IEvent): void;
|
|
1966
|
+
/**
|
|
1967
|
+
* Pushes a block onto the runtime stack.
|
|
1968
|
+
* This is a convenience method that wraps PushBlockAction.
|
|
1969
|
+
*
|
|
1970
|
+
* @param block The block to push
|
|
1971
|
+
* @param lifecycle Optional lifecycle options (startTime, etc.)
|
|
1972
|
+
*/
|
|
1973
|
+
pushBlock(block: IRuntimeBlock, lifecycle?: BlockLifecycleOptions$1): void;
|
|
1974
|
+
/**
|
|
1975
|
+
* Pops the current block from the runtime stack.
|
|
1976
|
+
* This is a convenience method that wraps PopBlockAction.
|
|
1977
|
+
* Handles the full lifecycle: unmount, pop, dispose, and parent notification.
|
|
1978
|
+
*
|
|
1979
|
+
* @param lifecycle Optional lifecycle options (createdAt, etc.)
|
|
1980
|
+
*/
|
|
1981
|
+
popBlock(lifecycle?: BlockLifecycleOptions$1): void;
|
|
1982
|
+
/**
|
|
1983
|
+
* Subscribe to output statements generated during execution.
|
|
1984
|
+
* Listeners are notified when a block unmounts and produces output.
|
|
1985
|
+
*
|
|
1986
|
+
* @param listener Callback invoked for each output statement
|
|
1987
|
+
* @returns Unsubscribe function to stop receiving notifications
|
|
1988
|
+
*
|
|
1989
|
+
* @example
|
|
1990
|
+
* ```typescript
|
|
1991
|
+
* const unsubscribe = runtime.subscribeToOutput((output) => {
|
|
1992
|
+
* console.log(`Block ${output.sourceBlockKey} completed:`, output.outputType);
|
|
1993
|
+
* console.log('Fragments:', output.metrics);
|
|
1994
|
+
* });
|
|
1995
|
+
* ```
|
|
1996
|
+
*/
|
|
1997
|
+
subscribeToOutput(listener: OutputListener): Unsubscribe;
|
|
1998
|
+
/**
|
|
1999
|
+
* Get all output statements generated so far during this execution.
|
|
2000
|
+
* Returns a copy of the internal array.
|
|
2001
|
+
*/
|
|
2002
|
+
getOutputStatements(): IOutputStatement[];
|
|
2003
|
+
/**
|
|
2004
|
+
* Ephemeral Tier-2 (running-total) snapshot for live display — the
|
|
2005
|
+
* analytics engine's current projection set, replaced each segment.
|
|
2006
|
+
* Display-only: never persisted to the output log. Optional because only
|
|
2007
|
+
* a concrete runtime with a wired analytics engine produces it; consumers
|
|
2008
|
+
* call it defensively (`runtime.getLiveAnalytics?.()`).
|
|
2009
|
+
*/
|
|
2010
|
+
getLiveAnalytics?(): IOutputStatement[];
|
|
2011
|
+
/**
|
|
2012
|
+
* Add an output statement to the collection and notify subscribers.
|
|
2013
|
+
* Used by BehaviorContext to emit outputs at any lifecycle point.
|
|
2014
|
+
*
|
|
2015
|
+
* @param output The output statement to add
|
|
2016
|
+
*
|
|
2017
|
+
* @example
|
|
2018
|
+
* ```typescript
|
|
2019
|
+
* runtime.addOutput(new OutputStatement({
|
|
2020
|
+
* outputType: 'segment',
|
|
2021
|
+
* timeSpan: new TimeSpan(start, end),
|
|
2022
|
+
* sourceBlockKey: block.key.toString(),
|
|
2023
|
+
* completionReason: 'user-advance',
|
|
2024
|
+
* metrics: [],
|
|
2025
|
+
* }));
|
|
2026
|
+
* ```
|
|
2027
|
+
*/
|
|
2028
|
+
addOutput(output: IOutputStatement): void;
|
|
2029
|
+
/**
|
|
2030
|
+
* Subscribe to stack snapshots. The observer receives a StackSnapshot
|
|
2031
|
+
* on every push/pop/clear event, containing the full stack state,
|
|
2032
|
+
* the affected block, depth, and clock time.
|
|
2033
|
+
*
|
|
2034
|
+
* New subscribers immediately receive an 'initial' snapshot with the
|
|
2035
|
+
* current stack state.
|
|
2036
|
+
*
|
|
2037
|
+
* This is the primary API for UI consumers to react to stack changes.
|
|
2038
|
+
*
|
|
2039
|
+
* @param observer Callback invoked with a StackSnapshot on each stack mutation
|
|
2040
|
+
* @returns Unsubscribe function to stop receiving notifications
|
|
2041
|
+
*
|
|
2042
|
+
* @example
|
|
2043
|
+
* ```typescript
|
|
2044
|
+
* const unsubscribe = runtime.subscribeToStack((snapshot) => {
|
|
2045
|
+
* console.log(`Stack ${snapshot.type}: depth=${snapshot.depth}`);
|
|
2046
|
+
* if (snapshot.affectedBlock) {
|
|
2047
|
+
* console.log(`Affected: ${snapshot.affectedBlock.label}`);
|
|
2048
|
+
* }
|
|
2049
|
+
* });
|
|
2050
|
+
* ```
|
|
2051
|
+
*/
|
|
2052
|
+
subscribeToStack(observer: StackObserver): Unsubscribe;
|
|
2053
|
+
/**
|
|
2054
|
+
* Set the analytics engine for the runtime.
|
|
2055
|
+
* The engine will process each output statement as it is added.
|
|
2056
|
+
*/
|
|
2057
|
+
setAnalyticsEngine(engine: IAnalyticsEngine): void;
|
|
2058
|
+
/**
|
|
2059
|
+
* Finalize the analytics engine and return any summary output statements.
|
|
2060
|
+
* These statements are also automatically added to the runtime's internal list.
|
|
2061
|
+
*/
|
|
2062
|
+
finalizeAnalytics(): IOutputStatement[];
|
|
2063
|
+
dispose(): void;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
type RuntimeState = 'idle' | 'running' | 'compiling' | 'completed';
|
|
2067
|
+
interface ScriptRuntimeDependencies {
|
|
2068
|
+
stack: IRuntimeStack;
|
|
2069
|
+
clock: IRuntimeClock;
|
|
2070
|
+
eventBus: IEventBus;
|
|
2071
|
+
}
|
|
2072
|
+
declare class ScriptRuntime implements IScriptRuntime {
|
|
2073
|
+
readonly script: WhiteboardScript;
|
|
2074
|
+
readonly eventBus: IEventBus;
|
|
2075
|
+
readonly clock: IRuntimeClock;
|
|
2076
|
+
readonly stack: IRuntimeStack;
|
|
2077
|
+
readonly jit: IJitCompiler;
|
|
2078
|
+
readonly errors: RuntimeError[];
|
|
2079
|
+
readonly options: RuntimeStackOptions;
|
|
2080
|
+
analyticsContext?: AnalyticsContext;
|
|
2081
|
+
private readonly _output;
|
|
2082
|
+
private readonly _observers;
|
|
2083
|
+
private _activeContext;
|
|
2084
|
+
private readonly _now;
|
|
2085
|
+
private _stackSubscriptionUnsub;
|
|
2086
|
+
private _nextHandlerUnsub;
|
|
2087
|
+
get nowProvider(): INowProvider;
|
|
2088
|
+
private _abortHandlerUnsub;
|
|
2089
|
+
constructor(script: WhiteboardScript, compiler: IJitCompiler, dependencies: ScriptRuntimeDependencies, options?: RuntimeStackOptions, now?: INowProvider);
|
|
2090
|
+
/**
|
|
2091
|
+
* Executes an action within a managed execution context (a "turn").
|
|
2092
|
+
* Actions can queue further actions which will be processed in the same turn.
|
|
2093
|
+
* Time is frozen during the turn.
|
|
2094
|
+
*/
|
|
2095
|
+
do(action: IRuntimeAction): void;
|
|
2096
|
+
/** Build the 'initial' snapshot of the current stack. Shared by settle-time
|
|
2097
|
+
* notification (sync, all observers) and subscribe-time catch-up (deferred,
|
|
2098
|
+
* new observer only). The two delivery timings differ deliberately — see
|
|
2099
|
+
* the call sites. */
|
|
2100
|
+
private _buildInitialSnapshot;
|
|
2101
|
+
private _notifyStackSettled;
|
|
2102
|
+
/**
|
|
2103
|
+
* Pushes multiple actions onto the execution stack in the correct order
|
|
2104
|
+
* for LIFO processing. The first action in the array will execute first.
|
|
2105
|
+
*
|
|
2106
|
+
* If called within an active execution context, delegates to it.
|
|
2107
|
+
* Otherwise, wraps in a new context as a composite action.
|
|
2108
|
+
*/
|
|
2109
|
+
doAll(actions: IRuntimeAction[]): void;
|
|
2110
|
+
/**
|
|
2111
|
+
* Dispatches an event to the event bus and executes any resulting actions
|
|
2112
|
+
* within a single execution turn.
|
|
2113
|
+
*/
|
|
2114
|
+
handle(event: IEvent): void;
|
|
2115
|
+
/**
|
|
2116
|
+
* Subscribe to output statements generated during execution.
|
|
2117
|
+
*/
|
|
2118
|
+
subscribeToOutput(listener: OutputListener): Unsubscribe;
|
|
2119
|
+
/**
|
|
2120
|
+
* Get all output statements generated so far.
|
|
2121
|
+
*/
|
|
2122
|
+
getOutputStatements(): IOutputStatement[];
|
|
2123
|
+
/**
|
|
2124
|
+
* Ephemeral Tier-2 (running-total) snapshot for live display; never persisted.
|
|
2125
|
+
*/
|
|
2126
|
+
getLiveAnalytics(): IOutputStatement[];
|
|
2127
|
+
/**
|
|
2128
|
+
* Add an output statement to the collection and notify subscribers.
|
|
2129
|
+
* Used by BehaviorContext to emit outputs at any lifecycle point.
|
|
2130
|
+
*/
|
|
2131
|
+
addOutput(output: IOutputStatement): void;
|
|
2132
|
+
setAnalyticsEngine(engine: IAnalyticsEngine): void;
|
|
2133
|
+
/**
|
|
2134
|
+
* Finalize the analytics engine and return any summary output statements.
|
|
2135
|
+
*/
|
|
2136
|
+
finalizeAnalytics(): IOutputStatement[];
|
|
2137
|
+
/**
|
|
2138
|
+
* Subscribe to stack snapshots. The observer receives a StackSnapshot
|
|
2139
|
+
* on every push/pop/clear event. New subscribers immediately receive
|
|
2140
|
+
* an 'initial' snapshot with the current stack state.
|
|
2141
|
+
*/
|
|
2142
|
+
subscribeToStack(observer: StackObserver): Unsubscribe;
|
|
2143
|
+
/**
|
|
2144
|
+
* Cleanup method that properly unmounts and disposes all blocks in the stack.
|
|
2145
|
+
*/
|
|
2146
|
+
dispose(): void;
|
|
2147
|
+
/**
|
|
2148
|
+
* Pushes a block onto the runtime stack.
|
|
2149
|
+
* This is a convenience method that wraps PushBlockAction.
|
|
2150
|
+
* Also handles hooks, tracking, wrapping, and logging if configured.
|
|
2151
|
+
*
|
|
2152
|
+
* @param block The block to push
|
|
2153
|
+
* @param lifecycle Optional lifecycle options (startTime, etc.)
|
|
2154
|
+
*/
|
|
2155
|
+
pushBlock(block: IRuntimeBlock, lifecycle?: BlockLifecycleOptions): void;
|
|
2156
|
+
/**
|
|
2157
|
+
* Pops the current block from the runtime stack.
|
|
2158
|
+
* This is a convenience method that wraps PopBlockAction.
|
|
2159
|
+
* Handles the full lifecycle: unmount, pop, dispose, and parent notification.
|
|
2160
|
+
* Also handles hooks, tracking, wrapping cleanup, and logging if configured.
|
|
2161
|
+
*
|
|
2162
|
+
* @param lifecycle Optional lifecycle options (createdAt, etc.)
|
|
2163
|
+
*/
|
|
2164
|
+
popBlock(lifecycle?: BlockLifecycleOptions): void;
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
/**
|
|
2168
|
+
* Timer direction for count-down vs count-up.
|
|
2169
|
+
*/
|
|
2170
|
+
type TimerDirection = 'up' | 'down';
|
|
2171
|
+
/**
|
|
2172
|
+
* Timer state stored in block memory.
|
|
2173
|
+
*
|
|
2174
|
+
* ## Glossary (docs/architecture/time-terminology.md)
|
|
2175
|
+
* - `spans` — **Time**: raw TimeSpan[] recordings the block tracks
|
|
2176
|
+
* - `durationMs` — **Duration**: planned target from the parser (e.g., 300 000 ms for "5:00")
|
|
2177
|
+
* - Derived values Elapsed and Total are computed from spans at collection time.
|
|
2178
|
+
*/
|
|
2179
|
+
interface TimerState {
|
|
2180
|
+
/**
|
|
2181
|
+
* **Time** — raw time segments being tracked.
|
|
2182
|
+
* Source of truth for Elapsed (Σ span durations) and Total (wall-clock bracket).
|
|
2183
|
+
*/
|
|
2184
|
+
readonly spans: readonly TimeSpan[];
|
|
2185
|
+
/** **Duration** — planned target in ms from the parser (for countdowns or goals). */
|
|
2186
|
+
readonly durationMs?: number;
|
|
2187
|
+
/** Timer direction preference */
|
|
2188
|
+
readonly direction: TimerDirection;
|
|
2189
|
+
/** Human-readable label for the timer */
|
|
2190
|
+
readonly label: string;
|
|
2191
|
+
/** Timer role for UI prioritization */
|
|
2192
|
+
readonly role?: 'primary' | 'secondary' | 'auto' | 'hidden';
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Round state stored in memory.
|
|
2196
|
+
* Represents current iteration and scale of a loop.
|
|
2197
|
+
*/
|
|
2198
|
+
interface RoundState {
|
|
2199
|
+
/** Current round number (typically 1-based) */
|
|
2200
|
+
readonly current: number;
|
|
2201
|
+
/** Total number of rounds planned (undefined for unbounded) */
|
|
2202
|
+
readonly total: number | undefined;
|
|
2203
|
+
}
|
|
2204
|
+
/** Written by ChildSelectionBehavior to children:status memory tag */
|
|
2205
|
+
interface ChildrenStatusState {
|
|
2206
|
+
/** Index of the next child to dispatch (0-based) */
|
|
2207
|
+
readonly childIndex: number;
|
|
2208
|
+
/** Total number of child groups */
|
|
2209
|
+
readonly totalChildren: number;
|
|
2210
|
+
/** True when all child groups have been dispatched at least once */
|
|
2211
|
+
readonly allExecuted: boolean;
|
|
2212
|
+
/** True when all dispatched children have completed (popped) */
|
|
2213
|
+
readonly allCompleted: boolean;
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Fragment state stored in memory.
|
|
2217
|
+
*
|
|
2218
|
+
* Fragment groups represent semantic groupings from compilation (e.g., per-round,
|
|
2219
|
+
* per-interval). Each inner array is one group produced by the metrics distributor.
|
|
2220
|
+
* This preserves the multi-dimensional structure through the entire pipeline:
|
|
2221
|
+
* Parser → Strategy → BlockBuilder → FragmentMemory → RuntimeBlock.metrics
|
|
2222
|
+
*/
|
|
2223
|
+
interface FragmentState {
|
|
2224
|
+
/** Fragment groups — each inner array is a semantic group (e.g., per-round metrics) */
|
|
2225
|
+
readonly groups: readonly MetricContainer[];
|
|
2226
|
+
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Display-ready metrics state stored in memory.
|
|
2229
|
+
*
|
|
2230
|
+
* Produced by applying precedence resolution to raw metrics groups.
|
|
2231
|
+
* The `resolved` array contains metric after per-type precedence
|
|
2232
|
+
* selection (user > runtime > compiler > parser).
|
|
2233
|
+
*/
|
|
2234
|
+
interface FragmentDisplayState {
|
|
2235
|
+
/** All raw metrics flattened from groups (before precedence) */
|
|
2236
|
+
readonly metrics: MetricContainer;
|
|
2237
|
+
/** Precedence-resolved metric ready for display */
|
|
2238
|
+
readonly resolved: readonly IMetric[];
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Completion state stored in memory.
|
|
2242
|
+
* Tracks whether a block has completed and why.
|
|
2243
|
+
*/
|
|
2244
|
+
interface CompletionState {
|
|
2245
|
+
/** Whether block is marked complete */
|
|
2246
|
+
readonly isComplete: boolean;
|
|
2247
|
+
/** Reason for completion */
|
|
2248
|
+
readonly reason?: 'timer-expired' | 'rounds-complete' | 'user-advance' | 'manual' | string;
|
|
2249
|
+
/** Timestamp of completion (epoch ms) */
|
|
2250
|
+
readonly createdAt?: number;
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Display state stored in memory.
|
|
2254
|
+
* UI-facing state for labels and modes.
|
|
2255
|
+
*/
|
|
2256
|
+
interface DisplayState {
|
|
2257
|
+
/** Current display mode */
|
|
2258
|
+
readonly mode: 'clock' | 'timer' | 'countdown' | 'hidden';
|
|
2259
|
+
/** Primary label */
|
|
2260
|
+
readonly label: string;
|
|
2261
|
+
/** Secondary/subtitle label */
|
|
2262
|
+
readonly subtitle?: string;
|
|
2263
|
+
/** Formatted round string (e.g., "Round 2/5") */
|
|
2264
|
+
readonly roundDisplay?: string;
|
|
2265
|
+
/** Exercise/action being performed */
|
|
2266
|
+
readonly actionDisplay?: string;
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Button style variants for UI rendering.
|
|
2270
|
+
*/
|
|
2271
|
+
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
|
|
2272
|
+
/**
|
|
2273
|
+
* Configuration for a single control button.
|
|
2274
|
+
*/
|
|
2275
|
+
interface ButtonConfig {
|
|
2276
|
+
/** Unique button identifier */
|
|
2277
|
+
readonly id: string;
|
|
2278
|
+
/** Display label */
|
|
2279
|
+
readonly label: string;
|
|
2280
|
+
/** Button style variant */
|
|
2281
|
+
readonly variant: ButtonVariant;
|
|
2282
|
+
/** Whether button is currently visible */
|
|
2283
|
+
readonly visible: boolean;
|
|
2284
|
+
/** Whether button is enabled (clickable) */
|
|
2285
|
+
readonly enabled: boolean;
|
|
2286
|
+
/**
|
|
2287
|
+
* Event name to emit when clicked.
|
|
2288
|
+
* External systems subscribe to these events to handle user actions.
|
|
2289
|
+
* Examples: 'timer:pause', 'block:next', 'workout:stop'
|
|
2290
|
+
*/
|
|
2291
|
+
readonly eventName?: string;
|
|
2292
|
+
/**
|
|
2293
|
+
* Whether this button is pinned (always visible).
|
|
2294
|
+
* Derived from `[:!action]` syntax in Whiteboard scripts.
|
|
2295
|
+
*/
|
|
2296
|
+
readonly isPinned?: boolean;
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Buttons state stored in memory.
|
|
2300
|
+
* Represents the current set of control buttons available to the user.
|
|
2301
|
+
*
|
|
2302
|
+
* The UI observes this memory to render action buttons. When buttons are
|
|
2303
|
+
* clicked, the UI emits the corresponding `eventName` as an external event.
|
|
2304
|
+
*/
|
|
2305
|
+
interface ButtonsState {
|
|
2306
|
+
/** Current button configurations */
|
|
2307
|
+
readonly buttons: readonly ButtonConfig[];
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Union of all valid memory type keys.
|
|
2311
|
+
*/
|
|
2312
|
+
type MemoryType = 'time' | 'round' | 'children:status' | 'metrics' | 'metric:display' | 'completion' | 'display' | 'controls';
|
|
2313
|
+
/**
|
|
2314
|
+
* Registry mapping memory types to their corresponding data shapes.
|
|
2315
|
+
* Enables compile-time type safety when accessing block memory.
|
|
2316
|
+
*
|
|
2317
|
+
* @example
|
|
2318
|
+
* const timer = block.getMemoryByTag('time')[0]; // Returns IMemoryLocation with TimerState
|
|
2319
|
+
*/
|
|
2320
|
+
interface MemoryTypeMap {
|
|
2321
|
+
'time': TimerState;
|
|
2322
|
+
round: RoundState;
|
|
2323
|
+
'children:status': ChildrenStatusState;
|
|
2324
|
+
metrics: FragmentState;
|
|
2325
|
+
'metric:display': FragmentDisplayState;
|
|
2326
|
+
completion: CompletionState;
|
|
2327
|
+
display: DisplayState;
|
|
2328
|
+
controls: ButtonsState;
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Utility to resolve the value type for a given memory type key.
|
|
2332
|
+
*/
|
|
2333
|
+
type MemoryValueOf<T extends MemoryType> = MemoryTypeMap[T];
|
|
2334
|
+
|
|
2335
|
+
declare global {
|
|
2336
|
+
interface Window {
|
|
2337
|
+
__chromecast_senderClockTimeMs?: () => number;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Returns the canonical "now" used for runtime timer interpolation.
|
|
2342
|
+
*
|
|
2343
|
+
* Browser workbench: falls back to Date.now().
|
|
2344
|
+
* Chromecast receiver: uses sender-synchronized clock when available.
|
|
2345
|
+
*/
|
|
2346
|
+
declare function getRuntimeNowMs(): number;
|
|
2347
|
+
/** Creates an INowProvider that reads the browser sender-clock when available. */
|
|
2348
|
+
declare function createBrowserRuntimeNow(): INowProvider;
|
|
2349
|
+
|
|
2350
|
+
export { type EventHandlerOptions as $, type AnalyticsContext as A, type BehaviorEventType as B, type CompletionDecision as C, type IRuntimeMemory as D, type EffortResolverOptions as E, TypedMemoryReference as F, type MemorySearchCriteria as G, type IMemoryReference as H, type IScript as I, type StackObserver as J, type StackSnapshot as K, type StackListener as L, type MemoryTag as M, MemoryTypeEnum as N, type OutputListener as O, type IAnchorValue as P, type IEventHandler as Q, type ResolvedEffort as R, type SubscribeOptions as S, TimeSpan as T, type Unsubscribe as U, type HandlerScope as V, WhiteboardScript as W, type IScriptRuntime as X, type TestableBlockConfig as Y, type IRuntimeOptions as Z, ScriptRuntime as _, type IEffortResolver as a, type EventCallback as a0, type TimerState as a1, type IRealtimeProcessor as a2, type ISummaryProcessor as a3, type ProjectionResult as a4, type ButtonConfig as a5, type ButtonVariant as a6, type ButtonsState as a7, type ChildrenStatusState as a8, type CompletionState as a9, disciplineFactorFor as aA, filterTagsByVisibility as aB, frozenNow as aC, getMetricVisibility as aD, getRuntimeNowMs as aE, isEffortDiscipline as aF, isFragmentTag as aG, wallClockNow as aH, DEFAULT_RESOLVER_OPTIONS as aa, DISCIPLINE_FACTORS as ab, type DisplayState as ac, EFFORT_DISCIPLINES as ad, EFFORT_REGISTRY_ORIGINS as ae, type EffortBaseAttributes as af, type EffortDerivation as ag, type EffortDiscipline as ah, type EffortResolvedFrom as ai, type FenceDialect as aj, type FragmentDisplayState as ak, type FragmentState as al, type IAnalyticsProcessorDescriptor as am, INTENSITY_TIERS as an, type IntensityTier as ao, MemoryLocation as ap, type MemoryType as aq, type MemoryTypeMap as ar, type MemoryValueOf as as, type RoundState as at, type RuntimeState as au, type ScriptRuntimeDependencies as av, type TimerDirection as aw, VISIBILITY_ICONS as ax, VISIBILITY_LABELS as ay, createBrowserRuntimeNow as az, type IEffortRegistry as b, type IEffort as c, type EffortResolutionOptions as d, type EffortRegistrySource as e, type IRuntimeActionable as f, type IEventBus as g, type IRuntimeStack as h, type IRuntimeClock as i, type IJitCompiler as j, type INowProvider as k, type RuntimeStackOptions as l, type RuntimeError as m, type IEvent as n, type IRuntimeAction as o, type IAnalyticsEngine as p, type IRuntimeBlock as q, type IBehaviorContext as r, type BehaviorEventListener as s, type Unsubscribe$1 as t, type OutputOptions as u, type IMemoryLocation as v, type IRuntimeBehavior as w, type IBlockContext as x, type MetricVisibility as y, type BlockLifecycleOptions as z };
|