@bitcobblers/wod-wiki-core 0.11.43 → 0.35.2115

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.cjs CHANGED
@@ -26,6 +26,88 @@ function getMetricOwnershipLayer(origin) {
26
26
  return LEGACY_ORIGIN_TO_OWNERSHIP_LAYER[origin ?? "parser"] ?? "parser";
27
27
  }
28
28
 
29
+ // src/models/Metric.ts
30
+ var MetricType = /* @__PURE__ */ ((MetricType2) => {
31
+ MetricType2["Duration"] = "duration";
32
+ MetricType2["Spans"] = "spans";
33
+ MetricType2["SystemTime"] = "system-time";
34
+ MetricType2["Time"] = "time";
35
+ MetricType2["Elapsed"] = "elapsed";
36
+ MetricType2["Total"] = "total";
37
+ MetricType2["Rep"] = "rep";
38
+ MetricType2["Effort"] = "effort";
39
+ MetricType2["Distance"] = "distance";
40
+ MetricType2["Rounds"] = "rounds";
41
+ MetricType2["CurrentRound"] = "current-round";
42
+ MetricType2["Action"] = "action";
43
+ MetricType2["Increment"] = "increment";
44
+ MetricType2["Group"] = "group";
45
+ MetricType2["Text"] = "text";
46
+ MetricType2["Resistance"] = "resistance";
47
+ MetricType2["Sound"] = "sound";
48
+ MetricType2["System"] = "system";
49
+ MetricType2["Label"] = "label";
50
+ MetricType2["Lap"] = "lap";
51
+ MetricType2["Metric"] = "metric";
52
+ MetricType2["Hint"] = "hint";
53
+ MetricType2["Custom"] = "custom";
54
+ MetricType2["Volume"] = "volume";
55
+ MetricType2["Intensity"] = "intensity";
56
+ MetricType2["Grade"] = "grade";
57
+ MetricType2["Load"] = "load";
58
+ MetricType2["Work"] = "work";
59
+ MetricType2["RIR"] = "rir";
60
+ MetricType2["SessionRPE"] = "session-rpe";
61
+ MetricType2["SessionLoad"] = "session-load";
62
+ MetricType2["METScore"] = "met-score";
63
+ MetricType2["TIS"] = "tis";
64
+ MetricType2["Calculated"] = "calculated";
65
+ MetricType2["Choice"] = "choice";
66
+ return MetricType2;
67
+ })(MetricType || {});
68
+ var Metric = class {
69
+ type;
70
+ metricType;
71
+ value;
72
+ origin;
73
+ unit;
74
+ action;
75
+ image;
76
+ sourceBlockKey;
77
+ timestamp;
78
+ metadata;
79
+ name;
80
+ ownershipLayer;
81
+ constructor(typeOrName, valueOrType, originOrValue, unit, origin, action, image) {
82
+ if (typeof valueOrType === "string" && (typeof originOrValue === "number" || typeof originOrValue === "string" || typeof originOrValue === "boolean" || originOrValue === null || originOrValue === void 0)) {
83
+ this.name = String(typeOrName);
84
+ this.type = valueOrType;
85
+ this.metricType = valueOrType;
86
+ this.value = originOrValue;
87
+ this.unit = unit;
88
+ this.origin = origin || "parser";
89
+ this.action = action === "suppress" ? "suppress" : void 0;
90
+ this.image = image ?? String(originOrValue ?? valueOrType);
91
+ } else {
92
+ this.type = typeOrName;
93
+ this.metricType = typeOrName;
94
+ this.value = valueOrType;
95
+ this.origin = originOrValue || "parser";
96
+ this.unit = unit;
97
+ this.action = typeof origin === "string" && origin === "suppress" ? "suppress" : action === "suppress" ? "suppress" : void 0;
98
+ this.image = image ?? String(valueOrType ?? typeOrName);
99
+ }
100
+ }
101
+ toNumber() {
102
+ if (typeof this.value === "number") return this.value;
103
+ if (typeof this.value === "string") {
104
+ const parsed = parseFloat(this.value);
105
+ return isNaN(parsed) ? 0 : parsed;
106
+ }
107
+ return 0;
108
+ }
109
+ };
110
+
29
111
  // src/ownership/ledger.ts
30
112
  var LAYER_RANK = {
31
113
  parser: 0,
@@ -41,6 +123,10 @@ function resolveLayer(metric) {
41
123
  }
42
124
  return getMetricOwnershipLayer(metric.origin);
43
125
  }
126
+ function ownershipRank(metric) {
127
+ const layer = resolveLayer(metric);
128
+ return LAYER_RANK[layer] ?? 0;
129
+ }
44
130
  function normalize(metrics) {
45
131
  return metrics.map((metric, index) => {
46
132
  const layer = resolveLayer(metric);
@@ -234,98 +320,6 @@ function createMetricOwnershipLedger(metrics) {
234
320
  }
235
321
  };
236
322
  }
237
-
238
- // src/models/Metric.ts
239
- var MetricType = /* @__PURE__ */ ((MetricType2) => {
240
- MetricType2["Duration"] = "duration";
241
- MetricType2["Spans"] = "spans";
242
- MetricType2["SystemTime"] = "system-time";
243
- MetricType2["Time"] = "time";
244
- MetricType2["Elapsed"] = "elapsed";
245
- MetricType2["Total"] = "total";
246
- MetricType2["Rep"] = "rep";
247
- MetricType2["Effort"] = "effort";
248
- MetricType2["Distance"] = "distance";
249
- MetricType2["Rounds"] = "rounds";
250
- MetricType2["CurrentRound"] = "current-round";
251
- MetricType2["Action"] = "action";
252
- MetricType2["Increment"] = "increment";
253
- MetricType2["Group"] = "group";
254
- MetricType2["Text"] = "text";
255
- MetricType2["Resistance"] = "resistance";
256
- MetricType2["Sound"] = "sound";
257
- MetricType2["System"] = "system";
258
- MetricType2["Label"] = "label";
259
- MetricType2["Lap"] = "lap";
260
- MetricType2["Metric"] = "metric";
261
- MetricType2["Hint"] = "hint";
262
- MetricType2["Custom"] = "custom";
263
- MetricType2["Volume"] = "volume";
264
- MetricType2["Intensity"] = "intensity";
265
- MetricType2["Load"] = "load";
266
- MetricType2["Work"] = "work";
267
- MetricType2["RIR"] = "rir";
268
- MetricType2["SessionRPE"] = "session-rpe";
269
- MetricType2["SessionLoad"] = "session-load";
270
- MetricType2["METScore"] = "met-score";
271
- MetricType2["TIS"] = "tis";
272
- MetricType2["Calculated"] = "calculated";
273
- MetricType2["Choice"] = "choice";
274
- return MetricType2;
275
- })(MetricType || {});
276
- var Metric = class {
277
- type;
278
- metricType;
279
- value;
280
- origin;
281
- unit;
282
- action;
283
- image;
284
- sourceBlockKey;
285
- timestamp;
286
- metadata;
287
- name;
288
- ownershipLayer;
289
- constructor(typeOrName, valueOrType, originOrValue, unit, origin, action, image) {
290
- if (typeof valueOrType === "string" && (typeof originOrValue === "number" || typeof originOrValue === "string" || typeof originOrValue === "boolean" || originOrValue === null || originOrValue === void 0)) {
291
- this.name = String(typeOrName);
292
- this.type = valueOrType;
293
- this.metricType = valueOrType;
294
- this.value = originOrValue;
295
- this.unit = unit;
296
- this.origin = origin || "parser";
297
- this.action = action === "suppress" ? "suppress" : void 0;
298
- this.image = image ?? String(originOrValue ?? valueOrType);
299
- } else {
300
- this.type = typeOrName;
301
- this.metricType = typeOrName;
302
- this.value = valueOrType;
303
- this.origin = originOrValue || "parser";
304
- this.unit = unit;
305
- this.action = typeof origin === "string" && origin === "suppress" ? "suppress" : action === "suppress" ? "suppress" : void 0;
306
- this.image = image ?? String(valueOrType ?? typeOrName);
307
- }
308
- }
309
- toNumber() {
310
- if (typeof this.value === "number") return this.value;
311
- if (typeof this.value === "string") {
312
- const parsed = parseFloat(this.value);
313
- return isNaN(parsed) ? 0 : parsed;
314
- }
315
- return 0;
316
- }
317
- };
318
-
319
- // src/ownership/OwnershipResolver.ts
320
- function resolveLayer2(metric) {
321
- const maybeLayer = metric.ownershipLayer;
322
- if (maybeLayer != null) return maybeLayer;
323
- return getMetricOwnershipLayer(metric.origin ?? "parser");
324
- }
325
- function ownershipRank(metric) {
326
- const layer = resolveLayer2(metric);
327
- return METRIC_OWNERSHIP_LAYER_CHAIN.indexOf(layer);
328
- }
329
323
  function applyFilter(metrics, filter) {
330
324
  let filtered = [...metrics];
331
325
  const wantsHints = filter?.types?.includes("hint" /* Hint */) ?? false;
@@ -360,6 +354,181 @@ var OwnershipResolver = class {
360
354
  }
361
355
  };
362
356
 
357
+ // src/fields/fieldIdentity.ts
358
+ function splitWords(component) {
359
+ const onSeparators = component.split(/[\s_\-\u2010-\u2015]+/).filter(Boolean);
360
+ const words = [];
361
+ for (const chunk of onSeparators) {
362
+ const cased = chunk.replace(/([\p{Ll}\p{Nd}])(\p{Lu})/gu, "$1\0$2").replace(/(\p{Lu})(\p{Lu}\p{Ll})/gu, "$1\0$2");
363
+ for (const word of cased.split("\0")) {
364
+ if (word) words.push(word);
365
+ }
366
+ }
367
+ return words;
368
+ }
369
+ function normalizeFieldComponent(component) {
370
+ const words = splitWords(component);
371
+ if (words.length === 0) return "";
372
+ return words.map((word, index) => {
373
+ const lower = word.toLowerCase();
374
+ return index === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
375
+ }).join("");
376
+ }
377
+ function normalizeFieldPath(path) {
378
+ return path.split(".").map(normalizeFieldComponent).filter((component) => component.length > 0).join(".");
379
+ }
380
+ function valueKindOf(value) {
381
+ if (value === null) return "null";
382
+ if (value === void 0) return "undefined";
383
+ if (Array.isArray(value)) return "array";
384
+ switch (typeof value) {
385
+ case "number":
386
+ return "number";
387
+ case "string":
388
+ return "string";
389
+ case "boolean":
390
+ return "boolean";
391
+ case "object":
392
+ return "object";
393
+ default:
394
+ return "undefined";
395
+ }
396
+ }
397
+ function flattenFieldLeaves(input, prefix = "") {
398
+ const leaves = [];
399
+ for (const [rawKey, value] of Object.entries(input)) {
400
+ const path = prefix ? `${prefix}.${normalizeFieldPath(rawKey)}` : normalizeFieldPath(rawKey);
401
+ if (!path) continue;
402
+ const kind = valueKindOf(value);
403
+ if (kind === "object") {
404
+ leaves.push(...flattenFieldLeaves(value, path));
405
+ } else if (kind !== "undefined") {
406
+ leaves.push({ path, kind, value, originalPath: prefix ? `${prefix}.${rawKey}` : rawKey });
407
+ }
408
+ }
409
+ return leaves;
410
+ }
411
+ function fieldRefKey(ref) {
412
+ return JSON.stringify([ref.path, ref.kind, ref.dimension ?? null]);
413
+ }
414
+ function fieldRefFromKey(key) {
415
+ const [path, kind, dimension] = JSON.parse(key);
416
+ return { path, kind, ...dimension ? { dimension } : {} };
417
+ }
418
+
419
+ // src/units/catalog.ts
420
+ var UnitConversionError = class extends Error {
421
+ code;
422
+ constructor(code, message) {
423
+ super(message);
424
+ this.name = "UnitConversionError";
425
+ this.code = code;
426
+ }
427
+ };
428
+ var UNIT_CATALOG = [
429
+ // Distance — base m
430
+ { canonical: "m", dimension: "distance", factor: 1, aliases: ["meter", "meters", "metre", "metres"] },
431
+ { canonical: "km", dimension: "distance", factor: 1e3, aliases: ["kilometer", "kilometers", "kilometre", "kilometres"] },
432
+ { canonical: "cm", dimension: "distance", factor: 0.01, aliases: ["centimeter", "centimeters"] },
433
+ { canonical: "mm", dimension: "distance", factor: 1e-3, aliases: ["millimeter", "millimeters"] },
434
+ { canonical: "ft", dimension: "distance", factor: 0.3048, aliases: ["foot", "feet"] },
435
+ { canonical: "in", dimension: "distance", factor: 0.0254, aliases: ["inch", "inches"] },
436
+ { canonical: "yd", dimension: "distance", factor: 0.9144, aliases: ["yard", "yards"] },
437
+ { canonical: "mi", dimension: "distance", factor: 1609.344, aliases: ["mile", "miles"] },
438
+ // Mass — base kg
439
+ { canonical: "kg", dimension: "mass", factor: 1, aliases: ["kilogram", "kilograms", "kilo", "kilos"] },
440
+ { canonical: "g", dimension: "mass", factor: 1e-3, aliases: ["gram", "grams"] },
441
+ { canonical: "lb", dimension: "mass", factor: 0.45359237, aliases: ["lbs", "pound", "pounds"] },
442
+ // Duration — base s
443
+ { canonical: "ms", dimension: "duration", factor: 1e-3, aliases: ["millisecond", "milliseconds"] },
444
+ { canonical: "s", dimension: "duration", factor: 1, aliases: ["sec", "secs", "second", "seconds"] },
445
+ { canonical: "min", dimension: "duration", factor: 60, aliases: ["mins", "minute", "minutes"] },
446
+ { canonical: "h", dimension: "duration", factor: 3600, aliases: ["hr", "hrs", "hour", "hours"] },
447
+ // Count — base count. Test-result scales ("pull-up", "thruster",
448
+ // "problem") are count-dimension scales: one of their units is one rep.
449
+ { canonical: "count", dimension: "count", factor: 1 },
450
+ { canonical: "rep", dimension: "count", factor: 1, aliases: ["reps"] },
451
+ { canonical: "pull-up", dimension: "count", factor: 1 },
452
+ { canonical: "thruster", dimension: "count", factor: 1 },
453
+ { canonical: "problem", dimension: "count", factor: 1 },
454
+ // Count-rate — base count/s; bpm (beats per minute) is its registered scale
455
+ { canonical: "count/s", dimension: "count-rate", factor: 1 },
456
+ { canonical: "bpm", dimension: "count-rate", factor: 1 / 60 },
457
+ // Energy — base cal
458
+ { canonical: "cal", dimension: "energy", factor: 1, aliases: ["calorie", "calories"] },
459
+ { canonical: "kcal", dimension: "energy", factor: 1e3, aliases: ["kilocalorie", "kilocalories"] },
460
+ // Speed — base m/s
461
+ { canonical: "m/s", dimension: "speed", factor: 1 },
462
+ { canonical: "km/h", dimension: "speed", factor: 1e3 / 3600, aliases: ["kmh", "kph"] },
463
+ { canonical: "mph", dimension: "speed", factor: 1609.344 / 3600 },
464
+ // Pace — base min/km
465
+ { canonical: "min/km", dimension: "pace", factor: 1 },
466
+ { canonical: "min/mi", dimension: "pace", factor: 1e3 / 1609.344 },
467
+ { canonical: "s/m", dimension: "pace", factor: 1 / 60 },
468
+ // Ratio — unitless base (factor 1); percent via 100% = 1.
469
+ // Dimensionless named scales (arbitrary units, points) share the ratio
470
+ // dimension but are convertible only to the base — never to each other.
471
+ { canonical: "ratio", dimension: "ratio", factor: 1, aliases: ["x"] },
472
+ { canonical: "%", dimension: "ratio", factor: 0.01, aliases: ["pct", "percent"] },
473
+ { canonical: "au", dimension: "ratio", factor: 1, aliases: ["AU"] },
474
+ { canonical: "pts", dimension: "ratio", factor: 1, aliases: ["point", "points"] },
475
+ { canonical: "rating", dimension: "ratio", factor: 1, aliases: ["ratings"] }
476
+ ];
477
+ var bySpelling = (() => {
478
+ const map = /* @__PURE__ */ new Map();
479
+ for (const def of UNIT_CATALOG) {
480
+ map.set(def.canonical.toLowerCase(), def);
481
+ for (const alias of def.aliases ?? []) {
482
+ if (!map.has(alias.toLowerCase())) map.set(alias.toLowerCase(), def);
483
+ }
484
+ }
485
+ return map;
486
+ })();
487
+ function lookupUnit(spelling) {
488
+ return bySpelling.get(spelling.trim().toLowerCase());
489
+ }
490
+ function dimensionKeyOf(spelling) {
491
+ return lookupUnit(spelling)?.dimension;
492
+ }
493
+ var SYSTEM_DEFAULT_OUTPUT_UNIT = Object.freeze({
494
+ mass: "kg",
495
+ distance: "m",
496
+ duration: "s",
497
+ count: "count",
498
+ "count-rate": "count/s",
499
+ energy: "cal",
500
+ speed: "m/s",
501
+ pace: "min/km",
502
+ ratio: void 0
503
+ });
504
+ function systemDefaultUnit(dimension) {
505
+ return SYSTEM_DEFAULT_OUTPUT_UNIT[dimension];
506
+ }
507
+ function convertViaCatalog(value, from, to) {
508
+ const fromDef = lookupUnit(from);
509
+ if (!fromDef) throw new UnitConversionError("unknown-unit", `Unknown unit "${from}"`);
510
+ const toDef = lookupUnit(to);
511
+ if (!toDef) throw new UnitConversionError("unknown-unit", `Unknown unit "${to}"`);
512
+ if (fromDef.dimension !== toDef.dimension) {
513
+ throw new UnitConversionError(
514
+ "dimension-mismatch",
515
+ `Cannot convert ${from} (${fromDef.dimension}) to ${to} (${toDef.dimension})`
516
+ );
517
+ }
518
+ if (fromDef.dimension === "ratio" && fromDef.canonical !== toDef.canonical) {
519
+ if (fromDef.canonical !== "ratio" && toDef.canonical !== "ratio") {
520
+ throw new UnitConversionError(
521
+ "scale-mismatch",
522
+ `Named scales ${from} and ${to} are not interchangeable`
523
+ );
524
+ }
525
+ }
526
+ if (!Number.isFinite(value)) {
527
+ throw new UnitConversionError("non-finite", `Non-finite value cannot be converted`);
528
+ }
529
+ return value * fromDef.factor / toDef.factor;
530
+ }
531
+
363
532
  // src/models/CodeMetadata.ts
364
533
  var CodeMetadata = class {
365
534
  constructor(line, startOffset, endOffset, length, columnStart = 0, columnEnd = 0, raw = "") {
@@ -686,7 +855,7 @@ var ParsedCodeStatement = class extends CodeStatement {
686
855
  };
687
856
 
688
857
  // src/models/OutputStatement.ts
689
- var OutputStatement = class _OutputStatement {
858
+ var OutputStatement = class _OutputStatement extends CodeStatement {
690
859
  static nextId = 1e6;
691
860
  id;
692
861
  outputType;
@@ -704,6 +873,7 @@ var OutputStatement = class _OutputStatement {
704
873
  statement;
705
874
  timestamp;
706
875
  constructor(optionsOrStatement, metricsArg, timestamp) {
876
+ super();
707
877
  if (optionsOrStatement && "outputType" in optionsOrStatement && "timeSpan" in optionsOrStatement) {
708
878
  const options = optionsOrStatement;
709
879
  this.id = _OutputStatement.nextId++;
@@ -711,6 +881,9 @@ var OutputStatement = class _OutputStatement {
711
881
  this.timeSpan = options.timeSpan;
712
882
  this.sourceBlockKey = options.sourceBlockKey ?? "";
713
883
  this.sourceStatementId = options.sourceStatementId;
884
+ this.line = options.line;
885
+ this.text = options.text;
886
+ this.dialect = options.dialect;
714
887
  this.stackLevel = options.stackLevel ?? 0;
715
888
  this.metrics = MetricContainer.from(options.metrics, this.id);
716
889
  this.metricMeta = options.metricMeta ?? /* @__PURE__ */ new Map();
@@ -733,27 +906,13 @@ var OutputStatement = class _OutputStatement {
733
906
  this.children = [];
734
907
  this.isLeaf = true;
735
908
  this.meta = stmt?.meta ?? new CodeMetadata(0, 0, 0, 0);
909
+ this.line = stmt?.line;
910
+ this.text = stmt?.text;
911
+ this.dialect = stmt?.dialect;
736
912
  this.timestamp = timestamp ?? Date.now();
737
913
  }
738
914
  }
739
- // ═══════════════════════════════════════════════════════════════
740
- // IMetricSource implementation
741
- // ═══════════════════════════════════════════════════════════════
742
- getDisplayMetrics(filter) {
743
- return this.metrics.getDisplayMetrics(filter);
744
- }
745
- getMetric(type) {
746
- return this.metrics.getMetric(type);
747
- }
748
- getAllMetricsByType(type) {
749
- return this.metrics.getAllMetricsByType(type);
750
- }
751
- hasMetric(type) {
752
- return this.metrics.hasMetric(type);
753
- }
754
- get rawMetrics() {
755
- return this.metrics.rawMetrics;
756
- }
915
+ // IMetricSource implementation is inherited from CodeStatement
757
916
  /**
758
917
  * Reset the ID counter (useful for testing)
759
918
  */
@@ -762,37 +921,6 @@ var OutputStatement = class _OutputStatement {
762
921
  }
763
922
  };
764
923
 
765
- // src/models/Duration.ts
766
- var Duration = class {
767
- constructor(original) {
768
- this.original = original;
769
- let remaining = original ?? 0;
770
- this.days = Math.floor(remaining / 864e5);
771
- remaining %= 864e5;
772
- this.hours = Math.floor(remaining / 36e5);
773
- remaining %= 36e5;
774
- this.minutes = Math.floor(remaining / 6e4);
775
- remaining %= 6e4;
776
- this.seconds = Math.floor(remaining / 1e3);
777
- this.milliseconds = Math.round(remaining - this.seconds * 1e3);
778
- }
779
- original;
780
- days;
781
- hours;
782
- minutes;
783
- seconds;
784
- milliseconds;
785
- };
786
- var SpanDuration = class extends Duration {
787
- constructor(spans) {
788
- const total = spans.reduce((sum, span) => {
789
- const end = span.ended ?? Date.now();
790
- return sum + Math.max(0, end - span.started);
791
- }, 0);
792
- super(total);
793
- }
794
- };
795
-
796
924
  // src/models/BlockKey.ts
797
925
  var BlockKey = class {
798
926
  value;
@@ -810,6 +938,17 @@ var BlockKey = class {
810
938
  }
811
939
  };
812
940
 
941
+ // src/models/blockContentId.ts
942
+ function blockContentId(content) {
943
+ const normalized = content.trim();
944
+ let hash = 2166136261;
945
+ for (let i = 0; i < normalized.length; i++) {
946
+ hash ^= normalized.charCodeAt(i);
947
+ hash = Math.imul(hash, 16777619);
948
+ }
949
+ return `bc-${(hash >>> 0).toString(16).padStart(8, "0")}`;
950
+ }
951
+
813
952
  // src/types/storage.ts
814
953
  var KNOWN_OUTPUT_TYPES = [
815
954
  "segment",
@@ -839,21 +978,399 @@ var toNotebookTag = (notebookId) => `${NOTEBOOK_TAG_PREFIX}${notebookId}`;
839
978
  var fromNotebookTag = (tag) => tag.startsWith(NOTEBOOK_TAG_PREFIX) ? tag.slice(NOTEBOOK_TAG_PREFIX.length) : null;
840
979
  var isNotebookTag = (tag) => tag.startsWith(NOTEBOOK_TAG_PREFIX);
841
980
 
981
+ // src/sections/blockDetection.ts
982
+ function matchDialectFence(trimmedLine) {
983
+ if (!trimmedLine.startsWith("```")) return null;
984
+ const tag = trimmedLine.slice(3).split(/[\s\t]/)[0].toLowerCase();
985
+ const [base, sport] = tag.split(":", 2);
986
+ if (VALID_FENCE_DIALECTS.includes(base)) {
987
+ return { dialect: base, sport: sport || void 0 };
988
+ }
989
+ return null;
990
+ }
991
+ function detectScriptBlocks(content) {
992
+ const lines = content.split("\n");
993
+ const blocks = [];
994
+ let inBlock = false;
995
+ let currentBlock = {};
996
+ let blockContent = [];
997
+ lines.forEach((line, index) => {
998
+ const trimmedLine = line.trim();
999
+ if (!inBlock) {
1000
+ const fence = matchDialectFence(trimmedLine);
1001
+ if (fence) {
1002
+ inBlock = true;
1003
+ const now = Date.now();
1004
+ currentBlock = {
1005
+ id: `${fence.dialect}-block-${now}-${Math.random().toString(36).slice(2, 9)}`,
1006
+ dialect: fence.dialect,
1007
+ sport: fence.sport,
1008
+ startLine: index,
1009
+ state: "idle",
1010
+ version: 1,
1011
+ createdAt: now
1012
+ };
1013
+ blockContent = [];
1014
+ return;
1015
+ }
1016
+ }
1017
+ if (inBlock && trimmedLine.startsWith("```")) {
1018
+ inBlock = false;
1019
+ currentBlock.endLine = index;
1020
+ currentBlock.content = blockContent.join("\n");
1021
+ blocks.push(currentBlock);
1022
+ currentBlock = {};
1023
+ blockContent = [];
1024
+ } else if (inBlock) {
1025
+ blockContent.push(line);
1026
+ }
1027
+ });
1028
+ if (inBlock && currentBlock.startLine !== void 0) {
1029
+ currentBlock.endLine = lines.length - 1;
1030
+ currentBlock.content = blockContent.join("\n");
1031
+ blocks.push(currentBlock);
1032
+ }
1033
+ return blocks;
1034
+ }
1035
+ function findBlockAtLine(blocks, lineNumber) {
1036
+ return blocks.find(
1037
+ (block) => lineNumber >= block.startLine && lineNumber <= block.endLine
1038
+ ) ?? null;
1039
+ }
1040
+ function extractBlockContent(content, startLine, endLine) {
1041
+ const lines = content.split("\n");
1042
+ return lines.slice(startLine + 1, endLine).join("\n");
1043
+ }
1044
+
1045
+ // src/sections/sectionParser.ts
1046
+ var METADATA_REGEX = /<!--\s*section-metadata\s+id:(\S+)\s+version:(\d+)\s+created:(\d+)\s*-->/i;
1047
+ function extractMetadata(text) {
1048
+ const match = text.match(METADATA_REGEX);
1049
+ if (!match) return { metadata: null, cleanText: text };
1050
+ return {
1051
+ metadata: {
1052
+ id: match[1],
1053
+ version: parseInt(match[2], 10),
1054
+ createdAt: parseInt(match[3], 10)
1055
+ },
1056
+ cleanText: text.replace(METADATA_REGEX, "").trim()
1057
+ };
1058
+ }
1059
+ function generateSectionId(type, startLine, content) {
1060
+ let hash = 0;
1061
+ for (let i = 0; i < content.length && i < 64; i++) {
1062
+ hash = (hash << 5) - hash + content.charCodeAt(i) | 0;
1063
+ }
1064
+ const hashHex = (hash >>> 0).toString(16).padStart(8, "0");
1065
+ return `${type}-${startLine}-${hashHex}`;
1066
+ }
1067
+ function detectUrlSubtype(url) {
1068
+ if (!url) return null;
1069
+ const withoutScheme = url.trim().replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
1070
+ const host = withoutScheme.split(/[/?#:]/, 1)[0].toLowerCase();
1071
+ if (host === "youtube.com" || host.endsWith(".youtube.com") || host === "youtu.be") return "youtube";
1072
+ if (host === "strava.com" || host.endsWith(".strava.com")) return "strava";
1073
+ if (host === "amazon.com" || host.endsWith(".amazon.com") || host === "amzn.to") return "amazon";
1074
+ return null;
1075
+ }
1076
+ function resolveFrontMatterSubtype(props) {
1077
+ const typeValue = (props["type"] || "").toLowerCase();
1078
+ if (typeValue === "youtube") return "youtube";
1079
+ if (typeValue === "strava") return "strava";
1080
+ if (typeValue === "amazon") return "amazon";
1081
+ if (typeValue === "file") return "file";
1082
+ if (typeValue === "effort") return "effort";
1083
+ const url = props["url"] || props["link"] || "";
1084
+ if (url) {
1085
+ const detected = detectUrlSubtype(url);
1086
+ if (detected) return detected;
1087
+ }
1088
+ if (props["baseAttributes"] !== void 0 || props["discipline"] !== void 0 || props["effortType"] !== void 0 || props["targetRPE"] !== void 0 || props["equipment"] !== void 0 || props["aliases"] !== void 0 || props["derivation"] !== void 0) {
1089
+ return "effort";
1090
+ }
1091
+ return "default";
1092
+ }
1093
+ function parseFrontMatterProperties(innerLines) {
1094
+ const props = {};
1095
+ for (const line of innerLines) {
1096
+ const match = line.match(/^([^:]+):\s*(.*)$/);
1097
+ if (match) {
1098
+ props[match[1].trim()] = match[2].trim();
1099
+ }
1100
+ }
1101
+ return props;
1102
+ }
1103
+ function matchMarkdownEmbed(trimmed) {
1104
+ const match = trimmed.match(/^(!)?\[([^\]]*)\]\(([^)]+)\)$/);
1105
+ if (!match) return null;
1106
+ const isImage = !!match[1];
1107
+ const label = match[2];
1108
+ const url = match[3];
1109
+ let type = isImage ? "image" : "link";
1110
+ if (/youtube\.com|youtu\.be/i.test(url)) {
1111
+ type = "youtube";
1112
+ }
1113
+ return { type, label, url, isImage };
1114
+ }
1115
+ function detectFrontMatterBlocks(lines, scriptBlocks) {
1116
+ const ranges = [];
1117
+ let i = 0;
1118
+ while (i < lines.length) {
1119
+ const inScript = scriptBlocks.some((b) => i >= b.startLine && i <= b.endLine);
1120
+ if (inScript) {
1121
+ i++;
1122
+ continue;
1123
+ }
1124
+ if (lines[i].trim() === "---") {
1125
+ const openLine = i;
1126
+ let j = i + 1;
1127
+ while (j < lines.length) {
1128
+ const inScriptInner = scriptBlocks.some((b) => j >= b.startLine && j <= b.endLine);
1129
+ if (inScriptInner) {
1130
+ j++;
1131
+ continue;
1132
+ }
1133
+ if (lines[j].trim() === "---") {
1134
+ ranges.push({ startLine: openLine, endLine: j });
1135
+ i = j + 1;
1136
+ break;
1137
+ }
1138
+ j++;
1139
+ }
1140
+ if (j >= lines.length) {
1141
+ i++;
1142
+ }
1143
+ } else {
1144
+ i++;
1145
+ }
1146
+ }
1147
+ return ranges;
1148
+ }
1149
+ function parseDocumentSections(content, scriptBlocks) {
1150
+ if (!content) return [];
1151
+ const blocks = scriptBlocks ?? detectScriptBlocks(content);
1152
+ const lines = content.split("\n");
1153
+ const sections = [];
1154
+ const fmRanges = detectFrontMatterBlocks(lines, blocks);
1155
+ const now = Date.now();
1156
+ let currentLine = 0;
1157
+ let isFirstTextBlock = true;
1158
+ function flushMarkdownLines(mdLines, startLine) {
1159
+ if (mdLines.length === 0) return;
1160
+ let currentGroup = [];
1161
+ let groupStartLine = startLine;
1162
+ const flushGroup = () => {
1163
+ if (currentGroup.length > 0) {
1164
+ const raw = currentGroup.join("\n");
1165
+ const { metadata, cleanText } = extractMetadata(raw);
1166
+ const trimmed = cleanText.trim();
1167
+ const embed = currentGroup.length === 1 ? matchMarkdownEmbed(trimmed) : null;
1168
+ if (embed) {
1169
+ sections.push({
1170
+ id: metadata?.id || generateSectionId("embed", groupStartLine, trimmed),
1171
+ type: "embed",
1172
+ rawContent: cleanText,
1173
+ displayContent: cleanText,
1174
+ startLine: groupStartLine,
1175
+ endLine: groupStartLine,
1176
+ lineCount: 1,
1177
+ embed,
1178
+ version: metadata?.version || 1,
1179
+ createdAt: metadata?.createdAt || now
1180
+ });
1181
+ } else if (isFirstTextBlock) {
1182
+ isFirstTextBlock = false;
1183
+ sections.push({
1184
+ id: metadata?.id || generateSectionId("title", groupStartLine, cleanText),
1185
+ type: "title",
1186
+ rawContent: cleanText,
1187
+ displayContent: cleanText,
1188
+ startLine: groupStartLine,
1189
+ endLine: groupStartLine + currentGroup.length - 1,
1190
+ lineCount: currentGroup.length,
1191
+ version: metadata?.version || 1,
1192
+ createdAt: metadata?.createdAt || now
1193
+ });
1194
+ } else {
1195
+ sections.push({
1196
+ id: metadata?.id || generateSectionId("markdown", groupStartLine, cleanText),
1197
+ type: "markdown",
1198
+ rawContent: cleanText,
1199
+ displayContent: cleanText,
1200
+ startLine: groupStartLine,
1201
+ endLine: groupStartLine + currentGroup.length - 1,
1202
+ lineCount: currentGroup.length,
1203
+ version: metadata?.version || 1,
1204
+ createdAt: metadata?.createdAt || now
1205
+ });
1206
+ }
1207
+ }
1208
+ currentGroup = [];
1209
+ };
1210
+ for (let i = 0; i < mdLines.length; i++) {
1211
+ const line = mdLines[i];
1212
+ if (line.trim().length === 0) {
1213
+ flushGroup();
1214
+ sections.push({
1215
+ id: generateSectionId("markdown", startLine + i, ""),
1216
+ type: "markdown",
1217
+ rawContent: "",
1218
+ displayContent: "",
1219
+ startLine: startLine + i,
1220
+ endLine: startLine + i,
1221
+ lineCount: 1,
1222
+ version: 1,
1223
+ createdAt: now
1224
+ });
1225
+ groupStartLine = startLine + i + 1;
1226
+ } else {
1227
+ currentGroup.push(line);
1228
+ }
1229
+ }
1230
+ flushGroup();
1231
+ }
1232
+ let mdBuffer = [];
1233
+ let mdBufferStart = 0;
1234
+ while (currentLine < lines.length) {
1235
+ const scriptBlock = blocks.find((b) => b.startLine === currentLine);
1236
+ if (scriptBlock) {
1237
+ flushMarkdownLines(mdBuffer, mdBufferStart);
1238
+ mdBuffer = [];
1239
+ const dialect = scriptBlock.dialect ?? "time";
1240
+ const { metadata, cleanText } = extractMetadata(scriptBlock.content);
1241
+ const fenceTag = scriptBlock.sport ? `${dialect}:${scriptBlock.sport}` : dialect;
1242
+ const cleanRawContent = `\`\`\`${fenceTag}
1243
+ ${cleanText}
1244
+ \`\`\``;
1245
+ const cleanLineCount = cleanRawContent.split("\n").length;
1246
+ const sectionId = metadata?.id || generateSectionId(dialect, scriptBlock.startLine, cleanText);
1247
+ const contentId = blockContentId(cleanText);
1248
+ sections.push({
1249
+ id: sectionId,
1250
+ contentId,
1251
+ type: dialect,
1252
+ sport: scriptBlock.sport,
1253
+ rawContent: cleanRawContent,
1254
+ displayContent: cleanText,
1255
+ startLine: scriptBlock.startLine,
1256
+ endLine: scriptBlock.startLine + cleanLineCount - 1,
1257
+ lineCount: cleanLineCount,
1258
+ scriptBlock: {
1259
+ ...scriptBlock,
1260
+ id: sectionId,
1261
+ contentId,
1262
+ content: cleanText,
1263
+ dialect,
1264
+ version: metadata?.version || 1,
1265
+ createdAt: metadata?.createdAt || now
1266
+ },
1267
+ version: metadata?.version || 1,
1268
+ createdAt: metadata?.createdAt || now
1269
+ });
1270
+ currentLine = scriptBlock.endLine + 1;
1271
+ mdBufferStart = currentLine;
1272
+ continue;
1273
+ }
1274
+ const fmRange = fmRanges.find((r) => r.startLine === currentLine);
1275
+ if (fmRange) {
1276
+ flushMarkdownLines(mdBuffer, mdBufferStart);
1277
+ mdBuffer = [];
1278
+ const rawLines = lines.slice(fmRange.startLine, fmRange.endLine + 1);
1279
+ const raw = rawLines.join("\n");
1280
+ const innerLines = lines.slice(fmRange.startLine + 1, fmRange.endLine);
1281
+ const { metadata, cleanText } = extractMetadata(innerLines.join("\n"));
1282
+ const properties = parseFrontMatterProperties(cleanText.split("\n"));
1283
+ const subtype = resolveFrontMatterSubtype(properties);
1284
+ const lineCount = fmRange.endLine - fmRange.startLine + 1;
1285
+ sections.push({
1286
+ id: metadata?.id || generateSectionId("frontmatter", fmRange.startLine, raw),
1287
+ type: "frontmatter",
1288
+ rawContent: raw,
1289
+ displayContent: cleanText,
1290
+ startLine: fmRange.startLine,
1291
+ endLine: fmRange.endLine,
1292
+ lineCount,
1293
+ properties,
1294
+ frontmatterType: subtype,
1295
+ version: metadata?.version || 1,
1296
+ createdAt: metadata?.createdAt || now
1297
+ });
1298
+ currentLine = fmRange.endLine + 1;
1299
+ mdBufferStart = currentLine;
1300
+ continue;
1301
+ }
1302
+ if (mdBuffer.length === 0) {
1303
+ mdBufferStart = currentLine;
1304
+ }
1305
+ mdBuffer.push(lines[currentLine]);
1306
+ currentLine++;
1307
+ }
1308
+ flushMarkdownLines(mdBuffer, mdBufferStart);
1309
+ return sections;
1310
+ }
1311
+ function buildRawContent(sections) {
1312
+ return sections.filter((s) => !s.deleted).map((s) => s.rawContent).join("\n");
1313
+ }
1314
+ function calculateTotalLines(sections) {
1315
+ const visible = sections.filter((s) => !s.deleted);
1316
+ if (visible.length === 0) return 0;
1317
+ const last = visible[visible.length - 1];
1318
+ return last.endLine + 1;
1319
+ }
1320
+ function matchSectionIds(oldSections, newSections) {
1321
+ return newSections.map((newSec) => {
1322
+ const { metadata } = extractMetadata(newSec.rawContent);
1323
+ if (metadata) {
1324
+ const updated = { ...newSec, id: metadata.id, version: metadata.version, createdAt: metadata.createdAt };
1325
+ if (updated.scriptBlock) {
1326
+ updated.scriptBlock = { ...updated.scriptBlock, id: metadata.id };
1327
+ }
1328
+ return updated;
1329
+ }
1330
+ const exactMatch = oldSections.find(
1331
+ (old) => old.type === newSec.type && old.startLine === newSec.startLine && old.displayContent === newSec.displayContent
1332
+ );
1333
+ if (exactMatch) {
1334
+ const updated = { ...newSec, id: exactMatch.id, version: exactMatch.version, createdAt: exactMatch.createdAt };
1335
+ if (updated.scriptBlock) {
1336
+ updated.scriptBlock = { ...updated.scriptBlock, id: exactMatch.id };
1337
+ }
1338
+ return updated;
1339
+ }
1340
+ const positionMatch = oldSections.find(
1341
+ (old) => old.type === newSec.type && old.startLine === newSec.startLine
1342
+ );
1343
+ if (positionMatch) {
1344
+ const newVersion = (positionMatch.version || 1) + 1;
1345
+ const updated = {
1346
+ ...newSec,
1347
+ id: positionMatch.id,
1348
+ version: newVersion,
1349
+ createdAt: positionMatch.createdAt
1350
+ };
1351
+ if (updated.scriptBlock) {
1352
+ updated.scriptBlock = { ...updated.scriptBlock, id: positionMatch.id, version: newVersion };
1353
+ }
1354
+ return updated;
1355
+ }
1356
+ return newSec;
1357
+ });
1358
+ }
1359
+
842
1360
  // src/version.ts
843
- var VERSION = "0.11.43";
844
- var GIT_SHA = "ed28149";
845
- var BUILD_TIME = "2026-08-26T16:34:15.927Z";
1361
+ var VERSION = "0.35.2115";
1362
+ var GIT_SHA = "37944a8";
1363
+ var BUILD_TIME = "2026-10-11T23:54:36.367Z";
846
1364
  var SEMVER = {
847
- version: "0.11.43",
848
- gitSha: "ed28149",
849
- buildTime: "2026-08-26T16:34:15.927Z"
1365
+ version: "0.35.2115",
1366
+ gitSha: "37944a8",
1367
+ buildTime: "2026-10-11T23:54:36.367Z"
850
1368
  };
851
1369
 
852
1370
  exports.BUILD_TIME = BUILD_TIME;
853
1371
  exports.BlockKey = BlockKey;
854
1372
  exports.CodeMetadata = CodeMetadata;
855
1373
  exports.CodeStatement = CodeStatement;
856
- exports.Duration = Duration;
857
1374
  exports.GIT_SHA = GIT_SHA;
858
1375
  exports.KNOWN_OUTPUT_TYPES = KNOWN_OUTPUT_TYPES;
859
1376
  exports.LEGACY_ORIGIN_TO_OWNERSHIP_LAYER = LEGACY_ORIGIN_TO_OWNERSHIP_LAYER;
@@ -866,16 +1383,39 @@ exports.OutputStatement = OutputStatement;
866
1383
  exports.OwnershipResolver = OwnershipResolver;
867
1384
  exports.ParsedCodeStatement = ParsedCodeStatement;
868
1385
  exports.SEMVER = SEMVER;
869
- exports.SpanDuration = SpanDuration;
1386
+ exports.SYSTEM_DEFAULT_OUTPUT_UNIT = SYSTEM_DEFAULT_OUTPUT_UNIT;
1387
+ exports.UNIT_CATALOG = UNIT_CATALOG;
1388
+ exports.UnitConversionError = UnitConversionError;
870
1389
  exports.VALID_FENCE_DIALECTS = VALID_FENCE_DIALECTS;
871
1390
  exports.VERSION = VERSION;
1391
+ exports.blockContentId = blockContentId;
1392
+ exports.buildRawContent = buildRawContent;
1393
+ exports.calculateTotalLines = calculateTotalLines;
1394
+ exports.convertViaCatalog = convertViaCatalog;
872
1395
  exports.createMetricOwnershipLedger = createMetricOwnershipLedger;
1396
+ exports.detectScriptBlocks = detectScriptBlocks;
1397
+ exports.dimensionKeyOf = dimensionKeyOf;
1398
+ exports.extractBlockContent = extractBlockContent;
1399
+ exports.fieldRefFromKey = fieldRefFromKey;
1400
+ exports.fieldRefKey = fieldRefKey;
1401
+ exports.findBlockAtLine = findBlockAtLine;
1402
+ exports.flattenFieldLeaves = flattenFieldLeaves;
873
1403
  exports.fromNotebookTag = fromNotebookTag;
1404
+ exports.generateSectionId = generateSectionId;
874
1405
  exports.getMetricOwnershipLayer = getMetricOwnershipLayer;
875
1406
  exports.isNotebookTag = isNotebookTag;
876
1407
  exports.isWorkoutSectionType = isWorkoutSectionType;
1408
+ exports.lookupUnit = lookupUnit;
1409
+ exports.matchDialectFence = matchDialectFence;
1410
+ exports.matchSectionIds = matchSectionIds;
1411
+ exports.normalizeFieldComponent = normalizeFieldComponent;
1412
+ exports.normalizeFieldPath = normalizeFieldPath;
877
1413
  exports.ownershipRank = ownershipRank;
1414
+ exports.parseDocumentSections = parseDocumentSections;
1415
+ exports.resolveLayer = resolveLayer;
878
1416
  exports.runAffordance = runAffordance;
1417
+ exports.systemDefaultUnit = systemDefaultUnit;
879
1418
  exports.toNotebookTag = toNotebookTag;
1419
+ exports.valueKindOf = valueKindOf;
880
1420
  //# sourceMappingURL=index.cjs.map
881
1421
  //# sourceMappingURL=index.cjs.map