@almadar/std 16.243.0 → 16.244.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 (140) hide show
  1. package/behaviors/behaviors-registry.json +4470 -513
  2. package/behaviors/lolo/agent/atoms/std-agent-context.lolo +24 -4
  3. package/behaviors/lolo/agent/atoms/std-agent-memory.lolo +20 -5
  4. package/behaviors/lolo/agent/atoms/std-agent-proposal.lolo +550 -0
  5. package/behaviors/lolo/agent/atoms/std-agent-session.lolo +284 -25
  6. package/behaviors/lolo/agent/atoms/std-agent-suggestions.lolo +841 -0
  7. package/behaviors/lolo/agent/atoms/std-agent-tool-loop.lolo +58 -8
  8. package/behaviors/lolo/agent/organisms/std-agent-assistant.lolo +1 -0
  9. package/behaviors/lolo/infra/atoms/std-page-treat.lolo +28 -1
  10. package/behaviors/lolo/ui/avl/atoms/ui-avl-state-machine.lolo +22 -0
  11. package/behaviors/lolo/ui/avl/atoms/ui-avl-transition-explainer.lolo +175 -0
  12. package/behaviors/lolo/ui/avl/atoms/ui-module-card.lolo +22 -0
  13. package/behaviors/lolo/ui/core/atoms/std-app-layout.lolo +23 -4
  14. package/behaviors/lolo/ui/core/atoms/std-hero.lolo +72 -33
  15. package/behaviors/lolo/ui/core/molecules/ui-agent-chat-feed.lolo +1 -1
  16. package/behaviors/lolo/ui/core/molecules/ui-markdown-content.lolo +54 -0
  17. package/behaviors/lolo/ui/core/molecules/ui-upload-drop-zone.lolo +5 -0
  18. package/behaviors/lolo/ui/core/organisms/ui-segment-renderer.lolo +127 -3
  19. package/behaviors/lolo/ui/core/organisms/ui-state-machine-view.lolo +22 -0
  20. package/behaviors/lolo/ui/core/templates/ui-dashboard-layout.lolo +2 -2
  21. package/behaviors/lolo/ui/learning/atoms/std-learn-arrangement.lolo +87 -17
  22. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +172 -37
  23. package/behaviors/lolo/ui/learning/atoms/std-learn-coupled-sim.lolo +183 -70
  24. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +78 -13
  25. package/behaviors/lolo/ui/learning/atoms/std-learn-diagram.lolo +58 -2
  26. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +107 -12
  27. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +83 -10
  28. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +130 -9
  29. package/behaviors/lolo/ui/learning/atoms/std-learn-grid-sim.lolo +143 -59
  30. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +96 -22
  31. package/behaviors/lolo/ui/learning/atoms/std-learn-live-sim.lolo +66 -5
  32. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +84 -6
  33. package/behaviors/lolo/ui/learning/atoms/std-learn-sandbox.lolo +80 -14
  34. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +99 -5
  35. package/behaviors/lolo/ui/learning/atoms/std-learn-scenario.lolo +81 -7
  36. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +83 -7
  37. package/behaviors/lolo/ui/learning/atoms/std-learn-stepper.lolo +224 -99
  38. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +63 -2
  39. package/behaviors/lolo/ui/learning/atoms/std-learn-trace-player.lolo +99 -38
  40. package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +7 -7
  41. package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +69 -4
  42. package/behaviors/lolo-behavior-contracts.json +1 -1
  43. package/behaviors/registry/agent/atoms/std-agent-context.orb +205 -6
  44. package/behaviors/registry/agent/atoms/std-agent-memory.orb +146 -4
  45. package/behaviors/registry/agent/atoms/std-agent-proposal.orb +1972 -0
  46. package/behaviors/registry/agent/atoms/std-agent-session.orb +2021 -597
  47. package/behaviors/registry/agent/atoms/std-agent-suggestions.orb +5079 -0
  48. package/behaviors/registry/agent/atoms/std-agent-tool-loop.orb +287 -5
  49. package/behaviors/registry/agent/organisms/std-agent-assistant.orb +7 -0
  50. package/behaviors/registry/factory-signatures.json +1 -1
  51. package/behaviors/registry/infra/atoms/std-page-treat.orb +327 -2
  52. package/behaviors/registry/ui/avl/atoms/ui-avl-state-machine.orb +134 -0
  53. package/behaviors/registry/ui/avl/atoms/ui-avl-transition-explainer.orb +678 -0
  54. package/behaviors/registry/ui/avl/atoms/ui-module-card.orb +122 -0
  55. package/behaviors/registry/ui/core/atoms/std-app-layout.orb +73 -4
  56. package/behaviors/registry/ui/core/atoms/std-hero.orb +174 -97
  57. package/behaviors/registry/ui/core/molecules/ui-markdown-content.orb +204 -1
  58. package/behaviors/registry/ui/core/molecules/ui-upload-drop-zone.orb +12 -0
  59. package/behaviors/registry/ui/core/organisms/ui-segment-renderer.orb +683 -30
  60. package/behaviors/registry/ui/learning/atoms/std-learn-arrangement.orb +748 -21
  61. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1429 -41
  62. package/behaviors/registry/ui/learning/atoms/std-learn-coupled-sim.orb +1031 -185
  63. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +807 -132
  64. package/behaviors/registry/ui/learning/atoms/std-learn-diagram.orb +553 -6
  65. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +987 -21
  66. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +582 -16
  67. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +954 -13
  68. package/behaviors/registry/ui/learning/atoms/std-learn-grid-sim.orb +828 -88
  69. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +730 -28
  70. package/behaviors/registry/ui/learning/atoms/std-learn-live-sim.orb +479 -9
  71. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +797 -46
  72. package/behaviors/registry/ui/learning/atoms/std-learn-sandbox.orb +757 -58
  73. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +1176 -164
  74. package/behaviors/registry/ui/learning/atoms/std-learn-scenario.orb +599 -12
  75. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +832 -150
  76. package/behaviors/registry/ui/learning/atoms/std-learn-stepper.orb +1561 -127
  77. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +655 -6
  78. package/behaviors/registry/ui/learning/atoms/std-learn-trace-player.orb +634 -43
  79. package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +85 -31
  80. package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +425 -9
  81. package/canonical-operators.json +45 -2
  82. package/dist/behaviors/exports-reader.js +16667 -7868
  83. package/dist/behaviors/functions/index.d.ts +711 -32
  84. package/dist/behaviors/functions/index.js +16651 -7869
  85. package/dist/behaviors/index.d.ts +1 -1
  86. package/dist/behaviors/index.js +16668 -7869
  87. package/dist/behaviors/query.js +16667 -7868
  88. package/dist/behaviors-embeddings.hash.json +3 -3
  89. package/dist/behaviors-embeddings.json +77 -53
  90. package/dist/behaviors-registry.json +4470 -513
  91. package/dist/index.d.ts +3 -3
  92. package/dist/index.js +16101 -7275
  93. package/dist/knob-embeddings.hash.json +3 -3
  94. package/dist/knob-embeddings.json +1309 -685
  95. package/dist/modules/anim.d.ts +1 -1
  96. package/dist/modules/array.d.ts +1 -1
  97. package/dist/modules/async.d.ts +1 -1
  98. package/dist/modules/behavior.d.ts +1 -1
  99. package/dist/modules/browser.d.ts +1 -1
  100. package/dist/modules/composition.d.ts +1 -1
  101. package/dist/modules/contract.d.ts +1 -1
  102. package/dist/modules/core.d.ts +1 -1
  103. package/dist/modules/core.js +29 -2
  104. package/dist/modules/data.d.ts +1 -1
  105. package/dist/modules/ease.d.ts +1 -1
  106. package/dist/modules/format.d.ts +1 -1
  107. package/dist/modules/geo.d.ts +1 -1
  108. package/dist/modules/graph.d.ts +1 -1
  109. package/dist/modules/grid.d.ts +1 -1
  110. package/dist/modules/i18n.d.ts +1 -1
  111. package/dist/modules/index.d.ts +1 -1
  112. package/dist/modules/index.js +29 -2
  113. package/dist/modules/integration.d.ts +1 -1
  114. package/dist/modules/json.d.ts +1 -1
  115. package/dist/modules/llm.d.ts +1 -1
  116. package/dist/modules/math.d.ts +1 -1
  117. package/dist/modules/memory.d.ts +1 -1
  118. package/dist/modules/nn.d.ts +1 -1
  119. package/dist/modules/noise.d.ts +1 -1
  120. package/dist/modules/object.d.ts +1 -1
  121. package/dist/modules/os.d.ts +1 -1
  122. package/dist/modules/path.d.ts +1 -1
  123. package/dist/modules/prob.d.ts +1 -1
  124. package/dist/modules/session.d.ts +1 -1
  125. package/dist/modules/str.d.ts +1 -1
  126. package/dist/modules/tensor.d.ts +1 -1
  127. package/dist/modules/time.d.ts +1 -1
  128. package/dist/modules/trace.d.ts +1 -1
  129. package/dist/modules/train.d.ts +1 -1
  130. package/dist/modules/validate.d.ts +1 -1
  131. package/dist/modules/vector.d.ts +1 -1
  132. package/dist/modules/workspace.d.ts +1 -1
  133. package/dist/registry/factory-signatures.json +1 -1
  134. package/dist/registry.d.ts +2 -2
  135. package/dist/registry.js +29 -2
  136. package/dist/{types-Cdb321uM.d.ts → types-Y-yYWEOX.d.ts} +1 -1
  137. package/i18n/ar.json +3 -1
  138. package/i18n/en.json +2 -0
  139. package/i18n/sl.json +3 -1
  140. package/package.json +3 -3
@@ -62,6 +62,9 @@ orbital LearnStepperOrbital {
62
62
  id : string!
63
63
  active : boolean = false
64
64
  title : string
65
+ stages : [LearnStepperStage]
66
+ categories : [LearnStepperCategory]
67
+ categorySummaries : [LearnStepperCategorySummary]
65
68
  cursorIndex : number
66
69
  activeFilter : string
67
70
  narration : string
@@ -78,35 +81,38 @@ orbital LearnStepperOrbital {
78
81
  initial: idle
79
82
 
80
83
  state idle {
81
- INIT -> idle when (== @config.matchMode "")
84
+ INIT -> idle when (and (== @config.matchMode "") (== @config.rosterSource "config"))
82
85
  (set @entity.active true)
83
86
  (set @entity.title @config.title)
87
+ (set @entity.stages @config.stages)
88
+ (set @entity.categories @config.categories)
89
+ (set @entity.categorySummaries @config.categorySummaries)
84
90
  (set @entity.cursorIndex -1)
85
91
  (set @entity.activeFilter "all")
86
92
  (let (
87
- (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @config.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
88
- (currentStage (if (>= @entity.cursorIndex 0) (array/nth @config.stages @entity.cursorIndex) null))
89
- (totalStages (array/len @config.stages))
93
+ (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @entity.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
94
+ (currentStage (if (>= @entity.cursorIndex 0) (array/nth @entity.stages @entity.cursorIndex) null))
95
+ (totalStages (array/len @entity.stages))
90
96
  (atEnd (and (>= @entity.cursorIndex 0) (>= @entity.cursorIndex (- @totalStages 1))))
91
97
  (stageLabel (if (!= @currentStage null) (object/get @currentStage label) ""))
92
98
  (stageDescription (if (!= @currentStage null) (object/get @currentStage description) @config.startPrompt))
93
99
  (boundedDescription (if @atEnd (str/concat @stageDescription @config.atEndNote) @stageDescription))
94
100
  (narration (if (!= @currentStage null) (str/concat (str/concat "" (object/get @currentStage position)) " — " @stageLabel ". " @boundedDescription) @config.startPrompt))
95
- (tallyParts (array/map @config.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
101
+ (tallyParts (array/map @entity.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
96
102
  (categoryTally (str/join @tallyParts " · "))
97
- (tallyItems (array/map @config.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
98
- (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @config.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
103
+ (tallyItems (array/map @entity.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
104
+ (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @entity.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
99
105
  (axisWidth (- @config.width (+ @config.axisMarginLeft @config.axisMarginRight)))
100
106
  (baseShapes [
101
107
  { type: line, id: "axis", x1: @config.axisMarginLeft, y1: @config.axisY, x2: (- @config.width @config.axisMarginRight), y2: @config.axisY, tone: "axis", stroke: "normal", opacity: 1 },
102
108
  { type: text, id: "minPositionLabel", x: @config.axisMarginLeft, y: (+ @config.axisY 26), text: (str/concat "" @config.minPosition), textSize: "xs", align: "left", tone: "label" },
103
109
  { type: text, id: "maxPositionLabel", x: (- @config.width @config.axisMarginRight), y: (+ @config.axisY 26), text: (str/concat "" @config.maxPosition), textSize: "xs", align: "right", tone: "label" }
104
110
  ])
105
- (markerShapes (array/flatten (array/map @config.stages (fn s (let (
111
+ (markerShapes (array/flatten (array/map @entity.stages (fn s (let (
106
112
  (isRevealed (<= @s.position @currentPosition))
107
113
  (isCurrent (== @s.position @currentPosition))
108
114
  (matchesFilter (or (== @entity.activeFilter "all") (== @s.category @entity.activeFilter)))
109
- (catColorObj (array/first (array/filter @config.categories (fn cc (== @cc.category @s.category)))))
115
+ (catColorObj (array/first (array/filter @entity.categories (fn cc (== @cc.category @s.category)))))
110
116
  (catColor (object/get @catColorObj color))
111
117
  (col (if @isRevealed @catColor @config.unreachedColor))
112
118
  (baseOpacity (if @isRevealed 0.9 0.35))
@@ -124,14 +130,14 @@ orbital LearnStepperOrbital {
124
130
  ]
125
131
  )
126
132
  )))))
127
- (legendShapes (array/flatten (array/map @config.categories [
128
- { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
129
- { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
133
+ (legendShapes (array/flatten (array/map @entity.categories [
134
+ { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
135
+ { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
130
136
  ])))
131
137
  (allShapes (array/concat @baseShapes @markerShapes @legendShapes))
132
138
  (filterButtons (array/concat [
133
139
  { type: button, action: FILTER_CATEGORY, actionPayload: { category: "all" }, label: @config.allLabel, variant: (if (== @entity.activeFilter "all") "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter "all") "true" "false") }
134
- ] (array/map @config.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
140
+ ] (array/map @entity.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
135
141
  )
136
142
  (do
137
143
  (set @entity.narration @narration)
@@ -154,15 +160,15 @@ orbital LearnStepperOrbital {
154
160
  { type: typography, variant: h4, content: @entity.title },
155
161
  (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
156
162
  ] },
157
- { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "success" "primary")) }
163
+ { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "success" "primary")) }
158
164
  ] },
159
165
  { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
160
- { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @config.stages) 1)) },
166
+ { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) },
161
167
  { type: button, action: PREV, label: "Prev", icon: "chevron-left", variant: secondary, disabled: (<= @entity.cursorIndex -1) },
162
168
  { type: button, action: RESET_STEPPER, label: "Reset", icon: "rotate-ccw", variant: ghost },
163
- { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)) }
169
+ { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)) }
164
170
  ] },
165
- { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @config.stages), showPercentage: false },
171
+ { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @entity.stages), showPercentage: false },
166
172
  { type: stack, direction: horizontal, align: center, gap: xs, wrap: true, role: "group", aria-label: "Category lens", children: @entity.filterButtons },
167
173
  { type: math-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, xMin: 0, xMax: @config.width, yMin: 0, yMax: @config.height, showAxes: false, showGrid: false, gridStep: 1, curves: [], points: [], vectors: [], shapes: @entity.shapes, interactive: @config.interactive, animate: @config.animate, keyMap: { ArrowRight: NEXT, ArrowLeft: PREV, KeyR: RESET_STEPPER } },
168
174
  { type: card, variant: bordered, padding: md, title: (if (!= @entity.currentStageLabel "") @entity.currentStageLabel "Not Started"), children: [
@@ -178,35 +184,38 @@ orbital LearnStepperOrbital {
178
184
  (set @entity.active false)
179
185
  (render-ui main null)
180
186
 
181
- MODE_SELECTED -> idle when (== ?mode @config.matchMode)
187
+ MODE_SELECTED -> idle when (and (== ?mode @config.matchMode) (== @config.rosterSource "config"))
182
188
  (set @entity.active true)
183
189
  (set @entity.title @config.title)
190
+ (set @entity.stages @config.stages)
191
+ (set @entity.categories @config.categories)
192
+ (set @entity.categorySummaries @config.categorySummaries)
184
193
  (set @entity.cursorIndex -1)
185
194
  (set @entity.activeFilter "all")
186
195
  (let (
187
- (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @config.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
188
- (currentStage (if (>= @entity.cursorIndex 0) (array/nth @config.stages @entity.cursorIndex) null))
189
- (totalStages (array/len @config.stages))
196
+ (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @entity.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
197
+ (currentStage (if (>= @entity.cursorIndex 0) (array/nth @entity.stages @entity.cursorIndex) null))
198
+ (totalStages (array/len @entity.stages))
190
199
  (atEnd (and (>= @entity.cursorIndex 0) (>= @entity.cursorIndex (- @totalStages 1))))
191
200
  (stageLabel (if (!= @currentStage null) (object/get @currentStage label) ""))
192
201
  (stageDescription (if (!= @currentStage null) (object/get @currentStage description) @config.startPrompt))
193
202
  (boundedDescription (if @atEnd (str/concat @stageDescription @config.atEndNote) @stageDescription))
194
203
  (narration (if (!= @currentStage null) (str/concat (str/concat "" (object/get @currentStage position)) " — " @stageLabel ". " @boundedDescription) @config.startPrompt))
195
- (tallyParts (array/map @config.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
204
+ (tallyParts (array/map @entity.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
196
205
  (categoryTally (str/join @tallyParts " · "))
197
- (tallyItems (array/map @config.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
198
- (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @config.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
206
+ (tallyItems (array/map @entity.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
207
+ (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @entity.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
199
208
  (axisWidth (- @config.width (+ @config.axisMarginLeft @config.axisMarginRight)))
200
209
  (baseShapes [
201
210
  { type: line, id: "axis", x1: @config.axisMarginLeft, y1: @config.axisY, x2: (- @config.width @config.axisMarginRight), y2: @config.axisY, tone: "axis", stroke: "normal", opacity: 1 },
202
211
  { type: text, id: "minPositionLabel", x: @config.axisMarginLeft, y: (+ @config.axisY 26), text: (str/concat "" @config.minPosition), textSize: "xs", align: "left", tone: "label" },
203
212
  { type: text, id: "maxPositionLabel", x: (- @config.width @config.axisMarginRight), y: (+ @config.axisY 26), text: (str/concat "" @config.maxPosition), textSize: "xs", align: "right", tone: "label" }
204
213
  ])
205
- (markerShapes (array/flatten (array/map @config.stages (fn s (let (
214
+ (markerShapes (array/flatten (array/map @entity.stages (fn s (let (
206
215
  (isRevealed (<= @s.position @currentPosition))
207
216
  (isCurrent (== @s.position @currentPosition))
208
217
  (matchesFilter (or (== @entity.activeFilter "all") (== @s.category @entity.activeFilter)))
209
- (catColorObj (array/first (array/filter @config.categories (fn cc (== @cc.category @s.category)))))
218
+ (catColorObj (array/first (array/filter @entity.categories (fn cc (== @cc.category @s.category)))))
210
219
  (catColor (object/get @catColorObj color))
211
220
  (col (if @isRevealed @catColor @config.unreachedColor))
212
221
  (baseOpacity (if @isRevealed 0.9 0.35))
@@ -224,14 +233,14 @@ orbital LearnStepperOrbital {
224
233
  ]
225
234
  )
226
235
  )))))
227
- (legendShapes (array/flatten (array/map @config.categories [
228
- { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
229
- { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
236
+ (legendShapes (array/flatten (array/map @entity.categories [
237
+ { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
238
+ { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
230
239
  ])))
231
240
  (allShapes (array/concat @baseShapes @markerShapes @legendShapes))
232
241
  (filterButtons (array/concat [
233
242
  { type: button, action: FILTER_CATEGORY, actionPayload: { category: "all" }, label: @config.allLabel, variant: (if (== @entity.activeFilter "all") "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter "all") "true" "false") }
234
- ] (array/map @config.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
243
+ ] (array/map @entity.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
235
244
  )
236
245
  (do
237
246
  (set @entity.narration @narration)
@@ -254,15 +263,15 @@ orbital LearnStepperOrbital {
254
263
  { type: typography, variant: h4, content: @entity.title },
255
264
  (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
256
265
  ] },
257
- { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "success" "primary")) }
266
+ { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "success" "primary")) }
258
267
  ] },
259
268
  { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
260
- { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @config.stages) 1)) },
269
+ { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) },
261
270
  { type: button, action: PREV, label: "Prev", icon: "chevron-left", variant: secondary, disabled: (<= @entity.cursorIndex -1) },
262
271
  { type: button, action: RESET_STEPPER, label: "Reset", icon: "rotate-ccw", variant: ghost },
263
- { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)) }
272
+ { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)) }
264
273
  ] },
265
- { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @config.stages), showPercentage: false },
274
+ { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @entity.stages), showPercentage: false },
266
275
  { type: stack, direction: horizontal, align: center, gap: xs, wrap: true, role: "group", aria-label: "Category lens", children: @entity.filterButtons },
267
276
  { type: math-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, xMin: 0, xMax: @config.width, yMin: 0, yMax: @config.height, showAxes: false, showGrid: false, gridStep: 1, curves: [], points: [], vectors: [], shapes: @entity.shapes, interactive: @config.interactive, animate: @config.animate, keyMap: { ArrowRight: NEXT, ArrowLeft: PREV, KeyR: RESET_STEPPER } },
268
277
  { type: card, variant: bordered, padding: md, title: (if (!= @entity.currentStageLabel "") @entity.currentStageLabel "Not Started"), children: [
@@ -274,32 +283,136 @@ orbital LearnStepperOrbital {
274
283
  ]
275
284
  })
276
285
 
286
+ ROSTER_LOADED -> idle when (and (== @config.rosterSource "event") (and (== ?mode @config.matchMode) (and (> (array/len ?stages) 0) (> (array/len ?categories) 0))))
287
+ (set @entity.active true)
288
+ (set @entity.title @config.title)
289
+ (set @entity.stages ?stages)
290
+ (set @entity.categories ?categories)
291
+ (set @entity.categorySummaries ?categorySummaries)
292
+ (set @entity.cursorIndex -1)
293
+ (set @entity.activeFilter "all")
294
+ (let (
295
+ (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @entity.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
296
+ (currentStage (if (>= @entity.cursorIndex 0) (array/nth @entity.stages @entity.cursorIndex) null))
297
+ (totalStages (array/len @entity.stages))
298
+ (atEnd (and (>= @entity.cursorIndex 0) (>= @entity.cursorIndex (- @totalStages 1))))
299
+ (stageLabel (if (!= @currentStage null) (object/get @currentStage label) ""))
300
+ (stageDescription (if (!= @currentStage null) (object/get @currentStage description) @config.startPrompt))
301
+ (boundedDescription (if @atEnd (str/concat @stageDescription @config.atEndNote) @stageDescription))
302
+ (narration (if (!= @currentStage null) (str/concat (str/concat "" (object/get @currentStage position)) " — " @stageLabel ". " @boundedDescription) @config.startPrompt))
303
+ (tallyParts (array/map @entity.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
304
+ (categoryTally (str/join @tallyParts " · "))
305
+ (tallyItems (array/map @entity.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
306
+ (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @entity.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
307
+ (axisWidth (- @config.width (+ @config.axisMarginLeft @config.axisMarginRight)))
308
+ (baseShapes [
309
+ { type: line, id: "axis", x1: @config.axisMarginLeft, y1: @config.axisY, x2: (- @config.width @config.axisMarginRight), y2: @config.axisY, tone: "axis", stroke: "normal", opacity: 1 },
310
+ { type: text, id: "minPositionLabel", x: @config.axisMarginLeft, y: (+ @config.axisY 26), text: (str/concat "" @config.minPosition), textSize: "xs", align: "left", tone: "label" },
311
+ { type: text, id: "maxPositionLabel", x: (- @config.width @config.axisMarginRight), y: (+ @config.axisY 26), text: (str/concat "" @config.maxPosition), textSize: "xs", align: "right", tone: "label" }
312
+ ])
313
+ (markerShapes (array/flatten (array/map @entity.stages (fn s (let (
314
+ (isRevealed (<= @s.position @currentPosition))
315
+ (isCurrent (== @s.position @currentPosition))
316
+ (matchesFilter (or (== @entity.activeFilter "all") (== @s.category @entity.activeFilter)))
317
+ (catColorObj (array/first (array/filter @entity.categories (fn cc (== @cc.category @s.category)))))
318
+ (catColor (object/get @catColorObj color))
319
+ (col (if @isRevealed @catColor @config.unreachedColor))
320
+ (baseOpacity (if @isRevealed 0.9 0.35))
321
+ (finalOpacity (if @matchesFilter @baseOpacity (* @baseOpacity 0.25)))
322
+ (xPos (+ @config.axisMarginLeft (* (/ (- @s.position @config.minPosition) (- @config.maxPosition @config.minPosition)) @axisWidth)))
323
+ (rad (if @isCurrent @config.markerHighlightRadius @config.markerRadius))
324
+ )
325
+ (if @isCurrent
326
+ [
327
+ { type: circle, id: (str/concat "ring-" @s.position), x: @xPos, y: @config.axisY, radius: (+ @rad 5), tone: @catColor, fillStyle: "outline", stroke: "bold", opacity: 1 },
328
+ { type: circle, id: (str/concat "marker-" @s.position), x: @xPos, y: @config.axisY, radius: @rad, tone: @col, fillTone: @col, fillStyle: "solid", opacity: @finalOpacity }
329
+ ]
330
+ [
331
+ { type: circle, id: (str/concat "marker-" @s.position), x: @xPos, y: @config.axisY, radius: @rad, tone: @col, fillTone: @col, fillStyle: "solid", opacity: @finalOpacity }
332
+ ]
333
+ )
334
+ )))))
335
+ (legendShapes (array/flatten (array/map @entity.categories [
336
+ { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
337
+ { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
338
+ ])))
339
+ (allShapes (array/concat @baseShapes @markerShapes @legendShapes))
340
+ (filterButtons (array/concat [
341
+ { type: button, action: FILTER_CATEGORY, actionPayload: { category: "all" }, label: @config.allLabel, variant: (if (== @entity.activeFilter "all") "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter "all") "true" "false") }
342
+ ] (array/map @entity.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
343
+ )
344
+ (do
345
+ (set @entity.narration @narration)
346
+ (set @entity.currentStageLabel @stageLabel)
347
+ (set @entity.currentStageDescription @boundedDescription)
348
+ (set @entity.categoryTally @categoryTally)
349
+ (set @entity.tallyItems @tallyItems)
350
+ (set @entity.visibleThreadNote @visibleThreadNote)
351
+ (set @entity.shapes @allShapes)
352
+ (set @entity.filterButtons @filterButtons)
353
+ )
354
+ )
355
+ (render-ui main {
356
+ type: box
357
+ padding: md
358
+ children: [
359
+ { type: stack, direction: vertical, gap: md, children: [
360
+ { type: stack, direction: horizontal, justify: between, align: start, gap: md, children: [
361
+ { type: stack, direction: vertical, gap: xs, children: [
362
+ { type: typography, variant: h4, content: @entity.title },
363
+ (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
364
+ ] },
365
+ { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "success" "primary")) }
366
+ ] },
367
+ { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
368
+ { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) },
369
+ { type: button, action: PREV, label: "Prev", icon: "chevron-left", variant: secondary, disabled: (<= @entity.cursorIndex -1) },
370
+ { type: button, action: RESET_STEPPER, label: "Reset", icon: "rotate-ccw", variant: ghost },
371
+ { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)) }
372
+ ] },
373
+ { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @entity.stages), showPercentage: false },
374
+ { type: stack, direction: horizontal, align: center, gap: xs, wrap: true, role: "group", aria-label: "Category lens", children: @entity.filterButtons },
375
+ { type: math-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, xMin: 0, xMax: @config.width, yMin: 0, yMax: @config.height, showAxes: false, showGrid: false, gridStep: 1, curves: [], points: [], vectors: [], shapes: @entity.shapes, interactive: @config.interactive, animate: @config.animate, keyMap: { ArrowRight: NEXT, ArrowLeft: PREV, KeyR: RESET_STEPPER } },
376
+ { type: card, variant: bordered, padding: md, title: (if (!= @entity.currentStageLabel "") @entity.currentStageLabel "Not Started"), children: [
377
+ { type: typography, variant: body, content: @entity.currentStageDescription }
378
+ ] },
379
+ { type: stack, direction: horizontal, gap: xs, wrap: true, children: (array/map @entity.tallyItems (fn t { type: badge, variant: neutral, label: (str/concat (object/get @t label) ": " (str/concat "" (object/get @t count))) })) },
380
+ { type: typography, variant: caption, color: muted, content: @entity.visibleThreadNote }
381
+ ] }
382
+ ]
383
+ })
384
+
385
+ ROSTER_LOADED -> idle when (and (== @config.rosterSource "event") (not (== ?mode @config.matchMode)))
386
+ (set @entity.active false)
387
+ (render-ui main null)
388
+
389
+
277
390
  NEXT -> idle when (== @entity.active true)
278
- (set @entity.cursorIndex (math/min (+ @entity.cursorIndex 1) (- (array/len @config.stages) 1)))
391
+ (set @entity.cursorIndex (math/min (+ @entity.cursorIndex 1) (- (array/len @entity.stages) 1)))
279
392
  (let (
280
- (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @config.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
281
- (currentStage (if (>= @entity.cursorIndex 0) (array/nth @config.stages @entity.cursorIndex) null))
282
- (totalStages (array/len @config.stages))
393
+ (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @entity.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
394
+ (currentStage (if (>= @entity.cursorIndex 0) (array/nth @entity.stages @entity.cursorIndex) null))
395
+ (totalStages (array/len @entity.stages))
283
396
  (atEnd (and (>= @entity.cursorIndex 0) (>= @entity.cursorIndex (- @totalStages 1))))
284
397
  (stageLabel (if (!= @currentStage null) (object/get @currentStage label) ""))
285
398
  (stageDescription (if (!= @currentStage null) (object/get @currentStage description) @config.startPrompt))
286
399
  (boundedDescription (if @atEnd (str/concat @stageDescription @config.atEndNote) @stageDescription))
287
400
  (narration (if (!= @currentStage null) (str/concat (str/concat "" (object/get @currentStage position)) " — " @stageLabel ". " @boundedDescription) @config.startPrompt))
288
- (tallyParts (array/map @config.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
401
+ (tallyParts (array/map @entity.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
289
402
  (categoryTally (str/join @tallyParts " · "))
290
- (tallyItems (array/map @config.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
291
- (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @config.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
403
+ (tallyItems (array/map @entity.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
404
+ (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @entity.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
292
405
  (axisWidth (- @config.width (+ @config.axisMarginLeft @config.axisMarginRight)))
293
406
  (baseShapes [
294
407
  { type: line, id: "axis", x1: @config.axisMarginLeft, y1: @config.axisY, x2: (- @config.width @config.axisMarginRight), y2: @config.axisY, tone: "axis", stroke: "normal", opacity: 1 },
295
408
  { type: text, id: "minPositionLabel", x: @config.axisMarginLeft, y: (+ @config.axisY 26), text: (str/concat "" @config.minPosition), textSize: "xs", align: "left", tone: "label" },
296
409
  { type: text, id: "maxPositionLabel", x: (- @config.width @config.axisMarginRight), y: (+ @config.axisY 26), text: (str/concat "" @config.maxPosition), textSize: "xs", align: "right", tone: "label" }
297
410
  ])
298
- (markerShapes (array/flatten (array/map @config.stages (fn s (let (
411
+ (markerShapes (array/flatten (array/map @entity.stages (fn s (let (
299
412
  (isRevealed (<= @s.position @currentPosition))
300
413
  (isCurrent (== @s.position @currentPosition))
301
414
  (matchesFilter (or (== @entity.activeFilter "all") (== @s.category @entity.activeFilter)))
302
- (catColorObj (array/first (array/filter @config.categories (fn cc (== @cc.category @s.category)))))
415
+ (catColorObj (array/first (array/filter @entity.categories (fn cc (== @cc.category @s.category)))))
303
416
  (catColor (object/get @catColorObj color))
304
417
  (col (if @isRevealed @catColor @config.unreachedColor))
305
418
  (baseOpacity (if @isRevealed 0.9 0.35))
@@ -317,14 +430,14 @@ orbital LearnStepperOrbital {
317
430
  ]
318
431
  )
319
432
  )))))
320
- (legendShapes (array/flatten (array/map @config.categories [
321
- { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
322
- { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
433
+ (legendShapes (array/flatten (array/map @entity.categories [
434
+ { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
435
+ { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
323
436
  ])))
324
437
  (allShapes (array/concat @baseShapes @markerShapes @legendShapes))
325
438
  (filterButtons (array/concat [
326
439
  { type: button, action: FILTER_CATEGORY, actionPayload: { category: "all" }, label: @config.allLabel, variant: (if (== @entity.activeFilter "all") "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter "all") "true" "false") }
327
- ] (array/map @config.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
440
+ ] (array/map @entity.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
328
441
  )
329
442
  (do
330
443
  (set @entity.narration @narration)
@@ -347,15 +460,15 @@ orbital LearnStepperOrbital {
347
460
  { type: typography, variant: h4, content: @entity.title },
348
461
  (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
349
462
  ] },
350
- { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "success" "primary")) }
463
+ { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "success" "primary")) }
351
464
  ] },
352
465
  { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
353
- { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @config.stages) 1)) },
466
+ { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) },
354
467
  { type: button, action: PREV, label: "Prev", icon: "chevron-left", variant: secondary, disabled: (<= @entity.cursorIndex -1) },
355
468
  { type: button, action: RESET_STEPPER, label: "Reset", icon: "rotate-ccw", variant: ghost },
356
- { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)) }
469
+ { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)) }
357
470
  ] },
358
- { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @config.stages), showPercentage: false },
471
+ { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @entity.stages), showPercentage: false },
359
472
  { type: stack, direction: horizontal, align: center, gap: xs, wrap: true, role: "group", aria-label: "Category lens", children: @entity.filterButtons },
360
473
  { type: math-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, xMin: 0, xMax: @config.width, yMin: 0, yMax: @config.height, showAxes: false, showGrid: false, gridStep: 1, curves: [], points: [], vectors: [], shapes: @entity.shapes, interactive: @config.interactive, animate: @config.animate, keyMap: { ArrowRight: NEXT, ArrowLeft: PREV, KeyR: RESET_STEPPER } },
361
474
  { type: card, variant: bordered, padding: md, title: (if (!= @entity.currentStageLabel "") @entity.currentStageLabel "Not Started"), children: [
@@ -371,29 +484,29 @@ orbital LearnStepperOrbital {
371
484
  PREV -> idle when (== @entity.active true)
372
485
  (set @entity.cursorIndex (math/max (- @entity.cursorIndex 1) -1))
373
486
  (let (
374
- (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @config.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
375
- (currentStage (if (>= @entity.cursorIndex 0) (array/nth @config.stages @entity.cursorIndex) null))
376
- (totalStages (array/len @config.stages))
487
+ (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @entity.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
488
+ (currentStage (if (>= @entity.cursorIndex 0) (array/nth @entity.stages @entity.cursorIndex) null))
489
+ (totalStages (array/len @entity.stages))
377
490
  (atEnd (and (>= @entity.cursorIndex 0) (>= @entity.cursorIndex (- @totalStages 1))))
378
491
  (stageLabel (if (!= @currentStage null) (object/get @currentStage label) ""))
379
492
  (stageDescription (if (!= @currentStage null) (object/get @currentStage description) @config.startPrompt))
380
493
  (boundedDescription (if @atEnd (str/concat @stageDescription @config.atEndNote) @stageDescription))
381
494
  (narration (if (!= @currentStage null) (str/concat (str/concat "" (object/get @currentStage position)) " — " @stageLabel ". " @boundedDescription) @config.startPrompt))
382
- (tallyParts (array/map @config.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
495
+ (tallyParts (array/map @entity.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
383
496
  (categoryTally (str/join @tallyParts " · "))
384
- (tallyItems (array/map @config.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
385
- (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @config.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
497
+ (tallyItems (array/map @entity.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
498
+ (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @entity.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
386
499
  (axisWidth (- @config.width (+ @config.axisMarginLeft @config.axisMarginRight)))
387
500
  (baseShapes [
388
501
  { type: line, id: "axis", x1: @config.axisMarginLeft, y1: @config.axisY, x2: (- @config.width @config.axisMarginRight), y2: @config.axisY, tone: "axis", stroke: "normal", opacity: 1 },
389
502
  { type: text, id: "minPositionLabel", x: @config.axisMarginLeft, y: (+ @config.axisY 26), text: (str/concat "" @config.minPosition), textSize: "xs", align: "left", tone: "label" },
390
503
  { type: text, id: "maxPositionLabel", x: (- @config.width @config.axisMarginRight), y: (+ @config.axisY 26), text: (str/concat "" @config.maxPosition), textSize: "xs", align: "right", tone: "label" }
391
504
  ])
392
- (markerShapes (array/flatten (array/map @config.stages (fn s (let (
505
+ (markerShapes (array/flatten (array/map @entity.stages (fn s (let (
393
506
  (isRevealed (<= @s.position @currentPosition))
394
507
  (isCurrent (== @s.position @currentPosition))
395
508
  (matchesFilter (or (== @entity.activeFilter "all") (== @s.category @entity.activeFilter)))
396
- (catColorObj (array/first (array/filter @config.categories (fn cc (== @cc.category @s.category)))))
509
+ (catColorObj (array/first (array/filter @entity.categories (fn cc (== @cc.category @s.category)))))
397
510
  (catColor (object/get @catColorObj color))
398
511
  (col (if @isRevealed @catColor @config.unreachedColor))
399
512
  (baseOpacity (if @isRevealed 0.9 0.35))
@@ -411,14 +524,14 @@ orbital LearnStepperOrbital {
411
524
  ]
412
525
  )
413
526
  )))))
414
- (legendShapes (array/flatten (array/map @config.categories [
415
- { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
416
- { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
527
+ (legendShapes (array/flatten (array/map @entity.categories [
528
+ { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
529
+ { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
417
530
  ])))
418
531
  (allShapes (array/concat @baseShapes @markerShapes @legendShapes))
419
532
  (filterButtons (array/concat [
420
533
  { type: button, action: FILTER_CATEGORY, actionPayload: { category: "all" }, label: @config.allLabel, variant: (if (== @entity.activeFilter "all") "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter "all") "true" "false") }
421
- ] (array/map @config.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
534
+ ] (array/map @entity.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
422
535
  )
423
536
  (do
424
537
  (set @entity.narration @narration)
@@ -441,15 +554,15 @@ orbital LearnStepperOrbital {
441
554
  { type: typography, variant: h4, content: @entity.title },
442
555
  (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
443
556
  ] },
444
- { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "success" "primary")) }
557
+ { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "success" "primary")) }
445
558
  ] },
446
559
  { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
447
- { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @config.stages) 1)) },
560
+ { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) },
448
561
  { type: button, action: PREV, label: "Prev", icon: "chevron-left", variant: secondary, disabled: (<= @entity.cursorIndex -1) },
449
562
  { type: button, action: RESET_STEPPER, label: "Reset", icon: "rotate-ccw", variant: ghost },
450
- { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)) }
563
+ { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)) }
451
564
  ] },
452
- { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @config.stages), showPercentage: false },
565
+ { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @entity.stages), showPercentage: false },
453
566
  { type: stack, direction: horizontal, align: center, gap: xs, wrap: true, role: "group", aria-label: "Category lens", children: @entity.filterButtons },
454
567
  { type: math-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, xMin: 0, xMax: @config.width, yMin: 0, yMax: @config.height, showAxes: false, showGrid: false, gridStep: 1, curves: [], points: [], vectors: [], shapes: @entity.shapes, interactive: @config.interactive, animate: @config.animate, keyMap: { ArrowRight: NEXT, ArrowLeft: PREV, KeyR: RESET_STEPPER } },
455
568
  { type: card, variant: bordered, padding: md, title: (if (!= @entity.currentStageLabel "") @entity.currentStageLabel "Not Started"), children: [
@@ -465,29 +578,29 @@ orbital LearnStepperOrbital {
465
578
  FILTER_CATEGORY -> idle when (== @entity.active true)
466
579
  (set @entity.activeFilter ?category)
467
580
  (let (
468
- (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @config.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
469
- (currentStage (if (>= @entity.cursorIndex 0) (array/nth @config.stages @entity.cursorIndex) null))
470
- (totalStages (array/len @config.stages))
581
+ (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @entity.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
582
+ (currentStage (if (>= @entity.cursorIndex 0) (array/nth @entity.stages @entity.cursorIndex) null))
583
+ (totalStages (array/len @entity.stages))
471
584
  (atEnd (and (>= @entity.cursorIndex 0) (>= @entity.cursorIndex (- @totalStages 1))))
472
585
  (stageLabel (if (!= @currentStage null) (object/get @currentStage label) ""))
473
586
  (stageDescription (if (!= @currentStage null) (object/get @currentStage description) @config.startPrompt))
474
587
  (boundedDescription (if @atEnd (str/concat @stageDescription @config.atEndNote) @stageDescription))
475
588
  (narration (if (!= @currentStage null) (str/concat (str/concat "" (object/get @currentStage position)) " — " @stageLabel ". " @boundedDescription) @config.startPrompt))
476
- (tallyParts (array/map @config.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
589
+ (tallyParts (array/map @entity.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
477
590
  (categoryTally (str/join @tallyParts " · "))
478
- (tallyItems (array/map @config.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
479
- (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @config.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
591
+ (tallyItems (array/map @entity.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
592
+ (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @entity.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
480
593
  (axisWidth (- @config.width (+ @config.axisMarginLeft @config.axisMarginRight)))
481
594
  (baseShapes [
482
595
  { type: line, id: "axis", x1: @config.axisMarginLeft, y1: @config.axisY, x2: (- @config.width @config.axisMarginRight), y2: @config.axisY, tone: "axis", stroke: "normal", opacity: 1 },
483
596
  { type: text, id: "minPositionLabel", x: @config.axisMarginLeft, y: (+ @config.axisY 26), text: (str/concat "" @config.minPosition), textSize: "xs", align: "left", tone: "label" },
484
597
  { type: text, id: "maxPositionLabel", x: (- @config.width @config.axisMarginRight), y: (+ @config.axisY 26), text: (str/concat "" @config.maxPosition), textSize: "xs", align: "right", tone: "label" }
485
598
  ])
486
- (markerShapes (array/flatten (array/map @config.stages (fn s (let (
599
+ (markerShapes (array/flatten (array/map @entity.stages (fn s (let (
487
600
  (isRevealed (<= @s.position @currentPosition))
488
601
  (isCurrent (== @s.position @currentPosition))
489
602
  (matchesFilter (or (== @entity.activeFilter "all") (== @s.category @entity.activeFilter)))
490
- (catColorObj (array/first (array/filter @config.categories (fn cc (== @cc.category @s.category)))))
603
+ (catColorObj (array/first (array/filter @entity.categories (fn cc (== @cc.category @s.category)))))
491
604
  (catColor (object/get @catColorObj color))
492
605
  (col (if @isRevealed @catColor @config.unreachedColor))
493
606
  (baseOpacity (if @isRevealed 0.9 0.35))
@@ -505,14 +618,14 @@ orbital LearnStepperOrbital {
505
618
  ]
506
619
  )
507
620
  )))))
508
- (legendShapes (array/flatten (array/map @config.categories [
509
- { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
510
- { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
621
+ (legendShapes (array/flatten (array/map @entity.categories [
622
+ { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
623
+ { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
511
624
  ])))
512
625
  (allShapes (array/concat @baseShapes @markerShapes @legendShapes))
513
626
  (filterButtons (array/concat [
514
627
  { type: button, action: FILTER_CATEGORY, actionPayload: { category: "all" }, label: @config.allLabel, variant: (if (== @entity.activeFilter "all") "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter "all") "true" "false") }
515
- ] (array/map @config.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
628
+ ] (array/map @entity.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
516
629
  )
517
630
  (do
518
631
  (set @entity.narration @narration)
@@ -535,15 +648,15 @@ orbital LearnStepperOrbital {
535
648
  { type: typography, variant: h4, content: @entity.title },
536
649
  (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
537
650
  ] },
538
- { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "success" "primary")) }
651
+ { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "success" "primary")) }
539
652
  ] },
540
653
  { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
541
- { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @config.stages) 1)) },
654
+ { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) },
542
655
  { type: button, action: PREV, label: "Prev", icon: "chevron-left", variant: secondary, disabled: (<= @entity.cursorIndex -1) },
543
656
  { type: button, action: RESET_STEPPER, label: "Reset", icon: "rotate-ccw", variant: ghost },
544
- { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)) }
657
+ { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)) }
545
658
  ] },
546
- { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @config.stages), showPercentage: false },
659
+ { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @entity.stages), showPercentage: false },
547
660
  { type: stack, direction: horizontal, align: center, gap: xs, wrap: true, role: "group", aria-label: "Category lens", children: @entity.filterButtons },
548
661
  { type: math-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, xMin: 0, xMax: @config.width, yMin: 0, yMax: @config.height, showAxes: false, showGrid: false, gridStep: 1, curves: [], points: [], vectors: [], shapes: @entity.shapes, interactive: @config.interactive, animate: @config.animate, keyMap: { ArrowRight: NEXT, ArrowLeft: PREV, KeyR: RESET_STEPPER } },
549
662
  { type: card, variant: bordered, padding: md, title: (if (!= @entity.currentStageLabel "") @entity.currentStageLabel "Not Started"), children: [
@@ -560,29 +673,29 @@ orbital LearnStepperOrbital {
560
673
  (set @entity.cursorIndex -1)
561
674
  (set @entity.activeFilter "all")
562
675
  (let (
563
- (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @config.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
564
- (currentStage (if (>= @entity.cursorIndex 0) (array/nth @config.stages @entity.cursorIndex) null))
565
- (totalStages (array/len @config.stages))
676
+ (currentPosition (if (>= @entity.cursorIndex 0) (object/get (array/nth @entity.stages @entity.cursorIndex) position) (- @config.minPosition 1)))
677
+ (currentStage (if (>= @entity.cursorIndex 0) (array/nth @entity.stages @entity.cursorIndex) null))
678
+ (totalStages (array/len @entity.stages))
566
679
  (atEnd (and (>= @entity.cursorIndex 0) (>= @entity.cursorIndex (- @totalStages 1))))
567
680
  (stageLabel (if (!= @currentStage null) (object/get @currentStage label) ""))
568
681
  (stageDescription (if (!= @currentStage null) (object/get @currentStage description) @config.startPrompt))
569
682
  (boundedDescription (if @atEnd (str/concat @stageDescription @config.atEndNote) @stageDescription))
570
683
  (narration (if (!= @currentStage null) (str/concat (str/concat "" (object/get @currentStage position)) " — " @stageLabel ". " @boundedDescription) @config.startPrompt))
571
- (tallyParts (array/map @config.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
684
+ (tallyParts (array/map @entity.categories (fn cc (str/concat (object/get @cc label) ": " (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category))))))))))
572
685
  (categoryTally (str/join @tallyParts " · "))
573
- (tallyItems (array/map @config.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @config.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
574
- (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @config.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
686
+ (tallyItems (array/map @entity.categories (fn cc { label: (object/get @cc label), count: (array/len (array/filter @entity.stages (fn s (and (<= @s.position @currentPosition) (== @s.category (object/get @cc category)))))) })))
687
+ (visibleThreadNote (if (== @entity.activeFilter "all") @config.allSummary (object/get (array/first (array/filter @entity.categorySummaries (fn cs (== @cs.category @entity.activeFilter)))) summary)))
575
688
  (axisWidth (- @config.width (+ @config.axisMarginLeft @config.axisMarginRight)))
576
689
  (baseShapes [
577
690
  { type: line, id: "axis", x1: @config.axisMarginLeft, y1: @config.axisY, x2: (- @config.width @config.axisMarginRight), y2: @config.axisY, tone: "axis", stroke: "normal", opacity: 1 },
578
691
  { type: text, id: "minPositionLabel", x: @config.axisMarginLeft, y: (+ @config.axisY 26), text: (str/concat "" @config.minPosition), textSize: "xs", align: "left", tone: "label" },
579
692
  { type: text, id: "maxPositionLabel", x: (- @config.width @config.axisMarginRight), y: (+ @config.axisY 26), text: (str/concat "" @config.maxPosition), textSize: "xs", align: "right", tone: "label" }
580
693
  ])
581
- (markerShapes (array/flatten (array/map @config.stages (fn s (let (
694
+ (markerShapes (array/flatten (array/map @entity.stages (fn s (let (
582
695
  (isRevealed (<= @s.position @currentPosition))
583
696
  (isCurrent (== @s.position @currentPosition))
584
697
  (matchesFilter (or (== @entity.activeFilter "all") (== @s.category @entity.activeFilter)))
585
- (catColorObj (array/first (array/filter @config.categories (fn cc (== @cc.category @s.category)))))
698
+ (catColorObj (array/first (array/filter @entity.categories (fn cc (== @cc.category @s.category)))))
586
699
  (catColor (object/get @catColorObj color))
587
700
  (col (if @isRevealed @catColor @config.unreachedColor))
588
701
  (baseOpacity (if @isRevealed 0.9 0.35))
@@ -600,14 +713,14 @@ orbital LearnStepperOrbital {
600
713
  ]
601
714
  )
602
715
  )))))
603
- (legendShapes (array/flatten (array/map @config.categories [
604
- { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
605
- { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @config.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
716
+ (legendShapes (array/flatten (array/map @entity.categories [
717
+ { type: circle, id: (str/concat "legend-dot-" @index), x: (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))), y: 22, radius: 5, tone: (object/get @item color), fillTone: (object/get @item color), fillStyle: "solid", opacity: 1 },
718
+ { type: text, id: (str/concat "legend-label-" @index), x: (+ (+ @config.axisMarginLeft (* @index (/ @axisWidth (array/len @entity.categories)))) 12), y: 26, text: (object/get @item label), textSize: "xs", align: "left", tone: "label" }
606
719
  ])))
607
720
  (allShapes (array/concat @baseShapes @markerShapes @legendShapes))
608
721
  (filterButtons (array/concat [
609
722
  { type: button, action: FILTER_CATEGORY, actionPayload: { category: "all" }, label: @config.allLabel, variant: (if (== @entity.activeFilter "all") "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter "all") "true" "false") }
610
- ] (array/map @config.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
723
+ ] (array/map @entity.categories (fn cc { type: button, action: FILTER_CATEGORY, actionPayload: { category: (object/get @cc category) }, label: (object/get @cc label), variant: (if (== @entity.activeFilter (object/get @cc category)) "secondary" "ghost"), size: sm, aria-pressed: (if (== @entity.activeFilter (object/get @cc category)) "true" "false") }))))
611
724
  )
612
725
  (do
613
726
  (set @entity.narration @narration)
@@ -630,15 +743,15 @@ orbital LearnStepperOrbital {
630
743
  { type: typography, variant: h4, content: @entity.title },
631
744
  (if (== @config.summary "") null { type: typography, variant: body2, color: muted, content: @config.summary })
632
745
  ] },
633
- { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @config.stages) 1)) "success" "primary")) }
746
+ { type: badge, label: (if (<= @entity.cursorIndex -1) "Not started" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "Complete" (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)))), variant: (if (<= @entity.cursorIndex -1) "neutral" (if (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) "success" "primary")) }
634
747
  ] },
635
748
  { type: stack, direction: horizontal, align: center, gap: sm, wrap: true, children: [
636
- { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @config.stages) 1)) },
749
+ { type: button, action: NEXT, label: "Next", icon: "chevron-right", variant: primary, disabled: (>= @entity.cursorIndex (- (array/len @entity.stages) 1)) },
637
750
  { type: button, action: PREV, label: "Prev", icon: "chevron-left", variant: secondary, disabled: (<= @entity.cursorIndex -1) },
638
751
  { type: button, action: RESET_STEPPER, label: "Reset", icon: "rotate-ccw", variant: ghost },
639
- { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @config.stages)) }
752
+ { type: typography, variant: caption, color: muted, content: (str/concat "Stage " (+ @entity.cursorIndex 1) " of " (array/len @entity.stages)) }
640
753
  ] },
641
- { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @config.stages), showPercentage: false },
754
+ { type: progress-bar, value: (+ @entity.cursorIndex 1), max: (array/len @entity.stages), showPercentage: false },
642
755
  { type: stack, direction: horizontal, align: center, gap: xs, wrap: true, role: "group", aria-label: "Category lens", children: @entity.filterButtons },
643
756
  { type: math-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, xMin: 0, xMax: @config.width, yMin: 0, yMax: @config.height, showAxes: false, showGrid: false, gridStep: 1, curves: [], points: [], vectors: [], shapes: @entity.shapes, interactive: @config.interactive, animate: @config.animate, keyMap: { ArrowRight: NEXT, ArrowLeft: PREV, KeyR: RESET_STEPPER } },
644
757
  { type: card, variant: bordered, padding: md, title: (if (!= @entity.currentStageLabel "") @entity.currentStageLabel "Not Started"), children: [
@@ -659,6 +772,14 @@ orbital LearnStepperOrbital {
659
772
  }
660
773
  @description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
661
774
  @tier "domain"
775
+ ROSTER_LOADED {
776
+ mode : string!
777
+ stages : [LearnStepperStage]!
778
+ categories : [LearnStepperCategory]!
779
+ categorySummaries : [LearnStepperCategorySummary]!
780
+ }
781
+ @description "Runtime content feed, honoured only when rosterSource is event — mode names the instrument it is for (the matchMode it carries, blank when standalone) and stages, categories and categorySummaries are the roster; a matching instrument re-initialises from it exactly as INIT does from config, any other instrument hides."
782
+ @tier "domain"
662
783
  NEXT {
663
784
  }
664
785
  @description "Learner advanced the cursor to the next stage in the ordered roster; clamps at the last stage with a narrated end note rather than wrapping."
@@ -694,6 +815,10 @@ orbital LearnStepperOrbital {
694
815
  @label "Match Mode"
695
816
  @description "When composed behind a lab router, the MODE_SELECTED mode value this instrument responds to. Empty string means standalone — render immediately on INIT."
696
817
  @tier "presentation"
818
+ rosterSource : "config" | "event" = "config"
819
+ @label "Roster Source"
820
+ @description "Where the stages, categories and category summaries come from: config reads those knobs at INIT; event waits for ROSTER_LOADED and takes them from its payload. The axis range (minPosition/maxPosition) stays config."
821
+ @tier "domain"
697
822
  title : string = "Stepper"
698
823
  @label "Title"
699
824
  @description "The instrument's name, the header's one hero."