@bitcobblers/wod-wiki-lang 0.43.2152 → 0.44.2155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{browserRuntimeNow-M4hQ_DTl.d.cts → browserRuntimeNow-_zhhbzoE.d.cts} +294 -2
- package/dist/{browserRuntimeNow-M4hQ_DTl.d.ts → browserRuntimeNow-_zhhbzoE.d.ts} +294 -2
- package/dist/index.cjs +1497 -1116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +246 -325
- package/dist/index.d.ts +246 -325
- package/dist/index.mjs +1483 -1115
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -305,6 +305,125 @@ function computeColumnLabel(type) {
|
|
|
305
305
|
const normalized = key.toLowerCase();
|
|
306
306
|
return COLUMN_LABEL_MAP[normalized] ?? humanizeMetricKey(key);
|
|
307
307
|
}
|
|
308
|
+
function tagReferenceFor(tagIndex, slug) {
|
|
309
|
+
const resolved = resolveTagContributions(tagIndex, slug);
|
|
310
|
+
const sound = resolved.metrics.find(
|
|
311
|
+
(contribution) => contribution.type === wodWikiCore.MetricType.Sound && typeof contribution.value === "string"
|
|
312
|
+
);
|
|
313
|
+
return {
|
|
314
|
+
slug,
|
|
315
|
+
label: slug,
|
|
316
|
+
...resolved.functions.length > 0 ? { functions: [...resolved.functions] } : {},
|
|
317
|
+
...sound ? { soundProfileId: sound.value } : {}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function collectTagReferences(metrics, into = []) {
|
|
321
|
+
for (const metric of metrics) {
|
|
322
|
+
const reference = wodWikiCore.getTagReference(metric.metadata);
|
|
323
|
+
if (!reference) continue;
|
|
324
|
+
if (into.some((existing) => existing.slug === reference.slug)) continue;
|
|
325
|
+
into.push(reference);
|
|
326
|
+
}
|
|
327
|
+
return into;
|
|
328
|
+
}
|
|
329
|
+
var TAG_MARKER_PREFIX = "tag.";
|
|
330
|
+
var MAX_TAG_CONTRIBUTION_DEPTH = 8;
|
|
331
|
+
var EMPTY = { metrics: [], functions: [], tags: [] };
|
|
332
|
+
function resolveTagContributions(tagIndex, slug) {
|
|
333
|
+
if (!tagIndex) return EMPTY;
|
|
334
|
+
const metrics = [];
|
|
335
|
+
const functions = [];
|
|
336
|
+
const tags2 = [];
|
|
337
|
+
const seenFunctions = /* @__PURE__ */ new Set();
|
|
338
|
+
const visited = /* @__PURE__ */ new Set();
|
|
339
|
+
const walk = (currentSlug, depth) => {
|
|
340
|
+
if (depth > MAX_TAG_CONTRIBUTION_DEPTH) return;
|
|
341
|
+
if (visited.has(currentSlug)) return;
|
|
342
|
+
const definition = tagIndex.resolve(currentSlug);
|
|
343
|
+
if (!definition) return;
|
|
344
|
+
visited.add(currentSlug);
|
|
345
|
+
tags2.push(currentSlug);
|
|
346
|
+
for (const contribution of definition.contributions) {
|
|
347
|
+
if (contribution.kind === "metric") metrics.push(contribution);
|
|
348
|
+
else if (contribution.kind === "function") {
|
|
349
|
+
if (!seenFunctions.has(contribution.name)) {
|
|
350
|
+
seenFunctions.add(contribution.name);
|
|
351
|
+
functions.push(contribution.name);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
for (const contribution of definition.contributions) {
|
|
356
|
+
if (contribution.kind === "tag") walk(contribution.slug, depth + 1);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
walk(slug, 0);
|
|
360
|
+
return { metrics, functions, tags: tags2 };
|
|
361
|
+
}
|
|
362
|
+
function findTagContributionCycles(tagIndex) {
|
|
363
|
+
if (!tagIndex) return [];
|
|
364
|
+
const cycles = [];
|
|
365
|
+
const seen = /* @__PURE__ */ new Set();
|
|
366
|
+
const walk = (slug, path, onPath) => {
|
|
367
|
+
if (onPath.has(slug)) {
|
|
368
|
+
const start = path.indexOf(slug);
|
|
369
|
+
if (start >= 0) {
|
|
370
|
+
const cycle = [...path.slice(start), slug];
|
|
371
|
+
const key = cycleKey(cycle);
|
|
372
|
+
if (!seen.has(key)) {
|
|
373
|
+
seen.add(key);
|
|
374
|
+
cycles.push(cycle);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const definition = tagIndex.resolve(slug);
|
|
380
|
+
if (!definition) return;
|
|
381
|
+
const nextPath = [...path, slug];
|
|
382
|
+
const nextOnPath = new Set(onPath);
|
|
383
|
+
nextOnPath.add(slug);
|
|
384
|
+
for (const contribution of definition.contributions) {
|
|
385
|
+
if (contribution.kind === "tag") walk(contribution.slug, nextPath, nextOnPath);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
for (const definition of tagIndex.list()) {
|
|
389
|
+
walk(definition.slug, [], /* @__PURE__ */ new Set());
|
|
390
|
+
}
|
|
391
|
+
return cycles;
|
|
392
|
+
}
|
|
393
|
+
function cycleKey(cycle) {
|
|
394
|
+
const nodes = cycle.slice(0, -1);
|
|
395
|
+
if (nodes.length === 0) return "";
|
|
396
|
+
let smallest = 0;
|
|
397
|
+
for (let index = 1; index < nodes.length; index++) {
|
|
398
|
+
if (nodes[index] < nodes[smallest]) smallest = index;
|
|
399
|
+
}
|
|
400
|
+
return [...nodes.slice(smallest), ...nodes.slice(0, smallest)].join(">");
|
|
401
|
+
}
|
|
402
|
+
function tagMarkerType(slug) {
|
|
403
|
+
return `${TAG_MARKER_PREFIX}${slug}`;
|
|
404
|
+
}
|
|
405
|
+
function tagMarkerSlug(type) {
|
|
406
|
+
if (!type.startsWith(TAG_MARKER_PREFIX)) return void 0;
|
|
407
|
+
const slug = type.slice(TAG_MARKER_PREFIX.length);
|
|
408
|
+
return slug.length > 0 ? slug : void 0;
|
|
409
|
+
}
|
|
410
|
+
function tagMarkerSlugs(metrics) {
|
|
411
|
+
const slugs = [];
|
|
412
|
+
for (const metric of metrics) {
|
|
413
|
+
const slug = tagMarkerSlug(metric.type);
|
|
414
|
+
if (slug !== void 0 && !slugs.includes(slug)) slugs.push(slug);
|
|
415
|
+
}
|
|
416
|
+
return slugs;
|
|
417
|
+
}
|
|
418
|
+
function tagContributionMetrics(contributions, origin = "parser") {
|
|
419
|
+
return contributions.map((contribution) => ({
|
|
420
|
+
type: contribution.type,
|
|
421
|
+
value: contribution.value,
|
|
422
|
+
origin,
|
|
423
|
+
image: String(contribution.value),
|
|
424
|
+
...contribution.unit ? { unit: contribution.unit } : {}
|
|
425
|
+
}));
|
|
426
|
+
}
|
|
308
427
|
|
|
309
428
|
// src/metrics/presentation/MetricPresentationPolicy.ts
|
|
310
429
|
function isRestMetric(metric) {
|
|
@@ -368,6 +487,7 @@ function createPolicy() {
|
|
|
368
487
|
}
|
|
369
488
|
const t2 = metric.type;
|
|
370
489
|
if ((t2 === wodWikiCore.MetricType.Sound || t2 === "sound") && surface !== "debug") return true;
|
|
490
|
+
if (t2.startsWith(TAG_MARKER_PREFIX) && surface !== "debug") return true;
|
|
371
491
|
if (surface === "review-grid-column") {
|
|
372
492
|
if (t2 === wodWikiCore.MetricType.Elapsed || t2 === "elapsed" || t2 === wodWikiCore.MetricType.Total || t2 === "total") return true;
|
|
373
493
|
if (t2 === wodWikiCore.MetricType.System || t2 === "system") return true;
|
|
@@ -1505,15 +1625,16 @@ var TextMetric = class {
|
|
|
1505
1625
|
origin = "parser";
|
|
1506
1626
|
type = wodWikiCore.MetricType.Text;
|
|
1507
1627
|
};
|
|
1508
|
-
|
|
1509
|
-
// src/parser/semantic-classifier.ts
|
|
1510
1628
|
var PROPERTY_KEY_TO_METRIC_TYPE = {
|
|
1511
1629
|
rpe: wodWikiCore.MetricType.SessionRPE,
|
|
1512
1630
|
rir: wodWikiCore.MetricType.RIR,
|
|
1513
1631
|
intensity: wodWikiCore.MetricType.Intensity,
|
|
1514
1632
|
load: wodWikiCore.MetricType.Load,
|
|
1515
1633
|
volume: wodWikiCore.MetricType.Volume,
|
|
1516
|
-
work: wodWikiCore.MetricType.Work
|
|
1634
|
+
work: wodWikiCore.MetricType.Work,
|
|
1635
|
+
// `sound: classic` names a sound profile, the same way a `#sound-classic`
|
|
1636
|
+
// tag reference does.
|
|
1637
|
+
sound: wodWikiCore.MetricType.Sound
|
|
1517
1638
|
};
|
|
1518
1639
|
var STRUCTURAL_EFFORT_WORDS = /* @__PURE__ */ new Set(["rest", "amrap", "emom", "tabata", "for time"]);
|
|
1519
1640
|
var STANDARD_UNITS2 = UnitRegistry.standard();
|
|
@@ -1530,9 +1651,25 @@ function referenceMetadata(resolution) {
|
|
|
1530
1651
|
function classifyStatements(facts, references) {
|
|
1531
1652
|
return facts.statements.map((fact) => {
|
|
1532
1653
|
const statement = new wodWikiCore.ParsedCodeStatement();
|
|
1533
|
-
|
|
1654
|
+
const slots = [];
|
|
1534
1655
|
for (const primitive of fact.primitives) {
|
|
1535
|
-
|
|
1656
|
+
slots.push(...classifySlots(primitive, references));
|
|
1657
|
+
}
|
|
1658
|
+
const explicitTypes = /* @__PURE__ */ new Set();
|
|
1659
|
+
for (const slot of slots) {
|
|
1660
|
+
if (slot.kind === "explicit") explicitTypes.add(slot.pair.metrics.type);
|
|
1661
|
+
}
|
|
1662
|
+
const contributedTypes = /* @__PURE__ */ new Set();
|
|
1663
|
+
const metricPairs = [];
|
|
1664
|
+
for (const slot of slots) {
|
|
1665
|
+
if (slot.kind !== "contributed") {
|
|
1666
|
+
metricPairs.push(slot.pair);
|
|
1667
|
+
continue;
|
|
1668
|
+
}
|
|
1669
|
+
const type = slot.pair.metrics.type;
|
|
1670
|
+
if (explicitTypes.has(type) || contributedTypes.has(type)) continue;
|
|
1671
|
+
contributedTypes.add(type);
|
|
1672
|
+
metricPairs.push(slot.pair);
|
|
1536
1673
|
}
|
|
1537
1674
|
const mergedPairs = mergeFragments(metricPairs);
|
|
1538
1675
|
const finalPairs = references ? applyReferenceResolution(mergedPairs, fact, references) : mergedPairs;
|
|
@@ -1549,6 +1686,21 @@ function classifyStatements(facts, references) {
|
|
|
1549
1686
|
return statement;
|
|
1550
1687
|
});
|
|
1551
1688
|
}
|
|
1689
|
+
function equipmentMeta(resolution, authored, meta2) {
|
|
1690
|
+
if (resolution.matched !== "split" || !resolution.remaining) return meta2;
|
|
1691
|
+
const prefixEnd = authored.trimEnd().length - resolution.remaining.length;
|
|
1692
|
+
if (prefixEnd <= 0) return meta2;
|
|
1693
|
+
const raw = meta2.raw.slice(0, prefixEnd).trimEnd();
|
|
1694
|
+
const length = raw.length;
|
|
1695
|
+
if (length <= 0) return meta2;
|
|
1696
|
+
return {
|
|
1697
|
+
...meta2,
|
|
1698
|
+
endOffset: meta2.startOffset + length,
|
|
1699
|
+
length,
|
|
1700
|
+
columnEnd: meta2.columnStart + length,
|
|
1701
|
+
raw
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1552
1704
|
function applyReferenceResolution(pairs, fact, references) {
|
|
1553
1705
|
const { index, defaults } = references;
|
|
1554
1706
|
const result = [];
|
|
@@ -1573,7 +1725,10 @@ function applyReferenceResolution(pairs, fact, references) {
|
|
|
1573
1725
|
diagnostics: resolution.diagnostics
|
|
1574
1726
|
}
|
|
1575
1727
|
),
|
|
1576
|
-
|
|
1728
|
+
// The equipment is the prefix of the authored label; the effort is
|
|
1729
|
+
// what follows. Marking the whole phrase for both would let one mark
|
|
1730
|
+
// hide the other.
|
|
1731
|
+
meta: equipmentMeta(resolution, authored, meta2)
|
|
1577
1732
|
});
|
|
1578
1733
|
}
|
|
1579
1734
|
};
|
|
@@ -1703,14 +1858,7 @@ function classifyPrimitive(primitive) {
|
|
|
1703
1858
|
case "effort":
|
|
1704
1859
|
return [{ metrics: new EffortMetric(primitive.raw), meta: primitive.meta }];
|
|
1705
1860
|
case "property": {
|
|
1706
|
-
|
|
1707
|
-
return [{
|
|
1708
|
-
metrics: new PropertyMetric(primitive.key, primitive.value, {
|
|
1709
|
-
type: metricType,
|
|
1710
|
-
image: primitive.raw
|
|
1711
|
-
}),
|
|
1712
|
-
meta: primitive.meta
|
|
1713
|
-
}];
|
|
1861
|
+
return [propertyPair(primitive)];
|
|
1714
1862
|
}
|
|
1715
1863
|
case "metric_object": {
|
|
1716
1864
|
const pairs = primitive.pairs;
|
|
@@ -1730,6 +1878,85 @@ function classifyPrimitive(primitive) {
|
|
|
1730
1878
|
return [];
|
|
1731
1879
|
}
|
|
1732
1880
|
}
|
|
1881
|
+
function classifySlots(primitive, references) {
|
|
1882
|
+
if (primitive.kind === "property" && primitive.tagRefs !== void 0) {
|
|
1883
|
+
return propertyTagSlots(primitive, references);
|
|
1884
|
+
}
|
|
1885
|
+
if (primitive.kind === "tag_ref") {
|
|
1886
|
+
return fragmentTagSlots(primitive, references);
|
|
1887
|
+
}
|
|
1888
|
+
return classifyPrimitive(primitive).map((pair) => ({ kind: "explicit", pair }));
|
|
1889
|
+
}
|
|
1890
|
+
function propertyTagSlots(primitive, references) {
|
|
1891
|
+
const { slots, resolved } = referenceSlots(primitive.tagRefs ?? [], primitive.meta, references);
|
|
1892
|
+
if (resolved) return slots;
|
|
1893
|
+
return [{ kind: "explicit", pair: propertyPair(primitive) }];
|
|
1894
|
+
}
|
|
1895
|
+
function fragmentTagSlots(primitive, references) {
|
|
1896
|
+
const { slots, resolved } = referenceSlots([primitive.slug], primitive.meta, references);
|
|
1897
|
+
if (resolved) return slots;
|
|
1898
|
+
return [{
|
|
1899
|
+
kind: "explicit",
|
|
1900
|
+
pair: {
|
|
1901
|
+
metrics: {
|
|
1902
|
+
type: wodWikiCore.MetricType.Custom,
|
|
1903
|
+
value: primitive.raw,
|
|
1904
|
+
origin: "parser",
|
|
1905
|
+
image: primitive.raw
|
|
1906
|
+
},
|
|
1907
|
+
meta: primitive.meta
|
|
1908
|
+
}
|
|
1909
|
+
}];
|
|
1910
|
+
}
|
|
1911
|
+
function referenceSlots(slugs, meta2, references) {
|
|
1912
|
+
const slots = [];
|
|
1913
|
+
let resolved = false;
|
|
1914
|
+
for (const slug of slugs) {
|
|
1915
|
+
const contributions = resolveTagContributions(references?.tags, slug);
|
|
1916
|
+
const reference = tagReferenceFor(references?.tags, slug);
|
|
1917
|
+
const contributed = tagContributionMetrics(contributions.metrics);
|
|
1918
|
+
if (contributed.length > 0) resolved = true;
|
|
1919
|
+
for (const metric of contributed) {
|
|
1920
|
+
slots.push({
|
|
1921
|
+
kind: "contributed",
|
|
1922
|
+
pair: { metrics: withTagReference(metric, reference), meta: meta2 }
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
for (const tag of contributions.tags) {
|
|
1926
|
+
resolved = true;
|
|
1927
|
+
const tagReference = tag === slug ? reference : tagReferenceFor(references?.tags, tag);
|
|
1928
|
+
slots.push({
|
|
1929
|
+
kind: "marker",
|
|
1930
|
+
pair: {
|
|
1931
|
+
metrics: withTagReference({
|
|
1932
|
+
type: tagMarkerType(tag),
|
|
1933
|
+
value: tag,
|
|
1934
|
+
origin: "parser",
|
|
1935
|
+
image: tag
|
|
1936
|
+
}, tagReference),
|
|
1937
|
+
meta: meta2
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return { slots, resolved };
|
|
1943
|
+
}
|
|
1944
|
+
function withTagReference(metric, reference) {
|
|
1945
|
+
return {
|
|
1946
|
+
...metric,
|
|
1947
|
+
metadata: { ...metric.metadata ?? {}, [wodWikiCore.TAG_REFERENCE_METADATA_KEY]: reference }
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
function propertyPair(primitive) {
|
|
1951
|
+
const metricType = PROPERTY_KEY_TO_METRIC_TYPE[primitive.key.toLowerCase()] ?? wodWikiCore.MetricType.Custom;
|
|
1952
|
+
return {
|
|
1953
|
+
metrics: new PropertyMetric(primitive.key, primitive.value, {
|
|
1954
|
+
type: metricType,
|
|
1955
|
+
image: primitive.raw
|
|
1956
|
+
}),
|
|
1957
|
+
meta: primitive.meta
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1733
1960
|
function mergeFragments(pairs) {
|
|
1734
1961
|
if (pairs.length < 2) return pairs;
|
|
1735
1962
|
const result = [];
|
|
@@ -1809,33 +2036,34 @@ function mergeFragments(pairs) {
|
|
|
1809
2036
|
}
|
|
1810
2037
|
var parser = lr.LRParser.deserialize({
|
|
1811
2038
|
version: 14,
|
|
1812
|
-
states: "(
|
|
1813
|
-
stateData: "
|
|
1814
|
-
goto: "$
|
|
1815
|
-
nodeNames: "\u26A0 Program Property Identifier String Number Block Lap Fragment Duration Trend CollectibleTimer Timer Rounds Sequence RoundScheme Action Text Heading Quantity AtSign Question Slash Pipe Effort MetricObject True False Null",
|
|
1816
|
-
maxTerm:
|
|
2039
|
+
states: "(dQVQPOOO!mQPO'#DPO$TQPO'#CdOOQO'#Cd'#CdOOQO'#Cg'#CgOOQO'#Ch'#ChOOQO'#Cf'#CfO%hQPO'#CfO%pQPO'#CjOOQO'#Cl'#ClOOQO'#DP'#DPOOQO'#Cu'#CuO%xQPO'#CmOOQO'#Cn'#CnOOQO'#Co'#CoOOQO'#Cq'#CqOOQO'#Cr'#CrOOQO'#Cp'#CpO%}QPO'#CpOOQO'#Cs'#CsOOQO'#Ct'#CtO&tQPO'#CvOOQO'#Ce'#CeOOQO'#C|'#C|O(]QPO'#CcO&{QPO'#CcOOQO'#Cz'#CzQVQPOOO(dQPO,58xOOQO,59Q,59QO(rQPO'#CkOOQO'#C}'#C}O)QQPO,59UOOQO-E6}-E6}O)]QPO,59XOOQO,59[,59[OOQO'#DQ'#DQO)hQPO,59bOOQO,59b,59bOOQO-E6z-E6zOOQO,58},58}O)oQPO,58}OOQO-E6x-E6xOOQO'#C{'#C{OOQO1G.d1G.dO)vQPO'#DOO){QPO,59VOOQO-E6{-E6{OOQO1G.p1G.pO*ZQPO1G.sOOQO-E7O-E7OOOQO1G.|1G.|OOQO1G.i1G.iPOQO-E6y-E6yOOQO,59j,59jOOQO-E6|-E6|OOQO7+$_7+$_",
|
|
2040
|
+
stateData: "+V~OwOS~ORPOTaOUfO]UOyQOzRO{SO|VO}TO!OWO!QXO!RXO!S[O!TYO!V]O!W^O!X_O!Y`O!ZcO![dO!]eO!`jO~OxlORsXTsXUsX]sXysX{sX|sX}sX!OsX!QsX!RsX!SsX!TsX!VsX!WsX!XsX!YsX!ZsX![sX!]sX!`sX~ORWXTWXUWX]WXyWX{WX|WX}WX!OWX!QWX!RWX!SWX!TWX!VWX!WWX!XWX!YWX!ZWX![WX!]WX!`sX~O]mO}TO~ORoOTnO~OxrO~OTsO!Y`O~ORtOStOTtOktOltOmtOxtO!TtO!^tO~O!_vO~P&VORYOTaOUfO]UOyYO{SO|VO}TO!OWO!QXO!RXO!S[O!TYO!V]O!W^O!X_O!Y`O!ZcO![dO!]eO~O!`xO~P&{OR|OS|OT|OU{O~Oy}OR_XT_X!P_X~ORoOTnO!P!QO~ORYOyYO!TYO~O!_!TO~P&VO!`!UO~P&{OT!WO~Oy}OR_aT_a!P_a~ORYOyYO!TYO!U!YO~O!VU!W!Q!R}]!X{|x!Z![!T!]!_!^klmRyzT!`w!T~",
|
|
2041
|
+
goto: "$ZuPPvPPPPvz!O!V!^!eP!V!o!V!V!V!V!V!s!z!V!V!V!VPPP#U#[#_#j#p#v$TTjOkTiOkZgOhikyZfOhikyZVOhikyYUOhikyRmVToWpZbOhikyYaOhikyRsbQkORzkR|lShOkSwhyRyiQpWR!PpQ!OnR!X!OYZOhikyQq!RR!RrQueR!Su",
|
|
2042
|
+
nodeNames: "\u26A0 Program Property Identifier String Number TagRef Block Lap Fragment Duration Trend CollectibleTimer Timer Rounds Sequence RoundScheme Action Text Heading Quantity AtSign Question Slash Pipe Effort MetricObject True False Null",
|
|
2043
|
+
maxTerm: 62,
|
|
1817
2044
|
skippedNodes: [0],
|
|
1818
|
-
repeatNodeCount:
|
|
1819
|
-
tokenData: "
|
|
2045
|
+
repeatNodeCount: 7,
|
|
2046
|
+
tokenData: "7o~RvXY#iYZ#z]^$Ppq#iqr$Vrs$}st'dtu$Vuv$Vvw$Vxy)Pyz)Uz{)Z{|*T|}*Y}!O+S!O!P+X!P!Q,[!Q![-R![!]/U!_!`$V!a!b/n!b!c/s!c!}0m!}#O0{#O#P$V#P#Q1Q#Q#R1V#T#Y0m#Y#Z2P#Z#b0m#b#c4Q#c#h0m#h#i5p#i#o0m#o#p7`#p#q7e#q#r7j~#nSw~XY#iYZ#z]^$Ppq#i~$PO!`~~$SPYZ#z~$[Z!T~qr$Vtu$Vuv$Vvw$Vz{$V|}$V!O!P$V!_!`$V!b!c$V#O#P$V#Q#R$V~%QXOY$}Z]$}^r$}rs%ms#O$}#O#P&_#P;'S$};'S;=`'^<%lO$}~%rXS~OY$}Z]$}^r$}rs%ms#O$}#O#P&_#P;'S$};'S;=`'^<%lO$}~&bRO;'S$};'S;=`&k;=`O$}~&nYOY$}Z]$}^r$}rs%ms#O$}#O#P&_#P;'S$};'S;=`'^;=`<%l$}<%lO$}~'aP;=`<%l$}~'gXO!Q(S!QUO!O~~)ZO!P~~)bZ|~!T~qr$Vtu$Vuv$Vvw$Vz{$V|}$V!O!P$V!_!`$V!b!c$V#O#P$V#Q#R$V~*YOz~~*aZ!T~!^~qr$Vtu$Vuv$Vvw$Vz{$V|}$V!O!P$V!_!`$V!b!c$V#O#P$V#Q#R$V~+XOy~~+^[!T~qr$Vtu$Vuv$Vvw$Vz{$V|}$V!O!P$V!Q![,S!_!`$V!b!c$V#O#P$V#Q#R$V~,XPT~!Q![,S~,aP!Z~!P!Q,d~,iU!V~OY,dZ],d^s,dt;'S,d;'S;=`,{<%lO,d~-OP;=`<%l,d~-WUT~pq-j!O!P._!Q![-R![!].e!z!{.v#l#m.v~-mRpq-j!z!{-v#l#m-v~-yPpq-|~.PQpq-|!Q![.V~.[P!R~!Q![.V~.bP!Q![,S~.hP!Q![.k~.pQ]~!Q![.k![!].e~.yP!Q![.|~/RP!Q~!Q![.|~/ZQx~!Q![/a!a!b/i~/fP]~!Q![/a~/nO}~~/sO!Y~~/zZ!X~!T~qr$Vtu$Vuv$Vvw$Vz{$V|}$V!O!P$V!_!`$V!b!c$V#O#P$V#Q#R$V~0rRR~!Q![0m!c!}0m#T#o0m~1QO!S~~1VO!U~~1^Z{~!T~qr$Vtu$Vuv$Vvw$Vz{$V|}$V!O!P$V!_!`$V!b!c$V#O#P$V#Q#R$V~2USR~!Q![0m!c!}0m#T#U2b#U#o0m~2gTR~!Q![0m!c!}0m#T#`0m#`#a2v#a#o0m~2{TR~!Q![0m!c!}0m#T#g0m#g#h3[#h#o0m~3aTR~!Q![0m!c!}0m#T#X0m#X#Y3p#Y#o0m~3wRl~R~!Q![0m!c!}0m#T#o0m~4VTR~!Q![0m!c!}0m#T#i0m#i#j4f#j#o0m~4kTR~!Q![0m!c!}0m#T#`0m#`#a4z#a#o0m~5PTR~!Q![0m!c!}0m#T#`0m#`#a5`#a#o0m~5gRm~R~!Q![0m!c!}0m#T#o0m~5uTR~!Q![0m!c!}0m#T#f0m#f#g6U#g#o0m~6ZTR~!Q![0m!c!}0m#T#i0m#i#j6j#j#o0m~6oTR~!Q![0m!c!}0m#T#X0m#X#Y7O#Y#o0m~7VRk~R~!Q![0m!c!}0m#T#o0m~7eO!]~~7jO![~~7oO!_~",
|
|
1820
2047
|
tokenizers: [0],
|
|
1821
2048
|
topRules: { "Program": [0, 1] },
|
|
1822
|
-
tokenPrec:
|
|
2049
|
+
tokenPrec: 439
|
|
1823
2050
|
});
|
|
1824
2051
|
|
|
1825
2052
|
// src/grammar/parser.terms.ts
|
|
1826
|
-
var
|
|
1827
|
-
var
|
|
1828
|
-
var
|
|
1829
|
-
var
|
|
1830
|
-
var
|
|
1831
|
-
var
|
|
1832
|
-
var
|
|
1833
|
-
var
|
|
1834
|
-
var
|
|
1835
|
-
var
|
|
1836
|
-
var
|
|
1837
|
-
var
|
|
1838
|
-
var
|
|
2053
|
+
var TagRef = 6;
|
|
2054
|
+
var Lap = 8;
|
|
2055
|
+
var Fragment = 9;
|
|
2056
|
+
var Duration = 10;
|
|
2057
|
+
var Rounds = 14;
|
|
2058
|
+
var RoundScheme = 16;
|
|
2059
|
+
var Action = 17;
|
|
2060
|
+
var Text = 18;
|
|
2061
|
+
var Heading = 19;
|
|
2062
|
+
var Quantity = 20;
|
|
2063
|
+
var Slash = 23;
|
|
2064
|
+
var Pipe = 24;
|
|
2065
|
+
var Effort = 25;
|
|
2066
|
+
var MetricObject = 26;
|
|
1839
2067
|
|
|
1840
2068
|
// src/parser/syntax-parser.ts
|
|
1841
2069
|
function createLineLocator(source) {
|
|
@@ -1941,15 +2169,28 @@ function createPropertyPrimitive(locator, source, from, to) {
|
|
|
1941
2169
|
const match = raw.match(/^\s*([A-Za-z][A-Za-z0-9]*)\s*:\s*(.*?)\s*$/);
|
|
1942
2170
|
if (!match) return null;
|
|
1943
2171
|
const [, key, rawValue] = match;
|
|
2172
|
+
const tagRefs = parseTagRefs(rawValue);
|
|
1944
2173
|
return {
|
|
1945
2174
|
kind: "property",
|
|
1946
2175
|
raw,
|
|
1947
2176
|
meta: meta2,
|
|
1948
2177
|
key,
|
|
1949
2178
|
valueRaw: rawValue,
|
|
1950
|
-
value: parsePropertyValue(rawValue)
|
|
2179
|
+
value: tagRefs !== void 0 ? rawValue.trim() : parsePropertyValue(rawValue),
|
|
2180
|
+
...tagRefs !== void 0 ? { tagRefs } : {}
|
|
1951
2181
|
};
|
|
1952
2182
|
}
|
|
2183
|
+
function parseTagRefs(rawValue) {
|
|
2184
|
+
const parts = rawValue.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
2185
|
+
if (parts.length === 0) return void 0;
|
|
2186
|
+
const slugs = [];
|
|
2187
|
+
for (const part of parts) {
|
|
2188
|
+
const match = part.match(/^#([a-zA-Z0-9][a-zA-Z0-9-]*)$/);
|
|
2189
|
+
if (!match) return void 0;
|
|
2190
|
+
slugs.push(match[1]);
|
|
2191
|
+
}
|
|
2192
|
+
return slugs;
|
|
2193
|
+
}
|
|
1953
2194
|
function parsePropertyValue(rawValue) {
|
|
1954
2195
|
const trimmed = rawValue.trim();
|
|
1955
2196
|
if (/^"(?:[^"\\]|\\.)*"$/.test(trimmed)) {
|
|
@@ -2049,6 +2290,17 @@ function mapFragmentToPrimitive(locator, source, node) {
|
|
|
2049
2290
|
};
|
|
2050
2291
|
return primitive;
|
|
2051
2292
|
}
|
|
2293
|
+
case TagRef: {
|
|
2294
|
+
const match = raw.match(/^#([a-zA-Z0-9][a-zA-Z0-9-]*)$/);
|
|
2295
|
+
if (!match) return null;
|
|
2296
|
+
const primitive = {
|
|
2297
|
+
kind: "tag_ref",
|
|
2298
|
+
raw,
|
|
2299
|
+
meta: meta2,
|
|
2300
|
+
slug: match[1]
|
|
2301
|
+
};
|
|
2302
|
+
return primitive;
|
|
2303
|
+
}
|
|
2052
2304
|
case Slash: {
|
|
2053
2305
|
const primitive = {
|
|
2054
2306
|
kind: "effort",
|
|
@@ -2289,269 +2541,743 @@ var WhiteboardScript = class {
|
|
|
2289
2541
|
}
|
|
2290
2542
|
};
|
|
2291
2543
|
|
|
2292
|
-
// src/
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2544
|
+
// src/effort-registry/types.ts
|
|
2545
|
+
var EFFORT_REGISTRY_ORIGINS = ["bundled", "user"];
|
|
2546
|
+
var CATALOG_PRECEDENCE_RANK = {
|
|
2547
|
+
core: 0,
|
|
2548
|
+
community: 1,
|
|
2549
|
+
imported: 2,
|
|
2550
|
+
user: 3
|
|
2551
|
+
};
|
|
2552
|
+
function resolveSourceTier(sourceIdOrMetaKey, explicitTier) {
|
|
2553
|
+
if (explicitTier) return explicitTier;
|
|
2554
|
+
if (!sourceIdOrMetaKey) return "core";
|
|
2555
|
+
if (sourceIdOrMetaKey === "user") return "user";
|
|
2556
|
+
const id = sourceIdOrMetaKey.startsWith("seed:") ? sourceIdOrMetaKey.slice(5) : sourceIdOrMetaKey;
|
|
2557
|
+
if (id === "core" || id === "efforts" || id === "equipment") return "core";
|
|
2558
|
+
if (id === "community" || id.startsWith("community-") || id === "common") return "community";
|
|
2559
|
+
return "imported";
|
|
2560
|
+
}
|
|
2561
|
+
function resolveSourceRank(sourceIdOrMetaKey, explicitTier) {
|
|
2562
|
+
return CATALOG_PRECEDENCE_RANK[resolveSourceTier(sourceIdOrMetaKey, explicitTier)];
|
|
2563
|
+
}
|
|
2564
|
+
function resolveEntityTier(entity) {
|
|
2565
|
+
if (entity.registrySource === "user") return "user";
|
|
2566
|
+
return resolveSourceTier(entity.sourceId);
|
|
2567
|
+
}
|
|
2568
|
+
function resolveEntityRank(entity) {
|
|
2569
|
+
return CATALOG_PRECEDENCE_RANK[resolveEntityTier(entity)];
|
|
2570
|
+
}
|
|
2571
|
+
function dedupeEntitiesByPrecedence(entities) {
|
|
2572
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
2573
|
+
for (const entity of entities) {
|
|
2574
|
+
const key = entity.slug.toLowerCase();
|
|
2575
|
+
const prior = bySlug.get(key);
|
|
2576
|
+
if (!prior || resolveEntityRank(entity) >= resolveEntityRank(prior)) {
|
|
2577
|
+
bySlug.set(key, entity);
|
|
2578
|
+
}
|
|
2307
2579
|
}
|
|
2580
|
+
return Array.from(bySlug.values());
|
|
2308
2581
|
}
|
|
2582
|
+
var INTENSITY_TIERS = ["low", "moderate", "high"];
|
|
2583
|
+
var DEFAULT_RESOLVER_OPTIONS = {
|
|
2584
|
+
defaultMet: 5,
|
|
2585
|
+
defaultThreshold: 2
|
|
2586
|
+
};
|
|
2309
2587
|
|
|
2310
|
-
// src/
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2588
|
+
// src/effort-registry/fuzzyMatch.ts
|
|
2589
|
+
function levenshteinDistance(a, b) {
|
|
2590
|
+
if (a === b) return 0;
|
|
2591
|
+
if (a.length === 0) return b.length;
|
|
2592
|
+
if (b.length === 0) return a.length;
|
|
2593
|
+
if (a.length > b.length) {
|
|
2594
|
+
const tmp = a;
|
|
2595
|
+
a = b;
|
|
2596
|
+
b = tmp;
|
|
2315
2597
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
read(inputText, sport) {
|
|
2323
|
-
return parseScript(inputText, { sport, ...this.config });
|
|
2598
|
+
const m = a.length;
|
|
2599
|
+
const n = b.length;
|
|
2600
|
+
let prev = new Uint32Array(m + 1);
|
|
2601
|
+
let curr = new Uint32Array(m + 1);
|
|
2602
|
+
for (let i = 0; i <= m; i++) {
|
|
2603
|
+
prev[i] = i;
|
|
2324
2604
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2605
|
+
for (let j = 1; j <= n; j++) {
|
|
2606
|
+
curr[0] = j;
|
|
2607
|
+
const bj = b.charCodeAt(j - 1);
|
|
2608
|
+
for (let i = 1; i <= m; i++) {
|
|
2609
|
+
const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;
|
|
2610
|
+
curr[i] = Math.min(
|
|
2611
|
+
prev[i] + 1,
|
|
2612
|
+
// deletion
|
|
2613
|
+
curr[i - 1] + 1,
|
|
2614
|
+
// insertion
|
|
2615
|
+
prev[i - 1] + cost
|
|
2616
|
+
// substitution
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
const swap = prev;
|
|
2620
|
+
prev = curr;
|
|
2621
|
+
curr = swap;
|
|
2331
2622
|
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
// src/parser/parserInstance.ts
|
|
2335
|
-
function createParser(config) {
|
|
2336
|
-
return new MdTimerRuntime(config);
|
|
2623
|
+
return prev[m];
|
|
2337
2624
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2625
|
+
function normalizeForFuzzy(input) {
|
|
2626
|
+
return input.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, " ").replace(/[-_]+/g, " ").trim().replace(/\s+/g, " ");
|
|
2627
|
+
}
|
|
2628
|
+
function isWithinThreshold(a, b, threshold) {
|
|
2629
|
+
const na = normalizeForFuzzy(a);
|
|
2630
|
+
const nb = normalizeForFuzzy(b);
|
|
2631
|
+
if (na === nb) return true;
|
|
2632
|
+
if (Math.abs(na.length - nb.length) > threshold) return false;
|
|
2633
|
+
return levenshteinDistance(na, nb) <= threshold;
|
|
2634
|
+
}
|
|
2635
|
+
function findBestFuzzyMatch(query, candidates, threshold) {
|
|
2636
|
+
const nq = normalizeForFuzzy(query);
|
|
2637
|
+
let best = null;
|
|
2638
|
+
for (const candidate of candidates) {
|
|
2639
|
+
const nc = normalizeForFuzzy(candidate);
|
|
2640
|
+
if (Math.abs(nq.length - nc.length) > threshold) continue;
|
|
2641
|
+
const distance = levenshteinDistance(nq, nc);
|
|
2642
|
+
if (distance > threshold) continue;
|
|
2643
|
+
if (!best || distance < best.distance) {
|
|
2644
|
+
best = { target: candidate, distance };
|
|
2645
|
+
if (distance === 0) break;
|
|
2646
|
+
}
|
|
2356
2647
|
}
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
if (
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2648
|
+
return best;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
// src/effort-registry/reference-resolution.ts
|
|
2652
|
+
function buildExactIndex(entities) {
|
|
2653
|
+
const deduped = dedupeEntitiesByPrecedence(entities);
|
|
2654
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2655
|
+
const add = (key, entity) => {
|
|
2656
|
+
if (!key) return;
|
|
2657
|
+
const existing = byKey.get(key);
|
|
2658
|
+
if (existing) {
|
|
2659
|
+
if (!existing.includes(entity)) existing.push(entity);
|
|
2660
|
+
} else {
|
|
2661
|
+
byKey.set(key, [entity]);
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
for (const entity of deduped) {
|
|
2665
|
+
add(entity.slug.toLowerCase(), entity);
|
|
2666
|
+
add(normalizeForFuzzy(entity.label), entity);
|
|
2667
|
+
for (const alias of entity.aliases) add(normalizeForFuzzy(alias), entity);
|
|
2369
2668
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
return (tag) => {
|
|
2385
|
-
if (tag.modified.indexOf(mod) > -1)
|
|
2386
|
-
return tag;
|
|
2387
|
-
return Modifier.get(tag.base || tag, tag.modified.concat(mod).sort((a, b) => a.id - b.id));
|
|
2388
|
-
};
|
|
2389
|
-
}
|
|
2390
|
-
};
|
|
2391
|
-
var nextModifierID = 0;
|
|
2392
|
-
var Modifier = class _Modifier {
|
|
2393
|
-
constructor(name2) {
|
|
2394
|
-
this.name = name2;
|
|
2395
|
-
this.instances = [];
|
|
2396
|
-
this.id = nextModifierID++;
|
|
2397
|
-
}
|
|
2398
|
-
static get(base, mods) {
|
|
2399
|
-
if (!mods.length)
|
|
2400
|
-
return base;
|
|
2401
|
-
let exists = mods[0].instances.find((t2) => t2.base == base && sameArray(mods, t2.modified));
|
|
2402
|
-
if (exists)
|
|
2403
|
-
return exists;
|
|
2404
|
-
let set = [], tag = new Tag(base.name, set, base, mods);
|
|
2405
|
-
for (let m of mods)
|
|
2406
|
-
m.instances.push(tag);
|
|
2407
|
-
let configs = powerSet(mods);
|
|
2408
|
-
for (let parent of base.set)
|
|
2409
|
-
if (!parent.modified.length)
|
|
2410
|
-
for (let config of configs)
|
|
2411
|
-
set.push(_Modifier.get(parent, config));
|
|
2412
|
-
return tag;
|
|
2413
|
-
}
|
|
2414
|
-
};
|
|
2415
|
-
function sameArray(a, b) {
|
|
2416
|
-
return a.length == b.length && a.every((x, i) => x == b[i]);
|
|
2417
|
-
}
|
|
2418
|
-
function powerSet(array) {
|
|
2419
|
-
let sets = [[]];
|
|
2420
|
-
for (let i = 0; i < array.length; i++) {
|
|
2421
|
-
for (let j = 0, e = sets.length; j < e; j++) {
|
|
2422
|
-
sets.push(sets[j].concat(array[i]));
|
|
2669
|
+
return {
|
|
2670
|
+
byKey,
|
|
2671
|
+
lookup(key) {
|
|
2672
|
+
const direct = byKey.get(key) ?? byKey.get(normalizeForFuzzy(key));
|
|
2673
|
+
if (!direct || direct.length === 0) return { entity: null, ambiguous: [] };
|
|
2674
|
+
if (direct.length === 1) return { entity: direct[0], ambiguous: [] };
|
|
2675
|
+
let maxRank = -1;
|
|
2676
|
+
for (const candidate of direct) {
|
|
2677
|
+
const rank = resolveEntityRank(candidate);
|
|
2678
|
+
if (rank > maxRank) maxRank = rank;
|
|
2679
|
+
}
|
|
2680
|
+
const topTier = direct.filter((candidate) => resolveEntityRank(candidate) === maxRank);
|
|
2681
|
+
if (topTier.length === 1) return { entity: topTier[0], ambiguous: [] };
|
|
2682
|
+
return { entity: null, ambiguous: topTier };
|
|
2423
2683
|
}
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
function equipmentRef(entity) {
|
|
2687
|
+
return { kind: "equipment", slug: entity.slug, label: entity.label };
|
|
2688
|
+
}
|
|
2689
|
+
function dedupeRefs(refs) {
|
|
2690
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2691
|
+
const result = [];
|
|
2692
|
+
for (const ref of refs) {
|
|
2693
|
+
const key = `${ref.kind}:${ref.slug}`;
|
|
2694
|
+
if (seen.has(key)) continue;
|
|
2695
|
+
seen.add(key);
|
|
2696
|
+
result.push(ref);
|
|
2424
2697
|
}
|
|
2425
|
-
return
|
|
2698
|
+
return result;
|
|
2426
2699
|
}
|
|
2427
|
-
function
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2700
|
+
function createReferenceIndex(catalog, materialize) {
|
|
2701
|
+
const dedupedEquipment = dedupeEntitiesByPrecedence(catalog.equipment ?? []);
|
|
2702
|
+
const efforts = buildExactIndex(catalog.efforts ?? []);
|
|
2703
|
+
const equipment = buildExactIndex(dedupedEquipment);
|
|
2704
|
+
const prefixCandidates = dedupedEquipment.flatMap((entity) => [entity.label, ...entity.aliases].filter((name2) => !!name2 && !!name2.trim()).map((name2) => ({ lower: name2.trim().toLowerCase(), entity }))).sort((a, b) => b.lower.length - a.lower.length);
|
|
2705
|
+
const materializer = materialize ?? ((effort) => ({
|
|
2706
|
+
effort,
|
|
2707
|
+
definition: effort,
|
|
2708
|
+
slug: effort.slug,
|
|
2709
|
+
label: effort.label,
|
|
2710
|
+
met: effort.baseAttributes.met,
|
|
2711
|
+
baseAttributes: effort.baseAttributes,
|
|
2712
|
+
discipline: effort.baseAttributes.discipline,
|
|
2713
|
+
disciplineFactor: effort.baseAttributes.disciplineFactor ?? 1,
|
|
2714
|
+
intensityTier: effort.baseAttributes.intensityTier,
|
|
2715
|
+
modifiers: {},
|
|
2716
|
+
registrySource: effort.registrySource,
|
|
2717
|
+
resolvedFrom: effort.registrySource === "user" ? "user" : effort.registrySource === "bundled" ? "bundled" : "default",
|
|
2718
|
+
isEstimated: false
|
|
2719
|
+
}));
|
|
2720
|
+
const resolveEffortExact = (key) => {
|
|
2721
|
+
const found = efforts.lookup(key.trim());
|
|
2722
|
+
if (found.entity) return { entity: found.entity };
|
|
2723
|
+
if (found.ambiguous.length > 0) {
|
|
2724
|
+
return {
|
|
2725
|
+
entity: null,
|
|
2726
|
+
diagnostic: {
|
|
2727
|
+
reason: "ambiguous-effort",
|
|
2728
|
+
message: `"${key}" matches multiple efforts.`,
|
|
2729
|
+
token: key,
|
|
2730
|
+
candidates: found.ambiguous.map((e) => e.slug)
|
|
2456
2731
|
}
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
return {
|
|
2735
|
+
entity: null,
|
|
2736
|
+
diagnostic: { reason: "unknown-effort", message: `Unknown effort "${key}".`, token: key }
|
|
2737
|
+
};
|
|
2738
|
+
};
|
|
2739
|
+
const resolveEquipmentValues = (values) => {
|
|
2740
|
+
const refs = [];
|
|
2741
|
+
const diagnostics = [];
|
|
2742
|
+
for (const value of values) {
|
|
2743
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
2744
|
+
diagnostics.push({
|
|
2745
|
+
reason: "invalid-explicit",
|
|
2746
|
+
message: `Equipment reference must be a slug string, got ${JSON.stringify(value) ?? "null"}.`,
|
|
2747
|
+
token: String(value)
|
|
2748
|
+
});
|
|
2749
|
+
continue;
|
|
2462
2750
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2751
|
+
const found = equipment.lookup(value.trim());
|
|
2752
|
+
if (found.entity) {
|
|
2753
|
+
refs.push(equipmentRef(found.entity));
|
|
2754
|
+
} else if (found.ambiguous.length > 0) {
|
|
2755
|
+
diagnostics.push({
|
|
2756
|
+
reason: "ambiguous-equipment",
|
|
2757
|
+
message: `"${value}" matches multiple equipment entries.`,
|
|
2758
|
+
token: value,
|
|
2759
|
+
candidates: found.ambiguous.map((e) => e.slug)
|
|
2760
|
+
});
|
|
2473
2761
|
} else {
|
|
2474
|
-
|
|
2475
|
-
|
|
2762
|
+
diagnostics.push({
|
|
2763
|
+
reason: "invalid-explicit",
|
|
2764
|
+
message: `Unknown equipment "${value}".`,
|
|
2765
|
+
token: value
|
|
2766
|
+
});
|
|
2476
2767
|
}
|
|
2477
|
-
if (cur && cur.mode == take.mode && !take.context && !cur.context)
|
|
2478
|
-
continue;
|
|
2479
|
-
let copy = new Rule(take.tags, take.mode, take.context);
|
|
2480
|
-
if (cur)
|
|
2481
|
-
cur.next = copy;
|
|
2482
|
-
else
|
|
2483
|
-
root = copy;
|
|
2484
|
-
cur = copy;
|
|
2485
|
-
}
|
|
2486
|
-
return root;
|
|
2487
|
-
}
|
|
2488
|
-
});
|
|
2489
|
-
var Rule = class {
|
|
2490
|
-
constructor(tags2, mode, context, next) {
|
|
2491
|
-
this.tags = tags2;
|
|
2492
|
-
this.mode = mode;
|
|
2493
|
-
this.context = context;
|
|
2494
|
-
this.next = next;
|
|
2495
|
-
}
|
|
2496
|
-
get opaque() {
|
|
2497
|
-
return this.mode == 0;
|
|
2498
|
-
}
|
|
2499
|
-
get inherit() {
|
|
2500
|
-
return this.mode == 1;
|
|
2501
|
-
}
|
|
2502
|
-
sort(other) {
|
|
2503
|
-
if (!other || other.depth < this.depth) {
|
|
2504
|
-
this.next = other;
|
|
2505
|
-
return this;
|
|
2506
2768
|
}
|
|
2507
|
-
|
|
2508
|
-
return other;
|
|
2509
|
-
}
|
|
2510
|
-
get depth() {
|
|
2511
|
-
return this.context ? this.context.length : 0;
|
|
2512
|
-
}
|
|
2513
|
-
};
|
|
2514
|
-
Rule.empty = new Rule([], 2, null);
|
|
2515
|
-
function tagHighlighter(tags2, options) {
|
|
2516
|
-
let map = /* @__PURE__ */ Object.create(null);
|
|
2517
|
-
for (let style of tags2) {
|
|
2518
|
-
if (!Array.isArray(style.tag))
|
|
2519
|
-
map[style.tag.id] = style.class;
|
|
2520
|
-
else
|
|
2521
|
-
for (let tag of style.tag)
|
|
2522
|
-
map[tag.id] = style.class;
|
|
2523
|
-
}
|
|
2524
|
-
let { scope, all = null } = {};
|
|
2525
|
-
return {
|
|
2526
|
-
style: (tags3) => {
|
|
2527
|
-
let cls = all;
|
|
2528
|
-
for (let tag of tags3) {
|
|
2529
|
-
for (let sub of tag.set) {
|
|
2530
|
-
let tagClass = map[sub.id];
|
|
2531
|
-
if (tagClass) {
|
|
2532
|
-
cls = cls ? cls + " " + tagClass : tagClass;
|
|
2533
|
-
break;
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
return cls;
|
|
2538
|
-
},
|
|
2539
|
-
scope
|
|
2769
|
+
return { refs: dedupeRefs(refs), diagnostics };
|
|
2540
2770
|
};
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2771
|
+
const index = {
|
|
2772
|
+
resolveEffort(slugOrAlias) {
|
|
2773
|
+
return efforts.lookup(slugOrAlias).entity;
|
|
2774
|
+
},
|
|
2775
|
+
resolveEquipment(slugOrAlias) {
|
|
2776
|
+
return equipment.lookup(slugOrAlias).entity;
|
|
2777
|
+
},
|
|
2778
|
+
materialize(effort) {
|
|
2779
|
+
return materializer(effort);
|
|
2780
|
+
},
|
|
2781
|
+
resolve(label, defaults) {
|
|
2782
|
+
const source = (label ?? "").trim();
|
|
2783
|
+
const diagnostics = [];
|
|
2784
|
+
const push = (diagnostic) => {
|
|
2785
|
+
if (diagnostic) diagnostics.push(diagnostic);
|
|
2786
|
+
};
|
|
2787
|
+
let matched = "none";
|
|
2788
|
+
let effortEntity = null;
|
|
2789
|
+
let remaining = "";
|
|
2790
|
+
let equipmentRefs = [];
|
|
2791
|
+
let equipmentAuthored = false;
|
|
2792
|
+
const fillEquipment = () => {
|
|
2793
|
+
if (equipmentRefs.length > 0 || equipmentAuthored) return;
|
|
2794
|
+
if (effortEntity === null) return;
|
|
2795
|
+
if (defaults?.equipment?.length) {
|
|
2796
|
+
const resolvedDefaults = resolveEquipmentValues(defaults.equipment);
|
|
2797
|
+
equipmentRefs = resolvedDefaults.refs;
|
|
2798
|
+
for (const diagnostic of resolvedDefaults.diagnostics) {
|
|
2799
|
+
push({ ...diagnostic, reason: "unknown-equipment" });
|
|
2800
|
+
}
|
|
2801
|
+
if (equipmentRefs.length > 0) return;
|
|
2802
|
+
}
|
|
2803
|
+
if (effortEntity.equipment?.length) {
|
|
2804
|
+
const resolvedDefEquipment = resolveEquipmentValues(effortEntity.equipment);
|
|
2805
|
+
equipmentRefs = resolvedDefEquipment.refs;
|
|
2806
|
+
for (const diagnostic of resolvedDefEquipment.diagnostics) {
|
|
2807
|
+
push({ ...diagnostic, reason: "unknown-equipment" });
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
};
|
|
2811
|
+
if (!source) {
|
|
2812
|
+
if (defaults?.effort) {
|
|
2813
|
+
const found = resolveEffortExact(defaults.effort);
|
|
2814
|
+
if (found.entity) {
|
|
2815
|
+
effortEntity = found.entity;
|
|
2816
|
+
matched = "default";
|
|
2817
|
+
} else {
|
|
2818
|
+
push({
|
|
2819
|
+
reason: found.diagnostic?.reason === "ambiguous-effort" ? "ambiguous-effort" : "unknown-effort",
|
|
2820
|
+
message: `Default effort "${defaults.effort}" is not in the catalog.`,
|
|
2821
|
+
token: defaults.effort,
|
|
2822
|
+
candidates: found.diagnostic?.candidates
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
fillEquipment();
|
|
2827
|
+
return {
|
|
2828
|
+
matched,
|
|
2829
|
+
label: source,
|
|
2830
|
+
effort: effortEntity ? materializer(effortEntity) : null,
|
|
2831
|
+
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
2832
|
+
equipment: equipmentRefs,
|
|
2833
|
+
remaining: "",
|
|
2834
|
+
diagnostics
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
if (source.startsWith("{")) {
|
|
2838
|
+
let parsed;
|
|
2839
|
+
try {
|
|
2840
|
+
parsed = JSON.parse(source);
|
|
2841
|
+
} catch {
|
|
2842
|
+
return {
|
|
2843
|
+
matched: "none",
|
|
2844
|
+
label: source,
|
|
2845
|
+
effort: null,
|
|
2846
|
+
effortRef: null,
|
|
2847
|
+
equipment: [],
|
|
2848
|
+
remaining: "",
|
|
2849
|
+
diagnostics: [{
|
|
2850
|
+
reason: "malformed-json",
|
|
2851
|
+
message: "Reference object is not valid JSON.",
|
|
2852
|
+
token: source
|
|
2853
|
+
}]
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && ("effort" in parsed || "equipment" in parsed)) {
|
|
2857
|
+
matched = "explicit";
|
|
2858
|
+
const obj = parsed;
|
|
2859
|
+
if (obj.effort !== void 0) {
|
|
2860
|
+
if (typeof obj.effort === "string" && obj.effort.trim()) {
|
|
2861
|
+
const found = resolveEffortExact(obj.effort);
|
|
2862
|
+
effortEntity = found.entity;
|
|
2863
|
+
if (found.diagnostic) {
|
|
2864
|
+
push(found.diagnostic.reason === "ambiguous-effort" ? found.diagnostic : { ...found.diagnostic, reason: "invalid-explicit", message: `Explicit effort "${obj.effort}" is not in the catalog.` });
|
|
2865
|
+
}
|
|
2866
|
+
} else {
|
|
2867
|
+
push({
|
|
2868
|
+
reason: "invalid-explicit",
|
|
2869
|
+
message: 'Explicit "effort" must be a slug string.',
|
|
2870
|
+
token: String(obj.effort)
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
if (obj.equipment !== void 0) {
|
|
2875
|
+
equipmentAuthored = true;
|
|
2876
|
+
const values = Array.isArray(obj.equipment) ? obj.equipment : [obj.equipment];
|
|
2877
|
+
const resolvedEquipment = resolveEquipmentValues(values);
|
|
2878
|
+
equipmentRefs = resolvedEquipment.refs;
|
|
2879
|
+
diagnostics.push(...resolvedEquipment.diagnostics);
|
|
2880
|
+
if (effortEntity?.equipment?.length) {
|
|
2881
|
+
const definitionSlugs = new Set(effortEntity.equipment);
|
|
2882
|
+
const authoredSlugs = equipmentRefs.map((ref) => ref.slug);
|
|
2883
|
+
if (authoredSlugs.length > 0 && authoredSlugs.some((slug) => !definitionSlugs.has(slug))) {
|
|
2884
|
+
push({
|
|
2885
|
+
reason: "conflict",
|
|
2886
|
+
message: `Explicit equipment [${authoredSlugs.join(", ")}] contradicts effort "${effortEntity.slug}" definition equipment [${effortEntity.equipment.join(", ")}].`,
|
|
2887
|
+
token: source
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
fillEquipment();
|
|
2893
|
+
return {
|
|
2894
|
+
matched,
|
|
2895
|
+
label: source,
|
|
2896
|
+
effort: effortEntity ? materializer(effortEntity) : null,
|
|
2897
|
+
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
2898
|
+
equipment: equipmentRefs,
|
|
2899
|
+
remaining: "",
|
|
2900
|
+
diagnostics
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
const lowerSource = source.toLowerCase();
|
|
2905
|
+
for (const candidate of prefixCandidates) {
|
|
2906
|
+
if (!lowerSource.startsWith(candidate.lower)) continue;
|
|
2907
|
+
const after = source.slice(candidate.lower.length);
|
|
2908
|
+
if (after && !/^\s/.test(after)) continue;
|
|
2909
|
+
const candidateRemaining = after.trim();
|
|
2910
|
+
if (!candidateRemaining) continue;
|
|
2911
|
+
const equipmentFound = equipment.lookup(candidate.lower);
|
|
2912
|
+
if (equipmentFound.ambiguous.length > 0) {
|
|
2913
|
+
push({
|
|
2914
|
+
reason: "ambiguous-equipment",
|
|
2915
|
+
message: `"${candidate.lower}" matches multiple equipment entries.`,
|
|
2916
|
+
token: source,
|
|
2917
|
+
candidates: equipmentFound.ambiguous.map((e) => e.slug)
|
|
2918
|
+
});
|
|
2919
|
+
return {
|
|
2920
|
+
matched: "none",
|
|
2921
|
+
label: source,
|
|
2922
|
+
effort: null,
|
|
2923
|
+
effortRef: null,
|
|
2924
|
+
equipment: [],
|
|
2925
|
+
remaining: "",
|
|
2926
|
+
diagnostics
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
if (!equipmentFound.entity) continue;
|
|
2930
|
+
const effortFound = resolveEffortExact(candidateRemaining);
|
|
2931
|
+
if (effortFound.entity) {
|
|
2932
|
+
matched = "split";
|
|
2933
|
+
effortEntity = effortFound.entity;
|
|
2934
|
+
equipmentRefs = [equipmentRef(equipmentFound.entity)];
|
|
2935
|
+
equipmentAuthored = true;
|
|
2936
|
+
remaining = candidateRemaining;
|
|
2937
|
+
break;
|
|
2938
|
+
}
|
|
2939
|
+
if (effortFound.diagnostic?.reason === "ambiguous-effort") {
|
|
2940
|
+
push(effortFound.diagnostic);
|
|
2941
|
+
return {
|
|
2942
|
+
matched: "none",
|
|
2943
|
+
label: source,
|
|
2944
|
+
effort: null,
|
|
2945
|
+
effortRef: null,
|
|
2946
|
+
equipment: [],
|
|
2947
|
+
remaining: "",
|
|
2948
|
+
diagnostics
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
if (matched === "none") {
|
|
2953
|
+
const found = resolveEffortExact(source);
|
|
2954
|
+
if (found.entity) {
|
|
2955
|
+
effortEntity = found.entity;
|
|
2956
|
+
matched = "whole";
|
|
2957
|
+
} else if (found.diagnostic?.reason === "ambiguous-effort") {
|
|
2958
|
+
push(found.diagnostic);
|
|
2959
|
+
return {
|
|
2960
|
+
matched: "none",
|
|
2961
|
+
label: source,
|
|
2962
|
+
effort: null,
|
|
2963
|
+
effortRef: null,
|
|
2964
|
+
equipment: [],
|
|
2965
|
+
remaining: "",
|
|
2966
|
+
diagnostics
|
|
2967
|
+
};
|
|
2968
|
+
} else if (found.diagnostic) {
|
|
2969
|
+
push(found.diagnostic);
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
fillEquipment();
|
|
2973
|
+
return {
|
|
2974
|
+
matched,
|
|
2975
|
+
label: source,
|
|
2976
|
+
effort: effortEntity ? materializer(effortEntity) : null,
|
|
2977
|
+
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
2978
|
+
equipment: equipmentRefs,
|
|
2979
|
+
remaining,
|
|
2980
|
+
diagnostics
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
};
|
|
2984
|
+
return index;
|
|
2985
|
+
}
|
|
2986
|
+
function extractStatementRefs(statements) {
|
|
2987
|
+
const efforts = [];
|
|
2988
|
+
const equipment = [];
|
|
2989
|
+
const seenEffort = /* @__PURE__ */ new Set();
|
|
2990
|
+
const seenEquipment = /* @__PURE__ */ new Set();
|
|
2991
|
+
const pushUnique = (list, seen, value) => {
|
|
2992
|
+
if (!value) return;
|
|
2993
|
+
if (seen.has(value)) return;
|
|
2994
|
+
seen.add(value);
|
|
2995
|
+
list.push(value);
|
|
2996
|
+
};
|
|
2997
|
+
for (const statement of statements ?? []) {
|
|
2998
|
+
const metrics = Array.isArray(statement.metrics) ? statement.metrics : statement.metrics?.toArray?.() ?? [];
|
|
2999
|
+
for (const metric of metrics) {
|
|
3000
|
+
const refs = wodWikiCore.getReferenceMetadata(metric.metadata)?.refs;
|
|
3001
|
+
if (!refs) continue;
|
|
3002
|
+
for (const ref of refs) {
|
|
3003
|
+
if (ref.kind === "effort") pushUnique(efforts, seenEffort, ref.slug);
|
|
3004
|
+
if (ref.kind === "equipment") pushUnique(equipment, seenEquipment, ref.slug);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
return { efforts, equipment };
|
|
3009
|
+
}
|
|
3010
|
+
function slugifyRefLabel(label) {
|
|
3011
|
+
return normalizeForFuzzy(label).replace(/\s+/g, "-");
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
// src/parser/parseScript.ts
|
|
3015
|
+
function parseScript(inputText, options = {}) {
|
|
3016
|
+
if (!inputText || !inputText.trim()) {
|
|
3017
|
+
return new WhiteboardScript(inputText, [], []);
|
|
3018
|
+
}
|
|
3019
|
+
try {
|
|
3020
|
+
const doc = inputText.endsWith("\n") ? inputText : inputText + "\n";
|
|
3021
|
+
const tree = parser.parse(doc);
|
|
3022
|
+
const sport = options.sport ?? options.dialect;
|
|
3023
|
+
const references = options.references || options.tags ? {
|
|
3024
|
+
index: options.references ?? createReferenceIndex({ efforts: [], equipment: [] }),
|
|
3025
|
+
defaults: options.defaults,
|
|
3026
|
+
tags: options.tags
|
|
3027
|
+
} : void 0;
|
|
3028
|
+
const statements = options.withoutDialects ? extractStatementsRaw(inputText, tree, references) : extractStatements(inputText, tree, sport, references);
|
|
3029
|
+
return new WhiteboardScript(inputText, statements, []);
|
|
3030
|
+
} catch (error) {
|
|
3031
|
+
const msg = error instanceof Error ? error.message : "Unknown parse error";
|
|
3032
|
+
return new WhiteboardScript(inputText, [], [{ message: msg }]);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
// src/parser/md-timer.ts
|
|
3037
|
+
var MdTimerRuntime = class {
|
|
3038
|
+
config;
|
|
3039
|
+
constructor(config) {
|
|
3040
|
+
this.config = config;
|
|
3041
|
+
}
|
|
3042
|
+
/**
|
|
3043
|
+
* Parse WhiteboardScript source into a {@link WhiteboardScript}, running the
|
|
3044
|
+
* Dialect Stack on every statement.
|
|
3045
|
+
*
|
|
3046
|
+
* @param sport - The block's `:sport` fence suffix (` ```log:climbing `).
|
|
3047
|
+
*/
|
|
3048
|
+
read(inputText, sport) {
|
|
3049
|
+
return parseScript(inputText, { sport, ...this.config });
|
|
3050
|
+
}
|
|
3051
|
+
/**
|
|
3052
|
+
* Parse without running the Dialect Stack. Used by the parser test harness
|
|
3053
|
+
* which applies its own Dialect set. Production consumers should use {@link read}.
|
|
3054
|
+
*/
|
|
3055
|
+
readWithoutDialects(inputText) {
|
|
3056
|
+
return parseScript(inputText, { withoutDialects: true, ...this.config });
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3059
|
+
|
|
3060
|
+
// src/parser/parserInstance.ts
|
|
3061
|
+
function createParser(config) {
|
|
3062
|
+
return new MdTimerRuntime(config);
|
|
3063
|
+
}
|
|
3064
|
+
var nextTagID = 0;
|
|
3065
|
+
var Tag = class _Tag {
|
|
3066
|
+
/**
|
|
3067
|
+
@internal
|
|
3068
|
+
*/
|
|
3069
|
+
constructor(name2, set, base, modified) {
|
|
3070
|
+
this.name = name2;
|
|
3071
|
+
this.set = set;
|
|
3072
|
+
this.base = base;
|
|
3073
|
+
this.modified = modified;
|
|
3074
|
+
this.id = nextTagID++;
|
|
3075
|
+
}
|
|
3076
|
+
toString() {
|
|
3077
|
+
let { name: name2 } = this;
|
|
3078
|
+
for (let mod of this.modified)
|
|
3079
|
+
if (mod.name)
|
|
3080
|
+
name2 = `${mod.name}(${name2})`;
|
|
3081
|
+
return name2;
|
|
3082
|
+
}
|
|
3083
|
+
static define(nameOrParent, parent) {
|
|
3084
|
+
let name2 = typeof nameOrParent == "string" ? nameOrParent : "?";
|
|
3085
|
+
if (nameOrParent instanceof _Tag)
|
|
3086
|
+
parent = nameOrParent;
|
|
3087
|
+
if (parent === null || parent === void 0 ? void 0 : parent.base)
|
|
3088
|
+
throw new Error("Can not derive from a modified tag");
|
|
3089
|
+
let tag = new _Tag(name2, [], null, []);
|
|
3090
|
+
tag.set.push(tag);
|
|
3091
|
+
if (parent)
|
|
3092
|
+
for (let t2 of parent.set)
|
|
3093
|
+
tag.set.push(t2);
|
|
3094
|
+
return tag;
|
|
3095
|
+
}
|
|
3096
|
+
/**
|
|
3097
|
+
Define a tag _modifier_, which is a function that, given a tag,
|
|
3098
|
+
will return a tag that is a subtag of the original. Applying the
|
|
3099
|
+
same modifier to a twice tag will return the same value (`m1(t1)
|
|
3100
|
+
== m1(t1)`) and applying multiple modifiers will, regardless or
|
|
3101
|
+
order, produce the same tag (`m1(m2(t1)) == m2(m1(t1))`).
|
|
3102
|
+
|
|
3103
|
+
When multiple modifiers are applied to a given base tag, each
|
|
3104
|
+
smaller set of modifiers is registered as a parent, so that for
|
|
3105
|
+
example `m1(m2(m3(t1)))` is a subtype of `m1(m2(t1))`,
|
|
3106
|
+
`m1(m3(t1)`, and so on.
|
|
3107
|
+
*/
|
|
3108
|
+
static defineModifier(name2) {
|
|
3109
|
+
let mod = new Modifier(name2);
|
|
3110
|
+
return (tag) => {
|
|
3111
|
+
if (tag.modified.indexOf(mod) > -1)
|
|
3112
|
+
return tag;
|
|
3113
|
+
return Modifier.get(tag.base || tag, tag.modified.concat(mod).sort((a, b) => a.id - b.id));
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
var nextModifierID = 0;
|
|
3118
|
+
var Modifier = class _Modifier {
|
|
3119
|
+
constructor(name2) {
|
|
3120
|
+
this.name = name2;
|
|
3121
|
+
this.instances = [];
|
|
3122
|
+
this.id = nextModifierID++;
|
|
3123
|
+
}
|
|
3124
|
+
static get(base, mods) {
|
|
3125
|
+
if (!mods.length)
|
|
3126
|
+
return base;
|
|
3127
|
+
let exists = mods[0].instances.find((t2) => t2.base == base && sameArray(mods, t2.modified));
|
|
3128
|
+
if (exists)
|
|
3129
|
+
return exists;
|
|
3130
|
+
let set = [], tag = new Tag(base.name, set, base, mods);
|
|
3131
|
+
for (let m of mods)
|
|
3132
|
+
m.instances.push(tag);
|
|
3133
|
+
let configs = powerSet(mods);
|
|
3134
|
+
for (let parent of base.set)
|
|
3135
|
+
if (!parent.modified.length)
|
|
3136
|
+
for (let config of configs)
|
|
3137
|
+
set.push(_Modifier.get(parent, config));
|
|
3138
|
+
return tag;
|
|
3139
|
+
}
|
|
3140
|
+
};
|
|
3141
|
+
function sameArray(a, b) {
|
|
3142
|
+
return a.length == b.length && a.every((x, i) => x == b[i]);
|
|
3143
|
+
}
|
|
3144
|
+
function powerSet(array) {
|
|
3145
|
+
let sets = [[]];
|
|
3146
|
+
for (let i = 0; i < array.length; i++) {
|
|
3147
|
+
for (let j = 0, e = sets.length; j < e; j++) {
|
|
3148
|
+
sets.push(sets[j].concat(array[i]));
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
return sets.sort((a, b) => b.length - a.length);
|
|
3152
|
+
}
|
|
3153
|
+
function styleTags(spec) {
|
|
3154
|
+
let byName = /* @__PURE__ */ Object.create(null);
|
|
3155
|
+
for (let prop in spec) {
|
|
3156
|
+
let tags2 = spec[prop];
|
|
3157
|
+
if (!Array.isArray(tags2))
|
|
3158
|
+
tags2 = [tags2];
|
|
3159
|
+
for (let part of prop.split(" "))
|
|
3160
|
+
if (part) {
|
|
3161
|
+
let pieces = [], mode = 2, rest = part;
|
|
3162
|
+
for (let pos = 0; ; ) {
|
|
3163
|
+
if (rest == "..." && pos > 0 && pos + 3 == part.length) {
|
|
3164
|
+
mode = 1;
|
|
3165
|
+
break;
|
|
3166
|
+
}
|
|
3167
|
+
let m = /^"(?:[^"\\]|\\.)*?"|[^\/!]+/.exec(rest);
|
|
3168
|
+
if (!m)
|
|
3169
|
+
throw new RangeError("Invalid path: " + part);
|
|
3170
|
+
pieces.push(m[0] == "*" ? "" : m[0][0] == '"' ? JSON.parse(m[0]) : m[0]);
|
|
3171
|
+
pos += m[0].length;
|
|
3172
|
+
if (pos == part.length)
|
|
3173
|
+
break;
|
|
3174
|
+
let next = part[pos++];
|
|
3175
|
+
if (pos == part.length && next == "!") {
|
|
3176
|
+
mode = 0;
|
|
3177
|
+
break;
|
|
3178
|
+
}
|
|
3179
|
+
if (next != "/")
|
|
3180
|
+
throw new RangeError("Invalid path: " + part);
|
|
3181
|
+
rest = part.slice(pos);
|
|
3182
|
+
}
|
|
3183
|
+
let last = pieces.length - 1, inner = pieces[last];
|
|
3184
|
+
if (!inner)
|
|
3185
|
+
throw new RangeError("Invalid path: " + part);
|
|
3186
|
+
let rule = new Rule(tags2, mode, last > 0 ? pieces.slice(0, last) : null);
|
|
3187
|
+
byName[inner] = rule.sort(byName[inner]);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
return ruleNodeProp.add(byName);
|
|
3191
|
+
}
|
|
3192
|
+
var ruleNodeProp = new common.NodeProp({
|
|
3193
|
+
combine(a, b) {
|
|
3194
|
+
let cur, root, take;
|
|
3195
|
+
while (a || b) {
|
|
3196
|
+
if (!a || b && a.depth >= b.depth) {
|
|
3197
|
+
take = b;
|
|
3198
|
+
b = b.next;
|
|
3199
|
+
} else {
|
|
3200
|
+
take = a;
|
|
3201
|
+
a = a.next;
|
|
3202
|
+
}
|
|
3203
|
+
if (cur && cur.mode == take.mode && !take.context && !cur.context)
|
|
3204
|
+
continue;
|
|
3205
|
+
let copy = new Rule(take.tags, take.mode, take.context);
|
|
3206
|
+
if (cur)
|
|
3207
|
+
cur.next = copy;
|
|
3208
|
+
else
|
|
3209
|
+
root = copy;
|
|
3210
|
+
cur = copy;
|
|
3211
|
+
}
|
|
3212
|
+
return root;
|
|
3213
|
+
}
|
|
3214
|
+
});
|
|
3215
|
+
var Rule = class {
|
|
3216
|
+
constructor(tags2, mode, context, next) {
|
|
3217
|
+
this.tags = tags2;
|
|
3218
|
+
this.mode = mode;
|
|
3219
|
+
this.context = context;
|
|
3220
|
+
this.next = next;
|
|
3221
|
+
}
|
|
3222
|
+
get opaque() {
|
|
3223
|
+
return this.mode == 0;
|
|
3224
|
+
}
|
|
3225
|
+
get inherit() {
|
|
3226
|
+
return this.mode == 1;
|
|
3227
|
+
}
|
|
3228
|
+
sort(other) {
|
|
3229
|
+
if (!other || other.depth < this.depth) {
|
|
3230
|
+
this.next = other;
|
|
3231
|
+
return this;
|
|
3232
|
+
}
|
|
3233
|
+
other.next = this.sort(other.next);
|
|
3234
|
+
return other;
|
|
3235
|
+
}
|
|
3236
|
+
get depth() {
|
|
3237
|
+
return this.context ? this.context.length : 0;
|
|
3238
|
+
}
|
|
3239
|
+
};
|
|
3240
|
+
Rule.empty = new Rule([], 2, null);
|
|
3241
|
+
function tagHighlighter(tags2, options) {
|
|
3242
|
+
let map = /* @__PURE__ */ Object.create(null);
|
|
3243
|
+
for (let style of tags2) {
|
|
3244
|
+
if (!Array.isArray(style.tag))
|
|
3245
|
+
map[style.tag.id] = style.class;
|
|
3246
|
+
else
|
|
3247
|
+
for (let tag of style.tag)
|
|
3248
|
+
map[tag.id] = style.class;
|
|
3249
|
+
}
|
|
3250
|
+
let { scope, all = null } = {};
|
|
3251
|
+
return {
|
|
3252
|
+
style: (tags3) => {
|
|
3253
|
+
let cls = all;
|
|
3254
|
+
for (let tag of tags3) {
|
|
3255
|
+
for (let sub of tag.set) {
|
|
3256
|
+
let tagClass = map[sub.id];
|
|
3257
|
+
if (tagClass) {
|
|
3258
|
+
cls = cls ? cls + " " + tagClass : tagClass;
|
|
3259
|
+
break;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
return cls;
|
|
3264
|
+
},
|
|
3265
|
+
scope
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
var t = Tag.define;
|
|
3269
|
+
var comment = t();
|
|
3270
|
+
var name = t();
|
|
3271
|
+
var typeName = t(name);
|
|
3272
|
+
var propertyName = t(name);
|
|
3273
|
+
var literal = t();
|
|
3274
|
+
var string = t(literal);
|
|
3275
|
+
var number = t(literal);
|
|
3276
|
+
var content = t();
|
|
3277
|
+
var heading = t(content);
|
|
3278
|
+
var keyword = t();
|
|
3279
|
+
var operator = t();
|
|
3280
|
+
var punctuation = t();
|
|
2555
3281
|
var bracket = t(punctuation);
|
|
2556
3282
|
var meta = t();
|
|
2557
3283
|
var tags = {
|
|
@@ -2996,50 +3722,6 @@ function whiteboardScript() {
|
|
|
2996
3722
|
return new language.LanguageSupport(whiteboardScriptLanguage);
|
|
2997
3723
|
}
|
|
2998
3724
|
|
|
2999
|
-
// src/effort-registry/types.ts
|
|
3000
|
-
var EFFORT_REGISTRY_ORIGINS = ["bundled", "user"];
|
|
3001
|
-
var CATALOG_PRECEDENCE_RANK = {
|
|
3002
|
-
core: 0,
|
|
3003
|
-
community: 1,
|
|
3004
|
-
imported: 2,
|
|
3005
|
-
user: 3
|
|
3006
|
-
};
|
|
3007
|
-
function resolveSourceTier(sourceIdOrMetaKey, explicitTier) {
|
|
3008
|
-
if (explicitTier) return explicitTier;
|
|
3009
|
-
if (!sourceIdOrMetaKey) return "core";
|
|
3010
|
-
if (sourceIdOrMetaKey === "user") return "user";
|
|
3011
|
-
const id = sourceIdOrMetaKey.startsWith("seed:") ? sourceIdOrMetaKey.slice(5) : sourceIdOrMetaKey;
|
|
3012
|
-
if (id === "core" || id === "efforts" || id === "equipment") return "core";
|
|
3013
|
-
if (id === "community" || id.startsWith("community-") || id === "common") return "community";
|
|
3014
|
-
return "imported";
|
|
3015
|
-
}
|
|
3016
|
-
function resolveSourceRank(sourceIdOrMetaKey, explicitTier) {
|
|
3017
|
-
return CATALOG_PRECEDENCE_RANK[resolveSourceTier(sourceIdOrMetaKey, explicitTier)];
|
|
3018
|
-
}
|
|
3019
|
-
function resolveEntityTier(entity) {
|
|
3020
|
-
if (entity.registrySource === "user") return "user";
|
|
3021
|
-
return resolveSourceTier(entity.sourceId);
|
|
3022
|
-
}
|
|
3023
|
-
function resolveEntityRank(entity) {
|
|
3024
|
-
return CATALOG_PRECEDENCE_RANK[resolveEntityTier(entity)];
|
|
3025
|
-
}
|
|
3026
|
-
function dedupeEntitiesByPrecedence(entities) {
|
|
3027
|
-
const bySlug = /* @__PURE__ */ new Map();
|
|
3028
|
-
for (const entity of entities) {
|
|
3029
|
-
const key = entity.slug.toLowerCase();
|
|
3030
|
-
const prior = bySlug.get(key);
|
|
3031
|
-
if (!prior || resolveEntityRank(entity) >= resolveEntityRank(prior)) {
|
|
3032
|
-
bySlug.set(key, entity);
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
return Array.from(bySlug.values());
|
|
3036
|
-
}
|
|
3037
|
-
var INTENSITY_TIERS = ["low", "moderate", "high"];
|
|
3038
|
-
var DEFAULT_RESOLVER_OPTIONS = {
|
|
3039
|
-
defaultMet: 5,
|
|
3040
|
-
defaultThreshold: 2
|
|
3041
|
-
};
|
|
3042
|
-
|
|
3043
3725
|
// src/effort-registry/disciplines.ts
|
|
3044
3726
|
var EFFORT_DISCIPLINES = [
|
|
3045
3727
|
"bodyweight",
|
|
@@ -3493,71 +4175,6 @@ function equipmentToDocument(equipment) {
|
|
|
3493
4175
|
}
|
|
3494
4176
|
return lines.join("\n");
|
|
3495
4177
|
}
|
|
3496
|
-
|
|
3497
|
-
// src/effort-registry/fuzzyMatch.ts
|
|
3498
|
-
function levenshteinDistance(a, b) {
|
|
3499
|
-
if (a === b) return 0;
|
|
3500
|
-
if (a.length === 0) return b.length;
|
|
3501
|
-
if (b.length === 0) return a.length;
|
|
3502
|
-
if (a.length > b.length) {
|
|
3503
|
-
const tmp = a;
|
|
3504
|
-
a = b;
|
|
3505
|
-
b = tmp;
|
|
3506
|
-
}
|
|
3507
|
-
const m = a.length;
|
|
3508
|
-
const n = b.length;
|
|
3509
|
-
let prev = new Uint32Array(m + 1);
|
|
3510
|
-
let curr = new Uint32Array(m + 1);
|
|
3511
|
-
for (let i = 0; i <= m; i++) {
|
|
3512
|
-
prev[i] = i;
|
|
3513
|
-
}
|
|
3514
|
-
for (let j = 1; j <= n; j++) {
|
|
3515
|
-
curr[0] = j;
|
|
3516
|
-
const bj = b.charCodeAt(j - 1);
|
|
3517
|
-
for (let i = 1; i <= m; i++) {
|
|
3518
|
-
const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;
|
|
3519
|
-
curr[i] = Math.min(
|
|
3520
|
-
prev[i] + 1,
|
|
3521
|
-
// deletion
|
|
3522
|
-
curr[i - 1] + 1,
|
|
3523
|
-
// insertion
|
|
3524
|
-
prev[i - 1] + cost
|
|
3525
|
-
// substitution
|
|
3526
|
-
);
|
|
3527
|
-
}
|
|
3528
|
-
const swap = prev;
|
|
3529
|
-
prev = curr;
|
|
3530
|
-
curr = swap;
|
|
3531
|
-
}
|
|
3532
|
-
return prev[m];
|
|
3533
|
-
}
|
|
3534
|
-
function normalizeForFuzzy(input) {
|
|
3535
|
-
return input.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, " ").replace(/[-_]+/g, " ").trim().replace(/\s+/g, " ");
|
|
3536
|
-
}
|
|
3537
|
-
function isWithinThreshold(a, b, threshold) {
|
|
3538
|
-
const na = normalizeForFuzzy(a);
|
|
3539
|
-
const nb = normalizeForFuzzy(b);
|
|
3540
|
-
if (na === nb) return true;
|
|
3541
|
-
if (Math.abs(na.length - nb.length) > threshold) return false;
|
|
3542
|
-
return levenshteinDistance(na, nb) <= threshold;
|
|
3543
|
-
}
|
|
3544
|
-
function findBestFuzzyMatch(query, candidates, threshold) {
|
|
3545
|
-
const nq = normalizeForFuzzy(query);
|
|
3546
|
-
let best = null;
|
|
3547
|
-
for (const candidate of candidates) {
|
|
3548
|
-
const nc = normalizeForFuzzy(candidate);
|
|
3549
|
-
if (Math.abs(nq.length - nc.length) > threshold) continue;
|
|
3550
|
-
const distance = levenshteinDistance(nq, nc);
|
|
3551
|
-
if (distance > threshold) continue;
|
|
3552
|
-
if (!best || distance < best.distance) {
|
|
3553
|
-
best = { target: candidate, distance };
|
|
3554
|
-
if (distance === 0) break;
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
return best;
|
|
3558
|
-
}
|
|
3559
|
-
|
|
3560
|
-
// src/effort-registry/EffortResolver.ts
|
|
3561
4178
|
function normalizeSlug(label) {
|
|
3562
4179
|
return normalizeForFuzzy(label).replace(/\s+/g, "-");
|
|
3563
4180
|
}
|
|
@@ -3770,162 +4387,64 @@ var fixtureUserOverride = {
|
|
|
3770
4387
|
label: "Rowing (Calibrated)",
|
|
3771
4388
|
aliases: ["row", "rower", "erg"],
|
|
3772
4389
|
baseAttributes: { met: 8.5, discipline: "rowing", intensityTier: "high" },
|
|
3773
|
-
registrySource: "user",
|
|
3774
|
-
createdAt: "2024-02-01T08:00:00Z",
|
|
3775
|
-
updatedAt: "2024-02-01T08:00:00Z"
|
|
3776
|
-
};
|
|
3777
|
-
var commonFixtureSet = [
|
|
3778
|
-
fixtureRunning,
|
|
3779
|
-
fixtureRowing,
|
|
3780
|
-
fixtureBackSquat,
|
|
3781
|
-
fixtureBurpee,
|
|
3782
|
-
fixturePlank
|
|
3783
|
-
];
|
|
3784
|
-
var fullFixtureSet = [
|
|
3785
|
-
...commonFixtureSet,
|
|
3786
|
-
fixtureUserCustom,
|
|
3787
|
-
fixtureUserOverride
|
|
3788
|
-
];
|
|
3789
|
-
|
|
3790
|
-
// src/effort-registry/data/bundled-efforts.ts
|
|
3791
|
-
var bundledEfforts = commonFixtureSet;
|
|
3792
|
-
var BUNDLED_EFFORT_COUNT = bundledEfforts.length;
|
|
3793
|
-
|
|
3794
|
-
// src/effort-registry/InMemoryEffortRegistry.ts
|
|
3795
|
-
var InMemoryEffortRegistry = class {
|
|
3796
|
-
efforts = /* @__PURE__ */ new Map();
|
|
3797
|
-
sourceFilter = null;
|
|
3798
|
-
initialized = false;
|
|
3799
|
-
constructor(sourceFilter) {
|
|
3800
|
-
this.sourceFilter = sourceFilter ?? null;
|
|
3801
|
-
}
|
|
3802
|
-
/** Seed with bundled efforts */
|
|
3803
|
-
async loadBundled() {
|
|
3804
|
-
for (const effort of bundledEfforts) {
|
|
3805
|
-
if (!this.sourceFilter || effort.registrySource === this.sourceFilter) {
|
|
3806
|
-
this.efforts.set(effort.slug, effort);
|
|
3807
|
-
}
|
|
3808
|
-
}
|
|
3809
|
-
this.initialized = true;
|
|
3810
|
-
}
|
|
3811
|
-
isInitialized() {
|
|
3812
|
-
return this.initialized;
|
|
3813
|
-
}
|
|
3814
|
-
/** Seed with arbitrary efforts (useful for tests) */
|
|
3815
|
-
seed(efforts) {
|
|
3816
|
-
for (const effort of efforts) {
|
|
3817
|
-
this.efforts.set(effort.slug, effort);
|
|
3818
|
-
}
|
|
3819
|
-
}
|
|
3820
|
-
/** Clear all efforts */
|
|
3821
|
-
clear() {
|
|
3822
|
-
this.efforts.clear();
|
|
3823
|
-
this.initialized = false;
|
|
3824
|
-
}
|
|
3825
|
-
resolve(slug) {
|
|
3826
|
-
return this.efforts.get(slug) ?? null;
|
|
3827
|
-
}
|
|
3828
|
-
resolveByAlias(label) {
|
|
3829
|
-
const normalized = normalizeForFuzzy(label);
|
|
3830
|
-
for (const effort of this.efforts.values()) {
|
|
3831
|
-
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
3832
|
-
for (const alias of effort.aliases) {
|
|
3833
|
-
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
return null;
|
|
3837
|
-
}
|
|
3838
|
-
list() {
|
|
3839
|
-
return Array.from(this.efforts.values());
|
|
3840
|
-
}
|
|
3841
|
-
listByOrigin(origin) {
|
|
3842
|
-
return this.list().filter((e) => e.registrySource === origin);
|
|
3843
|
-
}
|
|
3844
|
-
async upsert(effort) {
|
|
3845
|
-
if (effort.registrySource !== "user") {
|
|
3846
|
-
throw new Error(
|
|
3847
|
-
`InMemoryEffortRegistry.upsert: only user efforts can be written. Received registrySource="${effort.registrySource}"`
|
|
3848
|
-
);
|
|
3849
|
-
}
|
|
3850
|
-
this.efforts.set(effort.slug, effort);
|
|
3851
|
-
}
|
|
3852
|
-
async delete(slug) {
|
|
3853
|
-
const existing = this.efforts.get(slug);
|
|
3854
|
-
if (existing && existing.registrySource !== "user") {
|
|
3855
|
-
throw new Error(
|
|
3856
|
-
`InMemoryEffortRegistry.delete: cannot delete non-user effort "${slug}"`
|
|
3857
|
-
);
|
|
3858
|
-
}
|
|
3859
|
-
this.efforts.delete(slug);
|
|
3860
|
-
}
|
|
3861
|
-
};
|
|
3862
|
-
|
|
3863
|
-
// src/effort-registry/CompositeEffortRegistry.ts
|
|
3864
|
-
var CompositeEffortRegistry = class {
|
|
3865
|
-
userEfforts = /* @__PURE__ */ new Map();
|
|
3866
|
-
bundledEfforts = /* @__PURE__ */ new Map();
|
|
3867
|
-
initialized = false;
|
|
3868
|
-
initialBundled;
|
|
3869
|
-
storage;
|
|
3870
|
-
constructor(options) {
|
|
3871
|
-
if (options) {
|
|
3872
|
-
this.initialBundled = options.bundled;
|
|
3873
|
-
this.storage = options.storage;
|
|
3874
|
-
}
|
|
3875
|
-
}
|
|
3876
|
-
/**
|
|
3877
|
-
* Initialize the registry:
|
|
3878
|
-
* 1. Load bundled efforts into memory
|
|
3879
|
-
* 2. Load user efforts from storage adapter into memory
|
|
3880
|
-
*
|
|
3881
|
-
* An explicit `customBundled` REPLACES the bundled tier — passing an empty
|
|
3882
|
-
* set clears it — so a seed source removal never leaves stale bundled
|
|
3883
|
-
* efforts cached. The no-argument call keeps the constructor/default set.
|
|
3884
|
-
* Both tiers rebuild only AFTER a successful adapter load: a failed load
|
|
3885
|
-
* propagates (readiness must never report over stale data), and deleted
|
|
3886
|
-
* custom rows disappear because the user map is cleared too.
|
|
3887
|
-
*/
|
|
3888
|
-
async loadBundled(customBundled) {
|
|
3889
|
-
this.bundledEfforts.clear();
|
|
3890
|
-
const source = customBundled ?? this.initialBundled ?? bundledEfforts;
|
|
3891
|
-
for (const effort of source) {
|
|
3892
|
-
const prior = this.bundledEfforts.get(effort.slug);
|
|
3893
|
-
if (!prior || resolveEntityRank(effort) >= resolveEntityRank(prior)) {
|
|
3894
|
-
this.bundledEfforts.set(effort.slug, effort);
|
|
3895
|
-
}
|
|
3896
|
-
}
|
|
3897
|
-
this.userEfforts.clear();
|
|
3898
|
-
if (this.storage) {
|
|
3899
|
-
try {
|
|
3900
|
-
const userEfforts = await this.storage.load();
|
|
3901
|
-
for (const effort of userEfforts) {
|
|
3902
|
-
if (effort.registrySource === "user") {
|
|
3903
|
-
this.userEfforts.set(effort.slug, effort);
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
} catch (err) {
|
|
3907
|
-
console.warn("[CompositeEffortRegistry] Failed to load user efforts from storage adapter:", err);
|
|
4390
|
+
registrySource: "user",
|
|
4391
|
+
createdAt: "2024-02-01T08:00:00Z",
|
|
4392
|
+
updatedAt: "2024-02-01T08:00:00Z"
|
|
4393
|
+
};
|
|
4394
|
+
var commonFixtureSet = [
|
|
4395
|
+
fixtureRunning,
|
|
4396
|
+
fixtureRowing,
|
|
4397
|
+
fixtureBackSquat,
|
|
4398
|
+
fixtureBurpee,
|
|
4399
|
+
fixturePlank
|
|
4400
|
+
];
|
|
4401
|
+
var fullFixtureSet = [
|
|
4402
|
+
...commonFixtureSet,
|
|
4403
|
+
fixtureUserCustom,
|
|
4404
|
+
fixtureUserOverride
|
|
4405
|
+
];
|
|
4406
|
+
|
|
4407
|
+
// src/effort-registry/data/bundled-efforts.ts
|
|
4408
|
+
var bundledEfforts = commonFixtureSet;
|
|
4409
|
+
var BUNDLED_EFFORT_COUNT = bundledEfforts.length;
|
|
4410
|
+
|
|
4411
|
+
// src/effort-registry/InMemoryEffortRegistry.ts
|
|
4412
|
+
var InMemoryEffortRegistry = class {
|
|
4413
|
+
efforts = /* @__PURE__ */ new Map();
|
|
4414
|
+
sourceFilter = null;
|
|
4415
|
+
initialized = false;
|
|
4416
|
+
constructor(sourceFilter) {
|
|
4417
|
+
this.sourceFilter = sourceFilter ?? null;
|
|
4418
|
+
}
|
|
4419
|
+
/** Seed with bundled efforts */
|
|
4420
|
+
async loadBundled() {
|
|
4421
|
+
for (const effort of bundledEfforts) {
|
|
4422
|
+
if (!this.sourceFilter || effort.registrySource === this.sourceFilter) {
|
|
4423
|
+
this.efforts.set(effort.slug, effort);
|
|
3908
4424
|
}
|
|
3909
4425
|
}
|
|
3910
4426
|
this.initialized = true;
|
|
3911
4427
|
}
|
|
3912
|
-
/** True after loadBundled() has resolved */
|
|
3913
4428
|
isInitialized() {
|
|
3914
4429
|
return this.initialized;
|
|
3915
4430
|
}
|
|
4431
|
+
/** Seed with arbitrary efforts (useful for tests) */
|
|
4432
|
+
seed(efforts) {
|
|
4433
|
+
for (const effort of efforts) {
|
|
4434
|
+
this.efforts.set(effort.slug, effort);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
/** Clear all efforts */
|
|
4438
|
+
clear() {
|
|
4439
|
+
this.efforts.clear();
|
|
4440
|
+
this.initialized = false;
|
|
4441
|
+
}
|
|
3916
4442
|
resolve(slug) {
|
|
3917
|
-
return this.
|
|
4443
|
+
return this.efforts.get(slug) ?? null;
|
|
3918
4444
|
}
|
|
3919
4445
|
resolveByAlias(label) {
|
|
3920
4446
|
const normalized = normalizeForFuzzy(label);
|
|
3921
|
-
for (const effort of this.
|
|
3922
|
-
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
3923
|
-
for (const alias of effort.aliases) {
|
|
3924
|
-
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
3927
|
-
const bundled = Array.from(this.bundledEfforts.values()).filter((effort) => !this.userEfforts.has(effort.slug)).sort((a, b) => resolveEntityRank(b) - resolveEntityRank(a));
|
|
3928
|
-
for (const effort of bundled) {
|
|
4447
|
+
for (const effort of this.efforts.values()) {
|
|
3929
4448
|
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
3930
4449
|
for (const alias of effort.aliases) {
|
|
3931
4450
|
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
@@ -3934,456 +4453,193 @@ var CompositeEffortRegistry = class {
|
|
|
3934
4453
|
return null;
|
|
3935
4454
|
}
|
|
3936
4455
|
list() {
|
|
3937
|
-
|
|
3938
|
-
for (const [slug, effort] of this.userEfforts) {
|
|
3939
|
-
merged.set(slug, effort);
|
|
3940
|
-
}
|
|
3941
|
-
return Array.from(merged.values());
|
|
4456
|
+
return Array.from(this.efforts.values());
|
|
3942
4457
|
}
|
|
3943
4458
|
listByOrigin(origin) {
|
|
3944
|
-
if (origin === "user") {
|
|
3945
|
-
return Array.from(this.userEfforts.values());
|
|
3946
|
-
}
|
|
3947
|
-
if (origin === "bundled") {
|
|
3948
|
-
return Array.from(this.bundledEfforts.values());
|
|
3949
|
-
}
|
|
3950
4459
|
return this.list().filter((e) => e.registrySource === origin);
|
|
3951
4460
|
}
|
|
3952
4461
|
async upsert(effort) {
|
|
3953
4462
|
if (effort.registrySource !== "user") {
|
|
3954
4463
|
throw new Error(
|
|
3955
|
-
`
|
|
3956
|
-
);
|
|
3957
|
-
}
|
|
3958
|
-
this.userEfforts.set(effort.slug, effort);
|
|
3959
|
-
if (this.storage) {
|
|
3960
|
-
try {
|
|
3961
|
-
await this.storage.save(effort);
|
|
3962
|
-
} catch (err) {
|
|
3963
|
-
console.warn("[CompositeEffortRegistry] Failed to persist effort to storage adapter:", err);
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
3967
|
-
async delete(slug) {
|
|
3968
|
-
const existing = this.resolve(slug);
|
|
3969
|
-
if (existing && existing.registrySource !== "user") {
|
|
3970
|
-
throw new Error(
|
|
3971
|
-
`CompositeEffortRegistry.delete: cannot delete non-user effort "${slug}"`
|
|
4464
|
+
`InMemoryEffortRegistry.upsert: only user efforts can be written. Received registrySource="${effort.registrySource}"`
|
|
3972
4465
|
);
|
|
3973
4466
|
}
|
|
3974
|
-
this.
|
|
3975
|
-
if (this.storage) {
|
|
3976
|
-
try {
|
|
3977
|
-
await this.storage.delete(slug);
|
|
3978
|
-
} catch (err) {
|
|
3979
|
-
console.warn("[CompositeEffortRegistry] Failed to delete effort from storage adapter:", err);
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
};
|
|
3984
|
-
|
|
3985
|
-
// src/effort-registry/aliasManagement.ts
|
|
3986
|
-
async function addAliasToEffort(registry, targetSlug, newAlias) {
|
|
3987
|
-
const trimmed = newAlias.trim();
|
|
3988
|
-
if (!trimmed) {
|
|
3989
|
-
throw new Error("Alias cannot be empty");
|
|
3990
|
-
}
|
|
3991
|
-
const existing = registry.resolve(targetSlug);
|
|
3992
|
-
if (!existing) {
|
|
3993
|
-
throw new Error(`Effort "${targetSlug}" not found in registry`);
|
|
3994
|
-
}
|
|
3995
|
-
const normalizedNew = normalizeForFuzzy(trimmed);
|
|
3996
|
-
const alreadyHas = normalizeForFuzzy(existing.label) === normalizedNew || existing.aliases.some((a) => normalizeForFuzzy(a) === normalizedNew);
|
|
3997
|
-
if (alreadyHas) {
|
|
3998
|
-
return existing;
|
|
3999
|
-
}
|
|
4000
|
-
const updatedAliases = [...existing.aliases, trimmed];
|
|
4001
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4002
|
-
if (existing.registrySource === "bundled") {
|
|
4003
|
-
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? `effort-user-${crypto.randomUUID()}` : `effort-user-${Date.now()}`;
|
|
4004
|
-
const userEffort = {
|
|
4005
|
-
...existing,
|
|
4006
|
-
id,
|
|
4007
|
-
registrySource: "user",
|
|
4008
|
-
aliases: updatedAliases,
|
|
4009
|
-
updatedAt: now,
|
|
4010
|
-
derivation: existing.derivation ?? {
|
|
4011
|
-
parentSlug: existing.slug,
|
|
4012
|
-
coefficients: {},
|
|
4013
|
-
hardOverrides: {}
|
|
4014
|
-
}
|
|
4015
|
-
};
|
|
4016
|
-
await registry.upsert(userEffort);
|
|
4017
|
-
return userEffort;
|
|
4018
|
-
}
|
|
4019
|
-
const updatedUserEffort = {
|
|
4020
|
-
...existing,
|
|
4021
|
-
aliases: updatedAliases,
|
|
4022
|
-
updatedAt: now
|
|
4023
|
-
};
|
|
4024
|
-
await registry.upsert(updatedUserEffort);
|
|
4025
|
-
return updatedUserEffort;
|
|
4026
|
-
}
|
|
4027
|
-
function buildExactIndex(entities) {
|
|
4028
|
-
const deduped = dedupeEntitiesByPrecedence(entities);
|
|
4029
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
4030
|
-
const add = (key, entity) => {
|
|
4031
|
-
if (!key) return;
|
|
4032
|
-
const existing = byKey.get(key);
|
|
4033
|
-
if (existing) {
|
|
4034
|
-
if (!existing.includes(entity)) existing.push(entity);
|
|
4035
|
-
} else {
|
|
4036
|
-
byKey.set(key, [entity]);
|
|
4037
|
-
}
|
|
4038
|
-
};
|
|
4039
|
-
for (const entity of deduped) {
|
|
4040
|
-
add(entity.slug.toLowerCase(), entity);
|
|
4041
|
-
add(normalizeForFuzzy(entity.label), entity);
|
|
4042
|
-
for (const alias of entity.aliases) add(normalizeForFuzzy(alias), entity);
|
|
4043
|
-
}
|
|
4044
|
-
return {
|
|
4045
|
-
byKey,
|
|
4046
|
-
lookup(key) {
|
|
4047
|
-
const direct = byKey.get(key) ?? byKey.get(normalizeForFuzzy(key));
|
|
4048
|
-
if (!direct || direct.length === 0) return { entity: null, ambiguous: [] };
|
|
4049
|
-
if (direct.length === 1) return { entity: direct[0], ambiguous: [] };
|
|
4050
|
-
let maxRank = -1;
|
|
4051
|
-
for (const candidate of direct) {
|
|
4052
|
-
const rank = resolveEntityRank(candidate);
|
|
4053
|
-
if (rank > maxRank) maxRank = rank;
|
|
4054
|
-
}
|
|
4055
|
-
const topTier = direct.filter((candidate) => resolveEntityRank(candidate) === maxRank);
|
|
4056
|
-
if (topTier.length === 1) return { entity: topTier[0], ambiguous: [] };
|
|
4057
|
-
return { entity: null, ambiguous: topTier };
|
|
4058
|
-
}
|
|
4059
|
-
};
|
|
4060
|
-
}
|
|
4061
|
-
function equipmentRef(entity) {
|
|
4062
|
-
return { kind: "equipment", slug: entity.slug, label: entity.label };
|
|
4063
|
-
}
|
|
4064
|
-
function dedupeRefs(refs) {
|
|
4065
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4066
|
-
const result = [];
|
|
4067
|
-
for (const ref of refs) {
|
|
4068
|
-
const key = `${ref.kind}:${ref.slug}`;
|
|
4069
|
-
if (seen.has(key)) continue;
|
|
4070
|
-
seen.add(key);
|
|
4071
|
-
result.push(ref);
|
|
4467
|
+
this.efforts.set(effort.slug, effort);
|
|
4072
4468
|
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
const prefixCandidates = dedupedEquipment.flatMap((entity) => [entity.label, ...entity.aliases].filter((name2) => !!name2 && !!name2.trim()).map((name2) => ({ lower: name2.trim().toLowerCase(), entity }))).sort((a, b) => b.lower.length - a.lower.length);
|
|
4080
|
-
const materializer = materialize ?? ((effort) => ({
|
|
4081
|
-
effort,
|
|
4082
|
-
definition: effort,
|
|
4083
|
-
slug: effort.slug,
|
|
4084
|
-
label: effort.label,
|
|
4085
|
-
met: effort.baseAttributes.met,
|
|
4086
|
-
baseAttributes: effort.baseAttributes,
|
|
4087
|
-
discipline: effort.baseAttributes.discipline,
|
|
4088
|
-
disciplineFactor: effort.baseAttributes.disciplineFactor ?? 1,
|
|
4089
|
-
intensityTier: effort.baseAttributes.intensityTier,
|
|
4090
|
-
modifiers: {},
|
|
4091
|
-
registrySource: effort.registrySource,
|
|
4092
|
-
resolvedFrom: effort.registrySource === "user" ? "user" : effort.registrySource === "bundled" ? "bundled" : "default",
|
|
4093
|
-
isEstimated: false
|
|
4094
|
-
}));
|
|
4095
|
-
const resolveEffortExact = (key) => {
|
|
4096
|
-
const found = efforts.lookup(key.trim());
|
|
4097
|
-
if (found.entity) return { entity: found.entity };
|
|
4098
|
-
if (found.ambiguous.length > 0) {
|
|
4099
|
-
return {
|
|
4100
|
-
entity: null,
|
|
4101
|
-
diagnostic: {
|
|
4102
|
-
reason: "ambiguous-effort",
|
|
4103
|
-
message: `"${key}" matches multiple efforts.`,
|
|
4104
|
-
token: key,
|
|
4105
|
-
candidates: found.ambiguous.map((e) => e.slug)
|
|
4106
|
-
}
|
|
4107
|
-
};
|
|
4108
|
-
}
|
|
4109
|
-
return {
|
|
4110
|
-
entity: null,
|
|
4111
|
-
diagnostic: { reason: "unknown-effort", message: `Unknown effort "${key}".`, token: key }
|
|
4112
|
-
};
|
|
4113
|
-
};
|
|
4114
|
-
const resolveEquipmentValues = (values) => {
|
|
4115
|
-
const refs = [];
|
|
4116
|
-
const diagnostics = [];
|
|
4117
|
-
for (const value of values) {
|
|
4118
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
4119
|
-
diagnostics.push({
|
|
4120
|
-
reason: "invalid-explicit",
|
|
4121
|
-
message: `Equipment reference must be a slug string, got ${JSON.stringify(value) ?? "null"}.`,
|
|
4122
|
-
token: String(value)
|
|
4123
|
-
});
|
|
4124
|
-
continue;
|
|
4125
|
-
}
|
|
4126
|
-
const found = equipment.lookup(value.trim());
|
|
4127
|
-
if (found.entity) {
|
|
4128
|
-
refs.push(equipmentRef(found.entity));
|
|
4129
|
-
} else if (found.ambiguous.length > 0) {
|
|
4130
|
-
diagnostics.push({
|
|
4131
|
-
reason: "ambiguous-equipment",
|
|
4132
|
-
message: `"${value}" matches multiple equipment entries.`,
|
|
4133
|
-
token: value,
|
|
4134
|
-
candidates: found.ambiguous.map((e) => e.slug)
|
|
4135
|
-
});
|
|
4136
|
-
} else {
|
|
4137
|
-
diagnostics.push({
|
|
4138
|
-
reason: "invalid-explicit",
|
|
4139
|
-
message: `Unknown equipment "${value}".`,
|
|
4140
|
-
token: value
|
|
4141
|
-
});
|
|
4142
|
-
}
|
|
4469
|
+
async delete(slug) {
|
|
4470
|
+
const existing = this.efforts.get(slug);
|
|
4471
|
+
if (existing && existing.registrySource !== "user") {
|
|
4472
|
+
throw new Error(
|
|
4473
|
+
`InMemoryEffortRegistry.delete: cannot delete non-user effort "${slug}"`
|
|
4474
|
+
);
|
|
4143
4475
|
}
|
|
4144
|
-
|
|
4145
|
-
}
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
equipmentRefs = resolvedDefEquipment.refs;
|
|
4181
|
-
for (const diagnostic of resolvedDefEquipment.diagnostics) {
|
|
4182
|
-
push({ ...diagnostic, reason: "unknown-equipment" });
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
};
|
|
4186
|
-
if (!source) {
|
|
4187
|
-
if (defaults?.effort) {
|
|
4188
|
-
const found = resolveEffortExact(defaults.effort);
|
|
4189
|
-
if (found.entity) {
|
|
4190
|
-
effortEntity = found.entity;
|
|
4191
|
-
matched = "default";
|
|
4192
|
-
} else {
|
|
4193
|
-
push({
|
|
4194
|
-
reason: found.diagnostic?.reason === "ambiguous-effort" ? "ambiguous-effort" : "unknown-effort",
|
|
4195
|
-
message: `Default effort "${defaults.effort}" is not in the catalog.`,
|
|
4196
|
-
token: defaults.effort,
|
|
4197
|
-
candidates: found.diagnostic?.candidates
|
|
4198
|
-
});
|
|
4199
|
-
}
|
|
4200
|
-
}
|
|
4201
|
-
fillEquipment();
|
|
4202
|
-
return {
|
|
4203
|
-
matched,
|
|
4204
|
-
label: source,
|
|
4205
|
-
effort: effortEntity ? materializer(effortEntity) : null,
|
|
4206
|
-
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
4207
|
-
equipment: equipmentRefs,
|
|
4208
|
-
remaining: "",
|
|
4209
|
-
diagnostics
|
|
4210
|
-
};
|
|
4476
|
+
this.efforts.delete(slug);
|
|
4477
|
+
}
|
|
4478
|
+
};
|
|
4479
|
+
|
|
4480
|
+
// src/effort-registry/CompositeEffortRegistry.ts
|
|
4481
|
+
var CompositeEffortRegistry = class {
|
|
4482
|
+
userEfforts = /* @__PURE__ */ new Map();
|
|
4483
|
+
bundledEfforts = /* @__PURE__ */ new Map();
|
|
4484
|
+
initialized = false;
|
|
4485
|
+
initialBundled;
|
|
4486
|
+
storage;
|
|
4487
|
+
constructor(options) {
|
|
4488
|
+
if (options) {
|
|
4489
|
+
this.initialBundled = options.bundled;
|
|
4490
|
+
this.storage = options.storage;
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
/**
|
|
4494
|
+
* Initialize the registry:
|
|
4495
|
+
* 1. Load bundled efforts into memory
|
|
4496
|
+
* 2. Load user efforts from storage adapter into memory
|
|
4497
|
+
*
|
|
4498
|
+
* An explicit `customBundled` REPLACES the bundled tier — passing an empty
|
|
4499
|
+
* set clears it — so a seed source removal never leaves stale bundled
|
|
4500
|
+
* efforts cached. The no-argument call keeps the constructor/default set.
|
|
4501
|
+
* Both tiers rebuild only AFTER a successful adapter load: a failed load
|
|
4502
|
+
* propagates (readiness must never report over stale data), and deleted
|
|
4503
|
+
* custom rows disappear because the user map is cleared too.
|
|
4504
|
+
*/
|
|
4505
|
+
async loadBundled(customBundled) {
|
|
4506
|
+
this.bundledEfforts.clear();
|
|
4507
|
+
const source = customBundled ?? this.initialBundled ?? bundledEfforts;
|
|
4508
|
+
for (const effort of source) {
|
|
4509
|
+
const prior = this.bundledEfforts.get(effort.slug);
|
|
4510
|
+
if (!prior || resolveEntityRank(effort) >= resolveEntityRank(prior)) {
|
|
4511
|
+
this.bundledEfforts.set(effort.slug, effort);
|
|
4211
4512
|
}
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
effort: null,
|
|
4221
|
-
effortRef: null,
|
|
4222
|
-
equipment: [],
|
|
4223
|
-
remaining: "",
|
|
4224
|
-
diagnostics: [{
|
|
4225
|
-
reason: "malformed-json",
|
|
4226
|
-
message: "Reference object is not valid JSON.",
|
|
4227
|
-
token: source
|
|
4228
|
-
}]
|
|
4229
|
-
};
|
|
4230
|
-
}
|
|
4231
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && ("effort" in parsed || "equipment" in parsed)) {
|
|
4232
|
-
matched = "explicit";
|
|
4233
|
-
const obj = parsed;
|
|
4234
|
-
if (obj.effort !== void 0) {
|
|
4235
|
-
if (typeof obj.effort === "string" && obj.effort.trim()) {
|
|
4236
|
-
const found = resolveEffortExact(obj.effort);
|
|
4237
|
-
effortEntity = found.entity;
|
|
4238
|
-
if (found.diagnostic) {
|
|
4239
|
-
push(found.diagnostic.reason === "ambiguous-effort" ? found.diagnostic : { ...found.diagnostic, reason: "invalid-explicit", message: `Explicit effort "${obj.effort}" is not in the catalog.` });
|
|
4240
|
-
}
|
|
4241
|
-
} else {
|
|
4242
|
-
push({
|
|
4243
|
-
reason: "invalid-explicit",
|
|
4244
|
-
message: 'Explicit "effort" must be a slug string.',
|
|
4245
|
-
token: String(obj.effort)
|
|
4246
|
-
});
|
|
4247
|
-
}
|
|
4248
|
-
}
|
|
4249
|
-
if (obj.equipment !== void 0) {
|
|
4250
|
-
equipmentAuthored = true;
|
|
4251
|
-
const values = Array.isArray(obj.equipment) ? obj.equipment : [obj.equipment];
|
|
4252
|
-
const resolvedEquipment = resolveEquipmentValues(values);
|
|
4253
|
-
equipmentRefs = resolvedEquipment.refs;
|
|
4254
|
-
diagnostics.push(...resolvedEquipment.diagnostics);
|
|
4255
|
-
if (effortEntity?.equipment?.length) {
|
|
4256
|
-
const definitionSlugs = new Set(effortEntity.equipment);
|
|
4257
|
-
const authoredSlugs = equipmentRefs.map((ref) => ref.slug);
|
|
4258
|
-
if (authoredSlugs.length > 0 && authoredSlugs.some((slug) => !definitionSlugs.has(slug))) {
|
|
4259
|
-
push({
|
|
4260
|
-
reason: "conflict",
|
|
4261
|
-
message: `Explicit equipment [${authoredSlugs.join(", ")}] contradicts effort "${effortEntity.slug}" definition equipment [${effortEntity.equipment.join(", ")}].`,
|
|
4262
|
-
token: source
|
|
4263
|
-
});
|
|
4264
|
-
}
|
|
4265
|
-
}
|
|
4513
|
+
}
|
|
4514
|
+
this.userEfforts.clear();
|
|
4515
|
+
if (this.storage) {
|
|
4516
|
+
try {
|
|
4517
|
+
const userEfforts = await this.storage.load();
|
|
4518
|
+
for (const effort of userEfforts) {
|
|
4519
|
+
if (effort.registrySource === "user") {
|
|
4520
|
+
this.userEfforts.set(effort.slug, effort);
|
|
4266
4521
|
}
|
|
4267
|
-
fillEquipment();
|
|
4268
|
-
return {
|
|
4269
|
-
matched,
|
|
4270
|
-
label: source,
|
|
4271
|
-
effort: effortEntity ? materializer(effortEntity) : null,
|
|
4272
|
-
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
4273
|
-
equipment: equipmentRefs,
|
|
4274
|
-
remaining: "",
|
|
4275
|
-
diagnostics
|
|
4276
|
-
};
|
|
4277
|
-
}
|
|
4278
|
-
}
|
|
4279
|
-
const lowerSource = source.toLowerCase();
|
|
4280
|
-
for (const candidate of prefixCandidates) {
|
|
4281
|
-
if (!lowerSource.startsWith(candidate.lower)) continue;
|
|
4282
|
-
const after = source.slice(candidate.lower.length);
|
|
4283
|
-
if (after && !/^\s/.test(after)) continue;
|
|
4284
|
-
const candidateRemaining = after.trim();
|
|
4285
|
-
if (!candidateRemaining) continue;
|
|
4286
|
-
const equipmentFound = equipment.lookup(candidate.lower);
|
|
4287
|
-
if (equipmentFound.ambiguous.length > 0) {
|
|
4288
|
-
push({
|
|
4289
|
-
reason: "ambiguous-equipment",
|
|
4290
|
-
message: `"${candidate.lower}" matches multiple equipment entries.`,
|
|
4291
|
-
token: source,
|
|
4292
|
-
candidates: equipmentFound.ambiguous.map((e) => e.slug)
|
|
4293
|
-
});
|
|
4294
|
-
return {
|
|
4295
|
-
matched: "none",
|
|
4296
|
-
label: source,
|
|
4297
|
-
effort: null,
|
|
4298
|
-
effortRef: null,
|
|
4299
|
-
equipment: [],
|
|
4300
|
-
remaining: "",
|
|
4301
|
-
diagnostics
|
|
4302
|
-
};
|
|
4303
|
-
}
|
|
4304
|
-
if (!equipmentFound.entity) continue;
|
|
4305
|
-
const effortFound = resolveEffortExact(candidateRemaining);
|
|
4306
|
-
if (effortFound.entity) {
|
|
4307
|
-
matched = "split";
|
|
4308
|
-
effortEntity = effortFound.entity;
|
|
4309
|
-
equipmentRefs = [equipmentRef(equipmentFound.entity)];
|
|
4310
|
-
equipmentAuthored = true;
|
|
4311
|
-
remaining = candidateRemaining;
|
|
4312
|
-
break;
|
|
4313
|
-
}
|
|
4314
|
-
if (effortFound.diagnostic?.reason === "ambiguous-effort") {
|
|
4315
|
-
push(effortFound.diagnostic);
|
|
4316
|
-
return {
|
|
4317
|
-
matched: "none",
|
|
4318
|
-
label: source,
|
|
4319
|
-
effort: null,
|
|
4320
|
-
effortRef: null,
|
|
4321
|
-
equipment: [],
|
|
4322
|
-
remaining: "",
|
|
4323
|
-
diagnostics
|
|
4324
|
-
};
|
|
4325
4522
|
}
|
|
4523
|
+
} catch (err) {
|
|
4524
|
+
console.warn("[CompositeEffortRegistry] Failed to load user efforts from storage adapter:", err);
|
|
4326
4525
|
}
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
} else if (found.diagnostic) {
|
|
4344
|
-
push(found.diagnostic);
|
|
4345
|
-
}
|
|
4526
|
+
}
|
|
4527
|
+
this.initialized = true;
|
|
4528
|
+
}
|
|
4529
|
+
/** True after loadBundled() has resolved */
|
|
4530
|
+
isInitialized() {
|
|
4531
|
+
return this.initialized;
|
|
4532
|
+
}
|
|
4533
|
+
resolve(slug) {
|
|
4534
|
+
return this.userEfforts.get(slug) ?? this.bundledEfforts.get(slug) ?? null;
|
|
4535
|
+
}
|
|
4536
|
+
resolveByAlias(label) {
|
|
4537
|
+
const normalized = normalizeForFuzzy(label);
|
|
4538
|
+
for (const effort of this.userEfforts.values()) {
|
|
4539
|
+
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
4540
|
+
for (const alias of effort.aliases) {
|
|
4541
|
+
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
4346
4542
|
}
|
|
4347
|
-
fillEquipment();
|
|
4348
|
-
return {
|
|
4349
|
-
matched,
|
|
4350
|
-
label: source,
|
|
4351
|
-
effort: effortEntity ? materializer(effortEntity) : null,
|
|
4352
|
-
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
4353
|
-
equipment: equipmentRefs,
|
|
4354
|
-
remaining,
|
|
4355
|
-
diagnostics
|
|
4356
|
-
};
|
|
4357
4543
|
}
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
const equipment = [];
|
|
4364
|
-
const seenEffort = /* @__PURE__ */ new Set();
|
|
4365
|
-
const seenEquipment = /* @__PURE__ */ new Set();
|
|
4366
|
-
const pushUnique = (list, seen, value) => {
|
|
4367
|
-
if (!value) return;
|
|
4368
|
-
if (seen.has(value)) return;
|
|
4369
|
-
seen.add(value);
|
|
4370
|
-
list.push(value);
|
|
4371
|
-
};
|
|
4372
|
-
for (const statement of statements ?? []) {
|
|
4373
|
-
const metrics = Array.isArray(statement.metrics) ? statement.metrics : statement.metrics?.toArray?.() ?? [];
|
|
4374
|
-
for (const metric of metrics) {
|
|
4375
|
-
const refs = wodWikiCore.getReferenceMetadata(metric.metadata)?.refs;
|
|
4376
|
-
if (!refs) continue;
|
|
4377
|
-
for (const ref of refs) {
|
|
4378
|
-
if (ref.kind === "effort") pushUnique(efforts, seenEffort, ref.slug);
|
|
4379
|
-
if (ref.kind === "equipment") pushUnique(equipment, seenEquipment, ref.slug);
|
|
4544
|
+
const bundled = Array.from(this.bundledEfforts.values()).filter((effort) => !this.userEfforts.has(effort.slug)).sort((a, b) => resolveEntityRank(b) - resolveEntityRank(a));
|
|
4545
|
+
for (const effort of bundled) {
|
|
4546
|
+
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
4547
|
+
for (const alias of effort.aliases) {
|
|
4548
|
+
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
4380
4549
|
}
|
|
4381
4550
|
}
|
|
4551
|
+
return null;
|
|
4552
|
+
}
|
|
4553
|
+
list() {
|
|
4554
|
+
const merged = new Map(this.bundledEfforts);
|
|
4555
|
+
for (const [slug, effort] of this.userEfforts) {
|
|
4556
|
+
merged.set(slug, effort);
|
|
4557
|
+
}
|
|
4558
|
+
return Array.from(merged.values());
|
|
4382
4559
|
}
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4560
|
+
listByOrigin(origin) {
|
|
4561
|
+
if (origin === "user") {
|
|
4562
|
+
return Array.from(this.userEfforts.values());
|
|
4563
|
+
}
|
|
4564
|
+
if (origin === "bundled") {
|
|
4565
|
+
return Array.from(this.bundledEfforts.values());
|
|
4566
|
+
}
|
|
4567
|
+
return this.list().filter((e) => e.registrySource === origin);
|
|
4568
|
+
}
|
|
4569
|
+
async upsert(effort) {
|
|
4570
|
+
if (effort.registrySource !== "user") {
|
|
4571
|
+
throw new Error(
|
|
4572
|
+
`CompositeEffortRegistry.upsert: only user efforts can be written. Received registrySource="${effort.registrySource}"`
|
|
4573
|
+
);
|
|
4574
|
+
}
|
|
4575
|
+
this.userEfforts.set(effort.slug, effort);
|
|
4576
|
+
if (this.storage) {
|
|
4577
|
+
try {
|
|
4578
|
+
await this.storage.save(effort);
|
|
4579
|
+
} catch (err) {
|
|
4580
|
+
console.warn("[CompositeEffortRegistry] Failed to persist effort to storage adapter:", err);
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
async delete(slug) {
|
|
4585
|
+
const existing = this.resolve(slug);
|
|
4586
|
+
if (existing && existing.registrySource !== "user") {
|
|
4587
|
+
throw new Error(
|
|
4588
|
+
`CompositeEffortRegistry.delete: cannot delete non-user effort "${slug}"`
|
|
4589
|
+
);
|
|
4590
|
+
}
|
|
4591
|
+
this.userEfforts.delete(slug);
|
|
4592
|
+
if (this.storage) {
|
|
4593
|
+
try {
|
|
4594
|
+
await this.storage.delete(slug);
|
|
4595
|
+
} catch (err) {
|
|
4596
|
+
console.warn("[CompositeEffortRegistry] Failed to delete effort from storage adapter:", err);
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
};
|
|
4601
|
+
|
|
4602
|
+
// src/effort-registry/aliasManagement.ts
|
|
4603
|
+
async function addAliasToEffort(registry, targetSlug, newAlias) {
|
|
4604
|
+
const trimmed = newAlias.trim();
|
|
4605
|
+
if (!trimmed) {
|
|
4606
|
+
throw new Error("Alias cannot be empty");
|
|
4607
|
+
}
|
|
4608
|
+
const existing = registry.resolve(targetSlug);
|
|
4609
|
+
if (!existing) {
|
|
4610
|
+
throw new Error(`Effort "${targetSlug}" not found in registry`);
|
|
4611
|
+
}
|
|
4612
|
+
const normalizedNew = normalizeForFuzzy(trimmed);
|
|
4613
|
+
const alreadyHas = normalizeForFuzzy(existing.label) === normalizedNew || existing.aliases.some((a) => normalizeForFuzzy(a) === normalizedNew);
|
|
4614
|
+
if (alreadyHas) {
|
|
4615
|
+
return existing;
|
|
4616
|
+
}
|
|
4617
|
+
const updatedAliases = [...existing.aliases, trimmed];
|
|
4618
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4619
|
+
if (existing.registrySource === "bundled") {
|
|
4620
|
+
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? `effort-user-${crypto.randomUUID()}` : `effort-user-${Date.now()}`;
|
|
4621
|
+
const userEffort = {
|
|
4622
|
+
...existing,
|
|
4623
|
+
id,
|
|
4624
|
+
registrySource: "user",
|
|
4625
|
+
aliases: updatedAliases,
|
|
4626
|
+
updatedAt: now,
|
|
4627
|
+
derivation: existing.derivation ?? {
|
|
4628
|
+
parentSlug: existing.slug,
|
|
4629
|
+
coefficients: {},
|
|
4630
|
+
hardOverrides: {}
|
|
4631
|
+
}
|
|
4632
|
+
};
|
|
4633
|
+
await registry.upsert(userEffort);
|
|
4634
|
+
return userEffort;
|
|
4635
|
+
}
|
|
4636
|
+
const updatedUserEffort = {
|
|
4637
|
+
...existing,
|
|
4638
|
+
aliases: updatedAliases,
|
|
4639
|
+
updatedAt: now
|
|
4640
|
+
};
|
|
4641
|
+
await registry.upsert(updatedUserEffort);
|
|
4642
|
+
return updatedUserEffort;
|
|
4387
4643
|
}
|
|
4388
4644
|
|
|
4389
4645
|
// src/runtime/contracts/IRuntimeOptions.ts
|
|
@@ -6547,6 +6803,8 @@ var RuntimeBlock = class {
|
|
|
6547
6803
|
blockType;
|
|
6548
6804
|
context;
|
|
6549
6805
|
sourceIds;
|
|
6806
|
+
/** The tags this block's statements referenced, resolved at compile time. */
|
|
6807
|
+
tagComposition;
|
|
6550
6808
|
// List-based memory storage
|
|
6551
6809
|
_memory = [];
|
|
6552
6810
|
/**
|
|
@@ -6583,13 +6841,15 @@ var RuntimeBlock = class {
|
|
|
6583
6841
|
key,
|
|
6584
6842
|
blockType,
|
|
6585
6843
|
label,
|
|
6586
|
-
metrics
|
|
6844
|
+
metrics,
|
|
6845
|
+
tagComposition
|
|
6587
6846
|
} = options;
|
|
6588
6847
|
this._runtime = runtime;
|
|
6589
6848
|
this.sourceIds = sourceIds;
|
|
6590
6849
|
this.behaviors = behaviors;
|
|
6591
6850
|
this.key = key ?? new wodWikiCore.BlockKey();
|
|
6592
6851
|
this.blockType = blockType;
|
|
6852
|
+
this.tagComposition = tagComposition;
|
|
6593
6853
|
this.context = context ?? new BlockContext(runtime, this.key.toString());
|
|
6594
6854
|
if (label) {
|
|
6595
6855
|
this._memory.push(new MemoryLocation("metric:label", [{
|
|
@@ -8553,61 +8813,153 @@ var SoundMetric = class {
|
|
|
8553
8813
|
image;
|
|
8554
8814
|
};
|
|
8555
8815
|
|
|
8556
|
-
// src/runtime/
|
|
8557
|
-
|
|
8816
|
+
// src/runtime/sound/SoundProfile.ts
|
|
8817
|
+
function parseSoundTrigger(notation) {
|
|
8818
|
+
const trimmed = notation.trim();
|
|
8819
|
+
if (trimmed.length === 0) return void 0;
|
|
8820
|
+
let anchor;
|
|
8821
|
+
let magnitude;
|
|
8822
|
+
if (trimmed.startsWith("-")) {
|
|
8823
|
+
anchor = "end";
|
|
8824
|
+
magnitude = trimmed.slice(1);
|
|
8825
|
+
} else {
|
|
8826
|
+
anchor = "start";
|
|
8827
|
+
magnitude = trimmed;
|
|
8828
|
+
}
|
|
8829
|
+
if (magnitude.startsWith(":")) magnitude = magnitude.slice(1);
|
|
8830
|
+
const offsetMs = parseDurationMagnitude(magnitude);
|
|
8831
|
+
if (offsetMs === void 0) return void 0;
|
|
8832
|
+
return { anchor, offsetMs };
|
|
8833
|
+
}
|
|
8834
|
+
function parseDurationMagnitude(magnitude) {
|
|
8835
|
+
const cleaned = magnitude.trim().replace(/^:/, "");
|
|
8836
|
+
if (cleaned.length === 0) return 0;
|
|
8837
|
+
if (!/^\d+(:\d+)*$/.test(cleaned)) return void 0;
|
|
8838
|
+
const segments = cleaned.split(":").map(Number).reverse();
|
|
8839
|
+
const [seconds = 0, minutes = 0, hours = 0, days = 0] = segments;
|
|
8840
|
+
if (segments.length > 4) return void 0;
|
|
8841
|
+
if (minutes > 59 || hours > 59) return void 0;
|
|
8842
|
+
return ((days * 24 + hours) * 60 + minutes) * 6e4 + seconds * 1e3;
|
|
8843
|
+
}
|
|
8844
|
+
function formatSoundTrigger(trigger) {
|
|
8845
|
+
const totalSeconds = Math.round(trigger.offsetMs / 1e3);
|
|
8846
|
+
const seconds = totalSeconds % 60;
|
|
8847
|
+
const minutes = Math.floor(totalSeconds / 60) % 60;
|
|
8848
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
8849
|
+
let magnitude;
|
|
8850
|
+
if (hours > 0) {
|
|
8851
|
+
magnitude = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
|
8852
|
+
} else if (minutes > 0) {
|
|
8853
|
+
magnitude = `${pad(minutes)}:${pad(seconds)}`;
|
|
8854
|
+
} else {
|
|
8855
|
+
magnitude = pad(seconds);
|
|
8856
|
+
}
|
|
8857
|
+
return `${trigger.anchor === "end" ? "-:" : ":"}${magnitude}`;
|
|
8858
|
+
}
|
|
8859
|
+
function pad(value) {
|
|
8860
|
+
return value < 10 ? `0${value}` : String(value);
|
|
8861
|
+
}
|
|
8862
|
+
function resolveProfileRules(profile, durationMs) {
|
|
8863
|
+
return profile.rules.filter((rule) => {
|
|
8864
|
+
const { anchor, offsetMs } = rule.trigger;
|
|
8865
|
+
if (anchor === "start") return true;
|
|
8866
|
+
if (offsetMs === 0) return true;
|
|
8867
|
+
if (durationMs === void 0) return false;
|
|
8868
|
+
return offsetMs <= durationMs;
|
|
8869
|
+
});
|
|
8870
|
+
}
|
|
8871
|
+
var DEFAULT_SOUND_PROFILE = {
|
|
8872
|
+
id: "default",
|
|
8873
|
+
name: "Default",
|
|
8874
|
+
rules: [
|
|
8875
|
+
{ trigger: { anchor: "end", offsetMs: 3e3 }, sound: "countdown-beep" },
|
|
8876
|
+
{ trigger: { anchor: "end", offsetMs: 2e3 }, sound: "countdown-beep" },
|
|
8877
|
+
{ trigger: { anchor: "end", offsetMs: 1e3 }, sound: "countdown-beep" },
|
|
8878
|
+
{ trigger: { anchor: "end", offsetMs: 0 }, sound: "timer-complete" }
|
|
8879
|
+
]
|
|
8880
|
+
};
|
|
8881
|
+
|
|
8882
|
+
// src/runtime/behaviors/SoundProfileBehavior.ts
|
|
8883
|
+
var SOUND_PLAY_EVENT = "sound:play";
|
|
8884
|
+
var SoundProfileBehavior = class {
|
|
8558
8885
|
constructor(config) {
|
|
8559
8886
|
this.config = config;
|
|
8560
8887
|
}
|
|
8561
8888
|
config;
|
|
8889
|
+
rules = [];
|
|
8890
|
+
/** Rule indices already fired, so each rule fires exactly once. */
|
|
8891
|
+
fired = /* @__PURE__ */ new Set();
|
|
8892
|
+
durationMs;
|
|
8562
8893
|
onMount(ctx) {
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
ctx.subscribe("tick", (_event, tickCtx) => {
|
|
8574
|
-
const timer = readTimer(tickCtx);
|
|
8575
|
-
if (!timer || timer.direction !== "down" || !timer.durationMs) return [];
|
|
8576
|
-
const now = tickCtx.clock.currentDate.getTime();
|
|
8577
|
-
let elapsed = 0;
|
|
8578
|
-
for (const span of timer.spans) {
|
|
8579
|
-
const end = span.ended ?? now;
|
|
8580
|
-
elapsed += end - span.started;
|
|
8581
|
-
}
|
|
8582
|
-
const remainingMs = timer.durationMs - elapsed;
|
|
8583
|
-
const remainingSeconds = Math.ceil(remainingMs / 1e3);
|
|
8584
|
-
for (const cue of countdownCues) {
|
|
8585
|
-
if (cue.atSeconds?.includes(remainingSeconds) && !playedSeconds.has(remainingSeconds)) {
|
|
8586
|
-
playedSeconds.add(remainingSeconds);
|
|
8587
|
-
tickCtx.emitOutput("system", [
|
|
8588
|
-
new SoundMetric(cue.sound, "countdown", { atSecond: remainingSeconds })
|
|
8589
|
-
], { label: `Countdown: ${remainingSeconds}s` });
|
|
8590
|
-
}
|
|
8591
|
-
}
|
|
8592
|
-
return [];
|
|
8593
|
-
}, { scope: "bubble" });
|
|
8594
|
-
}
|
|
8894
|
+
const timer = readTimer(ctx);
|
|
8895
|
+
this.durationMs = timer?.durationMs;
|
|
8896
|
+
this.rules = resolveProfileRules(this.config.profile, this.durationMs);
|
|
8897
|
+
this.fireDue(ctx, 0, this.durationMs);
|
|
8898
|
+
ctx.subscribe("tick", (_event, tickCtx) => {
|
|
8899
|
+
const elapsed = this.elapsedMs(tickCtx);
|
|
8900
|
+
if (elapsed === void 0) return [];
|
|
8901
|
+
this.fireDue(tickCtx, elapsed, this.durationMs);
|
|
8902
|
+
return [];
|
|
8903
|
+
}, { scope: "bubble" });
|
|
8595
8904
|
return [];
|
|
8596
8905
|
}
|
|
8597
8906
|
onNext(_ctx) {
|
|
8598
8907
|
return [];
|
|
8599
8908
|
}
|
|
8600
8909
|
onUnmount(ctx) {
|
|
8601
|
-
|
|
8602
|
-
if (cue.trigger === "unmount" || cue.trigger === "complete") {
|
|
8603
|
-
ctx.emitOutput("system", [
|
|
8604
|
-
new SoundMetric(cue.sound, cue.trigger)
|
|
8605
|
-
], { label: `Sound: ${cue.sound}` });
|
|
8606
|
-
}
|
|
8607
|
-
}
|
|
8910
|
+
this.fireDue(ctx, this.elapsedMs(ctx) ?? 0, this.durationMs, true);
|
|
8608
8911
|
return [];
|
|
8609
8912
|
}
|
|
8610
8913
|
onDispose(_ctx) {
|
|
8914
|
+
this.fired.clear();
|
|
8915
|
+
}
|
|
8916
|
+
/**
|
|
8917
|
+
* Elapsed milliseconds for the block's own timer, or `undefined` when the
|
|
8918
|
+
* block has no timer to measure against.
|
|
8919
|
+
*/
|
|
8920
|
+
elapsedMs(ctx) {
|
|
8921
|
+
const timer = readTimer(ctx);
|
|
8922
|
+
if (!timer) return void 0;
|
|
8923
|
+
return calculateElapsed(timer, ctx.clock.currentDate.getTime());
|
|
8924
|
+
}
|
|
8925
|
+
/**
|
|
8926
|
+
* Fire every rule whose trigger point has been reached.
|
|
8927
|
+
*
|
|
8928
|
+
* @param elapsed milliseconds elapsed on the block's timer
|
|
8929
|
+
* @param durationMs the block's total duration
|
|
8930
|
+
* @param ending whether the block is ending — a rule anchored to the end
|
|
8931
|
+
* with no offset fires then, even when the block has no duration to
|
|
8932
|
+
* count down from
|
|
8933
|
+
*/
|
|
8934
|
+
fireDue(ctx, elapsed, durationMs, ending = false) {
|
|
8935
|
+
const remaining = durationMs === void 0 ? void 0 : Math.max(0, durationMs - elapsed);
|
|
8936
|
+
this.rules.forEach((rule, index) => {
|
|
8937
|
+
if (this.fired.has(index)) return;
|
|
8938
|
+
const { anchor, offsetMs } = rule.trigger;
|
|
8939
|
+
const due = anchor === "start" ? elapsed >= offsetMs : offsetMs === 0 ? ending || remaining !== void 0 && remaining <= 0 : remaining !== void 0 && remaining <= offsetMs;
|
|
8940
|
+
if (!due) return;
|
|
8941
|
+
this.fired.add(index);
|
|
8942
|
+
this.play(ctx, rule, anchor, offsetMs);
|
|
8943
|
+
});
|
|
8944
|
+
}
|
|
8945
|
+
/**
|
|
8946
|
+
* Record the cue and command its playback.
|
|
8947
|
+
*/
|
|
8948
|
+
play(ctx, rule, anchor, offsetMs) {
|
|
8949
|
+
const trigger = anchor === "start" ? offsetMs === 0 ? "mount" : "countdown" : offsetMs === 0 ? "complete" : "countdown";
|
|
8950
|
+
ctx.emitOutput("system", [
|
|
8951
|
+
new SoundMetric(rule.sound, trigger, {
|
|
8952
|
+
atSecond: offsetMs / 1e3
|
|
8953
|
+
})
|
|
8954
|
+
], { label: `Sound: ${rule.sound}` });
|
|
8955
|
+
ctx.emitEvent({
|
|
8956
|
+
name: SOUND_PLAY_EVENT,
|
|
8957
|
+
timestamp: ctx.clock.currentDate,
|
|
8958
|
+
data: {
|
|
8959
|
+
sound: rule.sound,
|
|
8960
|
+
volume: 1
|
|
8961
|
+
}
|
|
8962
|
+
});
|
|
8611
8963
|
}
|
|
8612
8964
|
};
|
|
8613
8965
|
var ButtonBehavior = class {
|
|
@@ -8737,6 +9089,34 @@ var WaitingToStartInjectorBehavior = class {
|
|
|
8737
9089
|
onDispose(_ctx) {
|
|
8738
9090
|
}
|
|
8739
9091
|
};
|
|
9092
|
+
var SOUND_PROFILE_METRIC = wodWikiCore.MetricType.Sound;
|
|
9093
|
+
function resolveSoundProfile(statements, options) {
|
|
9094
|
+
const blockProfileId = findBlockProfileId(statements);
|
|
9095
|
+
if (blockProfileId) {
|
|
9096
|
+
const match = findProfile(options?.profiles, blockProfileId);
|
|
9097
|
+
if (match) return match;
|
|
9098
|
+
}
|
|
9099
|
+
if (options?.noteProfileId) {
|
|
9100
|
+
const match = findProfile(options?.profiles, options.noteProfileId);
|
|
9101
|
+
if (match) return match;
|
|
9102
|
+
}
|
|
9103
|
+
return DEFAULT_SOUND_PROFILE;
|
|
9104
|
+
}
|
|
9105
|
+
function findBlockProfileId(statements) {
|
|
9106
|
+
for (const statement of statements) {
|
|
9107
|
+
const metric = statement.metrics.find((m) => m.type === SOUND_PROFILE_METRIC);
|
|
9108
|
+
if (!metric) continue;
|
|
9109
|
+
const value = metric.value;
|
|
9110
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
9111
|
+
return value.trim();
|
|
9112
|
+
}
|
|
9113
|
+
}
|
|
9114
|
+
return void 0;
|
|
9115
|
+
}
|
|
9116
|
+
function findProfile(profiles, id) {
|
|
9117
|
+
if (!profiles) return void 0;
|
|
9118
|
+
return profiles.find((profile) => profile.id === id);
|
|
9119
|
+
}
|
|
8740
9120
|
|
|
8741
9121
|
// src/runtime/blocks/RestBlock.ts
|
|
8742
9122
|
var RestBlock = class _RestBlock extends RuntimeBlock {
|
|
@@ -8747,7 +9127,7 @@ var RestBlock = class _RestBlock extends RuntimeBlock {
|
|
|
8747
9127
|
const restLabel = config.label ?? "Rest";
|
|
8748
9128
|
const blockKey = new wodWikiCore.BlockKey("rest");
|
|
8749
9129
|
const context = new BlockContext(runtime, blockKey.toString(), "Rest");
|
|
8750
|
-
const behaviors = _RestBlock.buildBehaviors(config);
|
|
9130
|
+
const behaviors = _RestBlock.buildBehaviors(config, runtime.options?.soundOptions);
|
|
8751
9131
|
super({
|
|
8752
9132
|
runtime,
|
|
8753
9133
|
sourceIds: [],
|
|
@@ -8762,7 +9142,7 @@ var RestBlock = class _RestBlock extends RuntimeBlock {
|
|
|
8762
9142
|
/**
|
|
8763
9143
|
* Builds the behavior list for a rest block.
|
|
8764
9144
|
*/
|
|
8765
|
-
static buildBehaviors(config) {
|
|
9145
|
+
static buildBehaviors(config, soundOptions) {
|
|
8766
9146
|
const behaviors = [];
|
|
8767
9147
|
const restLabel = config.label ?? "Rest";
|
|
8768
9148
|
behaviors.push(new ReportOutputBehavior({ label: restLabel }));
|
|
@@ -8781,18 +9161,8 @@ var RestBlock = class _RestBlock extends RuntimeBlock {
|
|
|
8781
9161
|
mode: "timer",
|
|
8782
9162
|
label: restLabel
|
|
8783
9163
|
}));
|
|
8784
|
-
behaviors.push(new
|
|
8785
|
-
|
|
8786
|
-
{
|
|
8787
|
-
sound: "rest-over",
|
|
8788
|
-
trigger: "complete"
|
|
8789
|
-
},
|
|
8790
|
-
{
|
|
8791
|
-
sound: "countdown-beep",
|
|
8792
|
-
trigger: "countdown",
|
|
8793
|
-
atSeconds: [3, 2, 1]
|
|
8794
|
-
}
|
|
8795
|
-
]
|
|
9164
|
+
behaviors.push(new SoundProfileBehavior({
|
|
9165
|
+
profile: resolveSoundProfile([], soundOptions)
|
|
8796
9166
|
}));
|
|
8797
9167
|
return behaviors;
|
|
8798
9168
|
}
|
|
@@ -8823,6 +9193,8 @@ var BlockBuilder = class {
|
|
|
8823
9193
|
blockType = "Block";
|
|
8824
9194
|
metrics;
|
|
8825
9195
|
sourceIds = [];
|
|
9196
|
+
/** The tags the block's statements referenced, resolved at compile time. */
|
|
9197
|
+
tagComposition = [];
|
|
8826
9198
|
/** Pending round config stored by asRepeater(), consumed by asContainer() */
|
|
8827
9199
|
pendingRoundConfig;
|
|
8828
9200
|
/** Declared exit intent — resolved into a single ExitBehavior in build(). */
|
|
@@ -8922,6 +9294,11 @@ var BlockBuilder = class {
|
|
|
8922
9294
|
this.sourceIds = ids;
|
|
8923
9295
|
return this;
|
|
8924
9296
|
}
|
|
9297
|
+
/** The tags the block's statements referenced, and what each carried. */
|
|
9298
|
+
setTagComposition(composition) {
|
|
9299
|
+
this.tagComposition = composition;
|
|
9300
|
+
return this;
|
|
9301
|
+
}
|
|
8925
9302
|
getLabel() {
|
|
8926
9303
|
return this.label;
|
|
8927
9304
|
}
|
|
@@ -9043,6 +9420,7 @@ var BlockBuilder = class {
|
|
|
9043
9420
|
const block = new RuntimeBlock({
|
|
9044
9421
|
runtime: this.runtime,
|
|
9045
9422
|
sourceIds: this.sourceIds,
|
|
9423
|
+
tagComposition: this.tagComposition,
|
|
9046
9424
|
behaviors: Array.from(this.behaviors.values()),
|
|
9047
9425
|
context: this.context,
|
|
9048
9426
|
key: this.key,
|
|
@@ -9132,6 +9510,11 @@ var JitCompiler = class {
|
|
|
9132
9510
|
return void 0;
|
|
9133
9511
|
}
|
|
9134
9512
|
const builder = new BlockBuilder(runtime);
|
|
9513
|
+
const tagComposition = [];
|
|
9514
|
+
for (const node of effectiveNodes) {
|
|
9515
|
+
collectTagReferences(node.metrics, tagComposition);
|
|
9516
|
+
}
|
|
9517
|
+
builder.setTagComposition(tagComposition);
|
|
9135
9518
|
for (const strategy of matchingStrategies) {
|
|
9136
9519
|
strategy.apply(builder, effectiveNodes, runtime);
|
|
9137
9520
|
}
|
|
@@ -10646,6 +11029,16 @@ var CalcEngine = class {
|
|
|
10646
11029
|
const fenced = (d) => !d.fences || d.fences.includes(deps.dialect);
|
|
10647
11030
|
this.segmentCalcs = registry.byScope("segment").filter(fenced);
|
|
10648
11031
|
this.workoutCalcs = registry.byScope("workout").filter(fenced);
|
|
11032
|
+
for (const tag of deps.tags?.list() ?? []) {
|
|
11033
|
+
const resolved = resolveTagContributions(deps.tags, tag.slug);
|
|
11034
|
+
for (const name2 of resolved.functions) {
|
|
11035
|
+
const carriers = this.tagCarriedFunctions.get(name2);
|
|
11036
|
+
if (carriers) carriers.add(tag.slug);
|
|
11037
|
+
else this.tagCarriedFunctions.set(name2, /* @__PURE__ */ new Set([tag.slug]));
|
|
11038
|
+
}
|
|
11039
|
+
}
|
|
11040
|
+
this.runTags = [...deps.activeTags ?? []];
|
|
11041
|
+
for (const tag of this.runTags) this.activatedTags.add(tag);
|
|
10649
11042
|
}
|
|
10650
11043
|
registry;
|
|
10651
11044
|
deps;
|
|
@@ -10659,6 +11052,43 @@ var CalcEngine = class {
|
|
|
10659
11052
|
* calls (segments arrive in stream order, live and in replay).
|
|
10660
11053
|
*/
|
|
10661
11054
|
lastSegmentEffort;
|
|
11055
|
+
/**
|
|
11056
|
+
* The tags that carry each function: function name → the tags that carry
|
|
11057
|
+
* it. A function no tag carries runs everywhere, as it always did.
|
|
11058
|
+
*/
|
|
11059
|
+
tagCarriedFunctions = /* @__PURE__ */ new Map();
|
|
11060
|
+
/** The tags applied anywhere in the run so far. */
|
|
11061
|
+
activatedTags = /* @__PURE__ */ new Set();
|
|
11062
|
+
/** The tags the run activates — a note-level bundle of attachments. */
|
|
11063
|
+
runTags;
|
|
11064
|
+
/**
|
|
11065
|
+
* Whether a calculation runs here.
|
|
11066
|
+
*
|
|
11067
|
+
* A function a tag carries runs only where one of the tags that carry it
|
|
11068
|
+
* applies; every other function runs as it always did.
|
|
11069
|
+
*/
|
|
11070
|
+
isActive(def, appliedTags) {
|
|
11071
|
+
const carriers = this.tagCarriedFunctions.get(def.id);
|
|
11072
|
+
if (!carriers) return true;
|
|
11073
|
+
for (const slug of carriers) {
|
|
11074
|
+
if (appliedTags.includes(slug)) return true;
|
|
11075
|
+
}
|
|
11076
|
+
return false;
|
|
11077
|
+
}
|
|
11078
|
+
/**
|
|
11079
|
+
* The tags applied to a segment: the tags its statement marked, plus the
|
|
11080
|
+
* tags the run activates — a note-level tag applies to every segment of
|
|
11081
|
+
* the note it belongs to.
|
|
11082
|
+
*
|
|
11083
|
+
* The applied tags are recorded, so a workout-scope function the tag
|
|
11084
|
+
* carries runs at the end of the workout.
|
|
11085
|
+
*/
|
|
11086
|
+
appliedTags(output) {
|
|
11087
|
+
const slugs = tagMarkerSlugs(output.metrics.rawMetrics);
|
|
11088
|
+
for (const slug of slugs) this.activatedTags.add(slug);
|
|
11089
|
+
if (this.runTags.length === 0) return slugs;
|
|
11090
|
+
return [...slugs, ...this.runTags.filter((slug) => !slugs.includes(slug))];
|
|
11091
|
+
}
|
|
10662
11092
|
// ── Phase 1: segment scope ─────────────────────────────────────────────
|
|
10663
11093
|
process(output) {
|
|
10664
11094
|
if (output.outputType !== "segment" || !output.isLeaf) return output;
|
|
@@ -10674,11 +11104,13 @@ var CalcEngine = class {
|
|
|
10674
11104
|
if (discipline) effortTags.effortDiscipline = discipline;
|
|
10675
11105
|
if (intensityTier) effortTags.effortIntensityTier = intensityTier;
|
|
10676
11106
|
}
|
|
10677
|
-
const climbGrade =
|
|
11107
|
+
const climbGrade = output.metrics.rawMetrics.find((m) => m.type === "climb-grade")?.value;
|
|
10678
11108
|
if (climbGrade && typeof climbGrade === "object" && "raw" in climbGrade) {
|
|
10679
11109
|
effortTags.grade = String(climbGrade.raw);
|
|
10680
11110
|
}
|
|
11111
|
+
const appliedTags = this.appliedTags(output);
|
|
10681
11112
|
for (const def of this.segmentCalcs) {
|
|
11113
|
+
if (!this.isActive(def, appliedTags)) continue;
|
|
10682
11114
|
const ctx = this.makeContext(atoms);
|
|
10683
11115
|
if (!this.applicable(def.whenAst, ctx)) continue;
|
|
10684
11116
|
const variant = this.selectVariant(def, ctx);
|
|
@@ -10720,7 +11152,15 @@ var CalcEngine = class {
|
|
|
10720
11152
|
// ── Phase 2: workout scope ─────────────────────────────────────────────
|
|
10721
11153
|
summarize(outputs) {
|
|
10722
11154
|
const projections = [];
|
|
11155
|
+
for (const output of outputs) {
|
|
11156
|
+
if (output.outputType !== "segment") continue;
|
|
11157
|
+
for (const slug of tagMarkerSlugs(output.metrics.rawMetrics)) {
|
|
11158
|
+
this.activatedTags.add(slug);
|
|
11159
|
+
}
|
|
11160
|
+
}
|
|
11161
|
+
const appliedTags = [...this.activatedTags];
|
|
10723
11162
|
for (const def of this.workoutCalcs) {
|
|
11163
|
+
if (!this.isActive(def, appliedTags)) continue;
|
|
10724
11164
|
if (def.output?.isGrouped) {
|
|
10725
11165
|
projections.push(...this.summarizeGrouped(def, outputs));
|
|
10726
11166
|
} else {
|
|
@@ -11878,7 +12318,13 @@ function createCalcStack(options) {
|
|
|
11878
12318
|
}
|
|
11879
12319
|
function createCalcEngine(dialect, options) {
|
|
11880
12320
|
const stack = createCalcStack(options);
|
|
11881
|
-
return new CalcEngine(stack.registry, {
|
|
12321
|
+
return new CalcEngine(stack.registry, {
|
|
12322
|
+
dialect,
|
|
12323
|
+
lookups: stack.lookups,
|
|
12324
|
+
userProfile: options.userProfile,
|
|
12325
|
+
tags: options.tags,
|
|
12326
|
+
activeTags: options.activeTags
|
|
12327
|
+
});
|
|
11882
12328
|
}
|
|
11883
12329
|
|
|
11884
12330
|
// src/analytics/StandardAnalyticsProfile.ts
|
|
@@ -11893,7 +12339,9 @@ var StandardAnalyticsProfile = class {
|
|
|
11893
12339
|
const calcEngine = createCalcEngine(context.dialect, {
|
|
11894
12340
|
effortResolver: context.analyticsContext.effortResolver,
|
|
11895
12341
|
userProfile: context.userProfile,
|
|
11896
|
-
calcs: context.calcs
|
|
12342
|
+
calcs: context.calcs,
|
|
12343
|
+
tags: context.tags,
|
|
12344
|
+
activeTags: context.activeTags
|
|
11897
12345
|
});
|
|
11898
12346
|
realtime.push(calcEngine);
|
|
11899
12347
|
summary.push(calcEngine);
|
|
@@ -11945,7 +12393,9 @@ function createAnalyticsEngineForBlock(block, options) {
|
|
|
11945
12393
|
scriptMetricTypes,
|
|
11946
12394
|
analyticsContext,
|
|
11947
12395
|
userProfile: options?.userProfile,
|
|
11948
|
-
calcs: options?.calcs
|
|
12396
|
+
calcs: options?.calcs,
|
|
12397
|
+
tags: options?.tags,
|
|
12398
|
+
activeTags: options?.activeTags
|
|
11949
12399
|
};
|
|
11950
12400
|
const profile = new StandardAnalyticsProfile();
|
|
11951
12401
|
const { realtime, summary } = profile.build(context);
|
|
@@ -12322,13 +12772,6 @@ var AmrapLogicStrategy = class {
|
|
|
12322
12772
|
mode: "countdown",
|
|
12323
12773
|
label
|
|
12324
12774
|
}));
|
|
12325
|
-
builder.addBehavior(new SoundCueBehavior({
|
|
12326
|
-
cues: [
|
|
12327
|
-
{ sound: "start-beep", trigger: "mount" },
|
|
12328
|
-
{ sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
|
|
12329
|
-
{ sound: "timer-complete", trigger: "complete" }
|
|
12330
|
-
]
|
|
12331
|
-
}));
|
|
12332
12775
|
}
|
|
12333
12776
|
};
|
|
12334
12777
|
var IntervalLogicStrategy = class {
|
|
@@ -12386,13 +12829,6 @@ var IntervalLogicStrategy = class {
|
|
|
12386
12829
|
mode: "countdown",
|
|
12387
12830
|
label
|
|
12388
12831
|
}));
|
|
12389
|
-
builder.addBehavior(new SoundCueBehavior({
|
|
12390
|
-
cues: [
|
|
12391
|
-
{ sound: "interval-start", trigger: "mount" },
|
|
12392
|
-
{ sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
|
|
12393
|
-
{ sound: "interval-complete", trigger: "complete" }
|
|
12394
|
-
]
|
|
12395
|
-
}));
|
|
12396
12832
|
const hasResistance = statements.some((s) => s.metrics.some((m) => m.type === wodWikiCore.MetricType.Resistance));
|
|
12397
12833
|
const hasDistance = statements.some((s) => s.metrics.some((m) => m.type === wodWikiCore.MetricType.Distance));
|
|
12398
12834
|
const promotions = [
|
|
@@ -12585,14 +13021,6 @@ var GenericTimerStrategy = class {
|
|
|
12585
13021
|
} else {
|
|
12586
13022
|
builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
|
|
12587
13023
|
}
|
|
12588
|
-
if (isCountdown) {
|
|
12589
|
-
builder.addBehavior(new SoundCueBehavior({
|
|
12590
|
-
cues: [
|
|
12591
|
-
{ sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
|
|
12592
|
-
{ sound: "timer-complete", trigger: "complete" }
|
|
12593
|
-
]
|
|
12594
|
-
}));
|
|
12595
|
-
}
|
|
12596
13024
|
builder.addBehavior(new LabelingBehavior({
|
|
12597
13025
|
mode: durationMs ? "countdown" : "clock",
|
|
12598
13026
|
label
|
|
@@ -12653,29 +13081,17 @@ var SoundStrategy = class {
|
|
|
12653
13081
|
priority = 20;
|
|
12654
13082
|
id = "sound";
|
|
12655
13083
|
match(statements, _runtime) {
|
|
12656
|
-
return statements
|
|
13084
|
+
return statements.length > 0;
|
|
12657
13085
|
}
|
|
12658
|
-
apply(builder,
|
|
12659
|
-
if (builder.hasBehavior(
|
|
13086
|
+
apply(builder, statements, runtime) {
|
|
13087
|
+
if (builder.hasBehavior(SoundProfileBehavior)) {
|
|
12660
13088
|
return;
|
|
12661
13089
|
}
|
|
12662
|
-
|
|
12663
|
-
if (countdown) {
|
|
12664
|
-
builder.addBehavior(new SoundCueBehavior({
|
|
12665
|
-
cues: [
|
|
12666
|
-
{ sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
|
|
12667
|
-
{ sound: "timer-complete", trigger: "complete" }
|
|
12668
|
-
]
|
|
12669
|
-
}));
|
|
13090
|
+
if (!builder.hasTimerBehavior()) {
|
|
12670
13091
|
return;
|
|
12671
13092
|
}
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
cues: [
|
|
12675
|
-
{ sound: "start-beep", trigger: "mount" }
|
|
12676
|
-
]
|
|
12677
|
-
}));
|
|
12678
|
-
}
|
|
13093
|
+
const profile = resolveSoundProfile(statements, runtime.options?.soundOptions);
|
|
13094
|
+
builder.addBehavior(new SoundProfileBehavior({ profile }));
|
|
12679
13095
|
}
|
|
12680
13096
|
};
|
|
12681
13097
|
|
|
@@ -12708,20 +13124,6 @@ var NextEvent = class _NextEvent {
|
|
|
12708
13124
|
};
|
|
12709
13125
|
}
|
|
12710
13126
|
};
|
|
12711
|
-
|
|
12712
|
-
// src/runtime/models/SoundModels.ts
|
|
12713
|
-
var PREDEFINED_SOUNDS = {
|
|
12714
|
-
/** Short single beep - good for countdown ticks */
|
|
12715
|
-
BEEP: "beep",
|
|
12716
|
-
/** Long buzz - good for timer completion */
|
|
12717
|
-
BUZZER: "buzzer",
|
|
12718
|
-
/** Pleasant chime - good for milestones */
|
|
12719
|
-
CHIME: "chime",
|
|
12720
|
-
/** Clock tick - good for second-by-second countdown */
|
|
12721
|
-
TICK: "tick",
|
|
12722
|
-
/** Ascending sequence - good for timer start */
|
|
12723
|
-
START: "start"
|
|
12724
|
-
};
|
|
12725
13127
|
function metricsToLabel(metrics) {
|
|
12726
13128
|
if (metrics instanceof wodWikiCore.MetricContainer) {
|
|
12727
13129
|
return metricsToLabel(metrics.toArray());
|
|
@@ -13002,40 +13404,6 @@ var UnregisterEventHandlerAction = class {
|
|
|
13002
13404
|
}
|
|
13003
13405
|
};
|
|
13004
13406
|
|
|
13005
|
-
// src/runtime/actions/audio/PlaySoundAction.ts
|
|
13006
|
-
var PlaySoundAction = class {
|
|
13007
|
-
constructor(sound, volume = 1, cueId, now = wallClockNow) {
|
|
13008
|
-
this.sound = sound;
|
|
13009
|
-
this.volume = volume;
|
|
13010
|
-
this.cueId = cueId;
|
|
13011
|
-
this.now = now;
|
|
13012
|
-
if (volume < 0 || volume > 1) {
|
|
13013
|
-
throw new RangeError(`Volume must be between 0.0 and 1.0, got: ${volume}`);
|
|
13014
|
-
}
|
|
13015
|
-
}
|
|
13016
|
-
sound;
|
|
13017
|
-
volume;
|
|
13018
|
-
cueId;
|
|
13019
|
-
now;
|
|
13020
|
-
type = "play-sound";
|
|
13021
|
-
/**
|
|
13022
|
-
* Executes the sound playback by emitting a 'sound:play' event.
|
|
13023
|
-
* UI components should register handlers for this event to play audio.
|
|
13024
|
-
*/
|
|
13025
|
-
do(runtime) {
|
|
13026
|
-
const event = {
|
|
13027
|
-
name: "sound:play",
|
|
13028
|
-
timestamp: this.now.now(),
|
|
13029
|
-
data: {
|
|
13030
|
-
sound: this.sound,
|
|
13031
|
-
volume: this.volume,
|
|
13032
|
-
cueId: this.cueId
|
|
13033
|
-
}
|
|
13034
|
-
};
|
|
13035
|
-
return runtime.eventBus.dispatch(event, runtime);
|
|
13036
|
-
}
|
|
13037
|
-
};
|
|
13038
|
-
|
|
13039
13407
|
// src/runtime/utils/MemoryUtils.ts
|
|
13040
13408
|
function searchStackMemory(runtime, criteria) {
|
|
13041
13409
|
const results = [];
|
|
@@ -14878,6 +15246,7 @@ exports.CrossFitDialect = CrossFitDialect;
|
|
|
14878
15246
|
exports.DAY = DAY;
|
|
14879
15247
|
exports.DEFAULT_RESOLVER_OPTIONS = DEFAULT_RESOLVER_OPTIONS;
|
|
14880
15248
|
exports.DEFAULT_RUNTIME_OPTIONS = DEFAULT_RUNTIME_OPTIONS;
|
|
15249
|
+
exports.DEFAULT_SOUND_PROFILE = DEFAULT_SOUND_PROFILE;
|
|
14881
15250
|
exports.DEFAULT_UNRESOLVED_EFFORT_MET = DEFAULT_UNRESOLVED_EFFORT_MET;
|
|
14882
15251
|
exports.DIM_COUNT = DIM_COUNT;
|
|
14883
15252
|
exports.DIM_ENERGY = DIM_ENERGY;
|
|
@@ -14912,6 +15281,7 @@ exports.LabelingBehavior = LabelingBehavior;
|
|
|
14912
15281
|
exports.LocalEventProvider = LocalEventProvider;
|
|
14913
15282
|
exports.LocalRuntimeSubscription = LocalRuntimeSubscription;
|
|
14914
15283
|
exports.LookupRegistry = LookupRegistry;
|
|
15284
|
+
exports.MAX_TAG_CONTRIBUTION_DEPTH = MAX_TAG_CONTRIBUTION_DEPTH;
|
|
14915
15285
|
exports.MdTimerRuntime = MdTimerRuntime;
|
|
14916
15286
|
exports.MemoryLocation = MemoryLocation;
|
|
14917
15287
|
exports.MemoryTypeEnum = MemoryTypeEnum;
|
|
@@ -14921,9 +15291,7 @@ exports.NextAction = NextAction;
|
|
|
14921
15291
|
exports.NextEvent = NextEvent;
|
|
14922
15292
|
exports.NextEventHandler = NextEventHandler;
|
|
14923
15293
|
exports.OutputEmitter = OutputEmitter;
|
|
14924
|
-
exports.PREDEFINED_SOUNDS = PREDEFINED_SOUNDS;
|
|
14925
15294
|
exports.PRODUCTION_STRATEGIES = PRODUCTION_STRATEGIES;
|
|
14926
|
-
exports.PlaySoundAction = PlaySoundAction;
|
|
14927
15295
|
exports.PopBlockAction = PopBlockAction;
|
|
14928
15296
|
exports.PushBlockAction = PushBlockAction;
|
|
14929
15297
|
exports.PushRestBlockAction = PushRestBlockAction;
|
|
@@ -14945,6 +15313,8 @@ exports.RuntimeLogger = RuntimeLogger;
|
|
|
14945
15313
|
exports.RuntimeMemory = RuntimeMemory;
|
|
14946
15314
|
exports.RuntimeObservers = RuntimeObservers;
|
|
14947
15315
|
exports.RuntimeStack = RuntimeStack;
|
|
15316
|
+
exports.SOUND_PLAY_EVENT = SOUND_PLAY_EVENT;
|
|
15317
|
+
exports.SOUND_PROFILE_METRIC = SOUND_PROFILE_METRIC;
|
|
14948
15318
|
exports.STANDARD_UNITS = STANDARD_UNITS;
|
|
14949
15319
|
exports.STORE_CALCS = STORE_CALCS;
|
|
14950
15320
|
exports.STREAM_ATOMS = STREAM_ATOMS;
|
|
@@ -14952,13 +15322,14 @@ exports.ScriptRuntime = ScriptRuntime;
|
|
|
14952
15322
|
exports.SessionRootBlock = SessionRootBlock;
|
|
14953
15323
|
exports.SessionRootStrategy = SessionRootStrategy;
|
|
14954
15324
|
exports.SnapshotClock = SnapshotClock;
|
|
14955
|
-
exports.
|
|
15325
|
+
exports.SoundProfileBehavior = SoundProfileBehavior;
|
|
14956
15326
|
exports.SoundStrategy = SoundStrategy;
|
|
14957
15327
|
exports.SpanTrackingBehavior = SpanTrackingBehavior;
|
|
14958
15328
|
exports.StandardAnalyticsProfile = StandardAnalyticsProfile;
|
|
14959
15329
|
exports.StartSessionAction = StartSessionAction;
|
|
14960
15330
|
exports.StartWorkoutAction = StartWorkoutAction;
|
|
14961
15331
|
exports.SubscriptionManager = SubscriptionManager;
|
|
15332
|
+
exports.TAG_MARKER_PREFIX = TAG_MARKER_PREFIX;
|
|
14962
15333
|
exports.ThrowError = ThrowError;
|
|
14963
15334
|
exports.ThrowErrorAction = ThrowErrorAction;
|
|
14964
15335
|
exports.TimeSpan = TimeSpan;
|
|
@@ -15035,6 +15406,7 @@ exports.extractStatementsRaw = extractStatementsRaw;
|
|
|
15035
15406
|
exports.extractSyntaxFacts = extractSyntaxFacts;
|
|
15036
15407
|
exports.filterTagsByVisibility = filterTagsByVisibility;
|
|
15037
15408
|
exports.findBestFuzzyMatch = findBestFuzzyMatch;
|
|
15409
|
+
exports.findTagContributionCycles = findTagContributionCycles;
|
|
15038
15410
|
exports.findUnresolvedChoices = findUnresolvedChoices;
|
|
15039
15411
|
exports.fixtureBackSquat = fixtureBackSquat;
|
|
15040
15412
|
exports.fixtureBurpee = fixtureBurpee;
|
|
@@ -15048,6 +15420,7 @@ exports.formatDuration = formatDuration;
|
|
|
15048
15420
|
exports.formatRuntimeLogLine = formatRuntimeLogLine;
|
|
15049
15421
|
exports.formatRuntimeLogRule = formatRuntimeLogRule;
|
|
15050
15422
|
exports.formatRuntimeTimestamp = formatRuntimeTimestamp;
|
|
15423
|
+
exports.formatSoundTrigger = formatSoundTrigger;
|
|
15051
15424
|
exports.fragmentsToLabel = metricsToLabel;
|
|
15052
15425
|
exports.frozenNow = frozenNow;
|
|
15053
15426
|
exports.fullFixtureSet = fullFixtureSet;
|
|
@@ -15087,6 +15460,7 @@ exports.parseEquipmentFile = parseEquipmentFile;
|
|
|
15087
15460
|
exports.parseExpression = parseExpression;
|
|
15088
15461
|
exports.parseMetricLine = parseMetricLine;
|
|
15089
15462
|
exports.parseScript = parseScript;
|
|
15463
|
+
exports.parseSoundTrigger = parseSoundTrigger;
|
|
15090
15464
|
exports.printCalculation = printCalculation;
|
|
15091
15465
|
exports.readNestedSection = readNestedSection;
|
|
15092
15466
|
exports.readTimer = readTimer;
|
|
@@ -15095,14 +15469,21 @@ exports.renderMetric = renderMetric;
|
|
|
15095
15469
|
exports.resolveDominantOrigin = resolveDominantOrigin;
|
|
15096
15470
|
exports.resolveEntityRank = resolveEntityRank;
|
|
15097
15471
|
exports.resolveEntityTier = resolveEntityTier;
|
|
15472
|
+
exports.resolveProfileRules = resolveProfileRules;
|
|
15473
|
+
exports.resolveSoundProfile = resolveSoundProfile;
|
|
15098
15474
|
exports.resolveSourceRank = resolveSourceRank;
|
|
15099
15475
|
exports.resolveSourceTier = resolveSourceTier;
|
|
15476
|
+
exports.resolveTagContributions = resolveTagContributions;
|
|
15100
15477
|
exports.slugifyRefLabel = slugifyRefLabel;
|
|
15101
15478
|
exports.startSpan = startSpan;
|
|
15102
15479
|
exports.str = str;
|
|
15103
15480
|
exports.strategyRegistry = strategyRegistry;
|
|
15104
15481
|
exports.subDim = subDim;
|
|
15105
15482
|
exports.summaryProcessorRegistry = summaryProcessorRegistry;
|
|
15483
|
+
exports.tagContributionMetrics = tagContributionMetrics;
|
|
15484
|
+
exports.tagMarkerSlug = tagMarkerSlug;
|
|
15485
|
+
exports.tagMarkerSlugs = tagMarkerSlugs;
|
|
15486
|
+
exports.tagMarkerType = tagMarkerType;
|
|
15106
15487
|
exports.toStoredOutputStatement = toStoredOutputStatement;
|
|
15107
15488
|
exports.truthy = truthy;
|
|
15108
15489
|
exports.validateOutputUnit = validateOutputUnit;
|