@bitcobblers/wod-wiki-core 0.11.43 → 0.35.2116

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/index.d.ts CHANGED
@@ -1,58 +1,8 @@
1
1
  /**
2
2
  * Canonical ownership vocabulary for metric visibility decisions.
3
- *
4
- * This is intentionally separate from {@link MetricOrigin}, which still carries
5
- * legacy producer/state detail used by existing callers.
6
- *
7
3
  * Ordered from lowest to highest ownership precedence.
8
4
  */
9
5
  type MetricOwnershipLayer = 'parser' | 'dialect' | 'user-plan' | 'runtime' | 'user-entry';
10
- /**
11
- * Canonical low->high ownership chain used by characterization tests and
12
- * ownership resolution work.
13
- */
14
- declare const METRIC_OWNERSHIP_LAYER_CHAIN: readonly MetricOwnershipLayer[];
15
- /**
16
- * Compatibility map from the current mixed-purpose `origin` field into the
17
- * canonical ownership-layer vocabulary.
18
- */
19
- declare const LEGACY_ORIGIN_TO_OWNERSHIP_LAYER: Record<MetricOrigin, MetricOwnershipLayer>;
20
- interface MetricWithOptionalOwnershipLayer extends IMetric {
21
- readonly ownershipLayer?: MetricOwnershipLayer;
22
- }
23
- interface MetricOwnershipQuery {
24
- readonly types?: readonly (IMetric['type'])[];
25
- readonly layers?: readonly MetricOwnershipLayer[];
26
- }
27
- type MetricOwnershipOutcome = 'visible' | 'hidden-by-layer' | 'hidden-by-suppressor' | 'suppressor';
28
- interface MetricOwnershipResolvedContribution {
29
- readonly metric: IMetric;
30
- readonly type: IMetric['type'];
31
- readonly layer: MetricOwnershipLayer;
32
- readonly outcome: MetricOwnershipOutcome;
33
- }
34
- interface MetricOwnershipPromotionCandidate {
35
- readonly metric: IMetric;
36
- readonly type: IMetric['type'];
37
- readonly layer: MetricOwnershipLayer;
38
- readonly reason: 'shadowed-by-higher-layer' | 'suppressed';
39
- readonly blockedByLayer?: MetricOwnershipLayer;
40
- }
41
- interface MetricOwnershipTypeExplanation {
42
- readonly type: IMetric['type'];
43
- readonly winnerLayer?: MetricOwnershipLayer;
44
- readonly suppressed: boolean;
45
- readonly entries: readonly MetricOwnershipResolvedContribution[];
46
- }
47
- interface MetricOwnershipLedger {
48
- raw(query?: MetricOwnershipQuery): IMetric[];
49
- visible(query?: MetricOwnershipQuery): IMetric[];
50
- byLayer(query?: MetricOwnershipQuery): Partial<Record<MetricOwnershipLayer, IMetric[]>>;
51
- promotionCandidates(query?: MetricOwnershipQuery): MetricOwnershipPromotionCandidate[];
52
- explain(query?: MetricOwnershipQuery): MetricOwnershipTypeExplanation[];
53
- }
54
- declare function getMetricOwnershipLayer(origin: MetricOrigin | undefined): MetricOwnershipLayer;
55
-
56
6
  /**
57
7
  * Origin of a metric - where it was created and its current state.
58
8
  *
@@ -115,6 +65,12 @@ interface IMetric {
115
65
  readonly ownershipLayer?: MetricOwnershipLayer;
116
66
  /** Optional human-readable name or label */
117
67
  readonly name?: string;
68
+ /**
69
+ * Optional icon (emoji/glyph) for hint metrics. Hints without an icon are
70
+ * data-only — they never render in display surfaces; a hint WITH an icon
71
+ * renders as the icon alone, with the hint text on hover.
72
+ */
73
+ readonly icon?: string;
118
74
  }
119
75
  /**
120
76
  * Metric type enumeration.
@@ -184,6 +140,9 @@ declare enum MetricType {
184
140
  Custom = "custom",
185
141
  Volume = "volume",
186
142
  Intensity = "intensity",
143
+ /** Climb grade label carried by an observation (e.g. `V8`, `5.12a`) —
144
+ * user-authored grade metrics promote to the analytics `grade` dim. */
145
+ Grade = "grade",
187
146
  Load = "load",
188
147
  Work = "work",
189
148
  /** Per-set Reps in Reserve (real-time, strength) */
@@ -295,6 +254,233 @@ interface IMetricSource {
295
254
  readonly rawMetrics: IMetric[];
296
255
  }
297
256
 
257
+ /**
258
+ * Unified resolver for metric ownership precedence.
259
+ *
260
+ * Implements the canonical 5-tier ownership ledger resolution:
261
+ * parser -> dialect -> user-plan -> runtime -> user-entry
262
+ */
263
+ interface IMetricOwnershipResolver {
264
+ /** Resolve display-ready metrics, applying optional type/layer filters. */
265
+ resolve(metrics: readonly IMetric[], filter?: MetricFilter): IMetric[];
266
+ /** Resolve the single winning metric of a given type, or undefined. */
267
+ resolveOne(metrics: readonly IMetric[], type: MetricType | string): IMetric | undefined;
268
+ /** Resolve all metrics of a given type, sorted by ownership tier (highest first). */
269
+ resolveAll?(metrics: readonly IMetric[], type: MetricType | string): IMetric[];
270
+ }
271
+
272
+ /**
273
+ * Tag-Identity Registry Contract
274
+ *
275
+ * Single source of truth for dialect fence tags and runnable state,
276
+ * as defined in docs/adr/dialect-block-alignment.md and docs/adr/language-pack-api.md.
277
+ */
278
+ interface IDialectTagDescriptor {
279
+ /** Canonical fence tag (e.g. 'time', 'climb', 'wod') */
280
+ readonly tag: string;
281
+ /** Optional fence aliases (e.g. ['whiteboard', 'wod']) */
282
+ readonly aliases?: readonly string[];
283
+ /** Optional human-readable name */
284
+ readonly name?: string;
285
+ /** Whether blocks using this dialect produce runnable executions */
286
+ readonly runnable?: boolean;
287
+ }
288
+
289
+ /**
290
+ * Canonical low->high ownership chain used by characterization tests and
291
+ * ownership resolution work.
292
+ */
293
+ declare const METRIC_OWNERSHIP_LAYER_CHAIN: readonly MetricOwnershipLayer[];
294
+ /**
295
+ * Compatibility map from the current mixed-purpose `origin` field into the
296
+ * canonical ownership-layer vocabulary.
297
+ */
298
+ declare const LEGACY_ORIGIN_TO_OWNERSHIP_LAYER: Record<MetricOrigin, MetricOwnershipLayer>;
299
+ interface MetricWithOptionalOwnershipLayer extends IMetric {
300
+ readonly ownershipLayer?: MetricOwnershipLayer;
301
+ }
302
+ interface MetricOwnershipQuery {
303
+ readonly types?: readonly (IMetric['type'])[];
304
+ readonly layers?: readonly MetricOwnershipLayer[];
305
+ }
306
+ type MetricOwnershipOutcome = 'visible' | 'hidden-by-layer' | 'hidden-by-suppressor' | 'suppressor';
307
+ interface MetricOwnershipResolvedContribution {
308
+ readonly metric: IMetric;
309
+ readonly type: IMetric['type'];
310
+ readonly layer: MetricOwnershipLayer;
311
+ readonly outcome: MetricOwnershipOutcome;
312
+ }
313
+ interface MetricOwnershipPromotionCandidate {
314
+ readonly metric: IMetric;
315
+ readonly type: IMetric['type'];
316
+ readonly layer: MetricOwnershipLayer;
317
+ readonly reason: 'shadowed-by-higher-layer' | 'suppressed';
318
+ readonly blockedByLayer?: MetricOwnershipLayer;
319
+ }
320
+ interface MetricOwnershipTypeExplanation {
321
+ readonly type: IMetric['type'];
322
+ readonly winnerLayer?: MetricOwnershipLayer;
323
+ readonly suppressed: boolean;
324
+ readonly entries: readonly MetricOwnershipResolvedContribution[];
325
+ }
326
+ interface MetricOwnershipLedger {
327
+ raw(query?: MetricOwnershipQuery): IMetric[];
328
+ visible(query?: MetricOwnershipQuery): IMetric[];
329
+ byLayer(query?: MetricOwnershipQuery): Partial<Record<MetricOwnershipLayer, IMetric[]>>;
330
+ promotionCandidates(query?: MetricOwnershipQuery): MetricOwnershipPromotionCandidate[];
331
+ explain(query?: MetricOwnershipQuery): MetricOwnershipTypeExplanation[];
332
+ }
333
+ declare function getMetricOwnershipLayer(origin: MetricOrigin | undefined): MetricOwnershipLayer;
334
+
335
+ declare function resolveLayer(metric: IMetric): MetricOwnershipLayer;
336
+ declare function ownershipRank(metric: IMetric): number;
337
+ declare function createMetricOwnershipLedger(metrics: readonly IMetric[]): MetricOwnershipLedger;
338
+ declare class OwnershipResolver implements IMetricOwnershipResolver {
339
+ resolve(metrics: readonly IMetric[], filter?: MetricFilter): IMetric[];
340
+ resolveOne(metrics: readonly IMetric[], type: MetricType | string): IMetric | undefined;
341
+ resolveAll(metrics: readonly IMetric[], type: MetricType | string): IMetric[];
342
+ }
343
+
344
+ /**
345
+ * Field identity — the pure normalization/identity layer for typed metric
346
+ * fields (wayfinder datadog-analytics ticket 11, per the field discovery
347
+ * contract §Identity and discovery).
348
+ *
349
+ * A metric variant is identified by **normalized full property path + value
350
+ * kind + physical dimension where applicable**. Source, display label,
351
+ * original spelling, and convertible unit spelling are provenance, not
352
+ * identity. Built-in and custom metrics use the same rules.
353
+ *
354
+ * This module is pure data + functions with zero dependencies: `lang` and
355
+ * `wql` (and the app adapter) both consume it through their existing core
356
+ * dependency. Dimension resolution from units lives with the unit catalog —
357
+ * callers attach a resolved `dimension` when one is known.
358
+ */
359
+ /** The observed value kinds of a field variant (field discovery contract). */
360
+ type FieldValueKind = 'number' | 'string' | 'boolean' | 'array';
361
+ /** Presence/clearing marker — null records presence, never a value. */
362
+ type NullValueKind = 'null';
363
+ /**
364
+ * Typed field reference — the identity of one metric variant.
365
+ * `dimension` is the physical dimension for numeric kinds (resolved through
366
+ * the unit registry); undefined means dimensionless or unknown.
367
+ */
368
+ interface FieldRef {
369
+ /** Normalized full property path — lower camelCase components, dots delimit. */
370
+ readonly path: string;
371
+ /** Observed value kind of the variant. */
372
+ readonly kind: FieldValueKind | NullValueKind;
373
+ /** Physical dimension for numeric kinds, when known. */
374
+ readonly dimension?: string;
375
+ }
376
+ /** Lower camelCase one path component: `heart rate`, `heart_rate`,
377
+ * `HeartRate`, and `heartRate` all normalize to `heartRate`; `HRV`
378
+ * normalizes to `hrv`. No synonym inference: `heartrate` stays `heartrate`. */
379
+ declare function normalizeFieldComponent(component: string): string;
380
+ /**
381
+ * Normalize a full property path. Dots are reserved path separators, so
382
+ * `sleep_quality.score_value` → `sleepQuality.scoreValue` and nested/flat
383
+ * forms share one identity. Empty components collapse: `sleep..score` →
384
+ * `sleep.score`.
385
+ */
386
+ declare function normalizeFieldPath(path: string): string;
387
+ /** Classify an observed value into a field value kind. Objects and null are
388
+ * deliberately outside the value kinds: objects contribute nested leaves and
389
+ * null records presence/clearing — neither is a value. */
390
+ declare function valueKindOf(value: unknown): FieldValueKind | 'object' | 'null' | 'undefined';
391
+ /** One flattened leaf observation: identity path + kind, the raw value, and
392
+ * the original spelling/shape it was authored with (provenance). */
393
+ interface FieldLeaf {
394
+ /** Normalized full path. */
395
+ readonly path: string;
396
+ /** Value kind — 'null' leaves record presence/clearing, never a value. */
397
+ readonly kind: FieldValueKind | NullValueKind;
398
+ readonly value: unknown;
399
+ /** Original path spelling/shape as authored (flat dotted or nested). */
400
+ readonly originalPath: string;
401
+ }
402
+ /**
403
+ * Flatten an authored property object into typed leaves.
404
+ *
405
+ * - Dots in keys delimit paths: flat `{"sleep.score": 85}` ≡ nested
406
+ * `{sleep:{score:85}}` — one identity.
407
+ * - Objects contribute nested leaves; empty objects contribute nothing.
408
+ * - Arrays are collections: one collection-valued leaf, never per-element
409
+ * observations.
410
+ * - Null records presence under its path without a value.
411
+ */
412
+ declare function flattenFieldLeaves(input: Record<string, unknown>, prefix?: string): FieldLeaf[];
413
+ /**
414
+ * Collision-free canonical string for a typed variant identity. JSON array
415
+ * encoding keeps a separator character inside a path from masquerading as a
416
+ * structural boundary (`a|number` as path cannot collide with `a` + a
417
+ * `number` dimension).
418
+ */
419
+ declare function fieldRefKey(ref: FieldRef): string;
420
+ /** Parse a {@link fieldRefKey} back into a FieldRef (round-trip helper). */
421
+ declare function fieldRefFromKey(key: string): FieldRef;
422
+
423
+ /**
424
+ * Unit catalog — the shared home for unit recognition, conversion evidence,
425
+ * and the system default output table (wayfinder decision ticket 21;
426
+ * consumed by wql's arithmetic rewrite, ticket 13).
427
+ *
428
+ * Recognition spellings are case-insensitive through canonical spellings and
429
+ * aliases. Every entry carries its conversion evidence: the physical
430
+ * dimension it measures and the fixed factor converting it to that
431
+ * dimension's base unit. Compound units (speed, pace) convert through their
432
+ * own scale factors to the compound base.
433
+ *
434
+ * `bw` (body-weight-relative load) is deliberately NOT a fixed-factor entry:
435
+ * it requires genuine measurement-time context persisted with the value
436
+ * (ticket 14's conversion evidence) — never a constant kg conversion, never
437
+ * today's body mass reinterpreting history (missing-context limitation).
438
+ */
439
+ /** Named result dimensions — the keys of the system default table. */
440
+ type DimensionKey = 'mass' | 'distance' | 'duration' | 'count' | 'count-rate' | 'energy' | 'speed' | 'pace' | 'ratio';
441
+ /** Error taxonomy (decision ticket 21). */
442
+ type UnitErrorCode = 'unknown-unit' | 'dimension-mismatch' | 'scale-mismatch' | 'missing-context' | 'non-finite' | 'non-convertible';
443
+ declare class UnitConversionError extends Error {
444
+ readonly code: UnitErrorCode;
445
+ constructor(code: UnitErrorCode, message: string);
446
+ }
447
+ interface UnitDefinition {
448
+ /** Canonical spelling (e.g. `kg`, `mi`, `min/km`). */
449
+ readonly canonical: string;
450
+ /** Named result dimension. */
451
+ readonly dimension: DimensionKey;
452
+ /** Fixed factor converting a value of this unit to the dimension's base
453
+ * unit (conversion evidence). */
454
+ readonly factor: number;
455
+ /** Registered alternate spellings (case-insensitive at lookup). */
456
+ readonly aliases?: readonly string[];
457
+ }
458
+ /**
459
+ * Base units per dimension: mass `kg`, distance `m`, duration `s`,
460
+ * count `count`, energy `cal`, speed `m/s`, pace `min/km`; ratio is
461
+ * unitless (factor 1).
462
+ */
463
+ declare const UNIT_CATALOG: readonly UnitDefinition[];
464
+ /** Look up a unit by canonical spelling or alias (case-insensitive). */
465
+ declare function lookupUnit(spelling: string): UnitDefinition | undefined;
466
+ /** The named result dimension of a unit spelling, or undefined when the
467
+ * spelling is not in the catalog. */
468
+ declare function dimensionKeyOf(spelling: string): DimensionKey | undefined;
469
+ /** System default output unit per result dimension (arithmetic contract §5;
470
+ * values agreed in ticket 13, home resolved in ticket 21). Ratio defaults
471
+ * to unitless — `undefined`, never a fabricated unit label. */
472
+ declare const SYSTEM_DEFAULT_OUTPUT_UNIT: Readonly<Record<DimensionKey, string | undefined>>;
473
+ /** Default output unit for a result dimension — undefined means unitless. */
474
+ declare function systemDefaultUnit(dimension: DimensionKey): string | undefined;
475
+ /**
476
+ * Strict conversion through the catalog's conversion evidence.
477
+ *
478
+ * Errors (`UnitConversionError`) on unknown units (`unknown-unit`) and
479
+ * cross-dimension conversions (`dimension-mismatch`) — an explicit unknown
480
+ * unit is never passed through and never replaced by a default.
481
+ */
482
+ declare function convertViaCatalog(value: number, from: string, to: string): number;
483
+
298
484
  interface ICodeMetadata {
299
485
  line: number;
300
486
  startOffset: number;
@@ -526,6 +712,10 @@ interface OutputStatementOptions {
526
712
  stackLevel?: number;
527
713
  /** The source statement ID (optional) */
528
714
  sourceStatementId?: number;
715
+ /** Original source line/text/dialect (when rebuilding from stored records) */
716
+ line?: number;
717
+ text?: string;
718
+ dialect?: string;
529
719
  /** Runtime-collected metrics */
530
720
  metrics?: MetricContainer | IMetric[];
531
721
  /** Map of metrics to metadata */
@@ -540,7 +730,7 @@ interface OutputStatementOptions {
540
730
  /**
541
731
  * Concrete implementation of IOutputStatement.
542
732
  */
543
- declare class OutputStatement implements IOutputStatement, IMetricSource {
733
+ declare class OutputStatement extends CodeStatement implements IOutputStatement {
544
734
  private static nextId;
545
735
  readonly id: number;
546
736
  readonly outputType: OutputStatementType;
@@ -558,105 +748,12 @@ declare class OutputStatement implements IOutputStatement, IMetricSource {
558
748
  readonly statement?: ICodeStatement;
559
749
  readonly timestamp?: number;
560
750
  constructor(optionsOrStatement: OutputStatementOptions | ICodeStatement, metricsArg?: MetricContainer | IMetric[], timestamp?: number);
561
- getDisplayMetrics(filter?: MetricFilter): IMetric[];
562
- getMetric(type: MetricType | string): IMetric | undefined;
563
- getAllMetricsByType(type: MetricType | string): IMetric[];
564
- hasMetric(type: MetricType | string): boolean;
565
- get rawMetrics(): IMetric[];
566
751
  /**
567
752
  * Reset the ID counter (useful for testing)
568
753
  */
569
754
  static resetIdCounter(startAt?: number): void;
570
755
  }
571
756
 
572
- interface IMetricContainer extends IMetricSource {
573
- metrics: MetricContainer;
574
- clone(): IMetricContainer;
575
- merge(other: IMetricContainer | MetricContainer): this;
576
- }
577
- type SummaryStage = 'estimate' | 'summary';
578
- interface IMetricSummary extends IMetricContainer {
579
- readonly stage: SummaryStage;
580
- readonly sourceId: string | number;
581
- readonly producedAt: Date;
582
- }
583
- interface IEstimate extends IMetricSummary {
584
- readonly stage: 'estimate';
585
- }
586
- interface ISummary extends IMetricSummary {
587
- readonly stage: 'summary';
588
- readonly spans: IOutputStatement[];
589
- }
590
-
591
- /**
592
- * Unified resolver for metric ownership precedence.
593
- *
594
- * Implements the canonical 5-tier ownership ledger resolution:
595
- * parser -> dialect -> user-plan -> runtime -> user-entry
596
- */
597
- interface IMetricOwnershipResolver {
598
- /** Resolve display-ready metrics, applying optional type/layer filters. */
599
- resolve(metrics: readonly IMetric[], filter?: MetricFilter): IMetric[];
600
- /** Resolve the single winning metric of a given type, or undefined. */
601
- resolveOne(metrics: readonly IMetric[], type: MetricType | string): IMetric | undefined;
602
- /** Resolve all metrics of a given type, sorted by ownership tier (highest first). */
603
- resolveAll?(metrics: readonly IMetric[], type: MetricType | string): IMetric[];
604
- }
605
-
606
- /**
607
- * Tag-Identity Registry Contract
608
- *
609
- * Single source of truth for dialect fence tags and runnable state,
610
- * as defined in docs/adr/dialect-block-alignment.md and docs/adr/language-pack-api.md.
611
- */
612
- interface IDialectTagDescriptor {
613
- /** Canonical fence tag (e.g. 'time', 'climb', 'wod') */
614
- readonly tag: string;
615
- /** Optional fence aliases (e.g. ['whiteboard', 'wod']) */
616
- readonly aliases?: readonly string[];
617
- /** Optional human-readable name */
618
- readonly name?: string;
619
- /** Whether blocks using this dialect produce runnable executions */
620
- readonly runnable?: boolean;
621
- }
622
-
623
- declare function createMetricOwnershipLedger(metrics: readonly IMetric[]): MetricOwnershipLedger;
624
-
625
- /**
626
- * Ownership layer rank for a metric (0 = lowest = parser, 4 = highest = user-entry).
627
- * Higher rank wins.
628
- */
629
- declare function ownershipRank(metric: IMetric): number;
630
- /**
631
- * Single-path resolver wrapping `MetricOwnershipLedger` + legacy filter translation.
632
- */
633
- declare class OwnershipResolver implements IMetricOwnershipResolver {
634
- resolve(metrics: readonly IMetric[], filter?: MetricFilter): IMetric[];
635
- resolveOne(metrics: readonly IMetric[], type: MetricType | string): IMetric | undefined;
636
- resolveAll(metrics: readonly IMetric[], type: MetricType | string): IMetric[];
637
- }
638
-
639
- interface IDuration {
640
- days?: number;
641
- hours?: number;
642
- minutes?: number;
643
- seconds?: number;
644
- milliseconds?: number;
645
- original?: number;
646
- }
647
- declare class Duration implements IDuration {
648
- original?: number | undefined;
649
- days?: number;
650
- hours?: number;
651
- minutes?: number;
652
- seconds?: number;
653
- milliseconds?: number;
654
- constructor(original?: number | undefined);
655
- }
656
- declare class SpanDuration extends Duration {
657
- constructor(spans: TimeSpan[]);
658
- }
659
-
660
757
  interface IBlockKey {
661
758
  readonly value: string;
662
759
  toString(): string;
@@ -766,6 +863,15 @@ interface Segment {
766
863
  metrics?: MetricContainer;
767
864
  }
768
865
 
866
+ /**
867
+ * Content-stable identity for a workout block.
868
+ *
869
+ * A 32-bit FNV-1a hash of the trimmed fenced content, formatted as `bc-<8 hex>`.
870
+ * Survives clone / reorder / edit-above so results and facts keyed by it stay
871
+ * linked across runs.
872
+ */
873
+ declare function blockContentId(content: string): string;
874
+
769
875
  /**
770
876
  * Plain-data snapshot of a runtime OutputStatement safe for JSON / IndexedDB
771
877
  * round-trips. Class methods and non-serialisable fields (Set, Map,
@@ -831,17 +937,20 @@ interface WorkoutResults {
831
937
  }
832
938
 
833
939
  type SegmentDataType = 'script' | 'youtube' | 'markdown' | 'header' | 'frontmatter' | 'wod' | 'title' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
834
- type NoteKind = 'note' | 'template' | 'playground' | 'journal';
940
+ type NoteKind = 'journal' | 'template' | 'playground' | 'feed' | 'dashboard' | 'page' | (string & {});
835
941
  interface Note {
836
942
  id: string;
837
943
  title: string;
838
- slug?: string;
839
- pageId?: string;
944
+ date?: number;
840
945
  createdAt: number;
841
946
  type?: NoteKind;
842
947
  sourceId?: string;
843
948
  catalog?: string;
844
949
  tags?: string[];
950
+ /** Original seed file path (e.g. `markdown/collections/girls/fran.md`) — the
951
+ * deterministic UUID is derived from this path; retained for seed content
952
+ * reconstruction (canvas routes, collection READMEs, block-index lookup). */
953
+ sourcePath?: string;
845
954
  }
846
955
  interface Page {
847
956
  id: string;
@@ -850,7 +959,21 @@ interface Page {
850
959
  title?: string;
851
960
  createdAt: number;
852
961
  }
853
- type TagType = 'template' | 'playground' | 'qualification' | 'notebook' | 'general';
962
+ interface PageNote {
963
+ id: string;
964
+ pageId: string;
965
+ noteId: string;
966
+ position?: number;
967
+ createdAt: number;
968
+ }
969
+ type TagType = 'template' | 'playground' | 'qualification' | 'notebook' | 'general' | (string & {});
970
+ interface TagTypeRecord {
971
+ id: string;
972
+ name: string;
973
+ label: string;
974
+ color?: string;
975
+ createdAt: number;
976
+ }
854
977
  interface Tag {
855
978
  id: string;
856
979
  label: string;
@@ -891,7 +1014,7 @@ interface BlockIndexRow {
891
1014
  sourceId?: string;
892
1015
  }
893
1016
  type ResultOrigin = 'journal' | 'playground' | 'user';
894
- interface WorkoutResult {
1017
+ interface Session {
895
1018
  id: string;
896
1019
  segmentId?: string;
897
1020
  segmentVersion?: number;
@@ -905,7 +1028,20 @@ interface WorkoutResult {
905
1028
  * start, flipped to 'completed' at finalize. Absent = 'completed'
906
1029
  * (legacy rows predate streaming). */
907
1030
  status?: 'in-progress' | 'completed';
908
- data: WorkoutResults;
1031
+ /** When workout started */
1032
+ startTime: number;
1033
+ /** When workout ended */
1034
+ endTime: number;
1035
+ /** Total elapsed time (ms) */
1036
+ duration: number;
1037
+ /** Rounds completed (for rounds-based workouts) */
1038
+ roundsCompleted?: number;
1039
+ /** Total rounds (for rounds-based workouts) */
1040
+ totalRounds?: number;
1041
+ /** Reps completed (for rep-based workouts) */
1042
+ repsCompleted?: number;
1043
+ /** Whether workout was completed or stopped early */
1044
+ completed: boolean;
909
1045
  createdAt: number;
910
1046
  }
911
1047
  interface Attachment {
@@ -933,6 +1069,10 @@ interface AnalyticsDataPoint {
933
1069
  discipline?: string;
934
1070
  intensityTier?: string;
935
1071
  grade?: string;
1072
+ /** User-authored categorical dims (custom property metrics and grouped
1073
+ * partitions) — normalized key → value, resolved by factTagValue after
1074
+ * the structural switch so `by {coach}` / `{coach:greg}` work. */
1075
+ dimensions?: Record<string, string>;
936
1076
  segmentId: string;
937
1077
  segmentVersion: number;
938
1078
  resultId: string;
@@ -943,6 +1083,22 @@ interface AnalyticsDataPoint {
943
1083
  metricKey?: string;
944
1084
  metricLabel?: string;
945
1085
  metricUnit?: string;
1086
+ /** Typed field reference provenance (ticket 11) — normalized path + kind
1087
+ * of the variant this observation belongs to. */
1088
+ fieldRef?: {
1089
+ path: string;
1090
+ kind: string;
1091
+ dimension?: string;
1092
+ };
1093
+ /** The observation's own civil date (YYYY-MM-DD) when it is date-only
1094
+ * (ticket 12) — grouping uses this instead of a fabricated instant. */
1095
+ metricDate?: string;
1096
+ /** Temporal kind of the observation's anchor (ticket 12). */
1097
+ temporalKind?: 'instant' | 'civil-date';
1098
+ /** Ticket 14 provenance carried onto the fact (ticket 16 selection). */
1099
+ representationKind?: 'direct' | 'calculated' | 'substitute_summary';
1100
+ summaryCoverage?: SummaryCoverage;
1101
+ reducerStats?: ReducerStats;
946
1102
  timestamp: number;
947
1103
  createdAt: number;
948
1104
  }
@@ -954,7 +1110,7 @@ type EventGrain = 'event' | 'summary';
954
1110
  * open string + known-values module; unknowns are stored and returned,
955
1111
  * matched only by kind-agnostic logic). */
956
1112
  declare const KNOWN_OUTPUT_TYPES: readonly ["segment", "system", "load", "event", "compiler", "completion", "analytics", "wellness"];
957
- interface UnifiedEventRecord {
1113
+ interface EventRecord {
958
1114
  /**
959
1115
  * Event rows: `${resultId}:${seq}` — immutable, append-only.
960
1116
  * Summary rows: `${resultId}:summary:${metricKey}[:k=v…]` — deterministic
@@ -971,9 +1127,9 @@ interface UnifiedEventRecord {
971
1127
  /** Canonical time — when the workout happened, never when derived. */
972
1128
  timestamp: number;
973
1129
  grain: EventGrain;
1130
+ effortSlug?: string;
974
1131
  /** Open vocabulary — see KNOWN_OUTPUT_TYPES. */
975
1132
  outputType: string;
976
- effortSlug?: string;
977
1133
  /** Typed metric array; EXACTLY ONE entry when grain:'summary'. Summary
978
1134
  * fold identity (canonicalKey, groupTags, effort metadata) lives in
979
1135
  * metrics[0].metadata — shape-uniform with events. */
@@ -983,10 +1139,119 @@ interface UnifiedEventRecord {
983
1139
  ended?: number;
984
1140
  };
985
1141
  sourceBlockKey?: string;
1142
+ sourceStatementId?: number;
1143
+ line?: number;
1144
+ text?: string;
1145
+ dialect?: string;
986
1146
  stackLevel?: number;
987
1147
  completionReason?: string;
988
1148
  segmentId?: string;
989
1149
  segmentVersion?: number;
1150
+ /** Per-metric temporal anchors (ticket 12) — parallel to `metrics` when
1151
+ * the producing statement carries its own instants/dates:
1152
+ * 'instant' = recorded occurrence instant; 'civil-date' = recorded civil
1153
+ * date (date-only wellness), never a fabricated midnight. The row
1154
+ * timestamp remains the V16 fetch anchor until the by-metricDate index
1155
+ * (ticket 14); it must not relocate a metric whose own date differs. */
1156
+ metricTemporal?: MetricTemporal[];
1157
+ /** Ticket 14: civil-date keys (d:YYYY-MM-DD) of every metricTemporal with
1158
+ * temporalKind 'civil-date' — the multiEntry `by-metric-date` index
1159
+ * source. Kept distinct from the instant index on purpose: a date-only
1160
+ * observation is never a midnight instant. */
1161
+ metricDateKeys?: string[];
1162
+ /** Ticket 14 provenance: how this row represents the underlying
1163
+ * observations — directly recorded, calculated, or a substitute summary. */
1164
+ representationKind?: 'direct' | 'calculated' | 'substitute_summary';
1165
+ /** Ticket 14: the temporal/production scope a substitute summary covers. */
1166
+ summaryCoverage?: SummaryCoverage;
1167
+ /** Ticket 14: retained reducer statistics for substitution proofs. */
1168
+ reducerStats?: ReducerStats;
1169
+ }
1170
+ /** Scope descriptor of a substitute summary's coverage (ticket 14). */
1171
+ interface SummaryCoverage {
1172
+ scope: 'workout' | 'effort' | 'partition';
1173
+ effortSlug?: string;
1174
+ /** Partition identity (group-tag pairs, key-sorted). */
1175
+ groupTags?: Record<string, string>;
1176
+ }
1177
+ /** Retained statistics for substitution proofs (ticket 16 consumes).
1178
+ * `observedCount` is the true represented observation count — never
1179
+ * invented for legacy rows that lack it (undefined = insufficient
1180
+ * evidence, not 1). */
1181
+ interface ReducerStats {
1182
+ observedCount?: number;
1183
+ sum?: number;
1184
+ min?: number;
1185
+ max?: number;
1186
+ }
1187
+ interface MetricTemporal {
1188
+ temporalKind: 'instant' | 'civil-date';
1189
+ /** Occurrence instant (ms epoch) when temporalKind === 'instant'. */
1190
+ instant?: number;
1191
+ /** Civil YYYY-MM-DD when temporalKind === 'civil-date'. */
1192
+ civilDate?: string;
1193
+ }
1194
+ /** One typed field identity in the catalog. `id` is the collision-free
1195
+ * typed key (see fieldRefKey in fields/fieldIdentity). */
1196
+ interface FieldCatalogEntry {
1197
+ id: string;
1198
+ /** Normalized full path — the ordered prefix-lookup key for typeahead. */
1199
+ path: string;
1200
+ kind: string;
1201
+ /** Physical/named dimension of a numeric variant, when known. */
1202
+ dimension?: string;
1203
+ /** Observed original spellings → supporting-source counts (provenance,
1204
+ * not identity); a spelling disappears when its final support does. */
1205
+ spellings: Record<string, number>;
1206
+ /** Observed effective units → supporting-source counts. */
1207
+ units: Record<string, number>;
1208
+ /** Number of source records currently supporting this identity. */
1209
+ sourceCount: number;
1210
+ firstSeen: number;
1211
+ lastSeen: number;
1212
+ }
1213
+ /** One field identity a source record supports, with everything needed to
1214
+ * reverse the contribution (per-source unit/spelling/value evidence). */
1215
+ interface FieldContribution {
1216
+ fieldId: string;
1217
+ path: string;
1218
+ kind: string;
1219
+ unit?: string;
1220
+ /** Original spelling this source used. */
1221
+ spelling?: string;
1222
+ /** Categorical value (string/boolean fields only) — original spelling. */
1223
+ value?: string;
1224
+ }
1225
+ /** The contribution set of one stable source record — the reversal record
1226
+ * that makes re-saves idempotent and deletions reversible. Contributions
1227
+ * are attributed to their owning row id, so streaming appends, finalize
1228
+ * replacement, and cascade deletes reverse exactly the affected rows. */
1229
+ interface FieldSourceRecord {
1230
+ /** `${entityKind}:${recordId}` — e.g. `result:r1`, `note:n1`. */
1231
+ id: string;
1232
+ contributions: Array<FieldContribution & {
1233
+ rowId: string;
1234
+ }>;
1235
+ }
1236
+ /** One observed categorical value of a field (string/boolean only). */
1237
+ interface FieldValueRecord {
1238
+ key: [string, string];
1239
+ fieldId: string;
1240
+ value: string;
1241
+ sourceCount: number;
1242
+ }
1243
+ /** Backfill progress/completion marker (field_catalog_meta store). */
1244
+ interface CatalogBackfillState {
1245
+ id: 'backfill';
1246
+ status: 'initializing' | 'complete';
1247
+ /** Last processed source key per source store (resume cursor). */
1248
+ cursor?: {
1249
+ results?: string;
1250
+ notes?: string;
1251
+ events?: string;
1252
+ };
1253
+ revision: number;
1254
+ updatedAt: number;
990
1255
  }
991
1256
 
992
1257
  /**
@@ -1014,7 +1279,7 @@ declare const VALID_FENCE_DIALECTS: FenceDialect[];
1014
1279
  */
1015
1280
  declare function runAffordance(baseTag: string): 'run' | 'log' | null;
1016
1281
  /** Section types the editor can parse and render */
1017
- type SectionType = 'title' | 'markdown' | 'time' | 'log' | 'frontmatter' | 'embed';
1282
+ type SectionType = 'title' | 'markdown' | 'time' | 'log' | 'frontmatter' | 'embed' | 'code' | 'widget' | 'query';
1018
1283
  /** Workout section types — the runnable/recorded fence tags */
1019
1284
  type WorkoutSectionType = Extract<SectionType, FenceDialect>;
1020
1285
  /** Type guard for workout sections */
@@ -1059,6 +1324,7 @@ interface Section {
1059
1324
  id: string;
1060
1325
  contentId?: string;
1061
1326
  type: SectionType;
1327
+ subtype?: string;
1062
1328
  rawContent: string;
1063
1329
  displayContent: string;
1064
1330
  startLine: number;
@@ -1066,6 +1332,9 @@ interface Section {
1066
1332
  lineCount: number;
1067
1333
  level?: number;
1068
1334
  sport?: string;
1335
+ widgetName?: string;
1336
+ queryType?: string;
1337
+ queryError?: string;
1069
1338
  scriptBlock?: ScriptBlock;
1070
1339
  properties?: Record<string, string>;
1071
1340
  frontmatterType?: FrontMatterSubtype;
@@ -1118,17 +1387,67 @@ interface IScript {
1118
1387
  getAt(index: number): ICodeStatement | undefined;
1119
1388
  }
1120
1389
 
1390
+ /**
1391
+ * Try to match a line against known workout fence patterns.
1392
+ * Returns the base tag and optional :sport suffix, or null otherwise.
1393
+ */
1394
+ declare function matchDialectFence(trimmedLine: string): {
1395
+ dialect: FenceDialect;
1396
+ sport?: string;
1397
+ } | null;
1398
+ /**
1399
+ * Detects all WOD blocks in markdown content.
1400
+ * Recognises ```time and ```log as valid workout fences (optional :sport suffix).
1401
+ *
1402
+ * @param content - Markdown content to parse
1403
+ * @returns Array of detected WOD blocks (with dialect set)
1404
+ */
1405
+ declare function detectScriptBlocks(content: string): ScriptBlock[];
1406
+ /**
1407
+ * Find which block contains a given line number.
1408
+ *
1409
+ * @param blocks - Array of WOD blocks
1410
+ * @param lineNumber - Line number to search for (0-indexed)
1411
+ * @returns The block containing the line, or null if not found
1412
+ */
1413
+ declare function findBlockAtLine(blocks: ScriptBlock[], lineNumber: number): ScriptBlock | null;
1414
+ /**
1415
+ * Extract WOD block content from markdown (without backticks).
1416
+ *
1417
+ * @param content - Full markdown content
1418
+ * @param startLine - Start line of block (0-indexed)
1419
+ * @param endLine - End line of block (0-indexed)
1420
+ * @returns Extracted block content
1421
+ */
1422
+ declare function extractBlockContent(content: string, startLine: number, endLine: number): string;
1423
+
1424
+ interface SectionMetadata {
1425
+ id: string;
1426
+ version: number;
1427
+ createdAt: number;
1428
+ }
1429
+ /**
1430
+ * Generate a deterministic section ID from type, startLine (0-indexed), and a content hash.
1431
+ * Stable across re-parses when structure doesn't change.
1432
+ */
1433
+ declare function generateSectionId(type: SectionType | string, startLine: number, content: string): string;
1434
+
1435
+ declare function parseDocumentSections(content: string, scriptBlocks?: ScriptBlock[]): Section[];
1436
+ declare function buildRawContent(sections: Section[]): string;
1437
+ declare function calculateTotalLines(sections: Section[]): number;
1438
+ declare function matchSectionIds(oldSections: Section[], newSections: Section[]): Section[];
1439
+
1121
1440
  /**
1122
1441
  * Auto-generated build version metadata.
1123
1442
  * Do not edit directly; updated by scripts/stamp-version.ts
1124
1443
  */
1125
- declare const VERSION = "0.11.43";
1126
- declare const GIT_SHA = "ed28149";
1127
- declare const BUILD_TIME = "2026-08-26T16:34:15.927Z";
1444
+ declare const VERSION = "0.35.2116";
1445
+ declare const GIT_SHA = "a4d411f";
1446
+ declare const BUILD_TIME = "2026-10-12T00:14:24.795Z";
1128
1447
  declare const SEMVER: {
1129
- readonly version: "0.11.43";
1130
- readonly gitSha: "ed28149";
1131
- readonly buildTime: "2026-08-26T16:34:15.927Z";
1448
+ readonly version: "0.35.2116";
1449
+ readonly gitSha: "a4d411f";
1450
+ readonly buildTime: "2026-10-12T00:14:24.795Z";
1132
1451
  };
1133
1452
 
1134
- export { type AnalyticsDataPoint, type AnalyticsGraphConfig, type AnalyticsGroup, type Attachment, BUILD_TIME, type BlockIndexRow, BlockKey, CodeMetadata, CodeStatement, type DialectAnalysis, type DisplayStatus, Duration, type EventGrain, type FenceDialect, type FrontMatterSubtype, GIT_SHA, type IBlockKey, type ICodeMetadata, type ICodeStatement, type IDialect, type IDialectTagDescriptor, type IDuration, type IEstimate, type IMetric, type IMetricContainer, type IMetricOwnershipResolver, type IMetricSource, type IMetricSummary, type IOutputStatement, type IScript, type ISummary, KNOWN_OUTPUT_TYPES, LEGACY_ORIGIN_TO_OWNERSHIP_LAYER, METRIC_OWNERSHIP_LAYER_CHAIN, Metric, type MetricAction, MetricContainer, type MetricFilter, type MetricOrigin, type MetricOwnershipLayer, type MetricOwnershipLedger, type MetricOwnershipOutcome, type MetricOwnershipPromotionCandidate, type MetricOwnershipQuery, type MetricOwnershipResolvedContribution, type MetricOwnershipTypeExplanation, MetricType, type MetricWithOptionalOwnershipLayer, NOTEBOOK_TAG_PREFIX, type Note, type NoteKind, type NoteSegment, type NoteTag, type Notebook, type Origin, OutputStatement, type OutputStatementOptions, type OutputStatementType, OwnershipResolver, type Page, type ParseError, ParsedCodeStatement, type ResultOrigin, SEMVER, type ScriptBlock, type ScriptBlockState, type Section, type SectionDocument, type SectionType, type Segment, type SegmentDataType, SpanDuration, type StoredOutputStatement, type SummaryStage, type Tag, type TagType, type TimeSpan, type UnifiedEventRecord, VALID_FENCE_DIALECTS, VERSION, type VisualizerFilter, type VisualizerSize, type WorkoutResult, type WorkoutResults, type WorkoutSectionType, createMetricOwnershipLedger, fromNotebookTag, getMetricOwnershipLayer, isNotebookTag, isWorkoutSectionType, ownershipRank, runAffordance, toNotebookTag };
1453
+ export { type AnalyticsDataPoint, type AnalyticsGraphConfig, type AnalyticsGroup, type Attachment, BUILD_TIME, type BlockIndexRow, BlockKey, type CatalogBackfillState, CodeMetadata, CodeStatement, type DialectAnalysis, type DimensionKey, type DisplayStatus, type EventGrain, type EventRecord, type FenceDialect, type FieldCatalogEntry, type FieldContribution, type FieldLeaf, type FieldRef, type FieldSourceRecord, type FieldValueKind, type FieldValueRecord, type FrontMatterSubtype, GIT_SHA, type IBlockKey, type ICodeMetadata, type ICodeStatement, type IDialect, type IDialectTagDescriptor, type IMetric, type IMetricOwnershipResolver, type IMetricSource, type IOutputStatement, type IScript, KNOWN_OUTPUT_TYPES, LEGACY_ORIGIN_TO_OWNERSHIP_LAYER, METRIC_OWNERSHIP_LAYER_CHAIN, Metric, type MetricAction, MetricContainer, type MetricFilter, type MetricOrigin, type MetricOwnershipLayer, type MetricOwnershipLedger, type MetricOwnershipOutcome, type MetricOwnershipPromotionCandidate, type MetricOwnershipQuery, type MetricOwnershipResolvedContribution, type MetricOwnershipTypeExplanation, type MetricTemporal, MetricType, type MetricWithOptionalOwnershipLayer, NOTEBOOK_TAG_PREFIX, type Note, type NoteKind, type NoteSegment, type NoteTag, type Notebook, type NullValueKind, type Origin, OutputStatement, type OutputStatementOptions, type OutputStatementType, OwnershipResolver, type Page, type PageNote, type ParseError, ParsedCodeStatement, type ReducerStats, type ResultOrigin, SEMVER, SYSTEM_DEFAULT_OUTPUT_UNIT, type ScriptBlock, type ScriptBlockState, type Section, type SectionDocument, type SectionMetadata, type SectionType, type Segment, type SegmentDataType, type Session, type StoredOutputStatement, type SummaryCoverage, type Tag, type TagType, type TagTypeRecord, type TimeSpan, UNIT_CATALOG, UnitConversionError, type UnitDefinition, type UnitErrorCode, VALID_FENCE_DIALECTS, VERSION, type VisualizerFilter, type VisualizerSize, type WorkoutResults, type WorkoutSectionType, blockContentId, buildRawContent, calculateTotalLines, convertViaCatalog, createMetricOwnershipLedger, detectScriptBlocks, dimensionKeyOf, extractBlockContent, fieldRefFromKey, fieldRefKey, findBlockAtLine, flattenFieldLeaves, fromNotebookTag, generateSectionId, getMetricOwnershipLayer, isNotebookTag, isWorkoutSectionType, lookupUnit, matchDialectFence, matchSectionIds, normalizeFieldComponent, normalizeFieldPath, ownershipRank, parseDocumentSections, resolveLayer, runAffordance, systemDefaultUnit, toNotebookTag, valueKindOf };