@almadar/std 16.239.0 → 16.242.0

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.
Files changed (167) hide show
  1. package/behaviors/behaviors-registry.json +4814 -553
  2. package/behaviors/lolo/agent/atoms/std-agent-memory.lolo +38 -1
  3. package/behaviors/lolo/agent/atoms/std-agent-session.lolo +1074 -47
  4. package/behaviors/lolo/agent/atoms/std-agent-tool-loop.lolo +8 -7
  5. package/behaviors/lolo/agent/organisms/std-agent-assistant.lolo +56 -37
  6. package/behaviors/lolo/infra/atoms/std-page-open.lolo +59 -0
  7. package/behaviors/lolo/infra/atoms/std-page-search.lolo +134 -0
  8. package/behaviors/lolo/infra/atoms/std-page-treat.lolo +59 -0
  9. package/behaviors/lolo/infra/atoms/std-page-watch.lolo +86 -0
  10. package/behaviors/lolo/ui/core/atoms/std-board.lolo +53 -6
  11. package/behaviors/lolo/ui/core/atoms/std-book-reader.lolo +25 -1
  12. package/behaviors/lolo/ui/core/atoms/std-card-grid.lolo +9 -0
  13. package/behaviors/lolo/ui/core/atoms/std-doc-page.lolo +153 -0
  14. package/behaviors/lolo/ui/core/atoms/std-site-header.lolo +15 -3
  15. package/behaviors/lolo/ui/core/atoms/std-tag-strip.lolo +2 -2
  16. package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +7 -7
  17. package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +1 -1
  18. package/behaviors/lolo/ui/core/molecules/ui-header.lolo +5 -0
  19. package/behaviors/lolo/ui/learning/atoms/std-learn-arrangement.lolo +124 -92
  20. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +114 -55
  21. package/behaviors/lolo/ui/learning/atoms/std-learn-compare.lolo +40 -14
  22. package/behaviors/lolo/ui/learning/atoms/std-learn-coupled-sim.lolo +363 -123
  23. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +93 -48
  24. package/behaviors/lolo/ui/learning/atoms/std-learn-diagram.lolo +60 -28
  25. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +193 -96
  26. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +75 -32
  27. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +142 -86
  28. package/behaviors/lolo/ui/learning/atoms/std-learn-grid-sim.lolo +266 -98
  29. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +103 -65
  30. package/behaviors/lolo/ui/learning/atoms/std-learn-live-sim.lolo +223 -109
  31. package/behaviors/lolo/ui/learning/atoms/std-learn-measure.lolo +232 -64
  32. package/behaviors/lolo/ui/learning/atoms/std-learn-param-explorer.lolo +69 -29
  33. package/behaviors/lolo/ui/learning/atoms/std-learn-perturb.lolo +363 -123
  34. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +73 -41
  35. package/behaviors/lolo/ui/learning/atoms/std-learn-sample-accumulator.lolo +96 -38
  36. package/behaviors/lolo/ui/learning/atoms/std-learn-sandbox.lolo +65 -45
  37. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +243 -117
  38. package/behaviors/lolo/ui/learning/atoms/std-learn-scenario.lolo +76 -28
  39. package/behaviors/lolo/ui/learning/atoms/std-learn-sim-3d.lolo +228 -109
  40. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +95 -44
  41. package/behaviors/lolo/ui/learning/atoms/std-learn-stepper.lolo +199 -125
  42. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +92 -48
  43. package/behaviors/lolo/ui/learning/atoms/std-learn-trace-player.lolo +239 -78
  44. package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +147 -38
  45. package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +123 -58
  46. package/behaviors/lolo/ui/learning/atoms/ui-algo-graph-canvas.lolo +22 -0
  47. package/behaviors/lolo/ui/learning/atoms/ui-algorithm-canvas.lolo +26 -0
  48. package/behaviors/lolo/ui/learning/atoms/ui-biology-canvas.lolo +26 -0
  49. package/behaviors/lolo/ui/learning/atoms/ui-chemistry-canvas.lolo +27 -1
  50. package/behaviors/lolo/ui/learning/atoms/ui-learning-canvas.lolo +27 -1
  51. package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +29 -9
  52. package/behaviors/lolo/ui/learning/atoms/ui-physics-canvas.lolo +26 -0
  53. package/behaviors/lolo-behavior-contracts.json +1 -1
  54. package/behaviors/registry/agent/atoms/std-agent-memory.orb +372 -2
  55. package/behaviors/registry/agent/atoms/std-agent-session.orb +4752 -348
  56. package/behaviors/registry/agent/atoms/std-agent-tool-loop.orb +54 -42
  57. package/behaviors/registry/agent/organisms/std-agent-assistant.orb +356 -122
  58. package/behaviors/registry/factory-signatures.json +1 -1
  59. package/behaviors/registry/infra/atoms/std-page-open.orb +435 -0
  60. package/behaviors/registry/infra/atoms/std-page-search.orb +867 -0
  61. package/behaviors/registry/infra/atoms/std-page-treat.orb +494 -0
  62. package/behaviors/registry/infra/atoms/std-page-watch.orb +554 -0
  63. package/behaviors/registry/ui/core/atoms/std-board.orb +110 -14
  64. package/behaviors/registry/ui/core/atoms/std-book-reader.orb +119 -0
  65. package/behaviors/registry/ui/core/atoms/std-card-grid.orb +61 -0
  66. package/behaviors/registry/ui/core/atoms/std-doc-page.orb +594 -0
  67. package/behaviors/registry/ui/core/atoms/std-site-header.orb +40 -2
  68. package/behaviors/registry/ui/core/atoms/std-tag-strip.orb +2 -0
  69. package/behaviors/registry/ui/core/atoms/std-tag-taxonomy.orb +24 -0
  70. package/behaviors/registry/ui/core/atoms/std-version-history.orb +8 -1
  71. package/behaviors/registry/ui/core/molecules/ui-header.orb +7 -0
  72. package/behaviors/registry/ui/learning/atoms/std-learn-arrangement.orb +903 -369
  73. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1002 -232
  74. package/behaviors/registry/ui/learning/atoms/std-learn-compare.orb +253 -74
  75. package/behaviors/registry/ui/learning/atoms/std-learn-coupled-sim.orb +3624 -921
  76. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +685 -144
  77. package/behaviors/registry/ui/learning/atoms/std-learn-diagram.orb +404 -52
  78. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +1787 -210
  79. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +699 -71
  80. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +1102 -578
  81. package/behaviors/registry/ui/learning/atoms/std-learn-grid-sim.orb +2760 -872
  82. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +689 -152
  83. package/behaviors/registry/ui/learning/atoms/std-learn-live-sim.orb +1431 -633
  84. package/behaviors/registry/ui/learning/atoms/std-learn-measure.orb +2485 -512
  85. package/behaviors/registry/ui/learning/atoms/std-learn-param-explorer.orb +501 -197
  86. package/behaviors/registry/ui/learning/atoms/std-learn-perturb.orb +3642 -928
  87. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +705 -125
  88. package/behaviors/registry/ui/learning/atoms/std-learn-sample-accumulator.orb +624 -114
  89. package/behaviors/registry/ui/learning/atoms/std-learn-sandbox.orb +405 -153
  90. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +1911 -611
  91. package/behaviors/registry/ui/learning/atoms/std-learn-scenario.orb +436 -128
  92. package/behaviors/registry/ui/learning/atoms/std-learn-sim-3d.orb +2482 -485
  93. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +896 -192
  94. package/behaviors/registry/ui/learning/atoms/std-learn-stepper.orb +2001 -759
  95. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +734 -228
  96. package/behaviors/registry/ui/learning/atoms/std-learn-trace-player.orb +2501 -764
  97. package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +1591 -173
  98. package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +883 -122
  99. package/behaviors/registry/ui/learning/atoms/ui-algo-graph-canvas.orb +176 -0
  100. package/behaviors/registry/ui/learning/atoms/ui-algorithm-canvas.orb +211 -0
  101. package/behaviors/registry/ui/learning/atoms/ui-biology-canvas.orb +211 -0
  102. package/behaviors/registry/ui/learning/atoms/ui-chemistry-canvas.orb +212 -1
  103. package/behaviors/registry/ui/learning/atoms/ui-learning-canvas.orb +212 -1
  104. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +203 -9
  105. package/behaviors/registry/ui/learning/atoms/ui-physics-canvas.orb +211 -0
  106. package/behaviors/signatures.lolo +325 -655
  107. package/canonical-operators.json +35 -1
  108. package/dist/behaviors/exports-reader.js +41980 -35508
  109. package/dist/behaviors/functions/index.d.ts +1063 -140
  110. package/dist/behaviors/functions/index.js +41951 -35506
  111. package/dist/behaviors/index.d.ts +1 -1
  112. package/dist/behaviors/index.js +41981 -35509
  113. package/dist/behaviors/query.js +41980 -35508
  114. package/dist/behaviors-embeddings.hash.json +3 -3
  115. package/dist/behaviors-embeddings.json +47 -27
  116. package/dist/behaviors-registry.json +4814 -553
  117. package/dist/index.d.ts +4 -4
  118. package/dist/index.js +42019 -35509
  119. package/dist/knob-embeddings.hash.json +3 -3
  120. package/dist/knob-embeddings.json +623 -263
  121. package/dist/modules/anim.d.ts +1 -1
  122. package/dist/modules/array.d.ts +1 -1
  123. package/dist/modules/async.d.ts +1 -1
  124. package/dist/modules/behavior.d.ts +1 -1
  125. package/dist/modules/browser.d.ts +1 -1
  126. package/dist/modules/composition.d.ts +1 -1
  127. package/dist/modules/contract.d.ts +1 -1
  128. package/dist/modules/core.d.ts +1 -1
  129. package/dist/modules/core.js +2 -0
  130. package/dist/modules/data.d.ts +1 -1
  131. package/dist/modules/ease.d.ts +1 -1
  132. package/dist/modules/format.d.ts +1 -1
  133. package/dist/modules/geo.d.ts +1 -1
  134. package/dist/modules/graph.d.ts +1 -1
  135. package/dist/modules/grid.d.ts +1 -1
  136. package/dist/modules/i18n.d.ts +17 -0
  137. package/dist/modules/i18n.js +22 -0
  138. package/dist/modules/index.d.ts +2 -1
  139. package/dist/modules/index.js +24 -1
  140. package/dist/modules/integration.d.ts +1 -1
  141. package/dist/modules/json.d.ts +1 -1
  142. package/dist/modules/llm.d.ts +1 -1
  143. package/dist/modules/math.d.ts +1 -1
  144. package/dist/modules/memory.d.ts +1 -1
  145. package/dist/modules/nn.d.ts +1 -1
  146. package/dist/modules/noise.d.ts +1 -1
  147. package/dist/modules/object.d.ts +1 -1
  148. package/dist/modules/os.d.ts +1 -1
  149. package/dist/modules/path.d.ts +1 -1
  150. package/dist/modules/prob.d.ts +1 -1
  151. package/dist/modules/session.d.ts +1 -1
  152. package/dist/modules/str.d.ts +1 -1
  153. package/dist/modules/tensor.d.ts +1 -1
  154. package/dist/modules/time.d.ts +1 -1
  155. package/dist/modules/trace.d.ts +1 -1
  156. package/dist/modules/train.d.ts +1 -1
  157. package/dist/modules/validate.d.ts +1 -1
  158. package/dist/modules/vector.d.ts +1 -1
  159. package/dist/modules/workspace.d.ts +1 -1
  160. package/dist/registry/factory-signatures.json +1 -1
  161. package/dist/registry.d.ts +10 -3
  162. package/dist/registry.js +31 -1
  163. package/dist/{types-DLx38UFZ.d.ts → types-Cdb321uM.d.ts} +9 -3
  164. package/i18n/ar.json +1 -0
  165. package/i18n/en.json +1 -0
  166. package/i18n/sl.json +1 -0
  167. package/package.json +3 -3
@@ -19,6 +19,8 @@ orbital LearnTracePlayerOrbital {
19
19
  title : string
20
20
  frameIndex : number = 0
21
21
  running : boolean = false
22
+ speed : number = 1
23
+ elapsedMs : number = 0
22
24
  bars : [TraceBar]
23
25
  frameLabel : string
24
26
  frameCaption : string
@@ -44,22 +46,38 @@ orbital LearnTracePlayerOrbital {
44
46
  padding: md
45
47
  children: [
46
48
  { type: stack, direction: vertical, gap: md, children: [
47
- { type: typography, variant: h4, content: @entity.title },
48
- { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false, label: (str/concat "Frame " (str/concat "" (+ @entity.frameIndex 1)) " of " (str/concat "" (array/len @config.frames))) },
49
- { type: algorithm-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
50
- { type: stack, direction: horizontal, gap: sm, children: [
51
- { type: button, action: PLAY, label: "Play", variant: primary, disabled: (if @entity.running true false) },
52
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
53
- { type: button, action: RESTART, label: "Restart", variant: ghost }
49
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
50
+ { type: stack, direction: vertical, gap: xs, children: [
51
+ { type: typography, variant: h4, content: @entity.title },
52
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
53
+ ] },
54
+ { type: badge, label: @entity.statusNote, variant: (if (== @entity.statusNote "Finished") "success" (if @entity.running "primary" "neutral")) }
54
55
  ] },
56
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
57
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (== @entity.statusNote "Finished") }),
58
+ { type: button, action: RESTART, label: "Restart", icon: "rotate-ccw", variant: ghost },
59
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
60
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
61
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
62
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
63
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
64
+ ] },
65
+ { type: typography, variant: caption, color: muted, content: (str/concat "Frame " (+ @entity.frameIndex 1) " of " (array/len @config.frames)) }
66
+ ] },
67
+ { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false },
68
+ { type: algorithm-canvas, keyMap: { Space: TOGGLE_RUN, KeyR: RESTART }, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
55
69
  { type: card, variant: bordered, padding: md, title: @entity.frameLabel, children: [
56
70
  { type: typography, variant: body, content: @entity.frameCaption }
57
- ] },
58
- { type: typography, variant: caption, content: (str/concat @entity.statusNote " · " (str/concat "" @config.tickMs) " ms/frame" (if @config.loop " · loops" "")) }
71
+ ] }
59
72
  ] }
60
73
  ]
61
74
  })
62
75
 
76
+ MODE_SELECTED -> idle when (and (not (== @config.matchMode "")) (not (== ?mode @config.matchMode)))
77
+ (set @entity.active false)
78
+ (set @entity.running false)
79
+ (render-ui main null)
80
+
63
81
  MODE_SELECTED -> idle when (== ?mode @config.matchMode)
64
82
  (set @entity.active true)
65
83
  (set @entity.title @config.title)
@@ -75,135 +93,264 @@ orbital LearnTracePlayerOrbital {
75
93
  padding: md
76
94
  children: [
77
95
  { type: stack, direction: vertical, gap: md, children: [
78
- { type: typography, variant: h4, content: @entity.title },
79
- { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false, label: (str/concat "Frame " (str/concat "" (+ @entity.frameIndex 1)) " of " (str/concat "" (array/len @config.frames))) },
80
- { type: algorithm-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
81
- { type: stack, direction: horizontal, gap: sm, children: [
82
- { type: button, action: PLAY, label: "Play", variant: primary, disabled: (if @entity.running true false) },
83
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
84
- { type: button, action: RESTART, label: "Restart", variant: ghost }
96
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
97
+ { type: stack, direction: vertical, gap: xs, children: [
98
+ { type: typography, variant: h4, content: @entity.title },
99
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
100
+ ] },
101
+ { type: badge, label: @entity.statusNote, variant: (if (== @entity.statusNote "Finished") "success" (if @entity.running "primary" "neutral")) }
85
102
  ] },
103
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
104
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (== @entity.statusNote "Finished") }),
105
+ { type: button, action: RESTART, label: "Restart", icon: "rotate-ccw", variant: ghost },
106
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
107
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
108
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
109
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
110
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
111
+ ] },
112
+ { type: typography, variant: caption, color: muted, content: (str/concat "Frame " (+ @entity.frameIndex 1) " of " (array/len @config.frames)) }
113
+ ] },
114
+ { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false },
115
+ { type: algorithm-canvas, keyMap: { Space: TOGGLE_RUN, KeyR: RESTART }, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
86
116
  { type: card, variant: bordered, padding: md, title: @entity.frameLabel, children: [
87
117
  { type: typography, variant: body, content: @entity.frameCaption }
88
- ] },
89
- { type: typography, variant: caption, content: (str/concat @entity.statusNote " · " (str/concat "" @config.tickMs) " ms/frame" (if @config.loop " · loops" "")) }
118
+ ] }
90
119
  ] }
91
120
  ]
92
121
  })
93
122
 
94
- PLAY -> idle when (== @entity.active true)
95
- (set @entity.running true)
96
- (set @entity.statusNote "Playing")
123
+ TOGGLE_RUN -> idle when (== @entity.active true)
124
+ (set @entity.running (not @entity.running))
125
+ (set @entity.statusNote (if @entity.running "Playing" "Paused"))
97
126
  (render-ui main {
98
127
  type: box
99
128
  padding: md
100
129
  children: [
101
130
  { type: stack, direction: vertical, gap: md, children: [
102
- { type: typography, variant: h4, content: @entity.title },
103
- { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false, label: (str/concat "Frame " (str/concat "" (+ @entity.frameIndex 1)) " of " (str/concat "" (array/len @config.frames))) },
104
- { type: algorithm-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
105
- { type: stack, direction: horizontal, gap: sm, children: [
106
- { type: button, action: PLAY, label: "Play", variant: primary, disabled: (if @entity.running true false) },
107
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
108
- { type: button, action: RESTART, label: "Restart", variant: ghost }
131
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
132
+ { type: stack, direction: vertical, gap: xs, children: [
133
+ { type: typography, variant: h4, content: @entity.title },
134
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
135
+ ] },
136
+ { type: badge, label: @entity.statusNote, variant: (if (== @entity.statusNote "Finished") "success" (if @entity.running "primary" "neutral")) }
137
+ ] },
138
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
139
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (== @entity.statusNote "Finished") }),
140
+ { type: button, action: RESTART, label: "Restart", icon: "rotate-ccw", variant: ghost },
141
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
142
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
143
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
144
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
145
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
146
+ ] },
147
+ { type: typography, variant: caption, color: muted, content: (str/concat "Frame " (+ @entity.frameIndex 1) " of " (array/len @config.frames)) }
109
148
  ] },
149
+ { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false },
150
+ { type: algorithm-canvas, keyMap: { Space: TOGGLE_RUN, KeyR: RESTART }, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
110
151
  { type: card, variant: bordered, padding: md, title: @entity.frameLabel, children: [
111
152
  { type: typography, variant: body, content: @entity.frameCaption }
112
- ] },
113
- { type: typography, variant: caption, content: (str/concat @entity.statusNote " · " (str/concat "" @config.tickMs) " ms/frame" (if @config.loop " · loops" "")) }
153
+ ] }
114
154
  ] }
115
155
  ]
116
156
  })
117
- (emit FRAME_ADVANCED { frameIndex: @entity.frameIndex, frameLabel: @entity.frameLabel })
118
157
 
119
- PAUSE -> idle when (== @entity.active true)
120
- (set @entity.running false)
121
- (set @entity.statusNote "Paused")
158
+ SPEED_SET -> idle when (== @entity.active true)
159
+ (set @entity.speed ?speed)
122
160
  (render-ui main {
123
161
  type: box
124
162
  padding: md
125
163
  children: [
126
164
  { type: stack, direction: vertical, gap: md, children: [
127
- { type: typography, variant: h4, content: @entity.title },
128
- { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false, label: (str/concat "Frame " (str/concat "" (+ @entity.frameIndex 1)) " of " (str/concat "" (array/len @config.frames))) },
129
- { type: algorithm-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
130
- { type: stack, direction: horizontal, gap: sm, children: [
131
- { type: button, action: PLAY, label: "Play", variant: primary, disabled: (if @entity.running true false) },
132
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
133
- { type: button, action: RESTART, label: "Restart", variant: ghost }
165
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
166
+ { type: stack, direction: vertical, gap: xs, children: [
167
+ { type: typography, variant: h4, content: @entity.title },
168
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
169
+ ] },
170
+ { type: badge, label: @entity.statusNote, variant: (if (== @entity.statusNote "Finished") "success" (if @entity.running "primary" "neutral")) }
134
171
  ] },
172
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
173
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (== @entity.statusNote "Finished") }),
174
+ { type: button, action: RESTART, label: "Restart", icon: "rotate-ccw", variant: ghost },
175
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
176
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
177
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
178
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
179
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
180
+ ] },
181
+ { type: typography, variant: caption, color: muted, content: (str/concat "Frame " (+ @entity.frameIndex 1) " of " (array/len @config.frames)) }
182
+ ] },
183
+ { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false },
184
+ { type: algorithm-canvas, keyMap: { Space: TOGGLE_RUN, KeyR: RESTART }, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
135
185
  { type: card, variant: bordered, padding: md, title: @entity.frameLabel, children: [
136
186
  { type: typography, variant: body, content: @entity.frameCaption }
187
+ ] }
188
+ ] }
189
+ ]
190
+ })
191
+
192
+ PLAY -> idle when (== @entity.active true)
193
+ (set @entity.running true)
194
+ (set @entity.statusNote "Playing")
195
+ (render-ui main {
196
+ type: box
197
+ padding: md
198
+ children: [
199
+ { type: stack, direction: vertical, gap: md, children: [
200
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
201
+ { type: stack, direction: vertical, gap: xs, children: [
202
+ { type: typography, variant: h4, content: @entity.title },
203
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
204
+ ] },
205
+ { type: badge, label: @entity.statusNote, variant: (if (== @entity.statusNote "Finished") "success" (if @entity.running "primary" "neutral")) }
206
+ ] },
207
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
208
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (== @entity.statusNote "Finished") }),
209
+ { type: button, action: RESTART, label: "Restart", icon: "rotate-ccw", variant: ghost },
210
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
211
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
212
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
213
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
214
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
215
+ ] },
216
+ { type: typography, variant: caption, color: muted, content: (str/concat "Frame " (+ @entity.frameIndex 1) " of " (array/len @config.frames)) }
137
217
  ] },
138
- { type: typography, variant: caption, content: (str/concat @entity.statusNote " · " (str/concat "" @config.tickMs) " ms/frame" (if @config.loop " · loops" "")) }
218
+ { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false },
219
+ { type: algorithm-canvas, keyMap: { Space: TOGGLE_RUN, KeyR: RESTART }, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
220
+ { type: card, variant: bordered, padding: md, title: @entity.frameLabel, children: [
221
+ { type: typography, variant: body, content: @entity.frameCaption }
222
+ ] }
139
223
  ] }
140
224
  ]
141
225
  })
142
226
  (emit FRAME_ADVANCED { frameIndex: @entity.frameIndex, frameLabel: @entity.frameLabel })
143
227
 
144
- RESTART -> idle when (== @entity.active true)
145
- (set @entity.frameIndex 0)
228
+ PAUSE -> idle when (== @entity.active true)
146
229
  (set @entity.running false)
147
230
  (set @entity.statusNote "Paused")
148
- (set @entity.frameLabel (object/get (array/nth @config.frames @entity.frameIndex) label))
149
- (set @entity.frameCaption (object/get (array/nth @config.frames @entity.frameIndex) caption))
150
- (set @entity.bars (array/map (object/get (array/nth @config.frames @entity.frameIndex) values)
151
- { value: @item, color: (if (array/includes (object/get (array/nth @config.frames @entity.frameIndex) highlights) @index) @config.highlightColor @config.baseColor), label: (str/concat "" @item) }))
152
231
  (render-ui main {
153
232
  type: box
154
233
  padding: md
155
234
  children: [
156
235
  { type: stack, direction: vertical, gap: md, children: [
157
- { type: typography, variant: h4, content: @entity.title },
158
- { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false, label: (str/concat "Frame " (str/concat "" (+ @entity.frameIndex 1)) " of " (str/concat "" (array/len @config.frames))) },
159
- { type: algorithm-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
160
- { type: stack, direction: horizontal, gap: sm, children: [
161
- { type: button, action: PLAY, label: "Play", variant: primary, disabled: (if @entity.running true false) },
162
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
163
- { type: button, action: RESTART, label: "Restart", variant: ghost }
236
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
237
+ { type: stack, direction: vertical, gap: xs, children: [
238
+ { type: typography, variant: h4, content: @entity.title },
239
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
240
+ ] },
241
+ { type: badge, label: @entity.statusNote, variant: (if (== @entity.statusNote "Finished") "success" (if @entity.running "primary" "neutral")) }
242
+ ] },
243
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
244
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (== @entity.statusNote "Finished") }),
245
+ { type: button, action: RESTART, label: "Restart", icon: "rotate-ccw", variant: ghost },
246
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
247
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
248
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
249
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
250
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
251
+ ] },
252
+ { type: typography, variant: caption, color: muted, content: (str/concat "Frame " (+ @entity.frameIndex 1) " of " (array/len @config.frames)) }
164
253
  ] },
254
+ { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false },
255
+ { type: algorithm-canvas, keyMap: { Space: TOGGLE_RUN, KeyR: RESTART }, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
165
256
  { type: card, variant: bordered, padding: md, title: @entity.frameLabel, children: [
166
257
  { type: typography, variant: body, content: @entity.frameCaption }
167
- ] },
168
- { type: typography, variant: caption, content: (str/concat @entity.statusNote " · " (str/concat "" @config.tickMs) " ms/frame" (if @config.loop " · loops" "")) }
258
+ ] }
169
259
  ] }
170
260
  ]
171
261
  })
172
262
  (emit FRAME_ADVANCED { frameIndex: @entity.frameIndex, frameLabel: @entity.frameLabel })
173
- }
174
263
 
175
- ticks {
176
- framePlayerTick every 500ms
177
- when @entity.running
178
- (set @entity.running (if (and (>= @entity.frameIndex (- (array/len @config.frames) 1)) (not @config.loop)) false @entity.running))
179
- (set @entity.frameIndex (if (>= @entity.frameIndex (- (array/len @config.frames) 1)) (if @config.loop 0 @entity.frameIndex) (+ @entity.frameIndex 1)))
264
+ RESTART -> idle when (== @entity.active true)
265
+ (set @entity.frameIndex 0)
266
+ (set @entity.running false)
267
+ (set @entity.statusNote "Paused")
180
268
  (set @entity.frameLabel (object/get (array/nth @config.frames @entity.frameIndex) label))
181
269
  (set @entity.frameCaption (object/get (array/nth @config.frames @entity.frameIndex) caption))
182
270
  (set @entity.bars (array/map (object/get (array/nth @config.frames @entity.frameIndex) values)
183
271
  { value: @item, color: (if (array/includes (object/get (array/nth @config.frames @entity.frameIndex) highlights) @index) @config.highlightColor @config.baseColor), label: (str/concat "" @item) }))
184
- (set @entity.statusNote (if @entity.running "Playing" (if (>= (+ @entity.frameIndex 1) (array/len @config.frames)) "Finished" "Paused")))
185
272
  (render-ui main {
186
273
  type: box
187
274
  padding: md
188
275
  children: [
189
276
  { type: stack, direction: vertical, gap: md, children: [
190
- { type: typography, variant: h4, content: @entity.title },
191
- { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false, label: (str/concat "Frame " (str/concat "" (+ @entity.frameIndex 1)) " of " (str/concat "" (array/len @config.frames))) },
192
- { type: algorithm-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
193
- { type: stack, direction: horizontal, gap: sm, children: [
194
- { type: button, action: PLAY, label: "Play", variant: primary, disabled: (if @entity.running true false) },
195
- { type: button, action: PAUSE, label: "Pause", variant: secondary, disabled: (if @entity.running false true) },
196
- { type: button, action: RESTART, label: "Restart", variant: ghost }
277
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
278
+ { type: stack, direction: vertical, gap: xs, children: [
279
+ { type: typography, variant: h4, content: @entity.title },
280
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
281
+ ] },
282
+ { type: badge, label: @entity.statusNote, variant: (if (== @entity.statusNote "Finished") "success" (if @entity.running "primary" "neutral")) }
283
+ ] },
284
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
285
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (== @entity.statusNote "Finished") }),
286
+ { type: button, action: RESTART, label: "Restart", icon: "rotate-ccw", variant: ghost },
287
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
288
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
289
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
290
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
291
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
292
+ ] },
293
+ { type: typography, variant: caption, color: muted, content: (str/concat "Frame " (+ @entity.frameIndex 1) " of " (array/len @config.frames)) }
197
294
  ] },
295
+ { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false },
296
+ { type: algorithm-canvas, keyMap: { Space: TOGGLE_RUN, KeyR: RESTART }, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
198
297
  { type: card, variant: bordered, padding: md, title: @entity.frameLabel, children: [
199
298
  { type: typography, variant: body, content: @entity.frameCaption }
200
- ] },
201
- { type: typography, variant: caption, content: (str/concat @entity.statusNote " · " (str/concat "" @config.tickMs) " ms/frame" (if @config.loop " · loops" "")) }
299
+ ] }
202
300
  ] }
203
301
  ]
204
302
  })
205
303
  (emit FRAME_ADVANCED { frameIndex: @entity.frameIndex, frameLabel: @entity.frameLabel })
206
- (if (and (not @config.loop) (not @entity.running) (>= (+ @entity.frameIndex 1) (array/len @config.frames))) (emit TRACE_DONE { totalFrames: (array/len @config.frames) }) null)
304
+ }
305
+
306
+ ticks {
307
+ framePlayerTick every 50ms
308
+ when @entity.running
309
+ (set @entity.elapsedMs (+ @entity.elapsedMs 50))
310
+ (when (>= (* @entity.elapsedMs @entity.speed) @config.tickMs)
311
+ (do
312
+ (set @entity.elapsedMs 0)
313
+ (set @entity.running (if (and (>= @entity.frameIndex (- (array/len @config.frames) 1)) (not @config.loop)) false @entity.running))
314
+ (set @entity.frameIndex (if (>= @entity.frameIndex (- (array/len @config.frames) 1)) (if @config.loop 0 @entity.frameIndex) (+ @entity.frameIndex 1)))
315
+ (set @entity.frameLabel (object/get (array/nth @config.frames @entity.frameIndex) label))
316
+ (set @entity.frameCaption (object/get (array/nth @config.frames @entity.frameIndex) caption))
317
+ (set @entity.bars (array/map (object/get (array/nth @config.frames @entity.frameIndex) values)
318
+ { value: @item, color: (if (array/includes (object/get (array/nth @config.frames @entity.frameIndex) highlights) @index) @config.highlightColor @config.baseColor), label: (str/concat "" @item) }))
319
+ (set @entity.statusNote (if @entity.running "Playing" (if (>= (+ @entity.frameIndex 1) (array/len @config.frames)) "Finished" "Paused")))
320
+ (render-ui main {
321
+ type: box
322
+ padding: md
323
+ children: [
324
+ { type: stack, direction: vertical, gap: md, children: [
325
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
326
+ { type: stack, direction: vertical, gap: xs, children: [
327
+ { type: typography, variant: h4, content: @entity.title },
328
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
329
+ ] },
330
+ { type: badge, label: @entity.statusNote, variant: (if (== @entity.statusNote "Finished") "success" (if @entity.running "primary" "neutral")) }
331
+ ] },
332
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
333
+ (if @entity.running { type: button, action: TOGGLE_RUN, label: "Pause", icon: "pause", variant: primary, aria-pressed: "true" } { type: button, action: TOGGLE_RUN, label: "Play", icon: "play", variant: primary, aria-pressed: "false", disabled: (== @entity.statusNote "Finished") }),
334
+ { type: button, action: RESTART, label: "Restart", icon: "rotate-ccw", variant: ghost },
335
+ { type: stack, direction: horizontal, align: center, gap: xs, role: "group", aria-label: "Speed", children: [
336
+ { type: button, action: SPEED_SET, actionPayload: { speed: 0.5 }, label: "0.5×", size: sm, variant: (if (== @entity.speed 0.5) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 0.5) "true" "false") },
337
+ { type: button, action: SPEED_SET, actionPayload: { speed: 1 }, label: "1×", size: sm, variant: (if (== @entity.speed 1) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 1) "true" "false") },
338
+ { type: button, action: SPEED_SET, actionPayload: { speed: 2 }, label: "2×", size: sm, variant: (if (== @entity.speed 2) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 2) "true" "false") },
339
+ { type: button, action: SPEED_SET, actionPayload: { speed: 4 }, label: "4×", size: sm, variant: (if (== @entity.speed 4) "secondary" "ghost"), aria-pressed: (if (== @entity.speed 4) "true" "false") }
340
+ ] },
341
+ { type: typography, variant: caption, color: muted, content: (str/concat "Frame " (+ @entity.frameIndex 1) " of " (array/len @config.frames)) }
342
+ ] },
343
+ { type: progress-bar, value: (+ @entity.frameIndex 1), max: (array/len @config.frames), showPercentage: false },
344
+ { type: algorithm-canvas, keyMap: { Space: TOGGLE_RUN, KeyR: RESTART }, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, bars: @entity.bars },
345
+ { type: card, variant: bordered, padding: md, title: @entity.frameLabel, children: [
346
+ { type: typography, variant: body, content: @entity.frameCaption }
347
+ ] }
348
+ ] }
349
+ ]
350
+ })
351
+ (emit FRAME_ADVANCED { frameIndex: @entity.frameIndex, frameLabel: @entity.frameLabel })
352
+ (if (and (not @config.loop) (not @entity.running) (>= (+ @entity.frameIndex 1) (array/len @config.frames))) (emit TRACE_DONE { totalFrames: (array/len @config.frames) }) null)
353
+ ))
207
354
  }
208
355
 
209
356
  listens {
@@ -212,6 +359,15 @@ orbital LearnTracePlayerOrbital {
212
359
  }
213
360
  @description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
214
361
  @tier "domain"
362
+ TOGGLE_RUN {
363
+ }
364
+ @description "Learner pressed the Play/Pause toggle — starts or freezes the frame cursor."
365
+ @tier "domain"
366
+ SPEED_SET {
367
+ speed : number
368
+ }
369
+ @description "The learner picked a playback speed; frames advance that many times faster."
370
+ @tier "presentation"
215
371
  PLAY {
216
372
  }
217
373
  @description "Learner pressed Play — the frame cursor begins advancing on the tick until the last frame is reached or Pause is pressed."
@@ -249,7 +405,11 @@ orbital LearnTracePlayerOrbital {
249
405
  @tier "presentation"
250
406
  title : string = "Trace Player"
251
407
  @label "Title"
252
- @description "Title shown above the instrument."
408
+ @description "The instrument's name, the header's one hero."
409
+ @tier "presentation"
410
+ summary : string = ""
411
+ @label "Concept"
412
+ @description "One line under the title saying what the learner is looking at (keep it under ~65 characters). Empty shows no line."
253
413
  @tier "presentation"
254
414
  width : number = 600
255
415
  @label "Width"
@@ -259,14 +419,15 @@ orbital LearnTracePlayerOrbital {
259
419
  @label "Height"
260
420
  @description "Canvas height in pixels."
261
421
  @tier "presentation"
262
- backgroundColor : string = "#f8fafc"
422
+ backgroundColor : string = ""
263
423
  @label "Background Color"
424
+ @description "Canvas ground override; empty uses the theme's drawing ground."
264
425
  @tier "presentation"
265
- baseColor : string = "#3b82f6"
426
+ baseColor : string = "series-1"
266
427
  @label "Base Bar Color"
267
428
  @description "Color of bars not highlighted in the current frame."
268
429
  @tier "presentation"
269
- highlightColor : string = "#f59e0b"
430
+ highlightColor : string = "highlight"
270
431
  @label "Highlight Bar Color"
271
432
  @description "Color of bars whose index appears in the current frame's highlights."
272
433
  @tier "presentation"