@bitcobblers/wod-wiki-lang 0.4.24 → 0.5.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -10628,234 +10628,6 @@ var RuntimeBuilder = class {
10628
10628
  return { runtime, options: { ...this.options } };
10629
10629
  }
10630
10630
  };
10631
-
10632
- // src/runtime/events/NextEvent.ts
10633
- var NextEvent = class _NextEvent {
10634
- constructor(data, now = wallClockNow) {
10635
- this.now = now;
10636
- const nowMs = this.now.nowMs();
10637
- _NextEvent._counter++;
10638
- this.timestamp = new Date(nowMs + _NextEvent._counter);
10639
- this._data = data;
10640
- }
10641
- now;
10642
- name = "next";
10643
- timestamp;
10644
- _data;
10645
- static _counter = 0;
10646
- get data() {
10647
- return this._data;
10648
- }
10649
- set data(value) {
10650
- this._data = value;
10651
- }
10652
- // Custom toJSON method for proper JSON serialization
10653
- toJSON() {
10654
- return {
10655
- name: this.name,
10656
- timestamp: this.timestamp,
10657
- data: this._data
10658
- };
10659
- }
10660
- };
10661
-
10662
- // src/runtime/models/SoundModels.ts
10663
- var PREDEFINED_SOUNDS = {
10664
- /** Short single beep - good for countdown ticks */
10665
- BEEP: "beep",
10666
- /** Long buzz - good for timer completion */
10667
- BUZZER: "buzzer",
10668
- /** Pleasant chime - good for milestones */
10669
- CHIME: "chime",
10670
- /** Clock tick - good for second-by-second countdown */
10671
- TICK: "tick",
10672
- /** Ascending sequence - good for timer start */
10673
- START: "start"
10674
- };
10675
- function metricsToLabel(metrics) {
10676
- if (metrics instanceof MetricContainer) {
10677
- return metricsToLabel(metrics.toArray());
10678
- }
10679
- const first = metrics[0];
10680
- const flat = Array.isArray(first) ? metrics.flat() : metrics;
10681
- const labelFragment = flat.find((f) => f.type === MetricType.Label);
10682
- if (labelFragment) return labelFragment.image || labelFragment.value?.toString() || "Block";
10683
- const primary = flat.find((f) => f.type === MetricType.Effort || f.type === MetricType.Action);
10684
- if (primary) return primary.image || primary.value?.toString() || "Block";
10685
- const firstWithImage = flat.find((f) => f.image);
10686
- if (firstWithImage) return firstWithImage.image || "Block";
10687
- return "Block";
10688
- }
10689
-
10690
- // src/runtime/actions/events/EmitEventAction.ts
10691
- var EmitEventAction = class {
10692
- constructor(eventName, data, timestamp, now = wallClockNow) {
10693
- this.eventName = eventName;
10694
- this.data = data;
10695
- this.timestamp = timestamp ?? now.now();
10696
- }
10697
- eventName;
10698
- data;
10699
- type = "emit-event";
10700
- timestamp;
10701
- do(runtime) {
10702
- const event = {
10703
- name: this.eventName,
10704
- timestamp: this.timestamp,
10705
- data: this.data
10706
- };
10707
- return runtime.eventBus.dispatch(event, runtime);
10708
- }
10709
- };
10710
-
10711
- // src/runtime/blocks/EffortBlock.ts
10712
- var EffortCompletionBehavior = class {
10713
- constructor(checkComplete) {
10714
- this.checkComplete = checkComplete;
10715
- }
10716
- checkComplete;
10717
- _forceComplete = false;
10718
- _isComplete = false;
10719
- forceComplete() {
10720
- this._forceComplete = true;
10721
- }
10722
- onMount(ctx) {
10723
- ctx.subscribe("next", () => {
10724
- this._forceComplete = true;
10725
- return [];
10726
- });
10727
- return [];
10728
- }
10729
- onNext(ctx) {
10730
- if (this._isComplete) {
10731
- return [];
10732
- }
10733
- if (this.checkComplete() || this._forceComplete) {
10734
- this._isComplete = true;
10735
- const now = ctx.clock.currentDate;
10736
- ctx.markComplete("target-achieved");
10737
- return [
10738
- new EmitEventAction("block:complete", { blockId: ctx.block.key.toString() }, now)
10739
- ];
10740
- }
10741
- return [];
10742
- }
10743
- onUnmount(_ctx) {
10744
- return [];
10745
- }
10746
- onDispose(_ctx) {
10747
- }
10748
- };
10749
- var EffortBlock = class extends RuntimeBlock {
10750
- constructor(runtime, sourceIds, config, metrics) {
10751
- super({
10752
- runtime,
10753
- sourceIds,
10754
- behaviors: [],
10755
- // Behaviors added below
10756
- blockType: "Effort",
10757
- label: `${config.targetReps} ${config.exerciseName}`,
10758
- metrics
10759
- });
10760
- this.config = config;
10761
- if (!config.exerciseName || config.exerciseName.trim() === "") {
10762
- throw new TypeError("EffortBlock requires a non-empty exerciseName");
10763
- }
10764
- if (config.targetReps < 1) {
10765
- throw new RangeError(`targetReps must be >= 1, got: ${config.targetReps}`);
10766
- }
10767
- const completionBehavior = new EffortCompletionBehavior(() => this.isTargetComplete());
10768
- this.behaviors.push(new LabelingBehavior({
10769
- mode: "clock",
10770
- label: `${config.targetReps} ${config.exerciseName}`,
10771
- actionDisplay: config.exerciseName
10772
- }));
10773
- this.behaviors.push(completionBehavior);
10774
- this.behaviors.push(new CountupTimerBehavior({
10775
- label: "Segment Timer",
10776
- role: "secondary"
10777
- }));
10778
- }
10779
- config;
10780
- currentReps = 0;
10781
- lastCompletionMode = "incremental";
10782
- mount(runtime, options) {
10783
- const actions = super.mount(runtime, options);
10784
- actions.push(...this.getMetricActions());
10785
- return actions;
10786
- }
10787
- unmount(runtime, options) {
10788
- return super.unmount(runtime, options);
10789
- }
10790
- dispose(runtime) {
10791
- super.dispose(runtime);
10792
- if (this.context) {
10793
- this.context.release();
10794
- }
10795
- }
10796
- /**
10797
- * Check if the effort target has been met.
10798
- * Note: This is distinct from the block's isComplete flag which is used by the stack.
10799
- */
10800
- isTargetComplete() {
10801
- return this.currentReps >= this.config.targetReps;
10802
- }
10803
- getCurrentReps() {
10804
- return this.currentReps;
10805
- }
10806
- getTargetReps() {
10807
- return this.config.targetReps;
10808
- }
10809
- getExerciseName() {
10810
- return this.config.exerciseName;
10811
- }
10812
- incrementRep() {
10813
- if (this.currentReps < this.config.targetReps) {
10814
- this.currentReps++;
10815
- this.lastCompletionMode = "incremental";
10816
- return this.getMetricActions();
10817
- }
10818
- return [];
10819
- }
10820
- setReps(count) {
10821
- if (count < 0 || count > this.config.targetReps) {
10822
- throw new RangeError(`setReps(${count}) out of valid range [0, ${this.config.targetReps}]`);
10823
- }
10824
- this.currentReps = count;
10825
- this.lastCompletionMode = "bulk";
10826
- return this.getMetricActions();
10827
- }
10828
- markComplete() {
10829
- this.currentReps = this.config.targetReps;
10830
- this.lastCompletionMode = "bulk";
10831
- const actions = this.getMetricActions();
10832
- actions.push(new EmitEventAction("reps:complete", {
10833
- blockId: this.key.toString(),
10834
- exerciseName: this.config.exerciseName,
10835
- finalReps: this.currentReps
10836
- }));
10837
- return actions;
10838
- }
10839
- getMetricActions() {
10840
- const blockId = this.key.toString();
10841
- const actions = [];
10842
- actions.push(new EmitEventAction("reps:updated", {
10843
- blockId,
10844
- exerciseName: this.config.exerciseName,
10845
- currentReps: this.currentReps,
10846
- targetReps: this.config.targetReps,
10847
- completionMode: this.lastCompletionMode
10848
- }));
10849
- if (this.isTargetComplete()) {
10850
- actions.push(new EmitEventAction("reps:complete", {
10851
- blockId,
10852
- exerciseName: this.config.exerciseName,
10853
- finalReps: this.currentReps
10854
- }));
10855
- }
10856
- return actions;
10857
- }
10858
- };
10859
10631
  var LabelComposer = class {
10860
10632
  /**
10861
10633
  * Builds a descriptive label from a set of statements.
@@ -11122,67 +10894,71 @@ var IntervalLogicStrategy = class {
11122
10894
  }
11123
10895
  }
11124
10896
  };
11125
- var GenericTimerStrategy = class {
11126
- priority = 50;
11127
- // Mid priority
11128
- id = "generic-timer";
10897
+ var EffortFallbackStrategy = class {
10898
+ priority = 0;
10899
+ id = "effort-fallback";
11129
10900
  match(statements, _runtime) {
11130
10901
  if (!statements || statements.length === 0) return false;
11131
- return statements.some((s) => s.metrics.some(
11132
- (f) => f.type === MetricType.Duration && f.origin !== "runtime"
11133
- ));
10902
+ const hasTimer = statements.some((s) => s.metrics.some((f) => f.type === MetricType.Duration && f.origin !== "runtime"));
10903
+ const hasRounds = statements.some((s) => s.metrics.some((f) => f.type === MetricType.Rounds && f.origin !== "runtime"));
10904
+ const hasChildren = statements.some((s) => s.children && s.children.length > 0);
10905
+ return !hasTimer && !hasRounds && !hasChildren;
11134
10906
  }
11135
10907
  apply(builder, statements, runtime) {
11136
- if (builder.hasTimerBehavior()) {
11137
- return;
11138
- }
11139
- const firstStatementWithTimer = statements.find((s) => s.metrics.some(
11140
- (f) => f.type === MetricType.Duration && f.origin !== "runtime"
11141
- )) || statements[0];
11142
- const timerFragment = firstStatementWithTimer.metrics.find(
11143
- (f) => f.type === MetricType.Duration && f.origin !== "runtime"
11144
- );
11145
- const direction = timerFragment?.direction || "up";
11146
- const durationMs = timerFragment?.value || void 0;
11147
- const isRequired = statements.some((s) => hasHint(s, CONSUMED_HINTS.REQUIRED_TIMER));
11148
- const injectRest = statements.some((s) => hasHint(s, CONSUMED_HINTS.INJECT_REST));
11149
- const isCountdown = !!(durationMs && direction === "down");
11150
- const template = {
11151
- blockType: "Timer",
11152
- defaultLabel: direction === "down" ? "Countdown" : "For Time",
10908
+ compose(builder, {
10909
+ blockType: "effort",
10910
+ defaultLabel: "Effort",
11153
10911
  statements,
11154
10912
  runtime,
10913
+ // Secondary countup timer so the stack display shows how long
10914
+ // the effort took. Role 'secondary' prevents it from stealing
10915
+ // primary display from a parent timer. Mode 'reset-interval'
10916
+ // means no timer completion (the user advances manually).
11155
10917
  timer: {
11156
- direction,
11157
- durationMs,
11158
- label: durationMs ? "Countdown" : "For Time",
11159
- role: "primary",
11160
- mode: isCountdown ? "complete-block" : "complete-block",
11161
- injectRest,
11162
- required: isRequired
10918
+ direction: "up",
10919
+ role: "secondary",
10920
+ mode: "reset-interval"
11163
10921
  },
11164
- pickStatement: (stmts) => stmts.find(
11165
- (s) => s.metrics.some((f) => f.type === MetricType.Duration && f.origin !== "runtime")
11166
- ) || stmts[0]
10922
+ filterMetrics: (f) => f.origin !== "runtime"
10923
+ });
10924
+ builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
10925
+ }
10926
+ };
10927
+ var GenericGroupStrategy = class {
10928
+ priority = 50;
10929
+ // Same as GenericTimer/GenericLoop; runs before ChildrenStrategy (same priority, registered earlier)
10930
+ id = "generic-group";
10931
+ match(statements, _runtime) {
10932
+ if (!statements || statements.length === 0) return false;
10933
+ const hasChildren = statements.some((s) => s.children && s.children.length > 0);
10934
+ const hasTimer = statements.some((s) => s.metrics.some((f) => f.type === MetricType.Duration && f.origin !== "runtime"));
10935
+ const hasRounds = statements.some((s) => s.metrics.some((f) => f.type === MetricType.Rounds && f.origin !== "runtime"));
10936
+ return hasChildren && !hasTimer && !hasRounds;
10937
+ }
10938
+ apply(builder, statements, runtime) {
10939
+ if (builder.hasTimerBehavior() || builder.hasRoundConfig()) {
10940
+ return;
10941
+ }
10942
+ const template = {
10943
+ blockType: "Group",
10944
+ defaultLabel: "Group",
10945
+ statements,
10946
+ runtime
11167
10947
  };
11168
10948
  const label = compose(builder, template);
11169
- if (isRequired && isCountdown) {
11170
- builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: false, onEvents: ["timer:complete"] }));
11171
- } else {
11172
- builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
11173
- }
11174
- if (isCountdown) {
11175
- builder.addBehavior(new SoundCueBehavior({
11176
- cues: [
11177
- { sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
11178
- { sound: "timer-complete", trigger: "complete" }
11179
- ]
11180
- }));
11181
- }
11182
10949
  builder.addBehavior(new LabelingBehavior({
11183
- mode: durationMs ? "countdown" : "clock",
10950
+ mode: "clock",
11184
10951
  label
11185
10952
  }));
10953
+ const hasResistance = statements.some((s) => s.metrics.some((m) => m.type === MetricType.Resistance));
10954
+ const hasDistance = statements.some((s) => s.metrics.some((m) => m.type === MetricType.Distance));
10955
+ const promotions = [
10956
+ ...hasResistance ? [{ metricType: MetricType.Resistance, origin: "compiler" }] : [],
10957
+ ...hasDistance ? [{ metricType: MetricType.Distance, origin: "compiler" }] : []
10958
+ ];
10959
+ if (promotions.length > 0) {
10960
+ builder.addBehavior(new MetricPromotionBehavior({ promotions }));
10961
+ }
11186
10962
  }
11187
10963
  };
11188
10964
  var GenericLoopStrategy = class {
@@ -11250,41 +11026,115 @@ var GenericLoopStrategy = class {
11250
11026
  builder.declareExit("immediate");
11251
11027
  }
11252
11028
  };
11253
- var GenericGroupStrategy = class {
11029
+ var GenericTimerStrategy = class {
11254
11030
  priority = 50;
11255
- // Same as GenericTimer/GenericLoop; runs before ChildrenStrategy (same priority, registered earlier)
11256
- id = "generic-group";
11031
+ // Mid priority
11032
+ id = "generic-timer";
11257
11033
  match(statements, _runtime) {
11258
11034
  if (!statements || statements.length === 0) return false;
11259
- const hasChildren = statements.some((s) => s.children && s.children.length > 0);
11260
- const hasTimer = statements.some((s) => s.metrics.some((f) => f.type === MetricType.Duration && f.origin !== "runtime"));
11261
- const hasRounds = statements.some((s) => s.metrics.some((f) => f.type === MetricType.Rounds && f.origin !== "runtime"));
11262
- return hasChildren && !hasTimer && !hasRounds;
11035
+ return statements.some((s) => s.metrics.some(
11036
+ (f) => f.type === MetricType.Duration && f.origin !== "runtime"
11037
+ ));
11263
11038
  }
11264
11039
  apply(builder, statements, runtime) {
11265
- if (builder.hasTimerBehavior() || builder.hasRoundConfig()) {
11040
+ if (builder.hasTimerBehavior()) {
11266
11041
  return;
11267
11042
  }
11043
+ const firstStatementWithTimer = statements.find((s) => s.metrics.some(
11044
+ (f) => f.type === MetricType.Duration && f.origin !== "runtime"
11045
+ )) || statements[0];
11046
+ const timerFragment = firstStatementWithTimer.metrics.find(
11047
+ (f) => f.type === MetricType.Duration && f.origin !== "runtime"
11048
+ );
11049
+ const direction = timerFragment?.direction || "up";
11050
+ const durationMs = timerFragment?.value || void 0;
11051
+ const isRequired = statements.some((s) => hasHint(s, CONSUMED_HINTS.REQUIRED_TIMER));
11052
+ const injectRest = statements.some((s) => hasHint(s, CONSUMED_HINTS.INJECT_REST));
11053
+ const isCountdown = !!(durationMs && direction === "down");
11268
11054
  const template = {
11269
- blockType: "Group",
11270
- defaultLabel: "Group",
11055
+ blockType: "Timer",
11056
+ defaultLabel: direction === "down" ? "Countdown" : "For Time",
11271
11057
  statements,
11272
- runtime
11058
+ runtime,
11059
+ timer: {
11060
+ direction,
11061
+ durationMs,
11062
+ label: durationMs ? "Countdown" : "For Time",
11063
+ role: "primary",
11064
+ mode: isCountdown ? "complete-block" : "complete-block",
11065
+ injectRest,
11066
+ required: isRequired
11067
+ },
11068
+ pickStatement: (stmts) => stmts.find(
11069
+ (s) => s.metrics.some((f) => f.type === MetricType.Duration && f.origin !== "runtime")
11070
+ ) || stmts[0]
11273
11071
  };
11274
11072
  const label = compose(builder, template);
11073
+ if (isRequired && isCountdown) {
11074
+ builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: false, onEvents: ["timer:complete"] }));
11075
+ } else {
11076
+ builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
11077
+ }
11078
+ if (isCountdown) {
11079
+ builder.addBehavior(new SoundCueBehavior({
11080
+ cues: [
11081
+ { sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
11082
+ { sound: "timer-complete", trigger: "complete" }
11083
+ ]
11084
+ }));
11085
+ }
11275
11086
  builder.addBehavior(new LabelingBehavior({
11276
- mode: "clock",
11087
+ mode: durationMs ? "countdown" : "clock",
11277
11088
  label
11278
11089
  }));
11279
- const hasResistance = statements.some((s) => s.metrics.some((m) => m.type === MetricType.Resistance));
11280
- const hasDistance = statements.some((s) => s.metrics.some((m) => m.type === MetricType.Distance));
11281
- const promotions = [
11282
- ...hasResistance ? [{ metricType: MetricType.Resistance, origin: "compiler" }] : [],
11283
- ...hasDistance ? [{ metricType: MetricType.Distance, origin: "compiler" }] : []
11284
- ];
11285
- if (promotions.length > 0) {
11286
- builder.addBehavior(new MetricPromotionBehavior({ promotions }));
11090
+ }
11091
+ };
11092
+
11093
+ // src/runtime/compiler/strategies/enhancements/ChildrenStrategy.ts
11094
+ var ChildrenStrategy = class {
11095
+ priority = 50;
11096
+ id = "children";
11097
+ match(statements, _runtime) {
11098
+ return statements && statements.length > 0 && statements[0].children && statements[0].children.length > 0;
11099
+ }
11100
+ apply(builder, statements, _runtime) {
11101
+ const children = statements[0].children;
11102
+ const childGroups = children.filter((group) => group.length > 0);
11103
+ const hasRoundsFromStrategy = builder.hasRoundConfig();
11104
+ const countdown = builder.getBehavior(CountdownTimerBehavior);
11105
+ const hasCountdownCompletion = !!countdown;
11106
+ if (!hasRoundsFromStrategy) {
11107
+ if (hasCountdownCompletion) {
11108
+ builder.asRepeater({ totalRounds: void 0, startRound: 1, addCompletion: false });
11109
+ } else {
11110
+ builder.asRepeater({ totalRounds: 1, startRound: 1, addCompletion: true });
11111
+ }
11112
+ }
11113
+ const shouldAddLoop = hasCountdownCompletion || hasRoundsFromStrategy;
11114
+ const loopCondition = hasRoundsFromStrategy ? "rounds-remaining" : "timer-active";
11115
+ const shouldInjectRest = hasCountdownCompletion && countdown?.config.mode === "reset-interval";
11116
+ builder.asContainer({
11117
+ childGroups,
11118
+ addLoop: shouldAddLoop,
11119
+ loopConfig: shouldAddLoop ? { condition: loopCondition } : void 0,
11120
+ injectRest: shouldInjectRest
11121
+ });
11122
+ builder.declareExit("deferred");
11123
+ }
11124
+ };
11125
+
11126
+ // src/runtime/compiler/strategies/enhancements/ReportOutputStrategy.ts
11127
+ var ReportOutputStrategy = class {
11128
+ priority = 15;
11129
+ id = "report-output";
11130
+ match(statements, _runtime) {
11131
+ return statements && statements.length > 0;
11132
+ }
11133
+ apply(builder, _statements, _runtime) {
11134
+ if (builder.hasBehavior(ReportOutputBehavior)) {
11135
+ return;
11287
11136
  }
11137
+ builder.addBehavior(new ReportOutputBehavior());
11288
11138
  }
11289
11139
  };
11290
11140
 
@@ -11319,81 +11169,231 @@ var SoundStrategy = class {
11319
11169
  }
11320
11170
  };
11321
11171
 
11322
- // src/runtime/compiler/strategies/enhancements/ReportOutputStrategy.ts
11323
- var ReportOutputStrategy = class {
11324
- priority = 15;
11325
- id = "report-output";
11326
- match(statements, _runtime) {
11327
- return statements && statements.length > 0;
11172
+ // src/runtime/events/NextEvent.ts
11173
+ var NextEvent = class _NextEvent {
11174
+ constructor(data, now = wallClockNow) {
11175
+ this.now = now;
11176
+ const nowMs = this.now.nowMs();
11177
+ _NextEvent._counter++;
11178
+ this.timestamp = new Date(nowMs + _NextEvent._counter);
11179
+ this._data = data;
11328
11180
  }
11329
- apply(builder, _statements, _runtime) {
11330
- if (builder.hasBehavior(ReportOutputBehavior)) {
11331
- return;
11332
- }
11333
- builder.addBehavior(new ReportOutputBehavior());
11181
+ now;
11182
+ name = "next";
11183
+ timestamp;
11184
+ _data;
11185
+ static _counter = 0;
11186
+ get data() {
11187
+ return this._data;
11188
+ }
11189
+ set data(value) {
11190
+ this._data = value;
11191
+ }
11192
+ // Custom toJSON method for proper JSON serialization
11193
+ toJSON() {
11194
+ return {
11195
+ name: this.name,
11196
+ timestamp: this.timestamp,
11197
+ data: this._data
11198
+ };
11334
11199
  }
11335
11200
  };
11336
11201
 
11337
- // src/runtime/compiler/strategies/enhancements/ChildrenStrategy.ts
11338
- var ChildrenStrategy = class {
11339
- priority = 50;
11340
- id = "children";
11341
- match(statements, _runtime) {
11342
- return statements && statements.length > 0 && statements[0].children && statements[0].children.length > 0;
11202
+ // src/runtime/models/SoundModels.ts
11203
+ var PREDEFINED_SOUNDS = {
11204
+ /** Short single beep - good for countdown ticks */
11205
+ BEEP: "beep",
11206
+ /** Long buzz - good for timer completion */
11207
+ BUZZER: "buzzer",
11208
+ /** Pleasant chime - good for milestones */
11209
+ CHIME: "chime",
11210
+ /** Clock tick - good for second-by-second countdown */
11211
+ TICK: "tick",
11212
+ /** Ascending sequence - good for timer start */
11213
+ START: "start"
11214
+ };
11215
+ function metricsToLabel(metrics) {
11216
+ if (metrics instanceof MetricContainer) {
11217
+ return metricsToLabel(metrics.toArray());
11343
11218
  }
11344
- apply(builder, statements, _runtime) {
11345
- const children = statements[0].children;
11346
- const childGroups = children.filter((group) => group.length > 0);
11347
- const hasRoundsFromStrategy = builder.hasRoundConfig();
11348
- const countdown = builder.getBehavior(CountdownTimerBehavior);
11349
- const hasCountdownCompletion = !!countdown;
11350
- if (!hasRoundsFromStrategy) {
11351
- if (hasCountdownCompletion) {
11352
- builder.asRepeater({ totalRounds: void 0, startRound: 1, addCompletion: false });
11353
- } else {
11354
- builder.asRepeater({ totalRounds: 1, startRound: 1, addCompletion: true });
11355
- }
11356
- }
11357
- const shouldAddLoop = hasCountdownCompletion || hasRoundsFromStrategy;
11358
- const loopCondition = hasRoundsFromStrategy ? "rounds-remaining" : "timer-active";
11359
- const shouldInjectRest = hasCountdownCompletion && countdown?.config.mode === "reset-interval";
11360
- builder.asContainer({
11361
- childGroups,
11362
- addLoop: shouldAddLoop,
11363
- loopConfig: shouldAddLoop ? { condition: loopCondition } : void 0,
11364
- injectRest: shouldInjectRest
11365
- });
11366
- builder.declareExit("deferred");
11219
+ const first = metrics[0];
11220
+ const flat = Array.isArray(first) ? metrics.flat() : metrics;
11221
+ const labelFragment = flat.find((f) => f.type === MetricType.Label);
11222
+ if (labelFragment) return labelFragment.image || labelFragment.value?.toString() || "Block";
11223
+ const primary = flat.find((f) => f.type === MetricType.Effort || f.type === MetricType.Action);
11224
+ if (primary) return primary.image || primary.value?.toString() || "Block";
11225
+ const firstWithImage = flat.find((f) => f.image);
11226
+ if (firstWithImage) return firstWithImage.image || "Block";
11227
+ return "Block";
11228
+ }
11229
+
11230
+ // src/runtime/actions/events/EmitEventAction.ts
11231
+ var EmitEventAction = class {
11232
+ constructor(eventName, data, timestamp, now = wallClockNow) {
11233
+ this.eventName = eventName;
11234
+ this.data = data;
11235
+ this.timestamp = timestamp ?? now.now();
11236
+ }
11237
+ eventName;
11238
+ data;
11239
+ type = "emit-event";
11240
+ timestamp;
11241
+ do(runtime) {
11242
+ const event = {
11243
+ name: this.eventName,
11244
+ timestamp: this.timestamp,
11245
+ data: this.data
11246
+ };
11247
+ return runtime.eventBus.dispatch(event, runtime);
11367
11248
  }
11368
11249
  };
11369
- var EffortFallbackStrategy = class {
11370
- priority = 0;
11371
- id = "effort-fallback";
11372
- match(statements, _runtime) {
11373
- if (!statements || statements.length === 0) return false;
11374
- const hasTimer = statements.some((s) => s.metrics.some((f) => f.type === MetricType.Duration && f.origin !== "runtime"));
11375
- const hasRounds = statements.some((s) => s.metrics.some((f) => f.type === MetricType.Rounds && f.origin !== "runtime"));
11376
- const hasChildren = statements.some((s) => s.children && s.children.length > 0);
11377
- return !hasTimer && !hasRounds && !hasChildren;
11250
+
11251
+ // src/runtime/blocks/EffortBlock.ts
11252
+ var EffortCompletionBehavior = class {
11253
+ constructor(checkComplete) {
11254
+ this.checkComplete = checkComplete;
11378
11255
  }
11379
- apply(builder, statements, runtime) {
11380
- compose(builder, {
11381
- blockType: "effort",
11382
- defaultLabel: "Effort",
11383
- statements,
11256
+ checkComplete;
11257
+ _forceComplete = false;
11258
+ _isComplete = false;
11259
+ forceComplete() {
11260
+ this._forceComplete = true;
11261
+ }
11262
+ onMount(ctx) {
11263
+ ctx.subscribe("next", () => {
11264
+ this._forceComplete = true;
11265
+ return [];
11266
+ });
11267
+ return [];
11268
+ }
11269
+ onNext(ctx) {
11270
+ if (this._isComplete) {
11271
+ return [];
11272
+ }
11273
+ if (this.checkComplete() || this._forceComplete) {
11274
+ this._isComplete = true;
11275
+ const now = ctx.clock.currentDate;
11276
+ ctx.markComplete("target-achieved");
11277
+ return [
11278
+ new EmitEventAction("block:complete", { blockId: ctx.block.key.toString() }, now)
11279
+ ];
11280
+ }
11281
+ return [];
11282
+ }
11283
+ onUnmount(_ctx) {
11284
+ return [];
11285
+ }
11286
+ onDispose(_ctx) {
11287
+ }
11288
+ };
11289
+ var EffortBlock = class extends RuntimeBlock {
11290
+ constructor(runtime, sourceIds, config, metrics) {
11291
+ super({
11384
11292
  runtime,
11385
- // Secondary countup timer so the stack display shows how long
11386
- // the effort took. Role 'secondary' prevents it from stealing
11387
- // primary display from a parent timer. Mode 'reset-interval'
11388
- // means no timer completion (the user advances manually).
11389
- timer: {
11390
- direction: "up",
11391
- role: "secondary",
11392
- mode: "reset-interval"
11393
- },
11394
- filterMetrics: (f) => f.origin !== "runtime"
11293
+ sourceIds,
11294
+ behaviors: [],
11295
+ // Behaviors added below
11296
+ blockType: "Effort",
11297
+ label: `${config.targetReps} ${config.exerciseName}`,
11298
+ metrics
11395
11299
  });
11396
- builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
11300
+ this.config = config;
11301
+ if (!config.exerciseName || config.exerciseName.trim() === "") {
11302
+ throw new TypeError("EffortBlock requires a non-empty exerciseName");
11303
+ }
11304
+ if (config.targetReps < 1) {
11305
+ throw new RangeError(`targetReps must be >= 1, got: ${config.targetReps}`);
11306
+ }
11307
+ const completionBehavior = new EffortCompletionBehavior(() => this.isTargetComplete());
11308
+ this.behaviors.push(new LabelingBehavior({
11309
+ mode: "clock",
11310
+ label: `${config.targetReps} ${config.exerciseName}`,
11311
+ actionDisplay: config.exerciseName
11312
+ }));
11313
+ this.behaviors.push(completionBehavior);
11314
+ this.behaviors.push(new CountupTimerBehavior({
11315
+ label: "Segment Timer",
11316
+ role: "secondary"
11317
+ }));
11318
+ }
11319
+ config;
11320
+ currentReps = 0;
11321
+ lastCompletionMode = "incremental";
11322
+ mount(runtime, options) {
11323
+ const actions = super.mount(runtime, options);
11324
+ actions.push(...this.getMetricActions());
11325
+ return actions;
11326
+ }
11327
+ unmount(runtime, options) {
11328
+ return super.unmount(runtime, options);
11329
+ }
11330
+ dispose(runtime) {
11331
+ super.dispose(runtime);
11332
+ if (this.context) {
11333
+ this.context.release();
11334
+ }
11335
+ }
11336
+ /**
11337
+ * Check if the effort target has been met.
11338
+ * Note: This is distinct from the block's isComplete flag which is used by the stack.
11339
+ */
11340
+ isTargetComplete() {
11341
+ return this.currentReps >= this.config.targetReps;
11342
+ }
11343
+ getCurrentReps() {
11344
+ return this.currentReps;
11345
+ }
11346
+ getTargetReps() {
11347
+ return this.config.targetReps;
11348
+ }
11349
+ getExerciseName() {
11350
+ return this.config.exerciseName;
11351
+ }
11352
+ incrementRep() {
11353
+ if (this.currentReps < this.config.targetReps) {
11354
+ this.currentReps++;
11355
+ this.lastCompletionMode = "incremental";
11356
+ return this.getMetricActions();
11357
+ }
11358
+ return [];
11359
+ }
11360
+ setReps(count) {
11361
+ if (count < 0 || count > this.config.targetReps) {
11362
+ throw new RangeError(`setReps(${count}) out of valid range [0, ${this.config.targetReps}]`);
11363
+ }
11364
+ this.currentReps = count;
11365
+ this.lastCompletionMode = "bulk";
11366
+ return this.getMetricActions();
11367
+ }
11368
+ markComplete() {
11369
+ this.currentReps = this.config.targetReps;
11370
+ this.lastCompletionMode = "bulk";
11371
+ const actions = this.getMetricActions();
11372
+ actions.push(new EmitEventAction("reps:complete", {
11373
+ blockId: this.key.toString(),
11374
+ exerciseName: this.config.exerciseName,
11375
+ finalReps: this.currentReps
11376
+ }));
11377
+ return actions;
11378
+ }
11379
+ getMetricActions() {
11380
+ const blockId = this.key.toString();
11381
+ const actions = [];
11382
+ actions.push(new EmitEventAction("reps:updated", {
11383
+ blockId,
11384
+ exerciseName: this.config.exerciseName,
11385
+ currentReps: this.currentReps,
11386
+ targetReps: this.config.targetReps,
11387
+ completionMode: this.lastCompletionMode
11388
+ }));
11389
+ if (this.isTargetComplete()) {
11390
+ actions.push(new EmitEventAction("reps:complete", {
11391
+ blockId,
11392
+ exerciseName: this.config.exerciseName,
11393
+ finalReps: this.currentReps
11394
+ }));
11395
+ }
11396
+ return actions;
11397
11397
  }
11398
11398
  };
11399
11399
 
@@ -13142,15 +13142,15 @@ function toStoredOutputStatement(output) {
13142
13142
  }
13143
13143
 
13144
13144
  // src/version.ts
13145
- var VERSION = "0.4.24";
13146
- var GIT_SHA = "90d86de";
13147
- var BUILD_TIME = "2026-08-18T20:19:27.340Z";
13145
+ var VERSION = "0.5.28";
13146
+ var GIT_SHA = "8aaaba1";
13147
+ var BUILD_TIME = "2026-08-18T20:49:38.789Z";
13148
13148
  var SEMVER = {
13149
- version: "0.4.24",
13150
- gitSha: "90d86de",
13151
- buildTime: "2026-08-18T20:19:27.340Z"
13149
+ version: "0.5.28",
13150
+ gitSha: "8aaaba1",
13151
+ buildTime: "2026-08-18T20:49:38.789Z"
13152
13152
  };
13153
13153
 
13154
- export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AbortSessionAction, AnalyticsEngine, AnalyticsTransformer, BUILD_TIME, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BehaviorContext, BlockBuilder, BlockContext, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalcEvalError, CalcParseError, CalcRegistrationError, CalcUnitError, CalculateBlockProcessor, CalculationRegistry, CardioDialect, ChoiceGroupMetric, ClearChildrenAction, ClimbDialect, ClimbMetricType, CompileAndPushBlockAction, 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, EffortResolver, EmitEventAction, EmitSystemOutputAction, ErrorAction, EventBus, ExecutionContext, GIT_SHA, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, JitCompiler, LocalEventProvider, LocalRuntimeSubscription, LookupRegistry, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, NAMED_COMPOUNDS, NextAction, NextEvent, NextEventHandler, OutputEmitter, PREDEFINED_SOUNDS, PRODUCTION_STRATEGIES, PlaySoundAction, PopBlockAction, PushBlockAction, PushRestBlockAction, REST_EXCLUSION, RPE_LABELS, RegisterEventHandlerAction, Registry, RestBlock, RuntimeAdapter, RuntimeBlock, RuntimeBuilder, RuntimeClock, RuntimeFactory, RuntimeLogger, RuntimeMemory, RuntimeObservers, RuntimeStack, SEMVER, STANDARD_UNITS, STORE_CALCS, STREAM_ATOMS, ScriptRuntime, SessionRootBlock, SnapshotClock, StandardAnalyticsProfile, StartSessionAction, StartWorkoutAction, SubscriptionManager, ThrowError, ThrowErrorAction, TimeSpan, TwoPassEffortResolutionProcess, TypedMemoryReference, UNITS, UnitRegistry, UnitSet, UnitsDialect, UnregisterEventHandlerAction, UpdateNextPreviewAction, VERSION, VISIBILITY_ICONS, VISIBILITY_LABELS, WaitingToStartBlock, WhiteboardScript, WodDialect, YogaDialect, addDim, buildStaticLookups, buildTooltip, bundledEfforts, calculateElapsed, checkCalcLine, classifyStatements, closeCurrentSpan, collapseUnresolvedChoices, commonFixtureSet, compileLineForm, composeDivUnit, composeMulUnit, compoundName, computeColumnLabel, computeLabel, computeWorkloadRollups, convertScalar, createAnalyticsEngineForBlock, createCalcEngine, createCalcStack, createCompiler, createDialectStack, createDisciplinesTable, createEffortTable, createLineLocator, createPolicy as createMetricPresentationPolicy, createMockClock, createParser, createProfileTable, createRpeLabelsTable, dailySessionLoads, dayBucket, dialectRegistry, dialectStack, dimEquals, disciplineFactorFor, evaluate, evaluateCalculateDefinitions, extractEffortData, extractMetrics, extractStatements, extractStatementsRaw, extractSyntaxFacts, filterTagsByVisibility, findBestFuzzyMatch, findUnresolvedChoices, fixtureBackSquat, fixtureBurpee, fixturePlank, fixtureRowing, fixtureRunning, fixtureUserCustom, fixtureUserOverride, formatDim, formatDuration, 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, parseCalcLine, parseCalculateBlock, parseExpression, parseScript, printCalculation, readTimer, realtimeProcessorRegistry, resolveDominantOrigin, sharedParser, startSpan, str, strategyRegistry, subDim, summaryProcessorRegistry, toStoredOutputStatement, truthy, validateOutputUnit, parser as whiteboardParser, whiteboardScript, whiteboardScriptLanguage, writeChoiceSelection };
13154
+ export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AbortSessionAction, AmrapLogicStrategy, AnalyticsEngine, AnalyticsTransformer, BUILD_TIME, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BehaviorContext, BlockBuilder, BlockContext, ButtonBehavior, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalcEvalError, CalcParseError, CalcRegistrationError, CalcUnitError, CalculateBlockProcessor, CalculationRegistry, CardioDialect, ChildSelectionBehavior, ChildrenStrategy, ChoiceGroupMetric, ClearChildrenAction, ClimbDialect, ClimbMetricType, CompileAndPushBlockAction, CompletionTimestampBehavior, 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, GIT_SHA, GenericGroupStrategy, GenericLoopStrategy, GenericTimerStrategy, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, IntervalLogicStrategy, JitCompiler, LabelingBehavior, LocalEventProvider, LocalRuntimeSubscription, LookupRegistry, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, MetricPromotionBehavior, NAMED_COMPOUNDS, NextAction, NextEvent, NextEventHandler, OutputEmitter, PREDEFINED_SOUNDS, PRODUCTION_STRATEGIES, PlaySoundAction, PopBlockAction, PushBlockAction, PushRestBlockAction, REST_EXCLUSION, RPE_LABELS, RegisterEventHandlerAction, Registry, ReportOutputBehavior, ReportOutputStrategy, RestBlock, RoundsMetric, RuntimeAdapter, RuntimeBlock, RuntimeBuilder, RuntimeClock, RuntimeFactory, RuntimeLogger, RuntimeMemory, RuntimeObservers, RuntimeStack, SEMVER, STANDARD_UNITS, STORE_CALCS, STREAM_ATOMS, ScriptRuntime, SessionRootBlock, SessionRootStrategy, SnapshotClock, SoundCueBehavior, SoundStrategy, SpanTrackingBehavior, StandardAnalyticsProfile, StartSessionAction, StartWorkoutAction, SubscriptionManager, ThrowError, ThrowErrorAction, TimeSpan, TwoPassEffortResolutionProcess, TypedMemoryReference, UNITS, UnitRegistry, UnitSet, UnitsDialect, UnregisterEventHandlerAction, UpdateNextPreviewAction, VERSION, VISIBILITY_ICONS, VISIBILITY_LABELS, WaitingToStartBlock, WaitingToStartInjectorBehavior, WhiteboardScript, WodDialect, YogaDialect, addDim, buildStaticLookups, buildTooltip, bundledEfforts, calculateElapsed, checkCalcLine, classifyStatements, closeCurrentSpan, collapseUnresolvedChoices, commonFixtureSet, compileLineForm, composeDivUnit, composeMulUnit, compoundName, computeColumnLabel, computeLabel, computeWorkloadRollups, convertScalar, createAnalyticsEngineForBlock, createCalcEngine, createCalcStack, createCompiler, createDialectStack, createDisciplinesTable, createEffortTable, createLineLocator, createPolicy as createMetricPresentationPolicy, createMockClock, createParser, createProfileTable, createRpeLabelsTable, dailySessionLoads, dayBucket, dialectRegistry, dialectStack, dimEquals, disciplineFactorFor, evaluate, evaluateCalculateDefinitions, extractEffortData, extractMetrics, extractStatements, extractStatementsRaw, extractSyntaxFacts, filterTagsByVisibility, findBestFuzzyMatch, findUnresolvedChoices, fixtureBackSquat, fixtureBurpee, fixturePlank, fixtureRowing, fixtureRunning, fixtureUserCustom, fixtureUserOverride, formatDim, formatDuration, 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, parseCalcLine, parseCalculateBlock, parseExpression, parseScript, printCalculation, readTimer, realtimeProcessorRegistry, resolveDominantOrigin, sharedParser, startSpan, str, strategyRegistry, subDim, summaryProcessorRegistry, toStoredOutputStatement, truthy, validateOutputUnit, parser as whiteboardParser, whiteboardScript, whiteboardScriptLanguage, writeChoiceSelection };
13155
13155
  //# sourceMappingURL=index.mjs.map
13156
13156
  //# sourceMappingURL=index.mjs.map