@bitcobblers/wod-wiki-lang 0.4.24 → 0.5.29

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,19 +13214,20 @@ function toStoredOutputStatement(output) {
13214
13214
  }
13215
13215
 
13216
13216
  // src/version.ts
13217
- var VERSION = "0.4.24";
13218
- var GIT_SHA = "90d86de";
13219
- var BUILD_TIME = "2026-08-18T20:19:27.340Z";
13217
+ var VERSION = "0.5.29";
13218
+ var GIT_SHA = "d808ea1";
13219
+ var BUILD_TIME = "2026-08-18T21:04:09.158Z";
13220
13220
  var SEMVER = {
13221
- version: "0.4.24",
13222
- gitSha: "90d86de",
13223
- buildTime: "2026-08-18T20:19:27.340Z"
13221
+ version: "0.5.29",
13222
+ gitSha: "d808ea1",
13223
+ buildTime: "2026-08-18T21:04:09.158Z"
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;
@@ -13235,6 +13236,7 @@ exports.BUNDLED_EFFORT_COUNT = BUNDLED_EFFORT_COUNT;
13235
13236
  exports.BehaviorContext = BehaviorContext;
13236
13237
  exports.BlockBuilder = BlockBuilder;
13237
13238
  exports.BlockContext = BlockContext;
13239
+ exports.ButtonBehavior = ButtonBehavior;
13238
13240
  exports.CONSUMED_HINTS = CONSUMED_HINTS;
13239
13241
  exports.CONSUMED_HINT_KEYS = CONSUMED_HINT_KEYS;
13240
13242
  exports.CONTEXT_ATOMS = CONTEXT_ATOMS;
@@ -13246,11 +13248,14 @@ exports.CalcUnitError = CalcUnitError;
13246
13248
  exports.CalculateBlockProcessor = CalculateBlockProcessor;
13247
13249
  exports.CalculationRegistry = CalculationRegistry;
13248
13250
  exports.CardioDialect = CardioDialect;
13251
+ exports.ChildSelectionBehavior = ChildSelectionBehavior;
13252
+ exports.ChildrenStrategy = ChildrenStrategy;
13249
13253
  exports.ChoiceGroupMetric = ChoiceGroupMetric;
13250
13254
  exports.ClearChildrenAction = ClearChildrenAction;
13251
13255
  exports.ClimbDialect = ClimbDialect;
13252
13256
  exports.ClimbMetricType = ClimbMetricType;
13253
13257
  exports.CompileAndPushBlockAction = CompileAndPushBlockAction;
13258
+ exports.CompletionTimestampBehavior = CompletionTimestampBehavior;
13254
13259
  exports.CountdownTimerBehavior = CountdownTimerBehavior;
13255
13260
  exports.CountupTimerBehavior = CountupTimerBehavior;
13256
13261
  exports.CrossFitDialect = CrossFitDialect;
@@ -13270,23 +13275,31 @@ exports.EFFORT_DATA_METRIC_TYPE = EFFORT_DATA_METRIC_TYPE;
13270
13275
  exports.EFFORT_DISCIPLINES = EFFORT_DISCIPLINES;
13271
13276
  exports.EFFORT_REGISTRY_ORIGINS = EFFORT_REGISTRY_ORIGINS;
13272
13277
  exports.EffortBlock = EffortBlock;
13278
+ exports.EffortFallbackStrategy = EffortFallbackStrategy;
13273
13279
  exports.EffortResolver = EffortResolver;
13274
13280
  exports.EmitEventAction = EmitEventAction;
13275
13281
  exports.EmitSystemOutputAction = EmitSystemOutputAction;
13276
13282
  exports.ErrorAction = ErrorAction;
13277
13283
  exports.EventBus = EventBus;
13278
13284
  exports.ExecutionContext = ExecutionContext;
13285
+ exports.ExitBehavior = ExitBehavior;
13279
13286
  exports.GIT_SHA = GIT_SHA;
13287
+ exports.GenericGroupStrategy = GenericGroupStrategy;
13288
+ exports.GenericLoopStrategy = GenericLoopStrategy;
13289
+ exports.GenericTimerStrategy = GenericTimerStrategy;
13280
13290
  exports.HabitsDialect = HabitsDialect;
13281
13291
  exports.INTENSITY_TIERS = INTENSITY_TIERS;
13282
13292
  exports.InMemoryEffortRegistry = InMemoryEffortRegistry;
13293
+ exports.IntervalLogicStrategy = IntervalLogicStrategy;
13283
13294
  exports.JitCompiler = JitCompiler;
13295
+ exports.LabelingBehavior = LabelingBehavior;
13284
13296
  exports.LocalEventProvider = LocalEventProvider;
13285
13297
  exports.LocalRuntimeSubscription = LocalRuntimeSubscription;
13286
13298
  exports.LookupRegistry = LookupRegistry;
13287
13299
  exports.MdTimerRuntime = MdTimerRuntime;
13288
13300
  exports.MemoryLocation = MemoryLocation;
13289
13301
  exports.MemoryTypeEnum = MemoryTypeEnum;
13302
+ exports.MetricPromotionBehavior = MetricPromotionBehavior;
13290
13303
  exports.NAMED_COMPOUNDS = NAMED_COMPOUNDS;
13291
13304
  exports.NextAction = NextAction;
13292
13305
  exports.NextEvent = NextEvent;
@@ -13302,7 +13315,10 @@ exports.REST_EXCLUSION = REST_EXCLUSION;
13302
13315
  exports.RPE_LABELS = RPE_LABELS;
13303
13316
  exports.RegisterEventHandlerAction = RegisterEventHandlerAction;
13304
13317
  exports.Registry = Registry;
13318
+ exports.ReportOutputBehavior = ReportOutputBehavior;
13319
+ exports.ReportOutputStrategy = ReportOutputStrategy;
13305
13320
  exports.RestBlock = RestBlock;
13321
+ exports.RoundsMetric = RoundsMetric;
13306
13322
  exports.RuntimeAdapter = RuntimeAdapter;
13307
13323
  exports.RuntimeBlock = RuntimeBlock;
13308
13324
  exports.RuntimeBuilder = RuntimeBuilder;
@@ -13318,7 +13334,11 @@ exports.STORE_CALCS = STORE_CALCS;
13318
13334
  exports.STREAM_ATOMS = STREAM_ATOMS;
13319
13335
  exports.ScriptRuntime = ScriptRuntime;
13320
13336
  exports.SessionRootBlock = SessionRootBlock;
13337
+ exports.SessionRootStrategy = SessionRootStrategy;
13321
13338
  exports.SnapshotClock = SnapshotClock;
13339
+ exports.SoundCueBehavior = SoundCueBehavior;
13340
+ exports.SoundStrategy = SoundStrategy;
13341
+ exports.SpanTrackingBehavior = SpanTrackingBehavior;
13322
13342
  exports.StandardAnalyticsProfile = StandardAnalyticsProfile;
13323
13343
  exports.StartSessionAction = StartSessionAction;
13324
13344
  exports.StartWorkoutAction = StartWorkoutAction;
@@ -13338,6 +13358,7 @@ exports.VERSION = VERSION;
13338
13358
  exports.VISIBILITY_ICONS = VISIBILITY_ICONS;
13339
13359
  exports.VISIBILITY_LABELS = VISIBILITY_LABELS;
13340
13360
  exports.WaitingToStartBlock = WaitingToStartBlock;
13361
+ exports.WaitingToStartInjectorBehavior = WaitingToStartInjectorBehavior;
13341
13362
  exports.WhiteboardScript = WhiteboardScript;
13342
13363
  exports.WodDialect = WodDialect;
13343
13364
  exports.YogaDialect = YogaDialect;