@bitcobblers/wod-wiki-lang 0.43.2153 → 0.44.2156
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.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { wallClockNow, formatDurationSmart } from './chunk-PFQDVQQO.mjs';
|
|
2
2
|
export { createBrowserRuntimeNow, frozenNow, getRuntimeNowMs, wallClockNow } from './chunk-PFQDVQQO.mjs';
|
|
3
|
-
import { MetricType, MetricContainer, BlockKey, REFERENCE_METADATA_KEY, ParsedCodeStatement, getReferenceMetadata, OutputStatement, valueKindOf, normalizeFieldPath } from '@bitcobblers/wod-wiki-core';
|
|
3
|
+
import { MetricType, MetricContainer, BlockKey, REFERENCE_METADATA_KEY, ParsedCodeStatement, TAG_REFERENCE_METADATA_KEY, getReferenceMetadata, OutputStatement, valueKindOf, normalizeFieldPath, getTagReference } from '@bitcobblers/wod-wiki-core';
|
|
4
4
|
import { LRParser } from '@lezer/lr';
|
|
5
5
|
import { LRLanguage, LanguageSupport } from '@codemirror/language';
|
|
6
6
|
import { NodeProp } from '@lezer/common';
|
|
@@ -265,6 +265,125 @@ function computeColumnLabel(type) {
|
|
|
265
265
|
const normalized = key.toLowerCase();
|
|
266
266
|
return COLUMN_LABEL_MAP[normalized] ?? humanizeMetricKey(key);
|
|
267
267
|
}
|
|
268
|
+
function tagReferenceFor(tagIndex, slug) {
|
|
269
|
+
const resolved = resolveTagContributions(tagIndex, slug);
|
|
270
|
+
const sound = resolved.metrics.find(
|
|
271
|
+
(contribution) => contribution.type === MetricType.Sound && typeof contribution.value === "string"
|
|
272
|
+
);
|
|
273
|
+
return {
|
|
274
|
+
slug,
|
|
275
|
+
label: slug,
|
|
276
|
+
...resolved.functions.length > 0 ? { functions: [...resolved.functions] } : {},
|
|
277
|
+
...sound ? { soundProfileId: sound.value } : {}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function collectTagReferences(metrics, into = []) {
|
|
281
|
+
for (const metric of metrics) {
|
|
282
|
+
const reference = getTagReference(metric.metadata);
|
|
283
|
+
if (!reference) continue;
|
|
284
|
+
if (into.some((existing) => existing.slug === reference.slug)) continue;
|
|
285
|
+
into.push(reference);
|
|
286
|
+
}
|
|
287
|
+
return into;
|
|
288
|
+
}
|
|
289
|
+
var TAG_MARKER_PREFIX = "tag.";
|
|
290
|
+
var MAX_TAG_CONTRIBUTION_DEPTH = 8;
|
|
291
|
+
var EMPTY = { metrics: [], functions: [], tags: [] };
|
|
292
|
+
function resolveTagContributions(tagIndex, slug) {
|
|
293
|
+
if (!tagIndex) return EMPTY;
|
|
294
|
+
const metrics = [];
|
|
295
|
+
const functions = [];
|
|
296
|
+
const tags2 = [];
|
|
297
|
+
const seenFunctions = /* @__PURE__ */ new Set();
|
|
298
|
+
const visited = /* @__PURE__ */ new Set();
|
|
299
|
+
const walk = (currentSlug, depth) => {
|
|
300
|
+
if (depth > MAX_TAG_CONTRIBUTION_DEPTH) return;
|
|
301
|
+
if (visited.has(currentSlug)) return;
|
|
302
|
+
const definition = tagIndex.resolve(currentSlug);
|
|
303
|
+
if (!definition) return;
|
|
304
|
+
visited.add(currentSlug);
|
|
305
|
+
tags2.push(currentSlug);
|
|
306
|
+
for (const contribution of definition.contributions) {
|
|
307
|
+
if (contribution.kind === "metric") metrics.push(contribution);
|
|
308
|
+
else if (contribution.kind === "function") {
|
|
309
|
+
if (!seenFunctions.has(contribution.name)) {
|
|
310
|
+
seenFunctions.add(contribution.name);
|
|
311
|
+
functions.push(contribution.name);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
for (const contribution of definition.contributions) {
|
|
316
|
+
if (contribution.kind === "tag") walk(contribution.slug, depth + 1);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
walk(slug, 0);
|
|
320
|
+
return { metrics, functions, tags: tags2 };
|
|
321
|
+
}
|
|
322
|
+
function findTagContributionCycles(tagIndex) {
|
|
323
|
+
if (!tagIndex) return [];
|
|
324
|
+
const cycles = [];
|
|
325
|
+
const seen = /* @__PURE__ */ new Set();
|
|
326
|
+
const walk = (slug, path, onPath) => {
|
|
327
|
+
if (onPath.has(slug)) {
|
|
328
|
+
const start = path.indexOf(slug);
|
|
329
|
+
if (start >= 0) {
|
|
330
|
+
const cycle = [...path.slice(start), slug];
|
|
331
|
+
const key = cycleKey(cycle);
|
|
332
|
+
if (!seen.has(key)) {
|
|
333
|
+
seen.add(key);
|
|
334
|
+
cycles.push(cycle);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const definition = tagIndex.resolve(slug);
|
|
340
|
+
if (!definition) return;
|
|
341
|
+
const nextPath = [...path, slug];
|
|
342
|
+
const nextOnPath = new Set(onPath);
|
|
343
|
+
nextOnPath.add(slug);
|
|
344
|
+
for (const contribution of definition.contributions) {
|
|
345
|
+
if (contribution.kind === "tag") walk(contribution.slug, nextPath, nextOnPath);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
for (const definition of tagIndex.list()) {
|
|
349
|
+
walk(definition.slug, [], /* @__PURE__ */ new Set());
|
|
350
|
+
}
|
|
351
|
+
return cycles;
|
|
352
|
+
}
|
|
353
|
+
function cycleKey(cycle) {
|
|
354
|
+
const nodes = cycle.slice(0, -1);
|
|
355
|
+
if (nodes.length === 0) return "";
|
|
356
|
+
let smallest = 0;
|
|
357
|
+
for (let index = 1; index < nodes.length; index++) {
|
|
358
|
+
if (nodes[index] < nodes[smallest]) smallest = index;
|
|
359
|
+
}
|
|
360
|
+
return [...nodes.slice(smallest), ...nodes.slice(0, smallest)].join(">");
|
|
361
|
+
}
|
|
362
|
+
function tagMarkerType(slug) {
|
|
363
|
+
return `${TAG_MARKER_PREFIX}${slug}`;
|
|
364
|
+
}
|
|
365
|
+
function tagMarkerSlug(type) {
|
|
366
|
+
if (!type.startsWith(TAG_MARKER_PREFIX)) return void 0;
|
|
367
|
+
const slug = type.slice(TAG_MARKER_PREFIX.length);
|
|
368
|
+
return slug.length > 0 ? slug : void 0;
|
|
369
|
+
}
|
|
370
|
+
function tagMarkerSlugs(metrics) {
|
|
371
|
+
const slugs = [];
|
|
372
|
+
for (const metric of metrics) {
|
|
373
|
+
const slug = tagMarkerSlug(metric.type);
|
|
374
|
+
if (slug !== void 0 && !slugs.includes(slug)) slugs.push(slug);
|
|
375
|
+
}
|
|
376
|
+
return slugs;
|
|
377
|
+
}
|
|
378
|
+
function tagContributionMetrics(contributions, origin = "parser") {
|
|
379
|
+
return contributions.map((contribution) => ({
|
|
380
|
+
type: contribution.type,
|
|
381
|
+
value: contribution.value,
|
|
382
|
+
origin,
|
|
383
|
+
image: String(contribution.value),
|
|
384
|
+
...contribution.unit ? { unit: contribution.unit } : {}
|
|
385
|
+
}));
|
|
386
|
+
}
|
|
268
387
|
|
|
269
388
|
// src/metrics/presentation/MetricPresentationPolicy.ts
|
|
270
389
|
function isRestMetric(metric) {
|
|
@@ -328,6 +447,7 @@ function createPolicy() {
|
|
|
328
447
|
}
|
|
329
448
|
const t2 = metric.type;
|
|
330
449
|
if ((t2 === MetricType.Sound || t2 === "sound") && surface !== "debug") return true;
|
|
450
|
+
if (t2.startsWith(TAG_MARKER_PREFIX) && surface !== "debug") return true;
|
|
331
451
|
if (surface === "review-grid-column") {
|
|
332
452
|
if (t2 === MetricType.Elapsed || t2 === "elapsed" || t2 === MetricType.Total || t2 === "total") return true;
|
|
333
453
|
if (t2 === MetricType.System || t2 === "system") return true;
|
|
@@ -1465,15 +1585,16 @@ var TextMetric = class {
|
|
|
1465
1585
|
origin = "parser";
|
|
1466
1586
|
type = MetricType.Text;
|
|
1467
1587
|
};
|
|
1468
|
-
|
|
1469
|
-
// src/parser/semantic-classifier.ts
|
|
1470
1588
|
var PROPERTY_KEY_TO_METRIC_TYPE = {
|
|
1471
1589
|
rpe: MetricType.SessionRPE,
|
|
1472
1590
|
rir: MetricType.RIR,
|
|
1473
1591
|
intensity: MetricType.Intensity,
|
|
1474
1592
|
load: MetricType.Load,
|
|
1475
1593
|
volume: MetricType.Volume,
|
|
1476
|
-
work: MetricType.Work
|
|
1594
|
+
work: MetricType.Work,
|
|
1595
|
+
// `sound: classic` names a sound profile, the same way a `#sound-classic`
|
|
1596
|
+
// tag reference does.
|
|
1597
|
+
sound: MetricType.Sound
|
|
1477
1598
|
};
|
|
1478
1599
|
var STRUCTURAL_EFFORT_WORDS = /* @__PURE__ */ new Set(["rest", "amrap", "emom", "tabata", "for time"]);
|
|
1479
1600
|
var STANDARD_UNITS2 = UnitRegistry.standard();
|
|
@@ -1490,9 +1611,25 @@ function referenceMetadata(resolution) {
|
|
|
1490
1611
|
function classifyStatements(facts, references) {
|
|
1491
1612
|
return facts.statements.map((fact) => {
|
|
1492
1613
|
const statement = new ParsedCodeStatement();
|
|
1493
|
-
|
|
1614
|
+
const slots = [];
|
|
1494
1615
|
for (const primitive of fact.primitives) {
|
|
1495
|
-
|
|
1616
|
+
slots.push(...classifySlots(primitive, references));
|
|
1617
|
+
}
|
|
1618
|
+
const explicitTypes = /* @__PURE__ */ new Set();
|
|
1619
|
+
for (const slot of slots) {
|
|
1620
|
+
if (slot.kind === "explicit") explicitTypes.add(slot.pair.metrics.type);
|
|
1621
|
+
}
|
|
1622
|
+
const contributedTypes = /* @__PURE__ */ new Set();
|
|
1623
|
+
const metricPairs = [];
|
|
1624
|
+
for (const slot of slots) {
|
|
1625
|
+
if (slot.kind !== "contributed") {
|
|
1626
|
+
metricPairs.push(slot.pair);
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
const type = slot.pair.metrics.type;
|
|
1630
|
+
if (explicitTypes.has(type) || contributedTypes.has(type)) continue;
|
|
1631
|
+
contributedTypes.add(type);
|
|
1632
|
+
metricPairs.push(slot.pair);
|
|
1496
1633
|
}
|
|
1497
1634
|
const mergedPairs = mergeFragments(metricPairs);
|
|
1498
1635
|
const finalPairs = references ? applyReferenceResolution(mergedPairs, fact, references) : mergedPairs;
|
|
@@ -1509,6 +1646,21 @@ function classifyStatements(facts, references) {
|
|
|
1509
1646
|
return statement;
|
|
1510
1647
|
});
|
|
1511
1648
|
}
|
|
1649
|
+
function equipmentMeta(resolution, authored, meta2) {
|
|
1650
|
+
if (resolution.matched !== "split" || !resolution.remaining) return meta2;
|
|
1651
|
+
const prefixEnd = authored.trimEnd().length - resolution.remaining.length;
|
|
1652
|
+
if (prefixEnd <= 0) return meta2;
|
|
1653
|
+
const raw = meta2.raw.slice(0, prefixEnd).trimEnd();
|
|
1654
|
+
const length = raw.length;
|
|
1655
|
+
if (length <= 0) return meta2;
|
|
1656
|
+
return {
|
|
1657
|
+
...meta2,
|
|
1658
|
+
endOffset: meta2.startOffset + length,
|
|
1659
|
+
length,
|
|
1660
|
+
columnEnd: meta2.columnStart + length,
|
|
1661
|
+
raw
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1512
1664
|
function applyReferenceResolution(pairs, fact, references) {
|
|
1513
1665
|
const { index, defaults } = references;
|
|
1514
1666
|
const result = [];
|
|
@@ -1533,7 +1685,10 @@ function applyReferenceResolution(pairs, fact, references) {
|
|
|
1533
1685
|
diagnostics: resolution.diagnostics
|
|
1534
1686
|
}
|
|
1535
1687
|
),
|
|
1536
|
-
|
|
1688
|
+
// The equipment is the prefix of the authored label; the effort is
|
|
1689
|
+
// what follows. Marking the whole phrase for both would let one mark
|
|
1690
|
+
// hide the other.
|
|
1691
|
+
meta: equipmentMeta(resolution, authored, meta2)
|
|
1537
1692
|
});
|
|
1538
1693
|
}
|
|
1539
1694
|
};
|
|
@@ -1663,14 +1818,7 @@ function classifyPrimitive(primitive) {
|
|
|
1663
1818
|
case "effort":
|
|
1664
1819
|
return [{ metrics: new EffortMetric(primitive.raw), meta: primitive.meta }];
|
|
1665
1820
|
case "property": {
|
|
1666
|
-
|
|
1667
|
-
return [{
|
|
1668
|
-
metrics: new PropertyMetric(primitive.key, primitive.value, {
|
|
1669
|
-
type: metricType,
|
|
1670
|
-
image: primitive.raw
|
|
1671
|
-
}),
|
|
1672
|
-
meta: primitive.meta
|
|
1673
|
-
}];
|
|
1821
|
+
return [propertyPair(primitive)];
|
|
1674
1822
|
}
|
|
1675
1823
|
case "metric_object": {
|
|
1676
1824
|
const pairs = primitive.pairs;
|
|
@@ -1690,6 +1838,85 @@ function classifyPrimitive(primitive) {
|
|
|
1690
1838
|
return [];
|
|
1691
1839
|
}
|
|
1692
1840
|
}
|
|
1841
|
+
function classifySlots(primitive, references) {
|
|
1842
|
+
if (primitive.kind === "property" && primitive.tagRefs !== void 0) {
|
|
1843
|
+
return propertyTagSlots(primitive, references);
|
|
1844
|
+
}
|
|
1845
|
+
if (primitive.kind === "tag_ref") {
|
|
1846
|
+
return fragmentTagSlots(primitive, references);
|
|
1847
|
+
}
|
|
1848
|
+
return classifyPrimitive(primitive).map((pair) => ({ kind: "explicit", pair }));
|
|
1849
|
+
}
|
|
1850
|
+
function propertyTagSlots(primitive, references) {
|
|
1851
|
+
const { slots, resolved } = referenceSlots(primitive.tagRefs ?? [], primitive.meta, references);
|
|
1852
|
+
if (resolved) return slots;
|
|
1853
|
+
return [{ kind: "explicit", pair: propertyPair(primitive) }];
|
|
1854
|
+
}
|
|
1855
|
+
function fragmentTagSlots(primitive, references) {
|
|
1856
|
+
const { slots, resolved } = referenceSlots([primitive.slug], primitive.meta, references);
|
|
1857
|
+
if (resolved) return slots;
|
|
1858
|
+
return [{
|
|
1859
|
+
kind: "explicit",
|
|
1860
|
+
pair: {
|
|
1861
|
+
metrics: {
|
|
1862
|
+
type: MetricType.Custom,
|
|
1863
|
+
value: primitive.raw,
|
|
1864
|
+
origin: "parser",
|
|
1865
|
+
image: primitive.raw
|
|
1866
|
+
},
|
|
1867
|
+
meta: primitive.meta
|
|
1868
|
+
}
|
|
1869
|
+
}];
|
|
1870
|
+
}
|
|
1871
|
+
function referenceSlots(slugs, meta2, references) {
|
|
1872
|
+
const slots = [];
|
|
1873
|
+
let resolved = false;
|
|
1874
|
+
for (const slug of slugs) {
|
|
1875
|
+
const contributions = resolveTagContributions(references?.tags, slug);
|
|
1876
|
+
const reference = tagReferenceFor(references?.tags, slug);
|
|
1877
|
+
const contributed = tagContributionMetrics(contributions.metrics);
|
|
1878
|
+
if (contributed.length > 0) resolved = true;
|
|
1879
|
+
for (const metric of contributed) {
|
|
1880
|
+
slots.push({
|
|
1881
|
+
kind: "contributed",
|
|
1882
|
+
pair: { metrics: withTagReference(metric, reference), meta: meta2 }
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
for (const tag of contributions.tags) {
|
|
1886
|
+
resolved = true;
|
|
1887
|
+
const tagReference = tag === slug ? reference : tagReferenceFor(references?.tags, tag);
|
|
1888
|
+
slots.push({
|
|
1889
|
+
kind: "marker",
|
|
1890
|
+
pair: {
|
|
1891
|
+
metrics: withTagReference({
|
|
1892
|
+
type: tagMarkerType(tag),
|
|
1893
|
+
value: tag,
|
|
1894
|
+
origin: "parser",
|
|
1895
|
+
image: tag
|
|
1896
|
+
}, tagReference),
|
|
1897
|
+
meta: meta2
|
|
1898
|
+
}
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return { slots, resolved };
|
|
1903
|
+
}
|
|
1904
|
+
function withTagReference(metric, reference) {
|
|
1905
|
+
return {
|
|
1906
|
+
...metric,
|
|
1907
|
+
metadata: { ...metric.metadata ?? {}, [TAG_REFERENCE_METADATA_KEY]: reference }
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
function propertyPair(primitive) {
|
|
1911
|
+
const metricType = PROPERTY_KEY_TO_METRIC_TYPE[primitive.key.toLowerCase()] ?? MetricType.Custom;
|
|
1912
|
+
return {
|
|
1913
|
+
metrics: new PropertyMetric(primitive.key, primitive.value, {
|
|
1914
|
+
type: metricType,
|
|
1915
|
+
image: primitive.raw
|
|
1916
|
+
}),
|
|
1917
|
+
meta: primitive.meta
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1693
1920
|
function mergeFragments(pairs) {
|
|
1694
1921
|
if (pairs.length < 2) return pairs;
|
|
1695
1922
|
const result = [];
|
|
@@ -1769,33 +1996,34 @@ function mergeFragments(pairs) {
|
|
|
1769
1996
|
}
|
|
1770
1997
|
var parser = LRParser.deserialize({
|
|
1771
1998
|
version: 14,
|
|
1772
|
-
states: "(
|
|
1773
|
-
stateData: "
|
|
1774
|
-
goto: "$
|
|
1775
|
-
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",
|
|
1776
|
-
maxTerm:
|
|
1999
|
+
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+$_",
|
|
2000
|
+
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~",
|
|
2001
|
+
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",
|
|
2002
|
+
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",
|
|
2003
|
+
maxTerm: 62,
|
|
1777
2004
|
skippedNodes: [0],
|
|
1778
|
-
repeatNodeCount:
|
|
1779
|
-
tokenData: "
|
|
2005
|
+
repeatNodeCount: 7,
|
|
2006
|
+
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!_~",
|
|
1780
2007
|
tokenizers: [0],
|
|
1781
2008
|
topRules: { "Program": [0, 1] },
|
|
1782
|
-
tokenPrec:
|
|
2009
|
+
tokenPrec: 439
|
|
1783
2010
|
});
|
|
1784
2011
|
|
|
1785
2012
|
// src/grammar/parser.terms.ts
|
|
1786
|
-
var
|
|
1787
|
-
var
|
|
1788
|
-
var
|
|
1789
|
-
var
|
|
1790
|
-
var
|
|
1791
|
-
var
|
|
1792
|
-
var
|
|
1793
|
-
var
|
|
1794
|
-
var
|
|
1795
|
-
var
|
|
1796
|
-
var
|
|
1797
|
-
var
|
|
1798
|
-
var
|
|
2013
|
+
var TagRef = 6;
|
|
2014
|
+
var Lap = 8;
|
|
2015
|
+
var Fragment = 9;
|
|
2016
|
+
var Duration = 10;
|
|
2017
|
+
var Rounds = 14;
|
|
2018
|
+
var RoundScheme = 16;
|
|
2019
|
+
var Action = 17;
|
|
2020
|
+
var Text = 18;
|
|
2021
|
+
var Heading = 19;
|
|
2022
|
+
var Quantity = 20;
|
|
2023
|
+
var Slash = 23;
|
|
2024
|
+
var Pipe = 24;
|
|
2025
|
+
var Effort = 25;
|
|
2026
|
+
var MetricObject = 26;
|
|
1799
2027
|
|
|
1800
2028
|
// src/parser/syntax-parser.ts
|
|
1801
2029
|
function createLineLocator(source) {
|
|
@@ -1901,15 +2129,28 @@ function createPropertyPrimitive(locator, source, from, to) {
|
|
|
1901
2129
|
const match = raw.match(/^\s*([A-Za-z][A-Za-z0-9]*)\s*:\s*(.*?)\s*$/);
|
|
1902
2130
|
if (!match) return null;
|
|
1903
2131
|
const [, key, rawValue] = match;
|
|
2132
|
+
const tagRefs = parseTagRefs(rawValue);
|
|
1904
2133
|
return {
|
|
1905
2134
|
kind: "property",
|
|
1906
2135
|
raw,
|
|
1907
2136
|
meta: meta2,
|
|
1908
2137
|
key,
|
|
1909
2138
|
valueRaw: rawValue,
|
|
1910
|
-
value: parsePropertyValue(rawValue)
|
|
2139
|
+
value: tagRefs !== void 0 ? rawValue.trim() : parsePropertyValue(rawValue),
|
|
2140
|
+
...tagRefs !== void 0 ? { tagRefs } : {}
|
|
1911
2141
|
};
|
|
1912
2142
|
}
|
|
2143
|
+
function parseTagRefs(rawValue) {
|
|
2144
|
+
const parts = rawValue.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
2145
|
+
if (parts.length === 0) return void 0;
|
|
2146
|
+
const slugs = [];
|
|
2147
|
+
for (const part of parts) {
|
|
2148
|
+
const match = part.match(/^#([a-zA-Z0-9][a-zA-Z0-9-]*)$/);
|
|
2149
|
+
if (!match) return void 0;
|
|
2150
|
+
slugs.push(match[1]);
|
|
2151
|
+
}
|
|
2152
|
+
return slugs;
|
|
2153
|
+
}
|
|
1913
2154
|
function parsePropertyValue(rawValue) {
|
|
1914
2155
|
const trimmed = rawValue.trim();
|
|
1915
2156
|
if (/^"(?:[^"\\]|\\.)*"$/.test(trimmed)) {
|
|
@@ -2009,6 +2250,17 @@ function mapFragmentToPrimitive(locator, source, node) {
|
|
|
2009
2250
|
};
|
|
2010
2251
|
return primitive;
|
|
2011
2252
|
}
|
|
2253
|
+
case TagRef: {
|
|
2254
|
+
const match = raw.match(/^#([a-zA-Z0-9][a-zA-Z0-9-]*)$/);
|
|
2255
|
+
if (!match) return null;
|
|
2256
|
+
const primitive = {
|
|
2257
|
+
kind: "tag_ref",
|
|
2258
|
+
raw,
|
|
2259
|
+
meta: meta2,
|
|
2260
|
+
slug: match[1]
|
|
2261
|
+
};
|
|
2262
|
+
return primitive;
|
|
2263
|
+
}
|
|
2012
2264
|
case Slash: {
|
|
2013
2265
|
const primitive = {
|
|
2014
2266
|
kind: "effort",
|
|
@@ -2249,269 +2501,743 @@ var WhiteboardScript = class {
|
|
|
2249
2501
|
}
|
|
2250
2502
|
};
|
|
2251
2503
|
|
|
2252
|
-
// src/
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2504
|
+
// src/effort-registry/types.ts
|
|
2505
|
+
var EFFORT_REGISTRY_ORIGINS = ["bundled", "user"];
|
|
2506
|
+
var CATALOG_PRECEDENCE_RANK = {
|
|
2507
|
+
core: 0,
|
|
2508
|
+
community: 1,
|
|
2509
|
+
imported: 2,
|
|
2510
|
+
user: 3
|
|
2511
|
+
};
|
|
2512
|
+
function resolveSourceTier(sourceIdOrMetaKey, explicitTier) {
|
|
2513
|
+
if (explicitTier) return explicitTier;
|
|
2514
|
+
if (!sourceIdOrMetaKey) return "core";
|
|
2515
|
+
if (sourceIdOrMetaKey === "user") return "user";
|
|
2516
|
+
const id = sourceIdOrMetaKey.startsWith("seed:") ? sourceIdOrMetaKey.slice(5) : sourceIdOrMetaKey;
|
|
2517
|
+
if (id === "core" || id === "efforts" || id === "equipment") return "core";
|
|
2518
|
+
if (id === "community" || id.startsWith("community-") || id === "common") return "community";
|
|
2519
|
+
return "imported";
|
|
2520
|
+
}
|
|
2521
|
+
function resolveSourceRank(sourceIdOrMetaKey, explicitTier) {
|
|
2522
|
+
return CATALOG_PRECEDENCE_RANK[resolveSourceTier(sourceIdOrMetaKey, explicitTier)];
|
|
2523
|
+
}
|
|
2524
|
+
function resolveEntityTier(entity) {
|
|
2525
|
+
if (entity.registrySource === "user") return "user";
|
|
2526
|
+
return resolveSourceTier(entity.sourceId);
|
|
2527
|
+
}
|
|
2528
|
+
function resolveEntityRank(entity) {
|
|
2529
|
+
return CATALOG_PRECEDENCE_RANK[resolveEntityTier(entity)];
|
|
2530
|
+
}
|
|
2531
|
+
function dedupeEntitiesByPrecedence(entities) {
|
|
2532
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
2533
|
+
for (const entity of entities) {
|
|
2534
|
+
const key = entity.slug.toLowerCase();
|
|
2535
|
+
const prior = bySlug.get(key);
|
|
2536
|
+
if (!prior || resolveEntityRank(entity) >= resolveEntityRank(prior)) {
|
|
2537
|
+
bySlug.set(key, entity);
|
|
2538
|
+
}
|
|
2267
2539
|
}
|
|
2540
|
+
return Array.from(bySlug.values());
|
|
2268
2541
|
}
|
|
2542
|
+
var INTENSITY_TIERS = ["low", "moderate", "high"];
|
|
2543
|
+
var DEFAULT_RESOLVER_OPTIONS = {
|
|
2544
|
+
defaultMet: 5,
|
|
2545
|
+
defaultThreshold: 2
|
|
2546
|
+
};
|
|
2269
2547
|
|
|
2270
|
-
// src/
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2548
|
+
// src/effort-registry/fuzzyMatch.ts
|
|
2549
|
+
function levenshteinDistance(a, b) {
|
|
2550
|
+
if (a === b) return 0;
|
|
2551
|
+
if (a.length === 0) return b.length;
|
|
2552
|
+
if (b.length === 0) return a.length;
|
|
2553
|
+
if (a.length > b.length) {
|
|
2554
|
+
const tmp = a;
|
|
2555
|
+
a = b;
|
|
2556
|
+
b = tmp;
|
|
2275
2557
|
}
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
read(inputText, sport) {
|
|
2283
|
-
return parseScript(inputText, { sport, ...this.config });
|
|
2558
|
+
const m = a.length;
|
|
2559
|
+
const n = b.length;
|
|
2560
|
+
let prev = new Uint32Array(m + 1);
|
|
2561
|
+
let curr = new Uint32Array(m + 1);
|
|
2562
|
+
for (let i = 0; i <= m; i++) {
|
|
2563
|
+
prev[i] = i;
|
|
2284
2564
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2565
|
+
for (let j = 1; j <= n; j++) {
|
|
2566
|
+
curr[0] = j;
|
|
2567
|
+
const bj = b.charCodeAt(j - 1);
|
|
2568
|
+
for (let i = 1; i <= m; i++) {
|
|
2569
|
+
const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;
|
|
2570
|
+
curr[i] = Math.min(
|
|
2571
|
+
prev[i] + 1,
|
|
2572
|
+
// deletion
|
|
2573
|
+
curr[i - 1] + 1,
|
|
2574
|
+
// insertion
|
|
2575
|
+
prev[i - 1] + cost
|
|
2576
|
+
// substitution
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
const swap = prev;
|
|
2580
|
+
prev = curr;
|
|
2581
|
+
curr = swap;
|
|
2291
2582
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
// src/parser/parserInstance.ts
|
|
2295
|
-
function createParser(config) {
|
|
2296
|
-
return new MdTimerRuntime(config);
|
|
2583
|
+
return prev[m];
|
|
2297
2584
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2585
|
+
function normalizeForFuzzy(input) {
|
|
2586
|
+
return input.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, " ").replace(/[-_]+/g, " ").trim().replace(/\s+/g, " ");
|
|
2587
|
+
}
|
|
2588
|
+
function isWithinThreshold(a, b, threshold) {
|
|
2589
|
+
const na = normalizeForFuzzy(a);
|
|
2590
|
+
const nb = normalizeForFuzzy(b);
|
|
2591
|
+
if (na === nb) return true;
|
|
2592
|
+
if (Math.abs(na.length - nb.length) > threshold) return false;
|
|
2593
|
+
return levenshteinDistance(na, nb) <= threshold;
|
|
2594
|
+
}
|
|
2595
|
+
function findBestFuzzyMatch(query, candidates, threshold) {
|
|
2596
|
+
const nq = normalizeForFuzzy(query);
|
|
2597
|
+
let best = null;
|
|
2598
|
+
for (const candidate of candidates) {
|
|
2599
|
+
const nc = normalizeForFuzzy(candidate);
|
|
2600
|
+
if (Math.abs(nq.length - nc.length) > threshold) continue;
|
|
2601
|
+
const distance = levenshteinDistance(nq, nc);
|
|
2602
|
+
if (distance > threshold) continue;
|
|
2603
|
+
if (!best || distance < best.distance) {
|
|
2604
|
+
best = { target: candidate, distance };
|
|
2605
|
+
if (distance === 0) break;
|
|
2606
|
+
}
|
|
2316
2607
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
if (
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2608
|
+
return best;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// src/effort-registry/reference-resolution.ts
|
|
2612
|
+
function buildExactIndex(entities) {
|
|
2613
|
+
const deduped = dedupeEntitiesByPrecedence(entities);
|
|
2614
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2615
|
+
const add = (key, entity) => {
|
|
2616
|
+
if (!key) return;
|
|
2617
|
+
const existing = byKey.get(key);
|
|
2618
|
+
if (existing) {
|
|
2619
|
+
if (!existing.includes(entity)) existing.push(entity);
|
|
2620
|
+
} else {
|
|
2621
|
+
byKey.set(key, [entity]);
|
|
2622
|
+
}
|
|
2623
|
+
};
|
|
2624
|
+
for (const entity of deduped) {
|
|
2625
|
+
add(entity.slug.toLowerCase(), entity);
|
|
2626
|
+
add(normalizeForFuzzy(entity.label), entity);
|
|
2627
|
+
for (const alias of entity.aliases) add(normalizeForFuzzy(alias), entity);
|
|
2329
2628
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
return (tag) => {
|
|
2345
|
-
if (tag.modified.indexOf(mod) > -1)
|
|
2346
|
-
return tag;
|
|
2347
|
-
return Modifier.get(tag.base || tag, tag.modified.concat(mod).sort((a, b) => a.id - b.id));
|
|
2348
|
-
};
|
|
2349
|
-
}
|
|
2350
|
-
};
|
|
2351
|
-
var nextModifierID = 0;
|
|
2352
|
-
var Modifier = class _Modifier {
|
|
2353
|
-
constructor(name2) {
|
|
2354
|
-
this.name = name2;
|
|
2355
|
-
this.instances = [];
|
|
2356
|
-
this.id = nextModifierID++;
|
|
2357
|
-
}
|
|
2358
|
-
static get(base, mods) {
|
|
2359
|
-
if (!mods.length)
|
|
2360
|
-
return base;
|
|
2361
|
-
let exists = mods[0].instances.find((t2) => t2.base == base && sameArray(mods, t2.modified));
|
|
2362
|
-
if (exists)
|
|
2363
|
-
return exists;
|
|
2364
|
-
let set = [], tag = new Tag(base.name, set, base, mods);
|
|
2365
|
-
for (let m of mods)
|
|
2366
|
-
m.instances.push(tag);
|
|
2367
|
-
let configs = powerSet(mods);
|
|
2368
|
-
for (let parent of base.set)
|
|
2369
|
-
if (!parent.modified.length)
|
|
2370
|
-
for (let config of configs)
|
|
2371
|
-
set.push(_Modifier.get(parent, config));
|
|
2372
|
-
return tag;
|
|
2373
|
-
}
|
|
2374
|
-
};
|
|
2375
|
-
function sameArray(a, b) {
|
|
2376
|
-
return a.length == b.length && a.every((x, i) => x == b[i]);
|
|
2377
|
-
}
|
|
2378
|
-
function powerSet(array) {
|
|
2379
|
-
let sets = [[]];
|
|
2380
|
-
for (let i = 0; i < array.length; i++) {
|
|
2381
|
-
for (let j = 0, e = sets.length; j < e; j++) {
|
|
2382
|
-
sets.push(sets[j].concat(array[i]));
|
|
2629
|
+
return {
|
|
2630
|
+
byKey,
|
|
2631
|
+
lookup(key) {
|
|
2632
|
+
const direct = byKey.get(key) ?? byKey.get(normalizeForFuzzy(key));
|
|
2633
|
+
if (!direct || direct.length === 0) return { entity: null, ambiguous: [] };
|
|
2634
|
+
if (direct.length === 1) return { entity: direct[0], ambiguous: [] };
|
|
2635
|
+
let maxRank = -1;
|
|
2636
|
+
for (const candidate of direct) {
|
|
2637
|
+
const rank = resolveEntityRank(candidate);
|
|
2638
|
+
if (rank > maxRank) maxRank = rank;
|
|
2639
|
+
}
|
|
2640
|
+
const topTier = direct.filter((candidate) => resolveEntityRank(candidate) === maxRank);
|
|
2641
|
+
if (topTier.length === 1) return { entity: topTier[0], ambiguous: [] };
|
|
2642
|
+
return { entity: null, ambiguous: topTier };
|
|
2383
2643
|
}
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
function equipmentRef(entity) {
|
|
2647
|
+
return { kind: "equipment", slug: entity.slug, label: entity.label };
|
|
2648
|
+
}
|
|
2649
|
+
function dedupeRefs(refs) {
|
|
2650
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2651
|
+
const result = [];
|
|
2652
|
+
for (const ref of refs) {
|
|
2653
|
+
const key = `${ref.kind}:${ref.slug}`;
|
|
2654
|
+
if (seen.has(key)) continue;
|
|
2655
|
+
seen.add(key);
|
|
2656
|
+
result.push(ref);
|
|
2384
2657
|
}
|
|
2385
|
-
return
|
|
2658
|
+
return result;
|
|
2386
2659
|
}
|
|
2387
|
-
function
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2660
|
+
function createReferenceIndex(catalog, materialize) {
|
|
2661
|
+
const dedupedEquipment = dedupeEntitiesByPrecedence(catalog.equipment ?? []);
|
|
2662
|
+
const efforts = buildExactIndex(catalog.efforts ?? []);
|
|
2663
|
+
const equipment = buildExactIndex(dedupedEquipment);
|
|
2664
|
+
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);
|
|
2665
|
+
const materializer = materialize ?? ((effort) => ({
|
|
2666
|
+
effort,
|
|
2667
|
+
definition: effort,
|
|
2668
|
+
slug: effort.slug,
|
|
2669
|
+
label: effort.label,
|
|
2670
|
+
met: effort.baseAttributes.met,
|
|
2671
|
+
baseAttributes: effort.baseAttributes,
|
|
2672
|
+
discipline: effort.baseAttributes.discipline,
|
|
2673
|
+
disciplineFactor: effort.baseAttributes.disciplineFactor ?? 1,
|
|
2674
|
+
intensityTier: effort.baseAttributes.intensityTier,
|
|
2675
|
+
modifiers: {},
|
|
2676
|
+
registrySource: effort.registrySource,
|
|
2677
|
+
resolvedFrom: effort.registrySource === "user" ? "user" : effort.registrySource === "bundled" ? "bundled" : "default",
|
|
2678
|
+
isEstimated: false
|
|
2679
|
+
}));
|
|
2680
|
+
const resolveEffortExact = (key) => {
|
|
2681
|
+
const found = efforts.lookup(key.trim());
|
|
2682
|
+
if (found.entity) return { entity: found.entity };
|
|
2683
|
+
if (found.ambiguous.length > 0) {
|
|
2684
|
+
return {
|
|
2685
|
+
entity: null,
|
|
2686
|
+
diagnostic: {
|
|
2687
|
+
reason: "ambiguous-effort",
|
|
2688
|
+
message: `"${key}" matches multiple efforts.`,
|
|
2689
|
+
token: key,
|
|
2690
|
+
candidates: found.ambiguous.map((e) => e.slug)
|
|
2416
2691
|
}
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
return {
|
|
2695
|
+
entity: null,
|
|
2696
|
+
diagnostic: { reason: "unknown-effort", message: `Unknown effort "${key}".`, token: key }
|
|
2697
|
+
};
|
|
2698
|
+
};
|
|
2699
|
+
const resolveEquipmentValues = (values) => {
|
|
2700
|
+
const refs = [];
|
|
2701
|
+
const diagnostics = [];
|
|
2702
|
+
for (const value of values) {
|
|
2703
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
2704
|
+
diagnostics.push({
|
|
2705
|
+
reason: "invalid-explicit",
|
|
2706
|
+
message: `Equipment reference must be a slug string, got ${JSON.stringify(value) ?? "null"}.`,
|
|
2707
|
+
token: String(value)
|
|
2708
|
+
});
|
|
2709
|
+
continue;
|
|
2422
2710
|
}
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2711
|
+
const found = equipment.lookup(value.trim());
|
|
2712
|
+
if (found.entity) {
|
|
2713
|
+
refs.push(equipmentRef(found.entity));
|
|
2714
|
+
} else if (found.ambiguous.length > 0) {
|
|
2715
|
+
diagnostics.push({
|
|
2716
|
+
reason: "ambiguous-equipment",
|
|
2717
|
+
message: `"${value}" matches multiple equipment entries.`,
|
|
2718
|
+
token: value,
|
|
2719
|
+
candidates: found.ambiguous.map((e) => e.slug)
|
|
2720
|
+
});
|
|
2433
2721
|
} else {
|
|
2434
|
-
|
|
2435
|
-
|
|
2722
|
+
diagnostics.push({
|
|
2723
|
+
reason: "invalid-explicit",
|
|
2724
|
+
message: `Unknown equipment "${value}".`,
|
|
2725
|
+
token: value
|
|
2726
|
+
});
|
|
2436
2727
|
}
|
|
2437
|
-
if (cur && cur.mode == take.mode && !take.context && !cur.context)
|
|
2438
|
-
continue;
|
|
2439
|
-
let copy = new Rule(take.tags, take.mode, take.context);
|
|
2440
|
-
if (cur)
|
|
2441
|
-
cur.next = copy;
|
|
2442
|
-
else
|
|
2443
|
-
root = copy;
|
|
2444
|
-
cur = copy;
|
|
2445
|
-
}
|
|
2446
|
-
return root;
|
|
2447
|
-
}
|
|
2448
|
-
});
|
|
2449
|
-
var Rule = class {
|
|
2450
|
-
constructor(tags2, mode, context, next) {
|
|
2451
|
-
this.tags = tags2;
|
|
2452
|
-
this.mode = mode;
|
|
2453
|
-
this.context = context;
|
|
2454
|
-
this.next = next;
|
|
2455
|
-
}
|
|
2456
|
-
get opaque() {
|
|
2457
|
-
return this.mode == 0;
|
|
2458
|
-
}
|
|
2459
|
-
get inherit() {
|
|
2460
|
-
return this.mode == 1;
|
|
2461
|
-
}
|
|
2462
|
-
sort(other) {
|
|
2463
|
-
if (!other || other.depth < this.depth) {
|
|
2464
|
-
this.next = other;
|
|
2465
|
-
return this;
|
|
2466
2728
|
}
|
|
2467
|
-
|
|
2468
|
-
return other;
|
|
2469
|
-
}
|
|
2470
|
-
get depth() {
|
|
2471
|
-
return this.context ? this.context.length : 0;
|
|
2472
|
-
}
|
|
2473
|
-
};
|
|
2474
|
-
Rule.empty = new Rule([], 2, null);
|
|
2475
|
-
function tagHighlighter(tags2, options) {
|
|
2476
|
-
let map = /* @__PURE__ */ Object.create(null);
|
|
2477
|
-
for (let style of tags2) {
|
|
2478
|
-
if (!Array.isArray(style.tag))
|
|
2479
|
-
map[style.tag.id] = style.class;
|
|
2480
|
-
else
|
|
2481
|
-
for (let tag of style.tag)
|
|
2482
|
-
map[tag.id] = style.class;
|
|
2483
|
-
}
|
|
2484
|
-
let { scope, all = null } = {};
|
|
2485
|
-
return {
|
|
2486
|
-
style: (tags3) => {
|
|
2487
|
-
let cls = all;
|
|
2488
|
-
for (let tag of tags3) {
|
|
2489
|
-
for (let sub of tag.set) {
|
|
2490
|
-
let tagClass = map[sub.id];
|
|
2491
|
-
if (tagClass) {
|
|
2492
|
-
cls = cls ? cls + " " + tagClass : tagClass;
|
|
2493
|
-
break;
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
return cls;
|
|
2498
|
-
},
|
|
2499
|
-
scope
|
|
2729
|
+
return { refs: dedupeRefs(refs), diagnostics };
|
|
2500
2730
|
};
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2731
|
+
const index = {
|
|
2732
|
+
resolveEffort(slugOrAlias) {
|
|
2733
|
+
return efforts.lookup(slugOrAlias).entity;
|
|
2734
|
+
},
|
|
2735
|
+
resolveEquipment(slugOrAlias) {
|
|
2736
|
+
return equipment.lookup(slugOrAlias).entity;
|
|
2737
|
+
},
|
|
2738
|
+
materialize(effort) {
|
|
2739
|
+
return materializer(effort);
|
|
2740
|
+
},
|
|
2741
|
+
resolve(label, defaults) {
|
|
2742
|
+
const source = (label ?? "").trim();
|
|
2743
|
+
const diagnostics = [];
|
|
2744
|
+
const push = (diagnostic) => {
|
|
2745
|
+
if (diagnostic) diagnostics.push(diagnostic);
|
|
2746
|
+
};
|
|
2747
|
+
let matched = "none";
|
|
2748
|
+
let effortEntity = null;
|
|
2749
|
+
let remaining = "";
|
|
2750
|
+
let equipmentRefs = [];
|
|
2751
|
+
let equipmentAuthored = false;
|
|
2752
|
+
const fillEquipment = () => {
|
|
2753
|
+
if (equipmentRefs.length > 0 || equipmentAuthored) return;
|
|
2754
|
+
if (effortEntity === null) return;
|
|
2755
|
+
if (defaults?.equipment?.length) {
|
|
2756
|
+
const resolvedDefaults = resolveEquipmentValues(defaults.equipment);
|
|
2757
|
+
equipmentRefs = resolvedDefaults.refs;
|
|
2758
|
+
for (const diagnostic of resolvedDefaults.diagnostics) {
|
|
2759
|
+
push({ ...diagnostic, reason: "unknown-equipment" });
|
|
2760
|
+
}
|
|
2761
|
+
if (equipmentRefs.length > 0) return;
|
|
2762
|
+
}
|
|
2763
|
+
if (effortEntity.equipment?.length) {
|
|
2764
|
+
const resolvedDefEquipment = resolveEquipmentValues(effortEntity.equipment);
|
|
2765
|
+
equipmentRefs = resolvedDefEquipment.refs;
|
|
2766
|
+
for (const diagnostic of resolvedDefEquipment.diagnostics) {
|
|
2767
|
+
push({ ...diagnostic, reason: "unknown-equipment" });
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
};
|
|
2771
|
+
if (!source) {
|
|
2772
|
+
if (defaults?.effort) {
|
|
2773
|
+
const found = resolveEffortExact(defaults.effort);
|
|
2774
|
+
if (found.entity) {
|
|
2775
|
+
effortEntity = found.entity;
|
|
2776
|
+
matched = "default";
|
|
2777
|
+
} else {
|
|
2778
|
+
push({
|
|
2779
|
+
reason: found.diagnostic?.reason === "ambiguous-effort" ? "ambiguous-effort" : "unknown-effort",
|
|
2780
|
+
message: `Default effort "${defaults.effort}" is not in the catalog.`,
|
|
2781
|
+
token: defaults.effort,
|
|
2782
|
+
candidates: found.diagnostic?.candidates
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
fillEquipment();
|
|
2787
|
+
return {
|
|
2788
|
+
matched,
|
|
2789
|
+
label: source,
|
|
2790
|
+
effort: effortEntity ? materializer(effortEntity) : null,
|
|
2791
|
+
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
2792
|
+
equipment: equipmentRefs,
|
|
2793
|
+
remaining: "",
|
|
2794
|
+
diagnostics
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
if (source.startsWith("{")) {
|
|
2798
|
+
let parsed;
|
|
2799
|
+
try {
|
|
2800
|
+
parsed = JSON.parse(source);
|
|
2801
|
+
} catch {
|
|
2802
|
+
return {
|
|
2803
|
+
matched: "none",
|
|
2804
|
+
label: source,
|
|
2805
|
+
effort: null,
|
|
2806
|
+
effortRef: null,
|
|
2807
|
+
equipment: [],
|
|
2808
|
+
remaining: "",
|
|
2809
|
+
diagnostics: [{
|
|
2810
|
+
reason: "malformed-json",
|
|
2811
|
+
message: "Reference object is not valid JSON.",
|
|
2812
|
+
token: source
|
|
2813
|
+
}]
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && ("effort" in parsed || "equipment" in parsed)) {
|
|
2817
|
+
matched = "explicit";
|
|
2818
|
+
const obj = parsed;
|
|
2819
|
+
if (obj.effort !== void 0) {
|
|
2820
|
+
if (typeof obj.effort === "string" && obj.effort.trim()) {
|
|
2821
|
+
const found = resolveEffortExact(obj.effort);
|
|
2822
|
+
effortEntity = found.entity;
|
|
2823
|
+
if (found.diagnostic) {
|
|
2824
|
+
push(found.diagnostic.reason === "ambiguous-effort" ? found.diagnostic : { ...found.diagnostic, reason: "invalid-explicit", message: `Explicit effort "${obj.effort}" is not in the catalog.` });
|
|
2825
|
+
}
|
|
2826
|
+
} else {
|
|
2827
|
+
push({
|
|
2828
|
+
reason: "invalid-explicit",
|
|
2829
|
+
message: 'Explicit "effort" must be a slug string.',
|
|
2830
|
+
token: String(obj.effort)
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
if (obj.equipment !== void 0) {
|
|
2835
|
+
equipmentAuthored = true;
|
|
2836
|
+
const values = Array.isArray(obj.equipment) ? obj.equipment : [obj.equipment];
|
|
2837
|
+
const resolvedEquipment = resolveEquipmentValues(values);
|
|
2838
|
+
equipmentRefs = resolvedEquipment.refs;
|
|
2839
|
+
diagnostics.push(...resolvedEquipment.diagnostics);
|
|
2840
|
+
if (effortEntity?.equipment?.length) {
|
|
2841
|
+
const definitionSlugs = new Set(effortEntity.equipment);
|
|
2842
|
+
const authoredSlugs = equipmentRefs.map((ref) => ref.slug);
|
|
2843
|
+
if (authoredSlugs.length > 0 && authoredSlugs.some((slug) => !definitionSlugs.has(slug))) {
|
|
2844
|
+
push({
|
|
2845
|
+
reason: "conflict",
|
|
2846
|
+
message: `Explicit equipment [${authoredSlugs.join(", ")}] contradicts effort "${effortEntity.slug}" definition equipment [${effortEntity.equipment.join(", ")}].`,
|
|
2847
|
+
token: source
|
|
2848
|
+
});
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
fillEquipment();
|
|
2853
|
+
return {
|
|
2854
|
+
matched,
|
|
2855
|
+
label: source,
|
|
2856
|
+
effort: effortEntity ? materializer(effortEntity) : null,
|
|
2857
|
+
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
2858
|
+
equipment: equipmentRefs,
|
|
2859
|
+
remaining: "",
|
|
2860
|
+
diagnostics
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
const lowerSource = source.toLowerCase();
|
|
2865
|
+
for (const candidate of prefixCandidates) {
|
|
2866
|
+
if (!lowerSource.startsWith(candidate.lower)) continue;
|
|
2867
|
+
const after = source.slice(candidate.lower.length);
|
|
2868
|
+
if (after && !/^\s/.test(after)) continue;
|
|
2869
|
+
const candidateRemaining = after.trim();
|
|
2870
|
+
if (!candidateRemaining) continue;
|
|
2871
|
+
const equipmentFound = equipment.lookup(candidate.lower);
|
|
2872
|
+
if (equipmentFound.ambiguous.length > 0) {
|
|
2873
|
+
push({
|
|
2874
|
+
reason: "ambiguous-equipment",
|
|
2875
|
+
message: `"${candidate.lower}" matches multiple equipment entries.`,
|
|
2876
|
+
token: source,
|
|
2877
|
+
candidates: equipmentFound.ambiguous.map((e) => e.slug)
|
|
2878
|
+
});
|
|
2879
|
+
return {
|
|
2880
|
+
matched: "none",
|
|
2881
|
+
label: source,
|
|
2882
|
+
effort: null,
|
|
2883
|
+
effortRef: null,
|
|
2884
|
+
equipment: [],
|
|
2885
|
+
remaining: "",
|
|
2886
|
+
diagnostics
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
if (!equipmentFound.entity) continue;
|
|
2890
|
+
const effortFound = resolveEffortExact(candidateRemaining);
|
|
2891
|
+
if (effortFound.entity) {
|
|
2892
|
+
matched = "split";
|
|
2893
|
+
effortEntity = effortFound.entity;
|
|
2894
|
+
equipmentRefs = [equipmentRef(equipmentFound.entity)];
|
|
2895
|
+
equipmentAuthored = true;
|
|
2896
|
+
remaining = candidateRemaining;
|
|
2897
|
+
break;
|
|
2898
|
+
}
|
|
2899
|
+
if (effortFound.diagnostic?.reason === "ambiguous-effort") {
|
|
2900
|
+
push(effortFound.diagnostic);
|
|
2901
|
+
return {
|
|
2902
|
+
matched: "none",
|
|
2903
|
+
label: source,
|
|
2904
|
+
effort: null,
|
|
2905
|
+
effortRef: null,
|
|
2906
|
+
equipment: [],
|
|
2907
|
+
remaining: "",
|
|
2908
|
+
diagnostics
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
if (matched === "none") {
|
|
2913
|
+
const found = resolveEffortExact(source);
|
|
2914
|
+
if (found.entity) {
|
|
2915
|
+
effortEntity = found.entity;
|
|
2916
|
+
matched = "whole";
|
|
2917
|
+
} else if (found.diagnostic?.reason === "ambiguous-effort") {
|
|
2918
|
+
push(found.diagnostic);
|
|
2919
|
+
return {
|
|
2920
|
+
matched: "none",
|
|
2921
|
+
label: source,
|
|
2922
|
+
effort: null,
|
|
2923
|
+
effortRef: null,
|
|
2924
|
+
equipment: [],
|
|
2925
|
+
remaining: "",
|
|
2926
|
+
diagnostics
|
|
2927
|
+
};
|
|
2928
|
+
} else if (found.diagnostic) {
|
|
2929
|
+
push(found.diagnostic);
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
fillEquipment();
|
|
2933
|
+
return {
|
|
2934
|
+
matched,
|
|
2935
|
+
label: source,
|
|
2936
|
+
effort: effortEntity ? materializer(effortEntity) : null,
|
|
2937
|
+
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
2938
|
+
equipment: equipmentRefs,
|
|
2939
|
+
remaining,
|
|
2940
|
+
diagnostics
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
};
|
|
2944
|
+
return index;
|
|
2945
|
+
}
|
|
2946
|
+
function extractStatementRefs(statements) {
|
|
2947
|
+
const efforts = [];
|
|
2948
|
+
const equipment = [];
|
|
2949
|
+
const seenEffort = /* @__PURE__ */ new Set();
|
|
2950
|
+
const seenEquipment = /* @__PURE__ */ new Set();
|
|
2951
|
+
const pushUnique = (list, seen, value) => {
|
|
2952
|
+
if (!value) return;
|
|
2953
|
+
if (seen.has(value)) return;
|
|
2954
|
+
seen.add(value);
|
|
2955
|
+
list.push(value);
|
|
2956
|
+
};
|
|
2957
|
+
for (const statement of statements ?? []) {
|
|
2958
|
+
const metrics = Array.isArray(statement.metrics) ? statement.metrics : statement.metrics?.toArray?.() ?? [];
|
|
2959
|
+
for (const metric of metrics) {
|
|
2960
|
+
const refs = getReferenceMetadata(metric.metadata)?.refs;
|
|
2961
|
+
if (!refs) continue;
|
|
2962
|
+
for (const ref of refs) {
|
|
2963
|
+
if (ref.kind === "effort") pushUnique(efforts, seenEffort, ref.slug);
|
|
2964
|
+
if (ref.kind === "equipment") pushUnique(equipment, seenEquipment, ref.slug);
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
return { efforts, equipment };
|
|
2969
|
+
}
|
|
2970
|
+
function slugifyRefLabel(label) {
|
|
2971
|
+
return normalizeForFuzzy(label).replace(/\s+/g, "-");
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
// src/parser/parseScript.ts
|
|
2975
|
+
function parseScript(inputText, options = {}) {
|
|
2976
|
+
if (!inputText || !inputText.trim()) {
|
|
2977
|
+
return new WhiteboardScript(inputText, [], []);
|
|
2978
|
+
}
|
|
2979
|
+
try {
|
|
2980
|
+
const doc = inputText.endsWith("\n") ? inputText : inputText + "\n";
|
|
2981
|
+
const tree = parser.parse(doc);
|
|
2982
|
+
const sport = options.sport ?? options.dialect;
|
|
2983
|
+
const references = options.references || options.tags ? {
|
|
2984
|
+
index: options.references ?? createReferenceIndex({ efforts: [], equipment: [] }),
|
|
2985
|
+
defaults: options.defaults,
|
|
2986
|
+
tags: options.tags
|
|
2987
|
+
} : void 0;
|
|
2988
|
+
const statements = options.withoutDialects ? extractStatementsRaw(inputText, tree, references) : extractStatements(inputText, tree, sport, references);
|
|
2989
|
+
return new WhiteboardScript(inputText, statements, []);
|
|
2990
|
+
} catch (error) {
|
|
2991
|
+
const msg = error instanceof Error ? error.message : "Unknown parse error";
|
|
2992
|
+
return new WhiteboardScript(inputText, [], [{ message: msg }]);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
// src/parser/md-timer.ts
|
|
2997
|
+
var MdTimerRuntime = class {
|
|
2998
|
+
config;
|
|
2999
|
+
constructor(config) {
|
|
3000
|
+
this.config = config;
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Parse WhiteboardScript source into a {@link WhiteboardScript}, running the
|
|
3004
|
+
* Dialect Stack on every statement.
|
|
3005
|
+
*
|
|
3006
|
+
* @param sport - The block's `:sport` fence suffix (` ```log:climbing `).
|
|
3007
|
+
*/
|
|
3008
|
+
read(inputText, sport) {
|
|
3009
|
+
return parseScript(inputText, { sport, ...this.config });
|
|
3010
|
+
}
|
|
3011
|
+
/**
|
|
3012
|
+
* Parse without running the Dialect Stack. Used by the parser test harness
|
|
3013
|
+
* which applies its own Dialect set. Production consumers should use {@link read}.
|
|
3014
|
+
*/
|
|
3015
|
+
readWithoutDialects(inputText) {
|
|
3016
|
+
return parseScript(inputText, { withoutDialects: true, ...this.config });
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
|
|
3020
|
+
// src/parser/parserInstance.ts
|
|
3021
|
+
function createParser(config) {
|
|
3022
|
+
return new MdTimerRuntime(config);
|
|
3023
|
+
}
|
|
3024
|
+
var nextTagID = 0;
|
|
3025
|
+
var Tag = class _Tag {
|
|
3026
|
+
/**
|
|
3027
|
+
@internal
|
|
3028
|
+
*/
|
|
3029
|
+
constructor(name2, set, base, modified) {
|
|
3030
|
+
this.name = name2;
|
|
3031
|
+
this.set = set;
|
|
3032
|
+
this.base = base;
|
|
3033
|
+
this.modified = modified;
|
|
3034
|
+
this.id = nextTagID++;
|
|
3035
|
+
}
|
|
3036
|
+
toString() {
|
|
3037
|
+
let { name: name2 } = this;
|
|
3038
|
+
for (let mod of this.modified)
|
|
3039
|
+
if (mod.name)
|
|
3040
|
+
name2 = `${mod.name}(${name2})`;
|
|
3041
|
+
return name2;
|
|
3042
|
+
}
|
|
3043
|
+
static define(nameOrParent, parent) {
|
|
3044
|
+
let name2 = typeof nameOrParent == "string" ? nameOrParent : "?";
|
|
3045
|
+
if (nameOrParent instanceof _Tag)
|
|
3046
|
+
parent = nameOrParent;
|
|
3047
|
+
if (parent === null || parent === void 0 ? void 0 : parent.base)
|
|
3048
|
+
throw new Error("Can not derive from a modified tag");
|
|
3049
|
+
let tag = new _Tag(name2, [], null, []);
|
|
3050
|
+
tag.set.push(tag);
|
|
3051
|
+
if (parent)
|
|
3052
|
+
for (let t2 of parent.set)
|
|
3053
|
+
tag.set.push(t2);
|
|
3054
|
+
return tag;
|
|
3055
|
+
}
|
|
3056
|
+
/**
|
|
3057
|
+
Define a tag _modifier_, which is a function that, given a tag,
|
|
3058
|
+
will return a tag that is a subtag of the original. Applying the
|
|
3059
|
+
same modifier to a twice tag will return the same value (`m1(t1)
|
|
3060
|
+
== m1(t1)`) and applying multiple modifiers will, regardless or
|
|
3061
|
+
order, produce the same tag (`m1(m2(t1)) == m2(m1(t1))`).
|
|
3062
|
+
|
|
3063
|
+
When multiple modifiers are applied to a given base tag, each
|
|
3064
|
+
smaller set of modifiers is registered as a parent, so that for
|
|
3065
|
+
example `m1(m2(m3(t1)))` is a subtype of `m1(m2(t1))`,
|
|
3066
|
+
`m1(m3(t1)`, and so on.
|
|
3067
|
+
*/
|
|
3068
|
+
static defineModifier(name2) {
|
|
3069
|
+
let mod = new Modifier(name2);
|
|
3070
|
+
return (tag) => {
|
|
3071
|
+
if (tag.modified.indexOf(mod) > -1)
|
|
3072
|
+
return tag;
|
|
3073
|
+
return Modifier.get(tag.base || tag, tag.modified.concat(mod).sort((a, b) => a.id - b.id));
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
var nextModifierID = 0;
|
|
3078
|
+
var Modifier = class _Modifier {
|
|
3079
|
+
constructor(name2) {
|
|
3080
|
+
this.name = name2;
|
|
3081
|
+
this.instances = [];
|
|
3082
|
+
this.id = nextModifierID++;
|
|
3083
|
+
}
|
|
3084
|
+
static get(base, mods) {
|
|
3085
|
+
if (!mods.length)
|
|
3086
|
+
return base;
|
|
3087
|
+
let exists = mods[0].instances.find((t2) => t2.base == base && sameArray(mods, t2.modified));
|
|
3088
|
+
if (exists)
|
|
3089
|
+
return exists;
|
|
3090
|
+
let set = [], tag = new Tag(base.name, set, base, mods);
|
|
3091
|
+
for (let m of mods)
|
|
3092
|
+
m.instances.push(tag);
|
|
3093
|
+
let configs = powerSet(mods);
|
|
3094
|
+
for (let parent of base.set)
|
|
3095
|
+
if (!parent.modified.length)
|
|
3096
|
+
for (let config of configs)
|
|
3097
|
+
set.push(_Modifier.get(parent, config));
|
|
3098
|
+
return tag;
|
|
3099
|
+
}
|
|
3100
|
+
};
|
|
3101
|
+
function sameArray(a, b) {
|
|
3102
|
+
return a.length == b.length && a.every((x, i) => x == b[i]);
|
|
3103
|
+
}
|
|
3104
|
+
function powerSet(array) {
|
|
3105
|
+
let sets = [[]];
|
|
3106
|
+
for (let i = 0; i < array.length; i++) {
|
|
3107
|
+
for (let j = 0, e = sets.length; j < e; j++) {
|
|
3108
|
+
sets.push(sets[j].concat(array[i]));
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
return sets.sort((a, b) => b.length - a.length);
|
|
3112
|
+
}
|
|
3113
|
+
function styleTags(spec) {
|
|
3114
|
+
let byName = /* @__PURE__ */ Object.create(null);
|
|
3115
|
+
for (let prop in spec) {
|
|
3116
|
+
let tags2 = spec[prop];
|
|
3117
|
+
if (!Array.isArray(tags2))
|
|
3118
|
+
tags2 = [tags2];
|
|
3119
|
+
for (let part of prop.split(" "))
|
|
3120
|
+
if (part) {
|
|
3121
|
+
let pieces = [], mode = 2, rest = part;
|
|
3122
|
+
for (let pos = 0; ; ) {
|
|
3123
|
+
if (rest == "..." && pos > 0 && pos + 3 == part.length) {
|
|
3124
|
+
mode = 1;
|
|
3125
|
+
break;
|
|
3126
|
+
}
|
|
3127
|
+
let m = /^"(?:[^"\\]|\\.)*?"|[^\/!]+/.exec(rest);
|
|
3128
|
+
if (!m)
|
|
3129
|
+
throw new RangeError("Invalid path: " + part);
|
|
3130
|
+
pieces.push(m[0] == "*" ? "" : m[0][0] == '"' ? JSON.parse(m[0]) : m[0]);
|
|
3131
|
+
pos += m[0].length;
|
|
3132
|
+
if (pos == part.length)
|
|
3133
|
+
break;
|
|
3134
|
+
let next = part[pos++];
|
|
3135
|
+
if (pos == part.length && next == "!") {
|
|
3136
|
+
mode = 0;
|
|
3137
|
+
break;
|
|
3138
|
+
}
|
|
3139
|
+
if (next != "/")
|
|
3140
|
+
throw new RangeError("Invalid path: " + part);
|
|
3141
|
+
rest = part.slice(pos);
|
|
3142
|
+
}
|
|
3143
|
+
let last = pieces.length - 1, inner = pieces[last];
|
|
3144
|
+
if (!inner)
|
|
3145
|
+
throw new RangeError("Invalid path: " + part);
|
|
3146
|
+
let rule = new Rule(tags2, mode, last > 0 ? pieces.slice(0, last) : null);
|
|
3147
|
+
byName[inner] = rule.sort(byName[inner]);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
return ruleNodeProp.add(byName);
|
|
3151
|
+
}
|
|
3152
|
+
var ruleNodeProp = new NodeProp({
|
|
3153
|
+
combine(a, b) {
|
|
3154
|
+
let cur, root, take;
|
|
3155
|
+
while (a || b) {
|
|
3156
|
+
if (!a || b && a.depth >= b.depth) {
|
|
3157
|
+
take = b;
|
|
3158
|
+
b = b.next;
|
|
3159
|
+
} else {
|
|
3160
|
+
take = a;
|
|
3161
|
+
a = a.next;
|
|
3162
|
+
}
|
|
3163
|
+
if (cur && cur.mode == take.mode && !take.context && !cur.context)
|
|
3164
|
+
continue;
|
|
3165
|
+
let copy = new Rule(take.tags, take.mode, take.context);
|
|
3166
|
+
if (cur)
|
|
3167
|
+
cur.next = copy;
|
|
3168
|
+
else
|
|
3169
|
+
root = copy;
|
|
3170
|
+
cur = copy;
|
|
3171
|
+
}
|
|
3172
|
+
return root;
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
var Rule = class {
|
|
3176
|
+
constructor(tags2, mode, context, next) {
|
|
3177
|
+
this.tags = tags2;
|
|
3178
|
+
this.mode = mode;
|
|
3179
|
+
this.context = context;
|
|
3180
|
+
this.next = next;
|
|
3181
|
+
}
|
|
3182
|
+
get opaque() {
|
|
3183
|
+
return this.mode == 0;
|
|
3184
|
+
}
|
|
3185
|
+
get inherit() {
|
|
3186
|
+
return this.mode == 1;
|
|
3187
|
+
}
|
|
3188
|
+
sort(other) {
|
|
3189
|
+
if (!other || other.depth < this.depth) {
|
|
3190
|
+
this.next = other;
|
|
3191
|
+
return this;
|
|
3192
|
+
}
|
|
3193
|
+
other.next = this.sort(other.next);
|
|
3194
|
+
return other;
|
|
3195
|
+
}
|
|
3196
|
+
get depth() {
|
|
3197
|
+
return this.context ? this.context.length : 0;
|
|
3198
|
+
}
|
|
3199
|
+
};
|
|
3200
|
+
Rule.empty = new Rule([], 2, null);
|
|
3201
|
+
function tagHighlighter(tags2, options) {
|
|
3202
|
+
let map = /* @__PURE__ */ Object.create(null);
|
|
3203
|
+
for (let style of tags2) {
|
|
3204
|
+
if (!Array.isArray(style.tag))
|
|
3205
|
+
map[style.tag.id] = style.class;
|
|
3206
|
+
else
|
|
3207
|
+
for (let tag of style.tag)
|
|
3208
|
+
map[tag.id] = style.class;
|
|
3209
|
+
}
|
|
3210
|
+
let { scope, all = null } = {};
|
|
3211
|
+
return {
|
|
3212
|
+
style: (tags3) => {
|
|
3213
|
+
let cls = all;
|
|
3214
|
+
for (let tag of tags3) {
|
|
3215
|
+
for (let sub of tag.set) {
|
|
3216
|
+
let tagClass = map[sub.id];
|
|
3217
|
+
if (tagClass) {
|
|
3218
|
+
cls = cls ? cls + " " + tagClass : tagClass;
|
|
3219
|
+
break;
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
return cls;
|
|
3224
|
+
},
|
|
3225
|
+
scope
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
var t = Tag.define;
|
|
3229
|
+
var comment = t();
|
|
3230
|
+
var name = t();
|
|
3231
|
+
var typeName = t(name);
|
|
3232
|
+
var propertyName = t(name);
|
|
3233
|
+
var literal = t();
|
|
3234
|
+
var string = t(literal);
|
|
3235
|
+
var number = t(literal);
|
|
3236
|
+
var content = t();
|
|
3237
|
+
var heading = t(content);
|
|
3238
|
+
var keyword = t();
|
|
3239
|
+
var operator = t();
|
|
3240
|
+
var punctuation = t();
|
|
2515
3241
|
var bracket = t(punctuation);
|
|
2516
3242
|
var meta = t();
|
|
2517
3243
|
var tags = {
|
|
@@ -2956,50 +3682,6 @@ function whiteboardScript() {
|
|
|
2956
3682
|
return new LanguageSupport(whiteboardScriptLanguage);
|
|
2957
3683
|
}
|
|
2958
3684
|
|
|
2959
|
-
// src/effort-registry/types.ts
|
|
2960
|
-
var EFFORT_REGISTRY_ORIGINS = ["bundled", "user"];
|
|
2961
|
-
var CATALOG_PRECEDENCE_RANK = {
|
|
2962
|
-
core: 0,
|
|
2963
|
-
community: 1,
|
|
2964
|
-
imported: 2,
|
|
2965
|
-
user: 3
|
|
2966
|
-
};
|
|
2967
|
-
function resolveSourceTier(sourceIdOrMetaKey, explicitTier) {
|
|
2968
|
-
if (explicitTier) return explicitTier;
|
|
2969
|
-
if (!sourceIdOrMetaKey) return "core";
|
|
2970
|
-
if (sourceIdOrMetaKey === "user") return "user";
|
|
2971
|
-
const id = sourceIdOrMetaKey.startsWith("seed:") ? sourceIdOrMetaKey.slice(5) : sourceIdOrMetaKey;
|
|
2972
|
-
if (id === "core" || id === "efforts" || id === "equipment") return "core";
|
|
2973
|
-
if (id === "community" || id.startsWith("community-") || id === "common") return "community";
|
|
2974
|
-
return "imported";
|
|
2975
|
-
}
|
|
2976
|
-
function resolveSourceRank(sourceIdOrMetaKey, explicitTier) {
|
|
2977
|
-
return CATALOG_PRECEDENCE_RANK[resolveSourceTier(sourceIdOrMetaKey, explicitTier)];
|
|
2978
|
-
}
|
|
2979
|
-
function resolveEntityTier(entity) {
|
|
2980
|
-
if (entity.registrySource === "user") return "user";
|
|
2981
|
-
return resolveSourceTier(entity.sourceId);
|
|
2982
|
-
}
|
|
2983
|
-
function resolveEntityRank(entity) {
|
|
2984
|
-
return CATALOG_PRECEDENCE_RANK[resolveEntityTier(entity)];
|
|
2985
|
-
}
|
|
2986
|
-
function dedupeEntitiesByPrecedence(entities) {
|
|
2987
|
-
const bySlug = /* @__PURE__ */ new Map();
|
|
2988
|
-
for (const entity of entities) {
|
|
2989
|
-
const key = entity.slug.toLowerCase();
|
|
2990
|
-
const prior = bySlug.get(key);
|
|
2991
|
-
if (!prior || resolveEntityRank(entity) >= resolveEntityRank(prior)) {
|
|
2992
|
-
bySlug.set(key, entity);
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
return Array.from(bySlug.values());
|
|
2996
|
-
}
|
|
2997
|
-
var INTENSITY_TIERS = ["low", "moderate", "high"];
|
|
2998
|
-
var DEFAULT_RESOLVER_OPTIONS = {
|
|
2999
|
-
defaultMet: 5,
|
|
3000
|
-
defaultThreshold: 2
|
|
3001
|
-
};
|
|
3002
|
-
|
|
3003
3685
|
// src/effort-registry/disciplines.ts
|
|
3004
3686
|
var EFFORT_DISCIPLINES = [
|
|
3005
3687
|
"bodyweight",
|
|
@@ -3453,71 +4135,6 @@ function equipmentToDocument(equipment) {
|
|
|
3453
4135
|
}
|
|
3454
4136
|
return lines.join("\n");
|
|
3455
4137
|
}
|
|
3456
|
-
|
|
3457
|
-
// src/effort-registry/fuzzyMatch.ts
|
|
3458
|
-
function levenshteinDistance(a, b) {
|
|
3459
|
-
if (a === b) return 0;
|
|
3460
|
-
if (a.length === 0) return b.length;
|
|
3461
|
-
if (b.length === 0) return a.length;
|
|
3462
|
-
if (a.length > b.length) {
|
|
3463
|
-
const tmp = a;
|
|
3464
|
-
a = b;
|
|
3465
|
-
b = tmp;
|
|
3466
|
-
}
|
|
3467
|
-
const m = a.length;
|
|
3468
|
-
const n = b.length;
|
|
3469
|
-
let prev = new Uint32Array(m + 1);
|
|
3470
|
-
let curr = new Uint32Array(m + 1);
|
|
3471
|
-
for (let i = 0; i <= m; i++) {
|
|
3472
|
-
prev[i] = i;
|
|
3473
|
-
}
|
|
3474
|
-
for (let j = 1; j <= n; j++) {
|
|
3475
|
-
curr[0] = j;
|
|
3476
|
-
const bj = b.charCodeAt(j - 1);
|
|
3477
|
-
for (let i = 1; i <= m; i++) {
|
|
3478
|
-
const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;
|
|
3479
|
-
curr[i] = Math.min(
|
|
3480
|
-
prev[i] + 1,
|
|
3481
|
-
// deletion
|
|
3482
|
-
curr[i - 1] + 1,
|
|
3483
|
-
// insertion
|
|
3484
|
-
prev[i - 1] + cost
|
|
3485
|
-
// substitution
|
|
3486
|
-
);
|
|
3487
|
-
}
|
|
3488
|
-
const swap = prev;
|
|
3489
|
-
prev = curr;
|
|
3490
|
-
curr = swap;
|
|
3491
|
-
}
|
|
3492
|
-
return prev[m];
|
|
3493
|
-
}
|
|
3494
|
-
function normalizeForFuzzy(input) {
|
|
3495
|
-
return input.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, " ").replace(/[-_]+/g, " ").trim().replace(/\s+/g, " ");
|
|
3496
|
-
}
|
|
3497
|
-
function isWithinThreshold(a, b, threshold) {
|
|
3498
|
-
const na = normalizeForFuzzy(a);
|
|
3499
|
-
const nb = normalizeForFuzzy(b);
|
|
3500
|
-
if (na === nb) return true;
|
|
3501
|
-
if (Math.abs(na.length - nb.length) > threshold) return false;
|
|
3502
|
-
return levenshteinDistance(na, nb) <= threshold;
|
|
3503
|
-
}
|
|
3504
|
-
function findBestFuzzyMatch(query, candidates, threshold) {
|
|
3505
|
-
const nq = normalizeForFuzzy(query);
|
|
3506
|
-
let best = null;
|
|
3507
|
-
for (const candidate of candidates) {
|
|
3508
|
-
const nc = normalizeForFuzzy(candidate);
|
|
3509
|
-
if (Math.abs(nq.length - nc.length) > threshold) continue;
|
|
3510
|
-
const distance = levenshteinDistance(nq, nc);
|
|
3511
|
-
if (distance > threshold) continue;
|
|
3512
|
-
if (!best || distance < best.distance) {
|
|
3513
|
-
best = { target: candidate, distance };
|
|
3514
|
-
if (distance === 0) break;
|
|
3515
|
-
}
|
|
3516
|
-
}
|
|
3517
|
-
return best;
|
|
3518
|
-
}
|
|
3519
|
-
|
|
3520
|
-
// src/effort-registry/EffortResolver.ts
|
|
3521
4138
|
function normalizeSlug(label) {
|
|
3522
4139
|
return normalizeForFuzzy(label).replace(/\s+/g, "-");
|
|
3523
4140
|
}
|
|
@@ -3730,162 +4347,64 @@ var fixtureUserOverride = {
|
|
|
3730
4347
|
label: "Rowing (Calibrated)",
|
|
3731
4348
|
aliases: ["row", "rower", "erg"],
|
|
3732
4349
|
baseAttributes: { met: 8.5, discipline: "rowing", intensityTier: "high" },
|
|
3733
|
-
registrySource: "user",
|
|
3734
|
-
createdAt: "2024-02-01T08:00:00Z",
|
|
3735
|
-
updatedAt: "2024-02-01T08:00:00Z"
|
|
3736
|
-
};
|
|
3737
|
-
var commonFixtureSet = [
|
|
3738
|
-
fixtureRunning,
|
|
3739
|
-
fixtureRowing,
|
|
3740
|
-
fixtureBackSquat,
|
|
3741
|
-
fixtureBurpee,
|
|
3742
|
-
fixturePlank
|
|
3743
|
-
];
|
|
3744
|
-
var fullFixtureSet = [
|
|
3745
|
-
...commonFixtureSet,
|
|
3746
|
-
fixtureUserCustom,
|
|
3747
|
-
fixtureUserOverride
|
|
3748
|
-
];
|
|
3749
|
-
|
|
3750
|
-
// src/effort-registry/data/bundled-efforts.ts
|
|
3751
|
-
var bundledEfforts = commonFixtureSet;
|
|
3752
|
-
var BUNDLED_EFFORT_COUNT = bundledEfforts.length;
|
|
3753
|
-
|
|
3754
|
-
// src/effort-registry/InMemoryEffortRegistry.ts
|
|
3755
|
-
var InMemoryEffortRegistry = class {
|
|
3756
|
-
efforts = /* @__PURE__ */ new Map();
|
|
3757
|
-
sourceFilter = null;
|
|
3758
|
-
initialized = false;
|
|
3759
|
-
constructor(sourceFilter) {
|
|
3760
|
-
this.sourceFilter = sourceFilter ?? null;
|
|
3761
|
-
}
|
|
3762
|
-
/** Seed with bundled efforts */
|
|
3763
|
-
async loadBundled() {
|
|
3764
|
-
for (const effort of bundledEfforts) {
|
|
3765
|
-
if (!this.sourceFilter || effort.registrySource === this.sourceFilter) {
|
|
3766
|
-
this.efforts.set(effort.slug, effort);
|
|
3767
|
-
}
|
|
3768
|
-
}
|
|
3769
|
-
this.initialized = true;
|
|
3770
|
-
}
|
|
3771
|
-
isInitialized() {
|
|
3772
|
-
return this.initialized;
|
|
3773
|
-
}
|
|
3774
|
-
/** Seed with arbitrary efforts (useful for tests) */
|
|
3775
|
-
seed(efforts) {
|
|
3776
|
-
for (const effort of efforts) {
|
|
3777
|
-
this.efforts.set(effort.slug, effort);
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
/** Clear all efforts */
|
|
3781
|
-
clear() {
|
|
3782
|
-
this.efforts.clear();
|
|
3783
|
-
this.initialized = false;
|
|
3784
|
-
}
|
|
3785
|
-
resolve(slug) {
|
|
3786
|
-
return this.efforts.get(slug) ?? null;
|
|
3787
|
-
}
|
|
3788
|
-
resolveByAlias(label) {
|
|
3789
|
-
const normalized = normalizeForFuzzy(label);
|
|
3790
|
-
for (const effort of this.efforts.values()) {
|
|
3791
|
-
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
3792
|
-
for (const alias of effort.aliases) {
|
|
3793
|
-
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
3794
|
-
}
|
|
3795
|
-
}
|
|
3796
|
-
return null;
|
|
3797
|
-
}
|
|
3798
|
-
list() {
|
|
3799
|
-
return Array.from(this.efforts.values());
|
|
3800
|
-
}
|
|
3801
|
-
listByOrigin(origin) {
|
|
3802
|
-
return this.list().filter((e) => e.registrySource === origin);
|
|
3803
|
-
}
|
|
3804
|
-
async upsert(effort) {
|
|
3805
|
-
if (effort.registrySource !== "user") {
|
|
3806
|
-
throw new Error(
|
|
3807
|
-
`InMemoryEffortRegistry.upsert: only user efforts can be written. Received registrySource="${effort.registrySource}"`
|
|
3808
|
-
);
|
|
3809
|
-
}
|
|
3810
|
-
this.efforts.set(effort.slug, effort);
|
|
3811
|
-
}
|
|
3812
|
-
async delete(slug) {
|
|
3813
|
-
const existing = this.efforts.get(slug);
|
|
3814
|
-
if (existing && existing.registrySource !== "user") {
|
|
3815
|
-
throw new Error(
|
|
3816
|
-
`InMemoryEffortRegistry.delete: cannot delete non-user effort "${slug}"`
|
|
3817
|
-
);
|
|
3818
|
-
}
|
|
3819
|
-
this.efforts.delete(slug);
|
|
3820
|
-
}
|
|
3821
|
-
};
|
|
3822
|
-
|
|
3823
|
-
// src/effort-registry/CompositeEffortRegistry.ts
|
|
3824
|
-
var CompositeEffortRegistry = class {
|
|
3825
|
-
userEfforts = /* @__PURE__ */ new Map();
|
|
3826
|
-
bundledEfforts = /* @__PURE__ */ new Map();
|
|
3827
|
-
initialized = false;
|
|
3828
|
-
initialBundled;
|
|
3829
|
-
storage;
|
|
3830
|
-
constructor(options) {
|
|
3831
|
-
if (options) {
|
|
3832
|
-
this.initialBundled = options.bundled;
|
|
3833
|
-
this.storage = options.storage;
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
/**
|
|
3837
|
-
* Initialize the registry:
|
|
3838
|
-
* 1. Load bundled efforts into memory
|
|
3839
|
-
* 2. Load user efforts from storage adapter into memory
|
|
3840
|
-
*
|
|
3841
|
-
* An explicit `customBundled` REPLACES the bundled tier — passing an empty
|
|
3842
|
-
* set clears it — so a seed source removal never leaves stale bundled
|
|
3843
|
-
* efforts cached. The no-argument call keeps the constructor/default set.
|
|
3844
|
-
* Both tiers rebuild only AFTER a successful adapter load: a failed load
|
|
3845
|
-
* propagates (readiness must never report over stale data), and deleted
|
|
3846
|
-
* custom rows disappear because the user map is cleared too.
|
|
3847
|
-
*/
|
|
3848
|
-
async loadBundled(customBundled) {
|
|
3849
|
-
this.bundledEfforts.clear();
|
|
3850
|
-
const source = customBundled ?? this.initialBundled ?? bundledEfforts;
|
|
3851
|
-
for (const effort of source) {
|
|
3852
|
-
const prior = this.bundledEfforts.get(effort.slug);
|
|
3853
|
-
if (!prior || resolveEntityRank(effort) >= resolveEntityRank(prior)) {
|
|
3854
|
-
this.bundledEfforts.set(effort.slug, effort);
|
|
3855
|
-
}
|
|
3856
|
-
}
|
|
3857
|
-
this.userEfforts.clear();
|
|
3858
|
-
if (this.storage) {
|
|
3859
|
-
try {
|
|
3860
|
-
const userEfforts = await this.storage.load();
|
|
3861
|
-
for (const effort of userEfforts) {
|
|
3862
|
-
if (effort.registrySource === "user") {
|
|
3863
|
-
this.userEfforts.set(effort.slug, effort);
|
|
3864
|
-
}
|
|
3865
|
-
}
|
|
3866
|
-
} catch (err) {
|
|
3867
|
-
console.warn("[CompositeEffortRegistry] Failed to load user efforts from storage adapter:", err);
|
|
4350
|
+
registrySource: "user",
|
|
4351
|
+
createdAt: "2024-02-01T08:00:00Z",
|
|
4352
|
+
updatedAt: "2024-02-01T08:00:00Z"
|
|
4353
|
+
};
|
|
4354
|
+
var commonFixtureSet = [
|
|
4355
|
+
fixtureRunning,
|
|
4356
|
+
fixtureRowing,
|
|
4357
|
+
fixtureBackSquat,
|
|
4358
|
+
fixtureBurpee,
|
|
4359
|
+
fixturePlank
|
|
4360
|
+
];
|
|
4361
|
+
var fullFixtureSet = [
|
|
4362
|
+
...commonFixtureSet,
|
|
4363
|
+
fixtureUserCustom,
|
|
4364
|
+
fixtureUserOverride
|
|
4365
|
+
];
|
|
4366
|
+
|
|
4367
|
+
// src/effort-registry/data/bundled-efforts.ts
|
|
4368
|
+
var bundledEfforts = commonFixtureSet;
|
|
4369
|
+
var BUNDLED_EFFORT_COUNT = bundledEfforts.length;
|
|
4370
|
+
|
|
4371
|
+
// src/effort-registry/InMemoryEffortRegistry.ts
|
|
4372
|
+
var InMemoryEffortRegistry = class {
|
|
4373
|
+
efforts = /* @__PURE__ */ new Map();
|
|
4374
|
+
sourceFilter = null;
|
|
4375
|
+
initialized = false;
|
|
4376
|
+
constructor(sourceFilter) {
|
|
4377
|
+
this.sourceFilter = sourceFilter ?? null;
|
|
4378
|
+
}
|
|
4379
|
+
/** Seed with bundled efforts */
|
|
4380
|
+
async loadBundled() {
|
|
4381
|
+
for (const effort of bundledEfforts) {
|
|
4382
|
+
if (!this.sourceFilter || effort.registrySource === this.sourceFilter) {
|
|
4383
|
+
this.efforts.set(effort.slug, effort);
|
|
3868
4384
|
}
|
|
3869
4385
|
}
|
|
3870
4386
|
this.initialized = true;
|
|
3871
4387
|
}
|
|
3872
|
-
/** True after loadBundled() has resolved */
|
|
3873
4388
|
isInitialized() {
|
|
3874
4389
|
return this.initialized;
|
|
3875
4390
|
}
|
|
4391
|
+
/** Seed with arbitrary efforts (useful for tests) */
|
|
4392
|
+
seed(efforts) {
|
|
4393
|
+
for (const effort of efforts) {
|
|
4394
|
+
this.efforts.set(effort.slug, effort);
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
/** Clear all efforts */
|
|
4398
|
+
clear() {
|
|
4399
|
+
this.efforts.clear();
|
|
4400
|
+
this.initialized = false;
|
|
4401
|
+
}
|
|
3876
4402
|
resolve(slug) {
|
|
3877
|
-
return this.
|
|
4403
|
+
return this.efforts.get(slug) ?? null;
|
|
3878
4404
|
}
|
|
3879
4405
|
resolveByAlias(label) {
|
|
3880
4406
|
const normalized = normalizeForFuzzy(label);
|
|
3881
|
-
for (const effort of this.
|
|
3882
|
-
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
3883
|
-
for (const alias of effort.aliases) {
|
|
3884
|
-
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
3885
|
-
}
|
|
3886
|
-
}
|
|
3887
|
-
const bundled = Array.from(this.bundledEfforts.values()).filter((effort) => !this.userEfforts.has(effort.slug)).sort((a, b) => resolveEntityRank(b) - resolveEntityRank(a));
|
|
3888
|
-
for (const effort of bundled) {
|
|
4407
|
+
for (const effort of this.efforts.values()) {
|
|
3889
4408
|
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
3890
4409
|
for (const alias of effort.aliases) {
|
|
3891
4410
|
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
@@ -3894,456 +4413,193 @@ var CompositeEffortRegistry = class {
|
|
|
3894
4413
|
return null;
|
|
3895
4414
|
}
|
|
3896
4415
|
list() {
|
|
3897
|
-
|
|
3898
|
-
for (const [slug, effort] of this.userEfforts) {
|
|
3899
|
-
merged.set(slug, effort);
|
|
3900
|
-
}
|
|
3901
|
-
return Array.from(merged.values());
|
|
4416
|
+
return Array.from(this.efforts.values());
|
|
3902
4417
|
}
|
|
3903
4418
|
listByOrigin(origin) {
|
|
3904
|
-
if (origin === "user") {
|
|
3905
|
-
return Array.from(this.userEfforts.values());
|
|
3906
|
-
}
|
|
3907
|
-
if (origin === "bundled") {
|
|
3908
|
-
return Array.from(this.bundledEfforts.values());
|
|
3909
|
-
}
|
|
3910
4419
|
return this.list().filter((e) => e.registrySource === origin);
|
|
3911
4420
|
}
|
|
3912
4421
|
async upsert(effort) {
|
|
3913
4422
|
if (effort.registrySource !== "user") {
|
|
3914
4423
|
throw new Error(
|
|
3915
|
-
`
|
|
3916
|
-
);
|
|
3917
|
-
}
|
|
3918
|
-
this.userEfforts.set(effort.slug, effort);
|
|
3919
|
-
if (this.storage) {
|
|
3920
|
-
try {
|
|
3921
|
-
await this.storage.save(effort);
|
|
3922
|
-
} catch (err) {
|
|
3923
|
-
console.warn("[CompositeEffortRegistry] Failed to persist effort to storage adapter:", err);
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
3927
|
-
async delete(slug) {
|
|
3928
|
-
const existing = this.resolve(slug);
|
|
3929
|
-
if (existing && existing.registrySource !== "user") {
|
|
3930
|
-
throw new Error(
|
|
3931
|
-
`CompositeEffortRegistry.delete: cannot delete non-user effort "${slug}"`
|
|
4424
|
+
`InMemoryEffortRegistry.upsert: only user efforts can be written. Received registrySource="${effort.registrySource}"`
|
|
3932
4425
|
);
|
|
3933
4426
|
}
|
|
3934
|
-
this.
|
|
3935
|
-
if (this.storage) {
|
|
3936
|
-
try {
|
|
3937
|
-
await this.storage.delete(slug);
|
|
3938
|
-
} catch (err) {
|
|
3939
|
-
console.warn("[CompositeEffortRegistry] Failed to delete effort from storage adapter:", err);
|
|
3940
|
-
}
|
|
3941
|
-
}
|
|
3942
|
-
}
|
|
3943
|
-
};
|
|
3944
|
-
|
|
3945
|
-
// src/effort-registry/aliasManagement.ts
|
|
3946
|
-
async function addAliasToEffort(registry, targetSlug, newAlias) {
|
|
3947
|
-
const trimmed = newAlias.trim();
|
|
3948
|
-
if (!trimmed) {
|
|
3949
|
-
throw new Error("Alias cannot be empty");
|
|
3950
|
-
}
|
|
3951
|
-
const existing = registry.resolve(targetSlug);
|
|
3952
|
-
if (!existing) {
|
|
3953
|
-
throw new Error(`Effort "${targetSlug}" not found in registry`);
|
|
3954
|
-
}
|
|
3955
|
-
const normalizedNew = normalizeForFuzzy(trimmed);
|
|
3956
|
-
const alreadyHas = normalizeForFuzzy(existing.label) === normalizedNew || existing.aliases.some((a) => normalizeForFuzzy(a) === normalizedNew);
|
|
3957
|
-
if (alreadyHas) {
|
|
3958
|
-
return existing;
|
|
3959
|
-
}
|
|
3960
|
-
const updatedAliases = [...existing.aliases, trimmed];
|
|
3961
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3962
|
-
if (existing.registrySource === "bundled") {
|
|
3963
|
-
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? `effort-user-${crypto.randomUUID()}` : `effort-user-${Date.now()}`;
|
|
3964
|
-
const userEffort = {
|
|
3965
|
-
...existing,
|
|
3966
|
-
id,
|
|
3967
|
-
registrySource: "user",
|
|
3968
|
-
aliases: updatedAliases,
|
|
3969
|
-
updatedAt: now,
|
|
3970
|
-
derivation: existing.derivation ?? {
|
|
3971
|
-
parentSlug: existing.slug,
|
|
3972
|
-
coefficients: {},
|
|
3973
|
-
hardOverrides: {}
|
|
3974
|
-
}
|
|
3975
|
-
};
|
|
3976
|
-
await registry.upsert(userEffort);
|
|
3977
|
-
return userEffort;
|
|
3978
|
-
}
|
|
3979
|
-
const updatedUserEffort = {
|
|
3980
|
-
...existing,
|
|
3981
|
-
aliases: updatedAliases,
|
|
3982
|
-
updatedAt: now
|
|
3983
|
-
};
|
|
3984
|
-
await registry.upsert(updatedUserEffort);
|
|
3985
|
-
return updatedUserEffort;
|
|
3986
|
-
}
|
|
3987
|
-
function buildExactIndex(entities) {
|
|
3988
|
-
const deduped = dedupeEntitiesByPrecedence(entities);
|
|
3989
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
3990
|
-
const add = (key, entity) => {
|
|
3991
|
-
if (!key) return;
|
|
3992
|
-
const existing = byKey.get(key);
|
|
3993
|
-
if (existing) {
|
|
3994
|
-
if (!existing.includes(entity)) existing.push(entity);
|
|
3995
|
-
} else {
|
|
3996
|
-
byKey.set(key, [entity]);
|
|
3997
|
-
}
|
|
3998
|
-
};
|
|
3999
|
-
for (const entity of deduped) {
|
|
4000
|
-
add(entity.slug.toLowerCase(), entity);
|
|
4001
|
-
add(normalizeForFuzzy(entity.label), entity);
|
|
4002
|
-
for (const alias of entity.aliases) add(normalizeForFuzzy(alias), entity);
|
|
4003
|
-
}
|
|
4004
|
-
return {
|
|
4005
|
-
byKey,
|
|
4006
|
-
lookup(key) {
|
|
4007
|
-
const direct = byKey.get(key) ?? byKey.get(normalizeForFuzzy(key));
|
|
4008
|
-
if (!direct || direct.length === 0) return { entity: null, ambiguous: [] };
|
|
4009
|
-
if (direct.length === 1) return { entity: direct[0], ambiguous: [] };
|
|
4010
|
-
let maxRank = -1;
|
|
4011
|
-
for (const candidate of direct) {
|
|
4012
|
-
const rank = resolveEntityRank(candidate);
|
|
4013
|
-
if (rank > maxRank) maxRank = rank;
|
|
4014
|
-
}
|
|
4015
|
-
const topTier = direct.filter((candidate) => resolveEntityRank(candidate) === maxRank);
|
|
4016
|
-
if (topTier.length === 1) return { entity: topTier[0], ambiguous: [] };
|
|
4017
|
-
return { entity: null, ambiguous: topTier };
|
|
4018
|
-
}
|
|
4019
|
-
};
|
|
4020
|
-
}
|
|
4021
|
-
function equipmentRef(entity) {
|
|
4022
|
-
return { kind: "equipment", slug: entity.slug, label: entity.label };
|
|
4023
|
-
}
|
|
4024
|
-
function dedupeRefs(refs) {
|
|
4025
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4026
|
-
const result = [];
|
|
4027
|
-
for (const ref of refs) {
|
|
4028
|
-
const key = `${ref.kind}:${ref.slug}`;
|
|
4029
|
-
if (seen.has(key)) continue;
|
|
4030
|
-
seen.add(key);
|
|
4031
|
-
result.push(ref);
|
|
4427
|
+
this.efforts.set(effort.slug, effort);
|
|
4032
4428
|
}
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
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);
|
|
4040
|
-
const materializer = materialize ?? ((effort) => ({
|
|
4041
|
-
effort,
|
|
4042
|
-
definition: effort,
|
|
4043
|
-
slug: effort.slug,
|
|
4044
|
-
label: effort.label,
|
|
4045
|
-
met: effort.baseAttributes.met,
|
|
4046
|
-
baseAttributes: effort.baseAttributes,
|
|
4047
|
-
discipline: effort.baseAttributes.discipline,
|
|
4048
|
-
disciplineFactor: effort.baseAttributes.disciplineFactor ?? 1,
|
|
4049
|
-
intensityTier: effort.baseAttributes.intensityTier,
|
|
4050
|
-
modifiers: {},
|
|
4051
|
-
registrySource: effort.registrySource,
|
|
4052
|
-
resolvedFrom: effort.registrySource === "user" ? "user" : effort.registrySource === "bundled" ? "bundled" : "default",
|
|
4053
|
-
isEstimated: false
|
|
4054
|
-
}));
|
|
4055
|
-
const resolveEffortExact = (key) => {
|
|
4056
|
-
const found = efforts.lookup(key.trim());
|
|
4057
|
-
if (found.entity) return { entity: found.entity };
|
|
4058
|
-
if (found.ambiguous.length > 0) {
|
|
4059
|
-
return {
|
|
4060
|
-
entity: null,
|
|
4061
|
-
diagnostic: {
|
|
4062
|
-
reason: "ambiguous-effort",
|
|
4063
|
-
message: `"${key}" matches multiple efforts.`,
|
|
4064
|
-
token: key,
|
|
4065
|
-
candidates: found.ambiguous.map((e) => e.slug)
|
|
4066
|
-
}
|
|
4067
|
-
};
|
|
4068
|
-
}
|
|
4069
|
-
return {
|
|
4070
|
-
entity: null,
|
|
4071
|
-
diagnostic: { reason: "unknown-effort", message: `Unknown effort "${key}".`, token: key }
|
|
4072
|
-
};
|
|
4073
|
-
};
|
|
4074
|
-
const resolveEquipmentValues = (values) => {
|
|
4075
|
-
const refs = [];
|
|
4076
|
-
const diagnostics = [];
|
|
4077
|
-
for (const value of values) {
|
|
4078
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
4079
|
-
diagnostics.push({
|
|
4080
|
-
reason: "invalid-explicit",
|
|
4081
|
-
message: `Equipment reference must be a slug string, got ${JSON.stringify(value) ?? "null"}.`,
|
|
4082
|
-
token: String(value)
|
|
4083
|
-
});
|
|
4084
|
-
continue;
|
|
4085
|
-
}
|
|
4086
|
-
const found = equipment.lookup(value.trim());
|
|
4087
|
-
if (found.entity) {
|
|
4088
|
-
refs.push(equipmentRef(found.entity));
|
|
4089
|
-
} else if (found.ambiguous.length > 0) {
|
|
4090
|
-
diagnostics.push({
|
|
4091
|
-
reason: "ambiguous-equipment",
|
|
4092
|
-
message: `"${value}" matches multiple equipment entries.`,
|
|
4093
|
-
token: value,
|
|
4094
|
-
candidates: found.ambiguous.map((e) => e.slug)
|
|
4095
|
-
});
|
|
4096
|
-
} else {
|
|
4097
|
-
diagnostics.push({
|
|
4098
|
-
reason: "invalid-explicit",
|
|
4099
|
-
message: `Unknown equipment "${value}".`,
|
|
4100
|
-
token: value
|
|
4101
|
-
});
|
|
4102
|
-
}
|
|
4429
|
+
async delete(slug) {
|
|
4430
|
+
const existing = this.efforts.get(slug);
|
|
4431
|
+
if (existing && existing.registrySource !== "user") {
|
|
4432
|
+
throw new Error(
|
|
4433
|
+
`InMemoryEffortRegistry.delete: cannot delete non-user effort "${slug}"`
|
|
4434
|
+
);
|
|
4103
4435
|
}
|
|
4104
|
-
|
|
4105
|
-
}
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
equipmentRefs = resolvedDefEquipment.refs;
|
|
4141
|
-
for (const diagnostic of resolvedDefEquipment.diagnostics) {
|
|
4142
|
-
push({ ...diagnostic, reason: "unknown-equipment" });
|
|
4143
|
-
}
|
|
4144
|
-
}
|
|
4145
|
-
};
|
|
4146
|
-
if (!source) {
|
|
4147
|
-
if (defaults?.effort) {
|
|
4148
|
-
const found = resolveEffortExact(defaults.effort);
|
|
4149
|
-
if (found.entity) {
|
|
4150
|
-
effortEntity = found.entity;
|
|
4151
|
-
matched = "default";
|
|
4152
|
-
} else {
|
|
4153
|
-
push({
|
|
4154
|
-
reason: found.diagnostic?.reason === "ambiguous-effort" ? "ambiguous-effort" : "unknown-effort",
|
|
4155
|
-
message: `Default effort "${defaults.effort}" is not in the catalog.`,
|
|
4156
|
-
token: defaults.effort,
|
|
4157
|
-
candidates: found.diagnostic?.candidates
|
|
4158
|
-
});
|
|
4159
|
-
}
|
|
4160
|
-
}
|
|
4161
|
-
fillEquipment();
|
|
4162
|
-
return {
|
|
4163
|
-
matched,
|
|
4164
|
-
label: source,
|
|
4165
|
-
effort: effortEntity ? materializer(effortEntity) : null,
|
|
4166
|
-
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
4167
|
-
equipment: equipmentRefs,
|
|
4168
|
-
remaining: "",
|
|
4169
|
-
diagnostics
|
|
4170
|
-
};
|
|
4436
|
+
this.efforts.delete(slug);
|
|
4437
|
+
}
|
|
4438
|
+
};
|
|
4439
|
+
|
|
4440
|
+
// src/effort-registry/CompositeEffortRegistry.ts
|
|
4441
|
+
var CompositeEffortRegistry = class {
|
|
4442
|
+
userEfforts = /* @__PURE__ */ new Map();
|
|
4443
|
+
bundledEfforts = /* @__PURE__ */ new Map();
|
|
4444
|
+
initialized = false;
|
|
4445
|
+
initialBundled;
|
|
4446
|
+
storage;
|
|
4447
|
+
constructor(options) {
|
|
4448
|
+
if (options) {
|
|
4449
|
+
this.initialBundled = options.bundled;
|
|
4450
|
+
this.storage = options.storage;
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
/**
|
|
4454
|
+
* Initialize the registry:
|
|
4455
|
+
* 1. Load bundled efforts into memory
|
|
4456
|
+
* 2. Load user efforts from storage adapter into memory
|
|
4457
|
+
*
|
|
4458
|
+
* An explicit `customBundled` REPLACES the bundled tier — passing an empty
|
|
4459
|
+
* set clears it — so a seed source removal never leaves stale bundled
|
|
4460
|
+
* efforts cached. The no-argument call keeps the constructor/default set.
|
|
4461
|
+
* Both tiers rebuild only AFTER a successful adapter load: a failed load
|
|
4462
|
+
* propagates (readiness must never report over stale data), and deleted
|
|
4463
|
+
* custom rows disappear because the user map is cleared too.
|
|
4464
|
+
*/
|
|
4465
|
+
async loadBundled(customBundled) {
|
|
4466
|
+
this.bundledEfforts.clear();
|
|
4467
|
+
const source = customBundled ?? this.initialBundled ?? bundledEfforts;
|
|
4468
|
+
for (const effort of source) {
|
|
4469
|
+
const prior = this.bundledEfforts.get(effort.slug);
|
|
4470
|
+
if (!prior || resolveEntityRank(effort) >= resolveEntityRank(prior)) {
|
|
4471
|
+
this.bundledEfforts.set(effort.slug, effort);
|
|
4171
4472
|
}
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
effort: null,
|
|
4181
|
-
effortRef: null,
|
|
4182
|
-
equipment: [],
|
|
4183
|
-
remaining: "",
|
|
4184
|
-
diagnostics: [{
|
|
4185
|
-
reason: "malformed-json",
|
|
4186
|
-
message: "Reference object is not valid JSON.",
|
|
4187
|
-
token: source
|
|
4188
|
-
}]
|
|
4189
|
-
};
|
|
4190
|
-
}
|
|
4191
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && ("effort" in parsed || "equipment" in parsed)) {
|
|
4192
|
-
matched = "explicit";
|
|
4193
|
-
const obj = parsed;
|
|
4194
|
-
if (obj.effort !== void 0) {
|
|
4195
|
-
if (typeof obj.effort === "string" && obj.effort.trim()) {
|
|
4196
|
-
const found = resolveEffortExact(obj.effort);
|
|
4197
|
-
effortEntity = found.entity;
|
|
4198
|
-
if (found.diagnostic) {
|
|
4199
|
-
push(found.diagnostic.reason === "ambiguous-effort" ? found.diagnostic : { ...found.diagnostic, reason: "invalid-explicit", message: `Explicit effort "${obj.effort}" is not in the catalog.` });
|
|
4200
|
-
}
|
|
4201
|
-
} else {
|
|
4202
|
-
push({
|
|
4203
|
-
reason: "invalid-explicit",
|
|
4204
|
-
message: 'Explicit "effort" must be a slug string.',
|
|
4205
|
-
token: String(obj.effort)
|
|
4206
|
-
});
|
|
4207
|
-
}
|
|
4208
|
-
}
|
|
4209
|
-
if (obj.equipment !== void 0) {
|
|
4210
|
-
equipmentAuthored = true;
|
|
4211
|
-
const values = Array.isArray(obj.equipment) ? obj.equipment : [obj.equipment];
|
|
4212
|
-
const resolvedEquipment = resolveEquipmentValues(values);
|
|
4213
|
-
equipmentRefs = resolvedEquipment.refs;
|
|
4214
|
-
diagnostics.push(...resolvedEquipment.diagnostics);
|
|
4215
|
-
if (effortEntity?.equipment?.length) {
|
|
4216
|
-
const definitionSlugs = new Set(effortEntity.equipment);
|
|
4217
|
-
const authoredSlugs = equipmentRefs.map((ref) => ref.slug);
|
|
4218
|
-
if (authoredSlugs.length > 0 && authoredSlugs.some((slug) => !definitionSlugs.has(slug))) {
|
|
4219
|
-
push({
|
|
4220
|
-
reason: "conflict",
|
|
4221
|
-
message: `Explicit equipment [${authoredSlugs.join(", ")}] contradicts effort "${effortEntity.slug}" definition equipment [${effortEntity.equipment.join(", ")}].`,
|
|
4222
|
-
token: source
|
|
4223
|
-
});
|
|
4224
|
-
}
|
|
4225
|
-
}
|
|
4473
|
+
}
|
|
4474
|
+
this.userEfforts.clear();
|
|
4475
|
+
if (this.storage) {
|
|
4476
|
+
try {
|
|
4477
|
+
const userEfforts = await this.storage.load();
|
|
4478
|
+
for (const effort of userEfforts) {
|
|
4479
|
+
if (effort.registrySource === "user") {
|
|
4480
|
+
this.userEfforts.set(effort.slug, effort);
|
|
4226
4481
|
}
|
|
4227
|
-
fillEquipment();
|
|
4228
|
-
return {
|
|
4229
|
-
matched,
|
|
4230
|
-
label: source,
|
|
4231
|
-
effort: effortEntity ? materializer(effortEntity) : null,
|
|
4232
|
-
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
4233
|
-
equipment: equipmentRefs,
|
|
4234
|
-
remaining: "",
|
|
4235
|
-
diagnostics
|
|
4236
|
-
};
|
|
4237
|
-
}
|
|
4238
|
-
}
|
|
4239
|
-
const lowerSource = source.toLowerCase();
|
|
4240
|
-
for (const candidate of prefixCandidates) {
|
|
4241
|
-
if (!lowerSource.startsWith(candidate.lower)) continue;
|
|
4242
|
-
const after = source.slice(candidate.lower.length);
|
|
4243
|
-
if (after && !/^\s/.test(after)) continue;
|
|
4244
|
-
const candidateRemaining = after.trim();
|
|
4245
|
-
if (!candidateRemaining) continue;
|
|
4246
|
-
const equipmentFound = equipment.lookup(candidate.lower);
|
|
4247
|
-
if (equipmentFound.ambiguous.length > 0) {
|
|
4248
|
-
push({
|
|
4249
|
-
reason: "ambiguous-equipment",
|
|
4250
|
-
message: `"${candidate.lower}" matches multiple equipment entries.`,
|
|
4251
|
-
token: source,
|
|
4252
|
-
candidates: equipmentFound.ambiguous.map((e) => e.slug)
|
|
4253
|
-
});
|
|
4254
|
-
return {
|
|
4255
|
-
matched: "none",
|
|
4256
|
-
label: source,
|
|
4257
|
-
effort: null,
|
|
4258
|
-
effortRef: null,
|
|
4259
|
-
equipment: [],
|
|
4260
|
-
remaining: "",
|
|
4261
|
-
diagnostics
|
|
4262
|
-
};
|
|
4263
|
-
}
|
|
4264
|
-
if (!equipmentFound.entity) continue;
|
|
4265
|
-
const effortFound = resolveEffortExact(candidateRemaining);
|
|
4266
|
-
if (effortFound.entity) {
|
|
4267
|
-
matched = "split";
|
|
4268
|
-
effortEntity = effortFound.entity;
|
|
4269
|
-
equipmentRefs = [equipmentRef(equipmentFound.entity)];
|
|
4270
|
-
equipmentAuthored = true;
|
|
4271
|
-
remaining = candidateRemaining;
|
|
4272
|
-
break;
|
|
4273
|
-
}
|
|
4274
|
-
if (effortFound.diagnostic?.reason === "ambiguous-effort") {
|
|
4275
|
-
push(effortFound.diagnostic);
|
|
4276
|
-
return {
|
|
4277
|
-
matched: "none",
|
|
4278
|
-
label: source,
|
|
4279
|
-
effort: null,
|
|
4280
|
-
effortRef: null,
|
|
4281
|
-
equipment: [],
|
|
4282
|
-
remaining: "",
|
|
4283
|
-
diagnostics
|
|
4284
|
-
};
|
|
4285
4482
|
}
|
|
4483
|
+
} catch (err) {
|
|
4484
|
+
console.warn("[CompositeEffortRegistry] Failed to load user efforts from storage adapter:", err);
|
|
4286
4485
|
}
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
} else if (found.diagnostic) {
|
|
4304
|
-
push(found.diagnostic);
|
|
4305
|
-
}
|
|
4486
|
+
}
|
|
4487
|
+
this.initialized = true;
|
|
4488
|
+
}
|
|
4489
|
+
/** True after loadBundled() has resolved */
|
|
4490
|
+
isInitialized() {
|
|
4491
|
+
return this.initialized;
|
|
4492
|
+
}
|
|
4493
|
+
resolve(slug) {
|
|
4494
|
+
return this.userEfforts.get(slug) ?? this.bundledEfforts.get(slug) ?? null;
|
|
4495
|
+
}
|
|
4496
|
+
resolveByAlias(label) {
|
|
4497
|
+
const normalized = normalizeForFuzzy(label);
|
|
4498
|
+
for (const effort of this.userEfforts.values()) {
|
|
4499
|
+
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
4500
|
+
for (const alias of effort.aliases) {
|
|
4501
|
+
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
4306
4502
|
}
|
|
4307
|
-
fillEquipment();
|
|
4308
|
-
return {
|
|
4309
|
-
matched,
|
|
4310
|
-
label: source,
|
|
4311
|
-
effort: effortEntity ? materializer(effortEntity) : null,
|
|
4312
|
-
effortRef: effortEntity ? { kind: "effort", slug: effortEntity.slug, label: effortEntity.label } : null,
|
|
4313
|
-
equipment: equipmentRefs,
|
|
4314
|
-
remaining,
|
|
4315
|
-
diagnostics
|
|
4316
|
-
};
|
|
4317
4503
|
}
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
const equipment = [];
|
|
4324
|
-
const seenEffort = /* @__PURE__ */ new Set();
|
|
4325
|
-
const seenEquipment = /* @__PURE__ */ new Set();
|
|
4326
|
-
const pushUnique = (list, seen, value) => {
|
|
4327
|
-
if (!value) return;
|
|
4328
|
-
if (seen.has(value)) return;
|
|
4329
|
-
seen.add(value);
|
|
4330
|
-
list.push(value);
|
|
4331
|
-
};
|
|
4332
|
-
for (const statement of statements ?? []) {
|
|
4333
|
-
const metrics = Array.isArray(statement.metrics) ? statement.metrics : statement.metrics?.toArray?.() ?? [];
|
|
4334
|
-
for (const metric of metrics) {
|
|
4335
|
-
const refs = getReferenceMetadata(metric.metadata)?.refs;
|
|
4336
|
-
if (!refs) continue;
|
|
4337
|
-
for (const ref of refs) {
|
|
4338
|
-
if (ref.kind === "effort") pushUnique(efforts, seenEffort, ref.slug);
|
|
4339
|
-
if (ref.kind === "equipment") pushUnique(equipment, seenEquipment, ref.slug);
|
|
4504
|
+
const bundled = Array.from(this.bundledEfforts.values()).filter((effort) => !this.userEfforts.has(effort.slug)).sort((a, b) => resolveEntityRank(b) - resolveEntityRank(a));
|
|
4505
|
+
for (const effort of bundled) {
|
|
4506
|
+
if (normalizeForFuzzy(effort.label) === normalized) return effort;
|
|
4507
|
+
for (const alias of effort.aliases) {
|
|
4508
|
+
if (normalizeForFuzzy(alias) === normalized) return effort;
|
|
4340
4509
|
}
|
|
4341
4510
|
}
|
|
4511
|
+
return null;
|
|
4512
|
+
}
|
|
4513
|
+
list() {
|
|
4514
|
+
const merged = new Map(this.bundledEfforts);
|
|
4515
|
+
for (const [slug, effort] of this.userEfforts) {
|
|
4516
|
+
merged.set(slug, effort);
|
|
4517
|
+
}
|
|
4518
|
+
return Array.from(merged.values());
|
|
4342
4519
|
}
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4520
|
+
listByOrigin(origin) {
|
|
4521
|
+
if (origin === "user") {
|
|
4522
|
+
return Array.from(this.userEfforts.values());
|
|
4523
|
+
}
|
|
4524
|
+
if (origin === "bundled") {
|
|
4525
|
+
return Array.from(this.bundledEfforts.values());
|
|
4526
|
+
}
|
|
4527
|
+
return this.list().filter((e) => e.registrySource === origin);
|
|
4528
|
+
}
|
|
4529
|
+
async upsert(effort) {
|
|
4530
|
+
if (effort.registrySource !== "user") {
|
|
4531
|
+
throw new Error(
|
|
4532
|
+
`CompositeEffortRegistry.upsert: only user efforts can be written. Received registrySource="${effort.registrySource}"`
|
|
4533
|
+
);
|
|
4534
|
+
}
|
|
4535
|
+
this.userEfforts.set(effort.slug, effort);
|
|
4536
|
+
if (this.storage) {
|
|
4537
|
+
try {
|
|
4538
|
+
await this.storage.save(effort);
|
|
4539
|
+
} catch (err) {
|
|
4540
|
+
console.warn("[CompositeEffortRegistry] Failed to persist effort to storage adapter:", err);
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
async delete(slug) {
|
|
4545
|
+
const existing = this.resolve(slug);
|
|
4546
|
+
if (existing && existing.registrySource !== "user") {
|
|
4547
|
+
throw new Error(
|
|
4548
|
+
`CompositeEffortRegistry.delete: cannot delete non-user effort "${slug}"`
|
|
4549
|
+
);
|
|
4550
|
+
}
|
|
4551
|
+
this.userEfforts.delete(slug);
|
|
4552
|
+
if (this.storage) {
|
|
4553
|
+
try {
|
|
4554
|
+
await this.storage.delete(slug);
|
|
4555
|
+
} catch (err) {
|
|
4556
|
+
console.warn("[CompositeEffortRegistry] Failed to delete effort from storage adapter:", err);
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
};
|
|
4561
|
+
|
|
4562
|
+
// src/effort-registry/aliasManagement.ts
|
|
4563
|
+
async function addAliasToEffort(registry, targetSlug, newAlias) {
|
|
4564
|
+
const trimmed = newAlias.trim();
|
|
4565
|
+
if (!trimmed) {
|
|
4566
|
+
throw new Error("Alias cannot be empty");
|
|
4567
|
+
}
|
|
4568
|
+
const existing = registry.resolve(targetSlug);
|
|
4569
|
+
if (!existing) {
|
|
4570
|
+
throw new Error(`Effort "${targetSlug}" not found in registry`);
|
|
4571
|
+
}
|
|
4572
|
+
const normalizedNew = normalizeForFuzzy(trimmed);
|
|
4573
|
+
const alreadyHas = normalizeForFuzzy(existing.label) === normalizedNew || existing.aliases.some((a) => normalizeForFuzzy(a) === normalizedNew);
|
|
4574
|
+
if (alreadyHas) {
|
|
4575
|
+
return existing;
|
|
4576
|
+
}
|
|
4577
|
+
const updatedAliases = [...existing.aliases, trimmed];
|
|
4578
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4579
|
+
if (existing.registrySource === "bundled") {
|
|
4580
|
+
const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? `effort-user-${crypto.randomUUID()}` : `effort-user-${Date.now()}`;
|
|
4581
|
+
const userEffort = {
|
|
4582
|
+
...existing,
|
|
4583
|
+
id,
|
|
4584
|
+
registrySource: "user",
|
|
4585
|
+
aliases: updatedAliases,
|
|
4586
|
+
updatedAt: now,
|
|
4587
|
+
derivation: existing.derivation ?? {
|
|
4588
|
+
parentSlug: existing.slug,
|
|
4589
|
+
coefficients: {},
|
|
4590
|
+
hardOverrides: {}
|
|
4591
|
+
}
|
|
4592
|
+
};
|
|
4593
|
+
await registry.upsert(userEffort);
|
|
4594
|
+
return userEffort;
|
|
4595
|
+
}
|
|
4596
|
+
const updatedUserEffort = {
|
|
4597
|
+
...existing,
|
|
4598
|
+
aliases: updatedAliases,
|
|
4599
|
+
updatedAt: now
|
|
4600
|
+
};
|
|
4601
|
+
await registry.upsert(updatedUserEffort);
|
|
4602
|
+
return updatedUserEffort;
|
|
4347
4603
|
}
|
|
4348
4604
|
|
|
4349
4605
|
// src/runtime/contracts/IRuntimeOptions.ts
|
|
@@ -6498,6 +6754,8 @@ var RuntimeBlock = class {
|
|
|
6498
6754
|
blockType;
|
|
6499
6755
|
context;
|
|
6500
6756
|
sourceIds;
|
|
6757
|
+
/** The tags this block's statements referenced, resolved at compile time. */
|
|
6758
|
+
tagComposition;
|
|
6501
6759
|
// List-based memory storage
|
|
6502
6760
|
_memory = [];
|
|
6503
6761
|
/**
|
|
@@ -6534,13 +6792,15 @@ var RuntimeBlock = class {
|
|
|
6534
6792
|
key,
|
|
6535
6793
|
blockType,
|
|
6536
6794
|
label,
|
|
6537
|
-
metrics
|
|
6795
|
+
metrics,
|
|
6796
|
+
tagComposition
|
|
6538
6797
|
} = options;
|
|
6539
6798
|
this._runtime = runtime;
|
|
6540
6799
|
this.sourceIds = sourceIds;
|
|
6541
6800
|
this.behaviors = behaviors;
|
|
6542
6801
|
this.key = key ?? new BlockKey();
|
|
6543
6802
|
this.blockType = blockType;
|
|
6803
|
+
this.tagComposition = tagComposition;
|
|
6544
6804
|
this.context = context ?? new BlockContext(runtime, this.key.toString());
|
|
6545
6805
|
if (label) {
|
|
6546
6806
|
this._memory.push(new MemoryLocation("metric:label", [{
|
|
@@ -8481,61 +8741,153 @@ var SoundMetric = class {
|
|
|
8481
8741
|
image;
|
|
8482
8742
|
};
|
|
8483
8743
|
|
|
8484
|
-
// src/runtime/
|
|
8485
|
-
|
|
8744
|
+
// src/runtime/sound/SoundProfile.ts
|
|
8745
|
+
function parseSoundTrigger(notation) {
|
|
8746
|
+
const trimmed = notation.trim();
|
|
8747
|
+
if (trimmed.length === 0) return void 0;
|
|
8748
|
+
let anchor;
|
|
8749
|
+
let magnitude;
|
|
8750
|
+
if (trimmed.startsWith("-")) {
|
|
8751
|
+
anchor = "end";
|
|
8752
|
+
magnitude = trimmed.slice(1);
|
|
8753
|
+
} else {
|
|
8754
|
+
anchor = "start";
|
|
8755
|
+
magnitude = trimmed;
|
|
8756
|
+
}
|
|
8757
|
+
if (magnitude.startsWith(":")) magnitude = magnitude.slice(1);
|
|
8758
|
+
const offsetMs = parseDurationMagnitude(magnitude);
|
|
8759
|
+
if (offsetMs === void 0) return void 0;
|
|
8760
|
+
return { anchor, offsetMs };
|
|
8761
|
+
}
|
|
8762
|
+
function parseDurationMagnitude(magnitude) {
|
|
8763
|
+
const cleaned = magnitude.trim().replace(/^:/, "");
|
|
8764
|
+
if (cleaned.length === 0) return 0;
|
|
8765
|
+
if (!/^\d+(:\d+)*$/.test(cleaned)) return void 0;
|
|
8766
|
+
const segments = cleaned.split(":").map(Number).reverse();
|
|
8767
|
+
const [seconds = 0, minutes = 0, hours = 0, days = 0] = segments;
|
|
8768
|
+
if (segments.length > 4) return void 0;
|
|
8769
|
+
if (minutes > 59 || hours > 59) return void 0;
|
|
8770
|
+
return ((days * 24 + hours) * 60 + minutes) * 6e4 + seconds * 1e3;
|
|
8771
|
+
}
|
|
8772
|
+
function formatSoundTrigger(trigger) {
|
|
8773
|
+
const totalSeconds = Math.round(trigger.offsetMs / 1e3);
|
|
8774
|
+
const seconds = totalSeconds % 60;
|
|
8775
|
+
const minutes = Math.floor(totalSeconds / 60) % 60;
|
|
8776
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
8777
|
+
let magnitude;
|
|
8778
|
+
if (hours > 0) {
|
|
8779
|
+
magnitude = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
|
8780
|
+
} else if (minutes > 0) {
|
|
8781
|
+
magnitude = `${pad(minutes)}:${pad(seconds)}`;
|
|
8782
|
+
} else {
|
|
8783
|
+
magnitude = pad(seconds);
|
|
8784
|
+
}
|
|
8785
|
+
return `${trigger.anchor === "end" ? "-:" : ":"}${magnitude}`;
|
|
8786
|
+
}
|
|
8787
|
+
function pad(value) {
|
|
8788
|
+
return value < 10 ? `0${value}` : String(value);
|
|
8789
|
+
}
|
|
8790
|
+
function resolveProfileRules(profile, durationMs) {
|
|
8791
|
+
return profile.rules.filter((rule) => {
|
|
8792
|
+
const { anchor, offsetMs } = rule.trigger;
|
|
8793
|
+
if (anchor === "start") return true;
|
|
8794
|
+
if (offsetMs === 0) return true;
|
|
8795
|
+
if (durationMs === void 0) return false;
|
|
8796
|
+
return offsetMs <= durationMs;
|
|
8797
|
+
});
|
|
8798
|
+
}
|
|
8799
|
+
var DEFAULT_SOUND_PROFILE = {
|
|
8800
|
+
id: "default",
|
|
8801
|
+
name: "Default",
|
|
8802
|
+
rules: [
|
|
8803
|
+
{ trigger: { anchor: "end", offsetMs: 3e3 }, sound: "countdown-beep" },
|
|
8804
|
+
{ trigger: { anchor: "end", offsetMs: 2e3 }, sound: "countdown-beep" },
|
|
8805
|
+
{ trigger: { anchor: "end", offsetMs: 1e3 }, sound: "countdown-beep" },
|
|
8806
|
+
{ trigger: { anchor: "end", offsetMs: 0 }, sound: "timer-complete" }
|
|
8807
|
+
]
|
|
8808
|
+
};
|
|
8809
|
+
|
|
8810
|
+
// src/runtime/behaviors/SoundProfileBehavior.ts
|
|
8811
|
+
var SOUND_PLAY_EVENT = "sound:play";
|
|
8812
|
+
var SoundProfileBehavior = class {
|
|
8486
8813
|
constructor(config) {
|
|
8487
8814
|
this.config = config;
|
|
8488
8815
|
}
|
|
8489
8816
|
config;
|
|
8817
|
+
rules = [];
|
|
8818
|
+
/** Rule indices already fired, so each rule fires exactly once. */
|
|
8819
|
+
fired = /* @__PURE__ */ new Set();
|
|
8820
|
+
durationMs;
|
|
8490
8821
|
onMount(ctx) {
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
ctx.subscribe("tick", (_event, tickCtx) => {
|
|
8502
|
-
const timer = readTimer(tickCtx);
|
|
8503
|
-
if (!timer || timer.direction !== "down" || !timer.durationMs) return [];
|
|
8504
|
-
const now = tickCtx.clock.currentDate.getTime();
|
|
8505
|
-
let elapsed = 0;
|
|
8506
|
-
for (const span of timer.spans) {
|
|
8507
|
-
const end = span.ended ?? now;
|
|
8508
|
-
elapsed += end - span.started;
|
|
8509
|
-
}
|
|
8510
|
-
const remainingMs = timer.durationMs - elapsed;
|
|
8511
|
-
const remainingSeconds = Math.ceil(remainingMs / 1e3);
|
|
8512
|
-
for (const cue of countdownCues) {
|
|
8513
|
-
if (cue.atSeconds?.includes(remainingSeconds) && !playedSeconds.has(remainingSeconds)) {
|
|
8514
|
-
playedSeconds.add(remainingSeconds);
|
|
8515
|
-
tickCtx.emitOutput("system", [
|
|
8516
|
-
new SoundMetric(cue.sound, "countdown", { atSecond: remainingSeconds })
|
|
8517
|
-
], { label: `Countdown: ${remainingSeconds}s` });
|
|
8518
|
-
}
|
|
8519
|
-
}
|
|
8520
|
-
return [];
|
|
8521
|
-
}, { scope: "bubble" });
|
|
8522
|
-
}
|
|
8822
|
+
const timer = readTimer(ctx);
|
|
8823
|
+
this.durationMs = timer?.durationMs;
|
|
8824
|
+
this.rules = resolveProfileRules(this.config.profile, this.durationMs);
|
|
8825
|
+
this.fireDue(ctx, 0, this.durationMs);
|
|
8826
|
+
ctx.subscribe("tick", (_event, tickCtx) => {
|
|
8827
|
+
const elapsed = this.elapsedMs(tickCtx);
|
|
8828
|
+
if (elapsed === void 0) return [];
|
|
8829
|
+
this.fireDue(tickCtx, elapsed, this.durationMs);
|
|
8830
|
+
return [];
|
|
8831
|
+
}, { scope: "bubble" });
|
|
8523
8832
|
return [];
|
|
8524
8833
|
}
|
|
8525
8834
|
onNext(_ctx) {
|
|
8526
8835
|
return [];
|
|
8527
8836
|
}
|
|
8528
8837
|
onUnmount(ctx) {
|
|
8529
|
-
|
|
8530
|
-
if (cue.trigger === "unmount" || cue.trigger === "complete") {
|
|
8531
|
-
ctx.emitOutput("system", [
|
|
8532
|
-
new SoundMetric(cue.sound, cue.trigger)
|
|
8533
|
-
], { label: `Sound: ${cue.sound}` });
|
|
8534
|
-
}
|
|
8535
|
-
}
|
|
8838
|
+
this.fireDue(ctx, this.elapsedMs(ctx) ?? 0, this.durationMs, true);
|
|
8536
8839
|
return [];
|
|
8537
8840
|
}
|
|
8538
8841
|
onDispose(_ctx) {
|
|
8842
|
+
this.fired.clear();
|
|
8843
|
+
}
|
|
8844
|
+
/**
|
|
8845
|
+
* Elapsed milliseconds for the block's own timer, or `undefined` when the
|
|
8846
|
+
* block has no timer to measure against.
|
|
8847
|
+
*/
|
|
8848
|
+
elapsedMs(ctx) {
|
|
8849
|
+
const timer = readTimer(ctx);
|
|
8850
|
+
if (!timer) return void 0;
|
|
8851
|
+
return calculateElapsed(timer, ctx.clock.currentDate.getTime());
|
|
8852
|
+
}
|
|
8853
|
+
/**
|
|
8854
|
+
* Fire every rule whose trigger point has been reached.
|
|
8855
|
+
*
|
|
8856
|
+
* @param elapsed milliseconds elapsed on the block's timer
|
|
8857
|
+
* @param durationMs the block's total duration
|
|
8858
|
+
* @param ending whether the block is ending — a rule anchored to the end
|
|
8859
|
+
* with no offset fires then, even when the block has no duration to
|
|
8860
|
+
* count down from
|
|
8861
|
+
*/
|
|
8862
|
+
fireDue(ctx, elapsed, durationMs, ending = false) {
|
|
8863
|
+
const remaining = durationMs === void 0 ? void 0 : Math.max(0, durationMs - elapsed);
|
|
8864
|
+
this.rules.forEach((rule, index) => {
|
|
8865
|
+
if (this.fired.has(index)) return;
|
|
8866
|
+
const { anchor, offsetMs } = rule.trigger;
|
|
8867
|
+
const due = anchor === "start" ? elapsed >= offsetMs : offsetMs === 0 ? ending || remaining !== void 0 && remaining <= 0 : remaining !== void 0 && remaining <= offsetMs;
|
|
8868
|
+
if (!due) return;
|
|
8869
|
+
this.fired.add(index);
|
|
8870
|
+
this.play(ctx, rule, anchor, offsetMs);
|
|
8871
|
+
});
|
|
8872
|
+
}
|
|
8873
|
+
/**
|
|
8874
|
+
* Record the cue and command its playback.
|
|
8875
|
+
*/
|
|
8876
|
+
play(ctx, rule, anchor, offsetMs) {
|
|
8877
|
+
const trigger = anchor === "start" ? offsetMs === 0 ? "mount" : "countdown" : offsetMs === 0 ? "complete" : "countdown";
|
|
8878
|
+
ctx.emitOutput("system", [
|
|
8879
|
+
new SoundMetric(rule.sound, trigger, {
|
|
8880
|
+
atSecond: offsetMs / 1e3
|
|
8881
|
+
})
|
|
8882
|
+
], { label: `Sound: ${rule.sound}` });
|
|
8883
|
+
ctx.emitEvent({
|
|
8884
|
+
name: SOUND_PLAY_EVENT,
|
|
8885
|
+
timestamp: ctx.clock.currentDate,
|
|
8886
|
+
data: {
|
|
8887
|
+
sound: rule.sound,
|
|
8888
|
+
volume: 1
|
|
8889
|
+
}
|
|
8890
|
+
});
|
|
8539
8891
|
}
|
|
8540
8892
|
};
|
|
8541
8893
|
var ButtonBehavior = class {
|
|
@@ -8665,6 +9017,34 @@ var WaitingToStartInjectorBehavior = class {
|
|
|
8665
9017
|
onDispose(_ctx) {
|
|
8666
9018
|
}
|
|
8667
9019
|
};
|
|
9020
|
+
var SOUND_PROFILE_METRIC = MetricType.Sound;
|
|
9021
|
+
function resolveSoundProfile(statements, options) {
|
|
9022
|
+
const blockProfileId = findBlockProfileId(statements);
|
|
9023
|
+
if (blockProfileId) {
|
|
9024
|
+
const match = findProfile(options?.profiles, blockProfileId);
|
|
9025
|
+
if (match) return match;
|
|
9026
|
+
}
|
|
9027
|
+
if (options?.noteProfileId) {
|
|
9028
|
+
const match = findProfile(options?.profiles, options.noteProfileId);
|
|
9029
|
+
if (match) return match;
|
|
9030
|
+
}
|
|
9031
|
+
return DEFAULT_SOUND_PROFILE;
|
|
9032
|
+
}
|
|
9033
|
+
function findBlockProfileId(statements) {
|
|
9034
|
+
for (const statement of statements) {
|
|
9035
|
+
const metric = statement.metrics.find((m) => m.type === SOUND_PROFILE_METRIC);
|
|
9036
|
+
if (!metric) continue;
|
|
9037
|
+
const value = metric.value;
|
|
9038
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
9039
|
+
return value.trim();
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9042
|
+
return void 0;
|
|
9043
|
+
}
|
|
9044
|
+
function findProfile(profiles, id) {
|
|
9045
|
+
if (!profiles) return void 0;
|
|
9046
|
+
return profiles.find((profile) => profile.id === id);
|
|
9047
|
+
}
|
|
8668
9048
|
|
|
8669
9049
|
// src/runtime/blocks/RestBlock.ts
|
|
8670
9050
|
var RestBlock = class _RestBlock extends RuntimeBlock {
|
|
@@ -8675,7 +9055,7 @@ var RestBlock = class _RestBlock extends RuntimeBlock {
|
|
|
8675
9055
|
const restLabel = config.label ?? "Rest";
|
|
8676
9056
|
const blockKey = new BlockKey("rest");
|
|
8677
9057
|
const context = new BlockContext(runtime, blockKey.toString(), "Rest");
|
|
8678
|
-
const behaviors = _RestBlock.buildBehaviors(config);
|
|
9058
|
+
const behaviors = _RestBlock.buildBehaviors(config, runtime.options?.soundOptions);
|
|
8679
9059
|
super({
|
|
8680
9060
|
runtime,
|
|
8681
9061
|
sourceIds: [],
|
|
@@ -8690,7 +9070,7 @@ var RestBlock = class _RestBlock extends RuntimeBlock {
|
|
|
8690
9070
|
/**
|
|
8691
9071
|
* Builds the behavior list for a rest block.
|
|
8692
9072
|
*/
|
|
8693
|
-
static buildBehaviors(config) {
|
|
9073
|
+
static buildBehaviors(config, soundOptions) {
|
|
8694
9074
|
const behaviors = [];
|
|
8695
9075
|
const restLabel = config.label ?? "Rest";
|
|
8696
9076
|
behaviors.push(new ReportOutputBehavior({ label: restLabel }));
|
|
@@ -8709,18 +9089,8 @@ var RestBlock = class _RestBlock extends RuntimeBlock {
|
|
|
8709
9089
|
mode: "timer",
|
|
8710
9090
|
label: restLabel
|
|
8711
9091
|
}));
|
|
8712
|
-
behaviors.push(new
|
|
8713
|
-
|
|
8714
|
-
{
|
|
8715
|
-
sound: "rest-over",
|
|
8716
|
-
trigger: "complete"
|
|
8717
|
-
},
|
|
8718
|
-
{
|
|
8719
|
-
sound: "countdown-beep",
|
|
8720
|
-
trigger: "countdown",
|
|
8721
|
-
atSeconds: [3, 2, 1]
|
|
8722
|
-
}
|
|
8723
|
-
]
|
|
9092
|
+
behaviors.push(new SoundProfileBehavior({
|
|
9093
|
+
profile: resolveSoundProfile([], soundOptions)
|
|
8724
9094
|
}));
|
|
8725
9095
|
return behaviors;
|
|
8726
9096
|
}
|
|
@@ -8751,6 +9121,8 @@ var BlockBuilder = class {
|
|
|
8751
9121
|
blockType = "Block";
|
|
8752
9122
|
metrics;
|
|
8753
9123
|
sourceIds = [];
|
|
9124
|
+
/** The tags the block's statements referenced, resolved at compile time. */
|
|
9125
|
+
tagComposition = [];
|
|
8754
9126
|
/** Pending round config stored by asRepeater(), consumed by asContainer() */
|
|
8755
9127
|
pendingRoundConfig;
|
|
8756
9128
|
/** Declared exit intent — resolved into a single ExitBehavior in build(). */
|
|
@@ -8850,6 +9222,11 @@ var BlockBuilder = class {
|
|
|
8850
9222
|
this.sourceIds = ids;
|
|
8851
9223
|
return this;
|
|
8852
9224
|
}
|
|
9225
|
+
/** The tags the block's statements referenced, and what each carried. */
|
|
9226
|
+
setTagComposition(composition) {
|
|
9227
|
+
this.tagComposition = composition;
|
|
9228
|
+
return this;
|
|
9229
|
+
}
|
|
8853
9230
|
getLabel() {
|
|
8854
9231
|
return this.label;
|
|
8855
9232
|
}
|
|
@@ -8971,6 +9348,7 @@ var BlockBuilder = class {
|
|
|
8971
9348
|
const block = new RuntimeBlock({
|
|
8972
9349
|
runtime: this.runtime,
|
|
8973
9350
|
sourceIds: this.sourceIds,
|
|
9351
|
+
tagComposition: this.tagComposition,
|
|
8974
9352
|
behaviors: Array.from(this.behaviors.values()),
|
|
8975
9353
|
context: this.context,
|
|
8976
9354
|
key: this.key,
|
|
@@ -9060,6 +9438,11 @@ var JitCompiler = class {
|
|
|
9060
9438
|
return void 0;
|
|
9061
9439
|
}
|
|
9062
9440
|
const builder = new BlockBuilder(runtime);
|
|
9441
|
+
const tagComposition = [];
|
|
9442
|
+
for (const node of effectiveNodes) {
|
|
9443
|
+
collectTagReferences(node.metrics, tagComposition);
|
|
9444
|
+
}
|
|
9445
|
+
builder.setTagComposition(tagComposition);
|
|
9063
9446
|
for (const strategy of matchingStrategies) {
|
|
9064
9447
|
strategy.apply(builder, effectiveNodes, runtime);
|
|
9065
9448
|
}
|
|
@@ -10574,6 +10957,16 @@ var CalcEngine = class {
|
|
|
10574
10957
|
const fenced = (d) => !d.fences || d.fences.includes(deps.dialect);
|
|
10575
10958
|
this.segmentCalcs = registry.byScope("segment").filter(fenced);
|
|
10576
10959
|
this.workoutCalcs = registry.byScope("workout").filter(fenced);
|
|
10960
|
+
for (const tag of deps.tags?.list() ?? []) {
|
|
10961
|
+
const resolved = resolveTagContributions(deps.tags, tag.slug);
|
|
10962
|
+
for (const name2 of resolved.functions) {
|
|
10963
|
+
const carriers = this.tagCarriedFunctions.get(name2);
|
|
10964
|
+
if (carriers) carriers.add(tag.slug);
|
|
10965
|
+
else this.tagCarriedFunctions.set(name2, /* @__PURE__ */ new Set([tag.slug]));
|
|
10966
|
+
}
|
|
10967
|
+
}
|
|
10968
|
+
this.runTags = [...deps.activeTags ?? []];
|
|
10969
|
+
for (const tag of this.runTags) this.activatedTags.add(tag);
|
|
10577
10970
|
}
|
|
10578
10971
|
registry;
|
|
10579
10972
|
deps;
|
|
@@ -10587,6 +10980,43 @@ var CalcEngine = class {
|
|
|
10587
10980
|
* calls (segments arrive in stream order, live and in replay).
|
|
10588
10981
|
*/
|
|
10589
10982
|
lastSegmentEffort;
|
|
10983
|
+
/**
|
|
10984
|
+
* The tags that carry each function: function name → the tags that carry
|
|
10985
|
+
* it. A function no tag carries runs everywhere, as it always did.
|
|
10986
|
+
*/
|
|
10987
|
+
tagCarriedFunctions = /* @__PURE__ */ new Map();
|
|
10988
|
+
/** The tags applied anywhere in the run so far. */
|
|
10989
|
+
activatedTags = /* @__PURE__ */ new Set();
|
|
10990
|
+
/** The tags the run activates — a note-level bundle of attachments. */
|
|
10991
|
+
runTags;
|
|
10992
|
+
/**
|
|
10993
|
+
* Whether a calculation runs here.
|
|
10994
|
+
*
|
|
10995
|
+
* A function a tag carries runs only where one of the tags that carry it
|
|
10996
|
+
* applies; every other function runs as it always did.
|
|
10997
|
+
*/
|
|
10998
|
+
isActive(def, appliedTags) {
|
|
10999
|
+
const carriers = this.tagCarriedFunctions.get(def.id);
|
|
11000
|
+
if (!carriers) return true;
|
|
11001
|
+
for (const slug of carriers) {
|
|
11002
|
+
if (appliedTags.includes(slug)) return true;
|
|
11003
|
+
}
|
|
11004
|
+
return false;
|
|
11005
|
+
}
|
|
11006
|
+
/**
|
|
11007
|
+
* The tags applied to a segment: the tags its statement marked, plus the
|
|
11008
|
+
* tags the run activates — a note-level tag applies to every segment of
|
|
11009
|
+
* the note it belongs to.
|
|
11010
|
+
*
|
|
11011
|
+
* The applied tags are recorded, so a workout-scope function the tag
|
|
11012
|
+
* carries runs at the end of the workout.
|
|
11013
|
+
*/
|
|
11014
|
+
appliedTags(output) {
|
|
11015
|
+
const slugs = tagMarkerSlugs(output.metrics.rawMetrics);
|
|
11016
|
+
for (const slug of slugs) this.activatedTags.add(slug);
|
|
11017
|
+
if (this.runTags.length === 0) return slugs;
|
|
11018
|
+
return [...slugs, ...this.runTags.filter((slug) => !slugs.includes(slug))];
|
|
11019
|
+
}
|
|
10590
11020
|
// ── Phase 1: segment scope ─────────────────────────────────────────────
|
|
10591
11021
|
process(output) {
|
|
10592
11022
|
if (output.outputType !== "segment" || !output.isLeaf) return output;
|
|
@@ -10602,11 +11032,13 @@ var CalcEngine = class {
|
|
|
10602
11032
|
if (discipline) effortTags.effortDiscipline = discipline;
|
|
10603
11033
|
if (intensityTier) effortTags.effortIntensityTier = intensityTier;
|
|
10604
11034
|
}
|
|
10605
|
-
const climbGrade =
|
|
11035
|
+
const climbGrade = output.metrics.rawMetrics.find((m) => m.type === "climb-grade")?.value;
|
|
10606
11036
|
if (climbGrade && typeof climbGrade === "object" && "raw" in climbGrade) {
|
|
10607
11037
|
effortTags.grade = String(climbGrade.raw);
|
|
10608
11038
|
}
|
|
11039
|
+
const appliedTags = this.appliedTags(output);
|
|
10609
11040
|
for (const def of this.segmentCalcs) {
|
|
11041
|
+
if (!this.isActive(def, appliedTags)) continue;
|
|
10610
11042
|
const ctx = this.makeContext(atoms);
|
|
10611
11043
|
if (!this.applicable(def.whenAst, ctx)) continue;
|
|
10612
11044
|
const variant = this.selectVariant(def, ctx);
|
|
@@ -10648,7 +11080,15 @@ var CalcEngine = class {
|
|
|
10648
11080
|
// ── Phase 2: workout scope ─────────────────────────────────────────────
|
|
10649
11081
|
summarize(outputs) {
|
|
10650
11082
|
const projections = [];
|
|
11083
|
+
for (const output of outputs) {
|
|
11084
|
+
if (output.outputType !== "segment") continue;
|
|
11085
|
+
for (const slug of tagMarkerSlugs(output.metrics.rawMetrics)) {
|
|
11086
|
+
this.activatedTags.add(slug);
|
|
11087
|
+
}
|
|
11088
|
+
}
|
|
11089
|
+
const appliedTags = [...this.activatedTags];
|
|
10651
11090
|
for (const def of this.workoutCalcs) {
|
|
11091
|
+
if (!this.isActive(def, appliedTags)) continue;
|
|
10652
11092
|
if (def.output?.isGrouped) {
|
|
10653
11093
|
projections.push(...this.summarizeGrouped(def, outputs));
|
|
10654
11094
|
} else {
|
|
@@ -11806,7 +12246,13 @@ function createCalcStack(options) {
|
|
|
11806
12246
|
}
|
|
11807
12247
|
function createCalcEngine(dialect, options) {
|
|
11808
12248
|
const stack = createCalcStack(options);
|
|
11809
|
-
return new CalcEngine(stack.registry, {
|
|
12249
|
+
return new CalcEngine(stack.registry, {
|
|
12250
|
+
dialect,
|
|
12251
|
+
lookups: stack.lookups,
|
|
12252
|
+
userProfile: options.userProfile,
|
|
12253
|
+
tags: options.tags,
|
|
12254
|
+
activeTags: options.activeTags
|
|
12255
|
+
});
|
|
11810
12256
|
}
|
|
11811
12257
|
|
|
11812
12258
|
// src/analytics/StandardAnalyticsProfile.ts
|
|
@@ -11821,7 +12267,9 @@ var StandardAnalyticsProfile = class {
|
|
|
11821
12267
|
const calcEngine = createCalcEngine(context.dialect, {
|
|
11822
12268
|
effortResolver: context.analyticsContext.effortResolver,
|
|
11823
12269
|
userProfile: context.userProfile,
|
|
11824
|
-
calcs: context.calcs
|
|
12270
|
+
calcs: context.calcs,
|
|
12271
|
+
tags: context.tags,
|
|
12272
|
+
activeTags: context.activeTags
|
|
11825
12273
|
});
|
|
11826
12274
|
realtime.push(calcEngine);
|
|
11827
12275
|
summary.push(calcEngine);
|
|
@@ -11873,7 +12321,9 @@ function createAnalyticsEngineForBlock(block, options) {
|
|
|
11873
12321
|
scriptMetricTypes,
|
|
11874
12322
|
analyticsContext,
|
|
11875
12323
|
userProfile: options?.userProfile,
|
|
11876
|
-
calcs: options?.calcs
|
|
12324
|
+
calcs: options?.calcs,
|
|
12325
|
+
tags: options?.tags,
|
|
12326
|
+
activeTags: options?.activeTags
|
|
11877
12327
|
};
|
|
11878
12328
|
const profile = new StandardAnalyticsProfile();
|
|
11879
12329
|
const { realtime, summary } = profile.build(context);
|
|
@@ -12250,13 +12700,6 @@ var AmrapLogicStrategy = class {
|
|
|
12250
12700
|
mode: "countdown",
|
|
12251
12701
|
label
|
|
12252
12702
|
}));
|
|
12253
|
-
builder.addBehavior(new SoundCueBehavior({
|
|
12254
|
-
cues: [
|
|
12255
|
-
{ sound: "start-beep", trigger: "mount" },
|
|
12256
|
-
{ sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
|
|
12257
|
-
{ sound: "timer-complete", trigger: "complete" }
|
|
12258
|
-
]
|
|
12259
|
-
}));
|
|
12260
12703
|
}
|
|
12261
12704
|
};
|
|
12262
12705
|
var IntervalLogicStrategy = class {
|
|
@@ -12314,13 +12757,6 @@ var IntervalLogicStrategy = class {
|
|
|
12314
12757
|
mode: "countdown",
|
|
12315
12758
|
label
|
|
12316
12759
|
}));
|
|
12317
|
-
builder.addBehavior(new SoundCueBehavior({
|
|
12318
|
-
cues: [
|
|
12319
|
-
{ sound: "interval-start", trigger: "mount" },
|
|
12320
|
-
{ sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
|
|
12321
|
-
{ sound: "interval-complete", trigger: "complete" }
|
|
12322
|
-
]
|
|
12323
|
-
}));
|
|
12324
12760
|
const hasResistance = statements.some((s) => s.metrics.some((m) => m.type === MetricType.Resistance));
|
|
12325
12761
|
const hasDistance = statements.some((s) => s.metrics.some((m) => m.type === MetricType.Distance));
|
|
12326
12762
|
const promotions = [
|
|
@@ -12513,14 +12949,6 @@ var GenericTimerStrategy = class {
|
|
|
12513
12949
|
} else {
|
|
12514
12950
|
builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
|
|
12515
12951
|
}
|
|
12516
|
-
if (isCountdown) {
|
|
12517
|
-
builder.addBehavior(new SoundCueBehavior({
|
|
12518
|
-
cues: [
|
|
12519
|
-
{ sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
|
|
12520
|
-
{ sound: "timer-complete", trigger: "complete" }
|
|
12521
|
-
]
|
|
12522
|
-
}));
|
|
12523
|
-
}
|
|
12524
12952
|
builder.addBehavior(new LabelingBehavior({
|
|
12525
12953
|
mode: durationMs ? "countdown" : "clock",
|
|
12526
12954
|
label
|
|
@@ -12581,29 +13009,17 @@ var SoundStrategy = class {
|
|
|
12581
13009
|
priority = 20;
|
|
12582
13010
|
id = "sound";
|
|
12583
13011
|
match(statements, _runtime) {
|
|
12584
|
-
return statements
|
|
13012
|
+
return statements.length > 0;
|
|
12585
13013
|
}
|
|
12586
|
-
apply(builder,
|
|
12587
|
-
if (builder.hasBehavior(
|
|
13014
|
+
apply(builder, statements, runtime) {
|
|
13015
|
+
if (builder.hasBehavior(SoundProfileBehavior)) {
|
|
12588
13016
|
return;
|
|
12589
13017
|
}
|
|
12590
|
-
|
|
12591
|
-
if (countdown) {
|
|
12592
|
-
builder.addBehavior(new SoundCueBehavior({
|
|
12593
|
-
cues: [
|
|
12594
|
-
{ sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
|
|
12595
|
-
{ sound: "timer-complete", trigger: "complete" }
|
|
12596
|
-
]
|
|
12597
|
-
}));
|
|
13018
|
+
if (!builder.hasTimerBehavior()) {
|
|
12598
13019
|
return;
|
|
12599
13020
|
}
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
cues: [
|
|
12603
|
-
{ sound: "start-beep", trigger: "mount" }
|
|
12604
|
-
]
|
|
12605
|
-
}));
|
|
12606
|
-
}
|
|
13021
|
+
const profile = resolveSoundProfile(statements, runtime.options?.soundOptions);
|
|
13022
|
+
builder.addBehavior(new SoundProfileBehavior({ profile }));
|
|
12607
13023
|
}
|
|
12608
13024
|
};
|
|
12609
13025
|
|
|
@@ -12636,20 +13052,6 @@ var NextEvent = class _NextEvent {
|
|
|
12636
13052
|
};
|
|
12637
13053
|
}
|
|
12638
13054
|
};
|
|
12639
|
-
|
|
12640
|
-
// src/runtime/models/SoundModels.ts
|
|
12641
|
-
var PREDEFINED_SOUNDS = {
|
|
12642
|
-
/** Short single beep - good for countdown ticks */
|
|
12643
|
-
BEEP: "beep",
|
|
12644
|
-
/** Long buzz - good for timer completion */
|
|
12645
|
-
BUZZER: "buzzer",
|
|
12646
|
-
/** Pleasant chime - good for milestones */
|
|
12647
|
-
CHIME: "chime",
|
|
12648
|
-
/** Clock tick - good for second-by-second countdown */
|
|
12649
|
-
TICK: "tick",
|
|
12650
|
-
/** Ascending sequence - good for timer start */
|
|
12651
|
-
START: "start"
|
|
12652
|
-
};
|
|
12653
13055
|
function metricsToLabel(metrics) {
|
|
12654
13056
|
if (metrics instanceof MetricContainer) {
|
|
12655
13057
|
return metricsToLabel(metrics.toArray());
|
|
@@ -12930,40 +13332,6 @@ var UnregisterEventHandlerAction = class {
|
|
|
12930
13332
|
}
|
|
12931
13333
|
};
|
|
12932
13334
|
|
|
12933
|
-
// src/runtime/actions/audio/PlaySoundAction.ts
|
|
12934
|
-
var PlaySoundAction = class {
|
|
12935
|
-
constructor(sound, volume = 1, cueId, now = wallClockNow) {
|
|
12936
|
-
this.sound = sound;
|
|
12937
|
-
this.volume = volume;
|
|
12938
|
-
this.cueId = cueId;
|
|
12939
|
-
this.now = now;
|
|
12940
|
-
if (volume < 0 || volume > 1) {
|
|
12941
|
-
throw new RangeError(`Volume must be between 0.0 and 1.0, got: ${volume}`);
|
|
12942
|
-
}
|
|
12943
|
-
}
|
|
12944
|
-
sound;
|
|
12945
|
-
volume;
|
|
12946
|
-
cueId;
|
|
12947
|
-
now;
|
|
12948
|
-
type = "play-sound";
|
|
12949
|
-
/**
|
|
12950
|
-
* Executes the sound playback by emitting a 'sound:play' event.
|
|
12951
|
-
* UI components should register handlers for this event to play audio.
|
|
12952
|
-
*/
|
|
12953
|
-
do(runtime) {
|
|
12954
|
-
const event = {
|
|
12955
|
-
name: "sound:play",
|
|
12956
|
-
timestamp: this.now.now(),
|
|
12957
|
-
data: {
|
|
12958
|
-
sound: this.sound,
|
|
12959
|
-
volume: this.volume,
|
|
12960
|
-
cueId: this.cueId
|
|
12961
|
-
}
|
|
12962
|
-
};
|
|
12963
|
-
return runtime.eventBus.dispatch(event, runtime);
|
|
12964
|
-
}
|
|
12965
|
-
};
|
|
12966
|
-
|
|
12967
13335
|
// src/runtime/utils/MemoryUtils.ts
|
|
12968
13336
|
function searchStackMemory(runtime, criteria) {
|
|
12969
13337
|
const results = [];
|
|
@@ -14766,6 +15134,6 @@ function compareStatement(expectations, actual, mode) {
|
|
|
14766
15134
|
return diffs;
|
|
14767
15135
|
}
|
|
14768
15136
|
|
|
14769
|
-
export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AbortSessionAction, AmrapLogicStrategy, AnalyticsEngine, AnalyticsTransformer, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BehaviorContext, BlockBuilder, BlockContext, ButtonBehavior, CATALOG_PRECEDENCE_RANK, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalcEvalError, CalcParseError, CalcRegistrationError, CalcUnitError, CalculateBlockProcessor, CalculationRegistry, CardioDialect, ChildSelectionBehavior, ChildrenStrategy, ChoiceGroupMetric, ClearChildrenAction, ClimbDialect, ClimbMetricType, CompileAndPushBlockAction, CompletionTimestampBehavior, CompositeEffortRegistry, CountdownTimerBehavior, CountupTimerBehavior, CrossFitDialect, DAY, DEFAULT_RESOLVER_OPTIONS, DEFAULT_RUNTIME_OPTIONS, DEFAULT_UNRESOLVED_EFFORT_MET, DIM_COUNT, DIM_ENERGY, DIM_LENGTH, DIM_MASS, DIM_TIME, DIM_ZERO, DISCIPLINE_FACTORS, DialectStack, EFFORT_DATA_METRIC_TYPE, EFFORT_DISCIPLINES, EFFORT_REGISTRY_ORIGINS, EffortBlock, EffortFallbackStrategy, EffortResolver, EmitEventAction, EmitSystemOutputAction, ErrorAction, EventBus, ExecutionContext, ExitBehavior, FixtureSyntaxError, GenericGroupStrategy, GenericLoopStrategy, GenericTimerStrategy, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, IntervalLogicStrategy, JitCompiler, LabelingBehavior, LocalEventProvider, LocalRuntimeSubscription, LookupRegistry, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, MetricPromotionBehavior, NAMED_COMPOUNDS, NextAction, NextEvent, NextEventHandler, OutputEmitter,
|
|
15137
|
+
export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AbortSessionAction, AmrapLogicStrategy, AnalyticsEngine, AnalyticsTransformer, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BehaviorContext, BlockBuilder, BlockContext, ButtonBehavior, CATALOG_PRECEDENCE_RANK, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalcEvalError, CalcParseError, CalcRegistrationError, CalcUnitError, CalculateBlockProcessor, CalculationRegistry, CardioDialect, ChildSelectionBehavior, ChildrenStrategy, ChoiceGroupMetric, ClearChildrenAction, ClimbDialect, ClimbMetricType, CompileAndPushBlockAction, CompletionTimestampBehavior, CompositeEffortRegistry, CountdownTimerBehavior, CountupTimerBehavior, CrossFitDialect, DAY, DEFAULT_RESOLVER_OPTIONS, DEFAULT_RUNTIME_OPTIONS, DEFAULT_SOUND_PROFILE, DEFAULT_UNRESOLVED_EFFORT_MET, DIM_COUNT, DIM_ENERGY, DIM_LENGTH, DIM_MASS, DIM_TIME, DIM_ZERO, DISCIPLINE_FACTORS, DialectStack, EFFORT_DATA_METRIC_TYPE, EFFORT_DISCIPLINES, EFFORT_REGISTRY_ORIGINS, EffortBlock, EffortFallbackStrategy, EffortResolver, EmitEventAction, EmitSystemOutputAction, ErrorAction, EventBus, ExecutionContext, ExitBehavior, FixtureSyntaxError, GenericGroupStrategy, GenericLoopStrategy, GenericTimerStrategy, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, IntervalLogicStrategy, JitCompiler, LabelingBehavior, LocalEventProvider, LocalRuntimeSubscription, LookupRegistry, MAX_TAG_CONTRIBUTION_DEPTH, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, MetricPromotionBehavior, NAMED_COMPOUNDS, NextAction, NextEvent, NextEventHandler, OutputEmitter, PRODUCTION_STRATEGIES, PopBlockAction, PushBlockAction, PushRestBlockAction, REST_EXCLUSION, RPE_LABELS, RUNTIME_LOG_SUBJECT_WIDTH, RegisterEventHandlerAction, Registry, ReportOutputBehavior, ReportOutputStrategy, RestBlock, RoundsMetric, RuntimeAdapter, RuntimeBlock, RuntimeBuilder, RuntimeClock, RuntimeFactory, RuntimeLogger, RuntimeMemory, RuntimeObservers, RuntimeStack, SOUND_PLAY_EVENT, SOUND_PROFILE_METRIC, STANDARD_UNITS, STORE_CALCS, STREAM_ATOMS, ScriptRuntime, SessionRootBlock, SessionRootStrategy, SnapshotClock, SoundProfileBehavior, SoundStrategy, SpanTrackingBehavior, StandardAnalyticsProfile, StartSessionAction, StartWorkoutAction, SubscriptionManager, TAG_MARKER_PREFIX, ThrowError, ThrowErrorAction, TimeSpan, TwoPassEffortResolutionProcess, TypedMemoryReference, UNITS, UnitRegistry, UnitSet, UnitsDialect, UnregisterEventHandlerAction, UpdateNextPreviewAction, VISIBILITY_ICONS, VISIBILITY_LABELS, WaitingToStartBlock, WaitingToStartInjectorBehavior, WhiteboardScript, WodDialect, YogaDialect, addAliasToEffort, addDim, buildStaticLookups, buildTooltip, bundledEfforts, calculateElapsed, canonicalizeType, checkCalcLine, classifyStatements, closeCurrentSpan, collapseUnresolvedChoices, commonFixtureSet, compareStatement, compileLineForm, composeDivUnit, composeMulUnit, compoundName, computeColumnLabel, computeLabel, computeWorkloadRollups, convertScalar, createAnalyticsEngineForBlock, createCalcEngine, createCalcStack, createCompiler, createDialectStack, createDisciplinesTable, createEffortTable, createLineLocator, createPolicy as createMetricPresentationPolicy, createMockClock, createParser, createProfileTable, createReferenceIndex, createRpeLabelsTable, dailySessionLoads, dayBucket, dedupeEntitiesByPrecedence, dialectRegistry, dialectStack, diffStatement, dimEquals, disciplineFactorFor, documentToEffort, documentToEquipment, effortToDocument, equipmentToDocument, evaluate, evaluateCalculateDefinitions, extractEffortData, extractMetrics, extractStatementRefs, extractStatements, extractStatementsRaw, extractSyntaxFacts, filterTagsByVisibility, findBestFuzzyMatch, findTagContributionCycles, findUnresolvedChoices, fixtureBackSquat, fixtureBurpee, fixturePlank, fixtureRowing, fixtureRunning, fixtureUserCustom, fixtureUserOverride, formatDim, formatDuration, formatRuntimeLogLine, formatRuntimeLogRule, formatRuntimeTimestamp, formatSoundTrigger, metricsToLabel as fragmentsToLabel, fullFixtureSet, fuseUnits, fuseUnitsInMetrics, getAnalyticsFromLogs, getAnalyticsFromRuntime, getHints, getMetricVisibility, getUnit, hasHint, hintMetric, hintsToContainer, inferDim, isCastSubscription, isChoiceResolved, isDimensionless, isEffortDiscipline, isFragmentTag, isTimeLikeMetric, isWithinThreshold, levenshteinDistance, metricPresentation, mutateTimerSpans, normalizeForFuzzy, num, openSpan, outputNodeId, outputNodeIds, padRuntimeLogEvent, padRuntimeLogSubject, parseCalcLine, parseCalculateBlock, parseEffortFile, parseEquipmentFile, parseExpression, parseMetricLine, parseScript, parseSoundTrigger, printCalculation, readNestedSection, readTimer, realtimeProcessorRegistry, renderMetric, resolveDominantOrigin, resolveEntityRank, resolveEntityTier, resolveProfileRules, resolveSoundProfile, resolveSourceRank, resolveSourceTier, resolveTagContributions, slugifyRefLabel, startSpan, str, strategyRegistry, subDim, summaryProcessorRegistry, tagContributionMetrics, tagMarkerSlug, tagMarkerSlugs, tagMarkerType, toStoredOutputStatement, truthy, validateOutputUnit, parser as whiteboardParser, whiteboardScript, whiteboardScriptLanguage, writeChoiceSelection };
|
|
14770
15138
|
//# sourceMappingURL=index.mjs.map
|
|
14771
15139
|
//# sourceMappingURL=index.mjs.map
|