@bitcobblers/wod-wiki-lang 0.3.23 → 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.cjs CHANGED
@@ -10700,234 +10700,6 @@ var RuntimeBuilder = class {
10700
10700
  return { runtime, options: { ...this.options } };
10701
10701
  }
10702
10702
  };
10703
-
10704
- // src/runtime/events/NextEvent.ts
10705
- var NextEvent = class _NextEvent {
10706
- constructor(data, now = wallClockNow) {
10707
- this.now = now;
10708
- const nowMs = this.now.nowMs();
10709
- _NextEvent._counter++;
10710
- this.timestamp = new Date(nowMs + _NextEvent._counter);
10711
- this._data = data;
10712
- }
10713
- now;
10714
- name = "next";
10715
- timestamp;
10716
- _data;
10717
- static _counter = 0;
10718
- get data() {
10719
- return this._data;
10720
- }
10721
- set data(value) {
10722
- this._data = value;
10723
- }
10724
- // Custom toJSON method for proper JSON serialization
10725
- toJSON() {
10726
- return {
10727
- name: this.name,
10728
- timestamp: this.timestamp,
10729
- data: this._data
10730
- };
10731
- }
10732
- };
10733
-
10734
- // src/runtime/models/SoundModels.ts
10735
- var PREDEFINED_SOUNDS = {
10736
- /** Short single beep - good for countdown ticks */
10737
- BEEP: "beep",
10738
- /** Long buzz - good for timer completion */
10739
- BUZZER: "buzzer",
10740
- /** Pleasant chime - good for milestones */
10741
- CHIME: "chime",
10742
- /** Clock tick - good for second-by-second countdown */
10743
- TICK: "tick",
10744
- /** Ascending sequence - good for timer start */
10745
- START: "start"
10746
- };
10747
- function metricsToLabel(metrics) {
10748
- if (metrics instanceof wodWikiCore.MetricContainer) {
10749
- return metricsToLabel(metrics.toArray());
10750
- }
10751
- const first = metrics[0];
10752
- const flat = Array.isArray(first) ? metrics.flat() : metrics;
10753
- const labelFragment = flat.find((f) => f.type === wodWikiCore.MetricType.Label);
10754
- if (labelFragment) return labelFragment.image || labelFragment.value?.toString() || "Block";
10755
- const primary = flat.find((f) => f.type === wodWikiCore.MetricType.Effort || f.type === wodWikiCore.MetricType.Action);
10756
- if (primary) return primary.image || primary.value?.toString() || "Block";
10757
- const firstWithImage = flat.find((f) => f.image);
10758
- if (firstWithImage) return firstWithImage.image || "Block";
10759
- return "Block";
10760
- }
10761
-
10762
- // src/runtime/actions/events/EmitEventAction.ts
10763
- var EmitEventAction = class {
10764
- constructor(eventName, data, timestamp, now = wallClockNow) {
10765
- this.eventName = eventName;
10766
- this.data = data;
10767
- this.timestamp = timestamp ?? now.now();
10768
- }
10769
- eventName;
10770
- data;
10771
- type = "emit-event";
10772
- timestamp;
10773
- do(runtime) {
10774
- const event = {
10775
- name: this.eventName,
10776
- timestamp: this.timestamp,
10777
- data: this.data
10778
- };
10779
- return runtime.eventBus.dispatch(event, runtime);
10780
- }
10781
- };
10782
-
10783
- // src/runtime/blocks/EffortBlock.ts
10784
- var EffortCompletionBehavior = class {
10785
- constructor(checkComplete) {
10786
- this.checkComplete = checkComplete;
10787
- }
10788
- checkComplete;
10789
- _forceComplete = false;
10790
- _isComplete = false;
10791
- forceComplete() {
10792
- this._forceComplete = true;
10793
- }
10794
- onMount(ctx) {
10795
- ctx.subscribe("next", () => {
10796
- this._forceComplete = true;
10797
- return [];
10798
- });
10799
- return [];
10800
- }
10801
- onNext(ctx) {
10802
- if (this._isComplete) {
10803
- return [];
10804
- }
10805
- if (this.checkComplete() || this._forceComplete) {
10806
- this._isComplete = true;
10807
- const now = ctx.clock.currentDate;
10808
- ctx.markComplete("target-achieved");
10809
- return [
10810
- new EmitEventAction("block:complete", { blockId: ctx.block.key.toString() }, now)
10811
- ];
10812
- }
10813
- return [];
10814
- }
10815
- onUnmount(_ctx) {
10816
- return [];
10817
- }
10818
- onDispose(_ctx) {
10819
- }
10820
- };
10821
- var EffortBlock = class extends RuntimeBlock {
10822
- constructor(runtime, sourceIds, config, metrics) {
10823
- super({
10824
- runtime,
10825
- sourceIds,
10826
- behaviors: [],
10827
- // Behaviors added below
10828
- blockType: "Effort",
10829
- label: `${config.targetReps} ${config.exerciseName}`,
10830
- metrics
10831
- });
10832
- this.config = config;
10833
- if (!config.exerciseName || config.exerciseName.trim() === "") {
10834
- throw new TypeError("EffortBlock requires a non-empty exerciseName");
10835
- }
10836
- if (config.targetReps < 1) {
10837
- throw new RangeError(`targetReps must be >= 1, got: ${config.targetReps}`);
10838
- }
10839
- const completionBehavior = new EffortCompletionBehavior(() => this.isTargetComplete());
10840
- this.behaviors.push(new LabelingBehavior({
10841
- mode: "clock",
10842
- label: `${config.targetReps} ${config.exerciseName}`,
10843
- actionDisplay: config.exerciseName
10844
- }));
10845
- this.behaviors.push(completionBehavior);
10846
- this.behaviors.push(new CountupTimerBehavior({
10847
- label: "Segment Timer",
10848
- role: "secondary"
10849
- }));
10850
- }
10851
- config;
10852
- currentReps = 0;
10853
- lastCompletionMode = "incremental";
10854
- mount(runtime, options) {
10855
- const actions = super.mount(runtime, options);
10856
- actions.push(...this.getMetricActions());
10857
- return actions;
10858
- }
10859
- unmount(runtime, options) {
10860
- return super.unmount(runtime, options);
10861
- }
10862
- dispose(runtime) {
10863
- super.dispose(runtime);
10864
- if (this.context) {
10865
- this.context.release();
10866
- }
10867
- }
10868
- /**
10869
- * Check if the effort target has been met.
10870
- * Note: This is distinct from the block's isComplete flag which is used by the stack.
10871
- */
10872
- isTargetComplete() {
10873
- return this.currentReps >= this.config.targetReps;
10874
- }
10875
- getCurrentReps() {
10876
- return this.currentReps;
10877
- }
10878
- getTargetReps() {
10879
- return this.config.targetReps;
10880
- }
10881
- getExerciseName() {
10882
- return this.config.exerciseName;
10883
- }
10884
- incrementRep() {
10885
- if (this.currentReps < this.config.targetReps) {
10886
- this.currentReps++;
10887
- this.lastCompletionMode = "incremental";
10888
- return this.getMetricActions();
10889
- }
10890
- return [];
10891
- }
10892
- setReps(count) {
10893
- if (count < 0 || count > this.config.targetReps) {
10894
- throw new RangeError(`setReps(${count}) out of valid range [0, ${this.config.targetReps}]`);
10895
- }
10896
- this.currentReps = count;
10897
- this.lastCompletionMode = "bulk";
10898
- return this.getMetricActions();
10899
- }
10900
- markComplete() {
10901
- this.currentReps = this.config.targetReps;
10902
- this.lastCompletionMode = "bulk";
10903
- const actions = this.getMetricActions();
10904
- actions.push(new EmitEventAction("reps:complete", {
10905
- blockId: this.key.toString(),
10906
- exerciseName: this.config.exerciseName,
10907
- finalReps: this.currentReps
10908
- }));
10909
- return actions;
10910
- }
10911
- getMetricActions() {
10912
- const blockId = this.key.toString();
10913
- const actions = [];
10914
- actions.push(new EmitEventAction("reps:updated", {
10915
- blockId,
10916
- exerciseName: this.config.exerciseName,
10917
- currentReps: this.currentReps,
10918
- targetReps: this.config.targetReps,
10919
- completionMode: this.lastCompletionMode
10920
- }));
10921
- if (this.isTargetComplete()) {
10922
- actions.push(new EmitEventAction("reps:complete", {
10923
- blockId,
10924
- exerciseName: this.config.exerciseName,
10925
- finalReps: this.currentReps
10926
- }));
10927
- }
10928
- return actions;
10929
- }
10930
- };
10931
10703
  var LabelComposer = class {
10932
10704
  /**
10933
10705
  * Builds a descriptive label from a set of statements.
@@ -11194,67 +10966,71 @@ var IntervalLogicStrategy = class {
11194
10966
  }
11195
10967
  }
11196
10968
  };
11197
- var GenericTimerStrategy = class {
11198
- priority = 50;
11199
- // Mid priority
11200
- id = "generic-timer";
10969
+ var EffortFallbackStrategy = class {
10970
+ priority = 0;
10971
+ id = "effort-fallback";
11201
10972
  match(statements, _runtime) {
11202
10973
  if (!statements || statements.length === 0) return false;
11203
- return statements.some((s) => s.metrics.some(
11204
- (f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"
11205
- ));
10974
+ const hasTimer = statements.some((s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"));
10975
+ const hasRounds = statements.some((s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Rounds && f.origin !== "runtime"));
10976
+ const hasChildren = statements.some((s) => s.children && s.children.length > 0);
10977
+ return !hasTimer && !hasRounds && !hasChildren;
11206
10978
  }
11207
10979
  apply(builder, statements, runtime) {
11208
- if (builder.hasTimerBehavior()) {
11209
- return;
11210
- }
11211
- const firstStatementWithTimer = statements.find((s) => s.metrics.some(
11212
- (f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"
11213
- )) || statements[0];
11214
- const timerFragment = firstStatementWithTimer.metrics.find(
11215
- (f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"
11216
- );
11217
- const direction = timerFragment?.direction || "up";
11218
- const durationMs = timerFragment?.value || void 0;
11219
- const isRequired = statements.some((s) => hasHint(s, CONSUMED_HINTS.REQUIRED_TIMER));
11220
- const injectRest = statements.some((s) => hasHint(s, CONSUMED_HINTS.INJECT_REST));
11221
- const isCountdown = !!(durationMs && direction === "down");
11222
- const template = {
11223
- blockType: "Timer",
11224
- defaultLabel: direction === "down" ? "Countdown" : "For Time",
10980
+ compose(builder, {
10981
+ blockType: "effort",
10982
+ defaultLabel: "Effort",
11225
10983
  statements,
11226
10984
  runtime,
10985
+ // Secondary countup timer so the stack display shows how long
10986
+ // the effort took. Role 'secondary' prevents it from stealing
10987
+ // primary display from a parent timer. Mode 'reset-interval'
10988
+ // means no timer completion (the user advances manually).
11227
10989
  timer: {
11228
- direction,
11229
- durationMs,
11230
- label: durationMs ? "Countdown" : "For Time",
11231
- role: "primary",
11232
- mode: isCountdown ? "complete-block" : "complete-block",
11233
- injectRest,
11234
- required: isRequired
10990
+ direction: "up",
10991
+ role: "secondary",
10992
+ mode: "reset-interval"
11235
10993
  },
11236
- pickStatement: (stmts) => stmts.find(
11237
- (s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime")
11238
- ) || stmts[0]
10994
+ filterMetrics: (f) => f.origin !== "runtime"
10995
+ });
10996
+ builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
10997
+ }
10998
+ };
10999
+ var GenericGroupStrategy = class {
11000
+ priority = 50;
11001
+ // Same as GenericTimer/GenericLoop; runs before ChildrenStrategy (same priority, registered earlier)
11002
+ id = "generic-group";
11003
+ match(statements, _runtime) {
11004
+ if (!statements || statements.length === 0) return false;
11005
+ const hasChildren = statements.some((s) => s.children && s.children.length > 0);
11006
+ const hasTimer = statements.some((s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"));
11007
+ const hasRounds = statements.some((s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Rounds && f.origin !== "runtime"));
11008
+ return hasChildren && !hasTimer && !hasRounds;
11009
+ }
11010
+ apply(builder, statements, runtime) {
11011
+ if (builder.hasTimerBehavior() || builder.hasRoundConfig()) {
11012
+ return;
11013
+ }
11014
+ const template = {
11015
+ blockType: "Group",
11016
+ defaultLabel: "Group",
11017
+ statements,
11018
+ runtime
11239
11019
  };
11240
11020
  const label = compose(builder, template);
11241
- if (isRequired && isCountdown) {
11242
- builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: false, onEvents: ["timer:complete"] }));
11243
- } else {
11244
- builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
11245
- }
11246
- if (isCountdown) {
11247
- builder.addBehavior(new SoundCueBehavior({
11248
- cues: [
11249
- { sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
11250
- { sound: "timer-complete", trigger: "complete" }
11251
- ]
11252
- }));
11253
- }
11254
11021
  builder.addBehavior(new LabelingBehavior({
11255
- mode: durationMs ? "countdown" : "clock",
11022
+ mode: "clock",
11256
11023
  label
11257
11024
  }));
11025
+ const hasResistance = statements.some((s) => s.metrics.some((m) => m.type === wodWikiCore.MetricType.Resistance));
11026
+ const hasDistance = statements.some((s) => s.metrics.some((m) => m.type === wodWikiCore.MetricType.Distance));
11027
+ const promotions = [
11028
+ ...hasResistance ? [{ metricType: wodWikiCore.MetricType.Resistance, origin: "compiler" }] : [],
11029
+ ...hasDistance ? [{ metricType: wodWikiCore.MetricType.Distance, origin: "compiler" }] : []
11030
+ ];
11031
+ if (promotions.length > 0) {
11032
+ builder.addBehavior(new MetricPromotionBehavior({ promotions }));
11033
+ }
11258
11034
  }
11259
11035
  };
11260
11036
  var GenericLoopStrategy = class {
@@ -11322,41 +11098,115 @@ var GenericLoopStrategy = class {
11322
11098
  builder.declareExit("immediate");
11323
11099
  }
11324
11100
  };
11325
- var GenericGroupStrategy = class {
11101
+ var GenericTimerStrategy = class {
11326
11102
  priority = 50;
11327
- // Same as GenericTimer/GenericLoop; runs before ChildrenStrategy (same priority, registered earlier)
11328
- id = "generic-group";
11103
+ // Mid priority
11104
+ id = "generic-timer";
11329
11105
  match(statements, _runtime) {
11330
11106
  if (!statements || statements.length === 0) return false;
11331
- const hasChildren = statements.some((s) => s.children && s.children.length > 0);
11332
- const hasTimer = statements.some((s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"));
11333
- const hasRounds = statements.some((s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Rounds && f.origin !== "runtime"));
11334
- return hasChildren && !hasTimer && !hasRounds;
11107
+ return statements.some((s) => s.metrics.some(
11108
+ (f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"
11109
+ ));
11335
11110
  }
11336
11111
  apply(builder, statements, runtime) {
11337
- if (builder.hasTimerBehavior() || builder.hasRoundConfig()) {
11112
+ if (builder.hasTimerBehavior()) {
11338
11113
  return;
11339
11114
  }
11115
+ const firstStatementWithTimer = statements.find((s) => s.metrics.some(
11116
+ (f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"
11117
+ )) || statements[0];
11118
+ const timerFragment = firstStatementWithTimer.metrics.find(
11119
+ (f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"
11120
+ );
11121
+ const direction = timerFragment?.direction || "up";
11122
+ const durationMs = timerFragment?.value || void 0;
11123
+ const isRequired = statements.some((s) => hasHint(s, CONSUMED_HINTS.REQUIRED_TIMER));
11124
+ const injectRest = statements.some((s) => hasHint(s, CONSUMED_HINTS.INJECT_REST));
11125
+ const isCountdown = !!(durationMs && direction === "down");
11340
11126
  const template = {
11341
- blockType: "Group",
11342
- defaultLabel: "Group",
11127
+ blockType: "Timer",
11128
+ defaultLabel: direction === "down" ? "Countdown" : "For Time",
11343
11129
  statements,
11344
- runtime
11130
+ runtime,
11131
+ timer: {
11132
+ direction,
11133
+ durationMs,
11134
+ label: durationMs ? "Countdown" : "For Time",
11135
+ role: "primary",
11136
+ mode: isCountdown ? "complete-block" : "complete-block",
11137
+ injectRest,
11138
+ required: isRequired
11139
+ },
11140
+ pickStatement: (stmts) => stmts.find(
11141
+ (s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime")
11142
+ ) || stmts[0]
11345
11143
  };
11346
11144
  const label = compose(builder, template);
11145
+ if (isRequired && isCountdown) {
11146
+ builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: false, onEvents: ["timer:complete"] }));
11147
+ } else {
11148
+ builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
11149
+ }
11150
+ if (isCountdown) {
11151
+ builder.addBehavior(new SoundCueBehavior({
11152
+ cues: [
11153
+ { sound: "countdown-beep", trigger: "countdown", atSeconds: [3, 2, 1] },
11154
+ { sound: "timer-complete", trigger: "complete" }
11155
+ ]
11156
+ }));
11157
+ }
11347
11158
  builder.addBehavior(new LabelingBehavior({
11348
- mode: "clock",
11159
+ mode: durationMs ? "countdown" : "clock",
11349
11160
  label
11350
11161
  }));
11351
- const hasResistance = statements.some((s) => s.metrics.some((m) => m.type === wodWikiCore.MetricType.Resistance));
11352
- const hasDistance = statements.some((s) => s.metrics.some((m) => m.type === wodWikiCore.MetricType.Distance));
11353
- const promotions = [
11354
- ...hasResistance ? [{ metricType: wodWikiCore.MetricType.Resistance, origin: "compiler" }] : [],
11355
- ...hasDistance ? [{ metricType: wodWikiCore.MetricType.Distance, origin: "compiler" }] : []
11356
- ];
11357
- if (promotions.length > 0) {
11358
- builder.addBehavior(new MetricPromotionBehavior({ promotions }));
11162
+ }
11163
+ };
11164
+
11165
+ // src/runtime/compiler/strategies/enhancements/ChildrenStrategy.ts
11166
+ var ChildrenStrategy = class {
11167
+ priority = 50;
11168
+ id = "children";
11169
+ match(statements, _runtime) {
11170
+ return statements && statements.length > 0 && statements[0].children && statements[0].children.length > 0;
11171
+ }
11172
+ apply(builder, statements, _runtime) {
11173
+ const children = statements[0].children;
11174
+ const childGroups = children.filter((group) => group.length > 0);
11175
+ const hasRoundsFromStrategy = builder.hasRoundConfig();
11176
+ const countdown = builder.getBehavior(CountdownTimerBehavior);
11177
+ const hasCountdownCompletion = !!countdown;
11178
+ if (!hasRoundsFromStrategy) {
11179
+ if (hasCountdownCompletion) {
11180
+ builder.asRepeater({ totalRounds: void 0, startRound: 1, addCompletion: false });
11181
+ } else {
11182
+ builder.asRepeater({ totalRounds: 1, startRound: 1, addCompletion: true });
11183
+ }
11184
+ }
11185
+ const shouldAddLoop = hasCountdownCompletion || hasRoundsFromStrategy;
11186
+ const loopCondition = hasRoundsFromStrategy ? "rounds-remaining" : "timer-active";
11187
+ const shouldInjectRest = hasCountdownCompletion && countdown?.config.mode === "reset-interval";
11188
+ builder.asContainer({
11189
+ childGroups,
11190
+ addLoop: shouldAddLoop,
11191
+ loopConfig: shouldAddLoop ? { condition: loopCondition } : void 0,
11192
+ injectRest: shouldInjectRest
11193
+ });
11194
+ builder.declareExit("deferred");
11195
+ }
11196
+ };
11197
+
11198
+ // src/runtime/compiler/strategies/enhancements/ReportOutputStrategy.ts
11199
+ var ReportOutputStrategy = class {
11200
+ priority = 15;
11201
+ id = "report-output";
11202
+ match(statements, _runtime) {
11203
+ return statements && statements.length > 0;
11204
+ }
11205
+ apply(builder, _statements, _runtime) {
11206
+ if (builder.hasBehavior(ReportOutputBehavior)) {
11207
+ return;
11359
11208
  }
11209
+ builder.addBehavior(new ReportOutputBehavior());
11360
11210
  }
11361
11211
  };
11362
11212
 
@@ -11391,81 +11241,231 @@ var SoundStrategy = class {
11391
11241
  }
11392
11242
  };
11393
11243
 
11394
- // src/runtime/compiler/strategies/enhancements/ReportOutputStrategy.ts
11395
- var ReportOutputStrategy = class {
11396
- priority = 15;
11397
- id = "report-output";
11398
- match(statements, _runtime) {
11399
- return statements && statements.length > 0;
11244
+ // src/runtime/events/NextEvent.ts
11245
+ var NextEvent = class _NextEvent {
11246
+ constructor(data, now = wallClockNow) {
11247
+ this.now = now;
11248
+ const nowMs = this.now.nowMs();
11249
+ _NextEvent._counter++;
11250
+ this.timestamp = new Date(nowMs + _NextEvent._counter);
11251
+ this._data = data;
11400
11252
  }
11401
- apply(builder, _statements, _runtime) {
11402
- if (builder.hasBehavior(ReportOutputBehavior)) {
11403
- return;
11404
- }
11405
- builder.addBehavior(new ReportOutputBehavior());
11253
+ now;
11254
+ name = "next";
11255
+ timestamp;
11256
+ _data;
11257
+ static _counter = 0;
11258
+ get data() {
11259
+ return this._data;
11260
+ }
11261
+ set data(value) {
11262
+ this._data = value;
11263
+ }
11264
+ // Custom toJSON method for proper JSON serialization
11265
+ toJSON() {
11266
+ return {
11267
+ name: this.name,
11268
+ timestamp: this.timestamp,
11269
+ data: this._data
11270
+ };
11406
11271
  }
11407
11272
  };
11408
11273
 
11409
- // src/runtime/compiler/strategies/enhancements/ChildrenStrategy.ts
11410
- var ChildrenStrategy = class {
11411
- priority = 50;
11412
- id = "children";
11413
- match(statements, _runtime) {
11414
- return statements && statements.length > 0 && statements[0].children && statements[0].children.length > 0;
11274
+ // src/runtime/models/SoundModels.ts
11275
+ var PREDEFINED_SOUNDS = {
11276
+ /** Short single beep - good for countdown ticks */
11277
+ BEEP: "beep",
11278
+ /** Long buzz - good for timer completion */
11279
+ BUZZER: "buzzer",
11280
+ /** Pleasant chime - good for milestones */
11281
+ CHIME: "chime",
11282
+ /** Clock tick - good for second-by-second countdown */
11283
+ TICK: "tick",
11284
+ /** Ascending sequence - good for timer start */
11285
+ START: "start"
11286
+ };
11287
+ function metricsToLabel(metrics) {
11288
+ if (metrics instanceof wodWikiCore.MetricContainer) {
11289
+ return metricsToLabel(metrics.toArray());
11415
11290
  }
11416
- apply(builder, statements, _runtime) {
11417
- const children = statements[0].children;
11418
- const childGroups = children.filter((group) => group.length > 0);
11419
- const hasRoundsFromStrategy = builder.hasRoundConfig();
11420
- const countdown = builder.getBehavior(CountdownTimerBehavior);
11421
- const hasCountdownCompletion = !!countdown;
11422
- if (!hasRoundsFromStrategy) {
11423
- if (hasCountdownCompletion) {
11424
- builder.asRepeater({ totalRounds: void 0, startRound: 1, addCompletion: false });
11425
- } else {
11426
- builder.asRepeater({ totalRounds: 1, startRound: 1, addCompletion: true });
11427
- }
11428
- }
11429
- const shouldAddLoop = hasCountdownCompletion || hasRoundsFromStrategy;
11430
- const loopCondition = hasRoundsFromStrategy ? "rounds-remaining" : "timer-active";
11431
- const shouldInjectRest = hasCountdownCompletion && countdown?.config.mode === "reset-interval";
11432
- builder.asContainer({
11433
- childGroups,
11434
- addLoop: shouldAddLoop,
11435
- loopConfig: shouldAddLoop ? { condition: loopCondition } : void 0,
11436
- injectRest: shouldInjectRest
11437
- });
11438
- builder.declareExit("deferred");
11291
+ const first = metrics[0];
11292
+ const flat = Array.isArray(first) ? metrics.flat() : metrics;
11293
+ const labelFragment = flat.find((f) => f.type === wodWikiCore.MetricType.Label);
11294
+ if (labelFragment) return labelFragment.image || labelFragment.value?.toString() || "Block";
11295
+ const primary = flat.find((f) => f.type === wodWikiCore.MetricType.Effort || f.type === wodWikiCore.MetricType.Action);
11296
+ if (primary) return primary.image || primary.value?.toString() || "Block";
11297
+ const firstWithImage = flat.find((f) => f.image);
11298
+ if (firstWithImage) return firstWithImage.image || "Block";
11299
+ return "Block";
11300
+ }
11301
+
11302
+ // src/runtime/actions/events/EmitEventAction.ts
11303
+ var EmitEventAction = class {
11304
+ constructor(eventName, data, timestamp, now = wallClockNow) {
11305
+ this.eventName = eventName;
11306
+ this.data = data;
11307
+ this.timestamp = timestamp ?? now.now();
11308
+ }
11309
+ eventName;
11310
+ data;
11311
+ type = "emit-event";
11312
+ timestamp;
11313
+ do(runtime) {
11314
+ const event = {
11315
+ name: this.eventName,
11316
+ timestamp: this.timestamp,
11317
+ data: this.data
11318
+ };
11319
+ return runtime.eventBus.dispatch(event, runtime);
11439
11320
  }
11440
11321
  };
11441
- var EffortFallbackStrategy = class {
11442
- priority = 0;
11443
- id = "effort-fallback";
11444
- match(statements, _runtime) {
11445
- if (!statements || statements.length === 0) return false;
11446
- const hasTimer = statements.some((s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Duration && f.origin !== "runtime"));
11447
- const hasRounds = statements.some((s) => s.metrics.some((f) => f.type === wodWikiCore.MetricType.Rounds && f.origin !== "runtime"));
11448
- const hasChildren = statements.some((s) => s.children && s.children.length > 0);
11449
- return !hasTimer && !hasRounds && !hasChildren;
11322
+
11323
+ // src/runtime/blocks/EffortBlock.ts
11324
+ var EffortCompletionBehavior = class {
11325
+ constructor(checkComplete) {
11326
+ this.checkComplete = checkComplete;
11450
11327
  }
11451
- apply(builder, statements, runtime) {
11452
- compose(builder, {
11453
- blockType: "effort",
11454
- defaultLabel: "Effort",
11455
- statements,
11328
+ checkComplete;
11329
+ _forceComplete = false;
11330
+ _isComplete = false;
11331
+ forceComplete() {
11332
+ this._forceComplete = true;
11333
+ }
11334
+ onMount(ctx) {
11335
+ ctx.subscribe("next", () => {
11336
+ this._forceComplete = true;
11337
+ return [];
11338
+ });
11339
+ return [];
11340
+ }
11341
+ onNext(ctx) {
11342
+ if (this._isComplete) {
11343
+ return [];
11344
+ }
11345
+ if (this.checkComplete() || this._forceComplete) {
11346
+ this._isComplete = true;
11347
+ const now = ctx.clock.currentDate;
11348
+ ctx.markComplete("target-achieved");
11349
+ return [
11350
+ new EmitEventAction("block:complete", { blockId: ctx.block.key.toString() }, now)
11351
+ ];
11352
+ }
11353
+ return [];
11354
+ }
11355
+ onUnmount(_ctx) {
11356
+ return [];
11357
+ }
11358
+ onDispose(_ctx) {
11359
+ }
11360
+ };
11361
+ var EffortBlock = class extends RuntimeBlock {
11362
+ constructor(runtime, sourceIds, config, metrics) {
11363
+ super({
11456
11364
  runtime,
11457
- // Secondary countup timer so the stack display shows how long
11458
- // the effort took. Role 'secondary' prevents it from stealing
11459
- // primary display from a parent timer. Mode 'reset-interval'
11460
- // means no timer completion (the user advances manually).
11461
- timer: {
11462
- direction: "up",
11463
- role: "secondary",
11464
- mode: "reset-interval"
11465
- },
11466
- filterMetrics: (f) => f.origin !== "runtime"
11365
+ sourceIds,
11366
+ behaviors: [],
11367
+ // Behaviors added below
11368
+ blockType: "Effort",
11369
+ label: `${config.targetReps} ${config.exerciseName}`,
11370
+ metrics
11467
11371
  });
11468
- builder.addBehavior(new ExitBehavior({ mode: "immediate", onNext: true }));
11372
+ this.config = config;
11373
+ if (!config.exerciseName || config.exerciseName.trim() === "") {
11374
+ throw new TypeError("EffortBlock requires a non-empty exerciseName");
11375
+ }
11376
+ if (config.targetReps < 1) {
11377
+ throw new RangeError(`targetReps must be >= 1, got: ${config.targetReps}`);
11378
+ }
11379
+ const completionBehavior = new EffortCompletionBehavior(() => this.isTargetComplete());
11380
+ this.behaviors.push(new LabelingBehavior({
11381
+ mode: "clock",
11382
+ label: `${config.targetReps} ${config.exerciseName}`,
11383
+ actionDisplay: config.exerciseName
11384
+ }));
11385
+ this.behaviors.push(completionBehavior);
11386
+ this.behaviors.push(new CountupTimerBehavior({
11387
+ label: "Segment Timer",
11388
+ role: "secondary"
11389
+ }));
11390
+ }
11391
+ config;
11392
+ currentReps = 0;
11393
+ lastCompletionMode = "incremental";
11394
+ mount(runtime, options) {
11395
+ const actions = super.mount(runtime, options);
11396
+ actions.push(...this.getMetricActions());
11397
+ return actions;
11398
+ }
11399
+ unmount(runtime, options) {
11400
+ return super.unmount(runtime, options);
11401
+ }
11402
+ dispose(runtime) {
11403
+ super.dispose(runtime);
11404
+ if (this.context) {
11405
+ this.context.release();
11406
+ }
11407
+ }
11408
+ /**
11409
+ * Check if the effort target has been met.
11410
+ * Note: This is distinct from the block's isComplete flag which is used by the stack.
11411
+ */
11412
+ isTargetComplete() {
11413
+ return this.currentReps >= this.config.targetReps;
11414
+ }
11415
+ getCurrentReps() {
11416
+ return this.currentReps;
11417
+ }
11418
+ getTargetReps() {
11419
+ return this.config.targetReps;
11420
+ }
11421
+ getExerciseName() {
11422
+ return this.config.exerciseName;
11423
+ }
11424
+ incrementRep() {
11425
+ if (this.currentReps < this.config.targetReps) {
11426
+ this.currentReps++;
11427
+ this.lastCompletionMode = "incremental";
11428
+ return this.getMetricActions();
11429
+ }
11430
+ return [];
11431
+ }
11432
+ setReps(count) {
11433
+ if (count < 0 || count > this.config.targetReps) {
11434
+ throw new RangeError(`setReps(${count}) out of valid range [0, ${this.config.targetReps}]`);
11435
+ }
11436
+ this.currentReps = count;
11437
+ this.lastCompletionMode = "bulk";
11438
+ return this.getMetricActions();
11439
+ }
11440
+ markComplete() {
11441
+ this.currentReps = this.config.targetReps;
11442
+ this.lastCompletionMode = "bulk";
11443
+ const actions = this.getMetricActions();
11444
+ actions.push(new EmitEventAction("reps:complete", {
11445
+ blockId: this.key.toString(),
11446
+ exerciseName: this.config.exerciseName,
11447
+ finalReps: this.currentReps
11448
+ }));
11449
+ return actions;
11450
+ }
11451
+ getMetricActions() {
11452
+ const blockId = this.key.toString();
11453
+ const actions = [];
11454
+ actions.push(new EmitEventAction("reps:updated", {
11455
+ blockId,
11456
+ exerciseName: this.config.exerciseName,
11457
+ currentReps: this.currentReps,
11458
+ targetReps: this.config.targetReps,
11459
+ completionMode: this.lastCompletionMode
11460
+ }));
11461
+ if (this.isTargetComplete()) {
11462
+ actions.push(new EmitEventAction("reps:complete", {
11463
+ blockId,
11464
+ exerciseName: this.config.exerciseName,
11465
+ finalReps: this.currentReps
11466
+ }));
11467
+ }
11468
+ return actions;
11469
11469
  }
11470
11470
  };
11471
11471
 
@@ -13214,26 +13214,29 @@ function toStoredOutputStatement(output) {
13214
13214
  }
13215
13215
 
13216
13216
  // src/version.ts
13217
- var VERSION = "0.3.23";
13218
- var GIT_SHA = "cbf19c2";
13219
- var BUILD_TIME = "2026-08-18T18:53:07.751Z";
13217
+ var VERSION = "0.5.28";
13218
+ var GIT_SHA = "8aaaba1";
13219
+ var BUILD_TIME = "2026-08-18T20:49:38.789Z";
13220
13220
  var SEMVER = {
13221
- version: "0.3.23",
13222
- gitSha: "cbf19c2",
13223
- buildTime: "2026-08-18T18:53:07.751Z"
13221
+ version: "0.5.28",
13222
+ gitSha: "8aaaba1",
13223
+ buildTime: "2026-08-18T20:49:38.789Z"
13224
13224
  };
13225
13225
 
13226
13226
  exports.ABSENT = ABSENT;
13227
13227
  exports.AGGREGATE_BUILTINS = AGGREGATE_BUILTINS;
13228
13228
  exports.AUTHORITATIVE_CASTS = AUTHORITATIVE_CASTS;
13229
13229
  exports.AbortSessionAction = AbortSessionAction;
13230
+ exports.AmrapLogicStrategy = AmrapLogicStrategy;
13230
13231
  exports.AnalyticsEngine = AnalyticsEngine;
13231
13232
  exports.AnalyticsTransformer = AnalyticsTransformer;
13232
13233
  exports.BUILD_TIME = BUILD_TIME;
13233
13234
  exports.BUILTIN_CALCS = BUILTIN_CALCS;
13234
13235
  exports.BUNDLED_EFFORT_COUNT = BUNDLED_EFFORT_COUNT;
13235
13236
  exports.BehaviorContext = BehaviorContext;
13237
+ exports.BlockBuilder = BlockBuilder;
13236
13238
  exports.BlockContext = BlockContext;
13239
+ exports.ButtonBehavior = ButtonBehavior;
13237
13240
  exports.CONSUMED_HINTS = CONSUMED_HINTS;
13238
13241
  exports.CONSUMED_HINT_KEYS = CONSUMED_HINT_KEYS;
13239
13242
  exports.CONTEXT_ATOMS = CONTEXT_ATOMS;
@@ -13245,13 +13248,20 @@ exports.CalcUnitError = CalcUnitError;
13245
13248
  exports.CalculateBlockProcessor = CalculateBlockProcessor;
13246
13249
  exports.CalculationRegistry = CalculationRegistry;
13247
13250
  exports.CardioDialect = CardioDialect;
13251
+ exports.ChildSelectionBehavior = ChildSelectionBehavior;
13252
+ exports.ChildrenStrategy = ChildrenStrategy;
13253
+ exports.ChoiceGroupMetric = ChoiceGroupMetric;
13248
13254
  exports.ClearChildrenAction = ClearChildrenAction;
13249
13255
  exports.ClimbDialect = ClimbDialect;
13250
13256
  exports.ClimbMetricType = ClimbMetricType;
13251
13257
  exports.CompileAndPushBlockAction = CompileAndPushBlockAction;
13258
+ exports.CompletionTimestampBehavior = CompletionTimestampBehavior;
13259
+ exports.CountdownTimerBehavior = CountdownTimerBehavior;
13260
+ exports.CountupTimerBehavior = CountupTimerBehavior;
13252
13261
  exports.CrossFitDialect = CrossFitDialect;
13253
13262
  exports.DAY = DAY;
13254
13263
  exports.DEFAULT_RESOLVER_OPTIONS = DEFAULT_RESOLVER_OPTIONS;
13264
+ exports.DEFAULT_RUNTIME_OPTIONS = DEFAULT_RUNTIME_OPTIONS;
13255
13265
  exports.DEFAULT_UNRESOLVED_EFFORT_MET = DEFAULT_UNRESOLVED_EFFORT_MET;
13256
13266
  exports.DIM_COUNT = DIM_COUNT;
13257
13267
  exports.DIM_ENERGY = DIM_ENERGY;
@@ -13265,23 +13275,31 @@ exports.EFFORT_DATA_METRIC_TYPE = EFFORT_DATA_METRIC_TYPE;
13265
13275
  exports.EFFORT_DISCIPLINES = EFFORT_DISCIPLINES;
13266
13276
  exports.EFFORT_REGISTRY_ORIGINS = EFFORT_REGISTRY_ORIGINS;
13267
13277
  exports.EffortBlock = EffortBlock;
13278
+ exports.EffortFallbackStrategy = EffortFallbackStrategy;
13268
13279
  exports.EffortResolver = EffortResolver;
13269
13280
  exports.EmitEventAction = EmitEventAction;
13270
13281
  exports.EmitSystemOutputAction = EmitSystemOutputAction;
13271
13282
  exports.ErrorAction = ErrorAction;
13272
13283
  exports.EventBus = EventBus;
13273
13284
  exports.ExecutionContext = ExecutionContext;
13285
+ exports.ExitBehavior = ExitBehavior;
13274
13286
  exports.GIT_SHA = GIT_SHA;
13287
+ exports.GenericGroupStrategy = GenericGroupStrategy;
13288
+ exports.GenericLoopStrategy = GenericLoopStrategy;
13289
+ exports.GenericTimerStrategy = GenericTimerStrategy;
13275
13290
  exports.HabitsDialect = HabitsDialect;
13276
13291
  exports.INTENSITY_TIERS = INTENSITY_TIERS;
13277
13292
  exports.InMemoryEffortRegistry = InMemoryEffortRegistry;
13293
+ exports.IntervalLogicStrategy = IntervalLogicStrategy;
13278
13294
  exports.JitCompiler = JitCompiler;
13295
+ exports.LabelingBehavior = LabelingBehavior;
13279
13296
  exports.LocalEventProvider = LocalEventProvider;
13280
13297
  exports.LocalRuntimeSubscription = LocalRuntimeSubscription;
13281
13298
  exports.LookupRegistry = LookupRegistry;
13282
13299
  exports.MdTimerRuntime = MdTimerRuntime;
13283
13300
  exports.MemoryLocation = MemoryLocation;
13284
13301
  exports.MemoryTypeEnum = MemoryTypeEnum;
13302
+ exports.MetricPromotionBehavior = MetricPromotionBehavior;
13285
13303
  exports.NAMED_COMPOUNDS = NAMED_COMPOUNDS;
13286
13304
  exports.NextAction = NextAction;
13287
13305
  exports.NextEvent = NextEvent;
@@ -13297,7 +13315,10 @@ exports.REST_EXCLUSION = REST_EXCLUSION;
13297
13315
  exports.RPE_LABELS = RPE_LABELS;
13298
13316
  exports.RegisterEventHandlerAction = RegisterEventHandlerAction;
13299
13317
  exports.Registry = Registry;
13318
+ exports.ReportOutputBehavior = ReportOutputBehavior;
13319
+ exports.ReportOutputStrategy = ReportOutputStrategy;
13300
13320
  exports.RestBlock = RestBlock;
13321
+ exports.RoundsMetric = RoundsMetric;
13301
13322
  exports.RuntimeAdapter = RuntimeAdapter;
13302
13323
  exports.RuntimeBlock = RuntimeBlock;
13303
13324
  exports.RuntimeBuilder = RuntimeBuilder;
@@ -13313,7 +13334,11 @@ exports.STORE_CALCS = STORE_CALCS;
13313
13334
  exports.STREAM_ATOMS = STREAM_ATOMS;
13314
13335
  exports.ScriptRuntime = ScriptRuntime;
13315
13336
  exports.SessionRootBlock = SessionRootBlock;
13337
+ exports.SessionRootStrategy = SessionRootStrategy;
13316
13338
  exports.SnapshotClock = SnapshotClock;
13339
+ exports.SoundCueBehavior = SoundCueBehavior;
13340
+ exports.SoundStrategy = SoundStrategy;
13341
+ exports.SpanTrackingBehavior = SpanTrackingBehavior;
13317
13342
  exports.StandardAnalyticsProfile = StandardAnalyticsProfile;
13318
13343
  exports.StartSessionAction = StartSessionAction;
13319
13344
  exports.StartWorkoutAction = StartWorkoutAction;
@@ -13333,6 +13358,7 @@ exports.VERSION = VERSION;
13333
13358
  exports.VISIBILITY_ICONS = VISIBILITY_ICONS;
13334
13359
  exports.VISIBILITY_LABELS = VISIBILITY_LABELS;
13335
13360
  exports.WaitingToStartBlock = WaitingToStartBlock;
13361
+ exports.WaitingToStartInjectorBehavior = WaitingToStartInjectorBehavior;
13336
13362
  exports.WhiteboardScript = WhiteboardScript;
13337
13363
  exports.WodDialect = WodDialect;
13338
13364
  exports.YogaDialect = YogaDialect;