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