@almadar/std 16.173.0 → 16.174.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 (203) hide show
  1. package/behaviors/lolo/infra/atoms/std-cross-reference.lolo +2 -4
  2. package/behaviors/lolo/ui/core/atoms/std-board.lolo +5 -9
  3. package/behaviors/lolo/ui/core/atoms/std-browse.lolo +8 -1
  4. package/behaviors/lolo/ui/core/atoms/std-esign-flow.lolo +2 -2
  5. package/behaviors/lolo/ui/core/atoms/std-event-log.lolo +3 -7
  6. package/behaviors/lolo/ui/core/atoms/std-form-advanced.lolo +1 -1
  7. package/behaviors/lolo/ui/core/atoms/std-image-upload-multi.lolo +1 -1
  8. package/behaviors/lolo/ui/core/atoms/std-list.lolo +3 -3
  9. package/behaviors/lolo/ui/core/atoms/std-modal.lolo +2 -2
  10. package/behaviors/lolo/ui/core/atoms/std-multi-party-flow.lolo +2 -4
  11. package/behaviors/lolo/ui/core/atoms/std-rating-review.lolo +5 -13
  12. package/behaviors/lolo/ui/core/atoms/std-recurrence.lolo +3 -7
  13. package/behaviors/lolo/ui/core/atoms/std-saved-search.lolo +1 -1
  14. package/behaviors/lolo/ui/core/atoms/std-signature-capture.lolo +2 -2
  15. package/behaviors/lolo/ui/core/atoms/std-step-flow.lolo +2 -4
  16. package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +1 -1
  17. package/behaviors/lolo/ui/core/atoms/std-thread.lolo +2 -2
  18. package/behaviors/lolo/ui/core/atoms/std-vote.lolo +2 -2
  19. package/behaviors/lolo/ui/core/atoms/std-wizard.lolo +3 -7
  20. package/behaviors/lolo/ui/core/atoms/ui-atlas-image.lolo +1 -1
  21. package/behaviors/lolo/ui/core/atoms/ui-atlas-panel.lolo +1 -1
  22. package/behaviors/lolo/ui/core/atoms/ui-center.lolo +1 -1
  23. package/behaviors/lolo/ui/core/atoms/ui-hstack.lolo +1 -1
  24. package/behaviors/lolo/ui/core/atoms/ui-icon.lolo +1 -1
  25. package/behaviors/lolo/ui/core/atoms/ui-stack.lolo +1 -1
  26. package/behaviors/lolo/ui/core/atoms/ui-typography.lolo +1 -1
  27. package/behaviors/lolo/ui/core/atoms/ui-vstack.lolo +1 -1
  28. package/behaviors/lolo/ui/core/molecules/ui-content-renderer.lolo +1 -1
  29. package/behaviors/lolo/ui/core/molecules/ui-form-layout.lolo +1 -1
  30. package/behaviors/lolo/ui/core/molecules/ui-grid.lolo +1 -1
  31. package/behaviors/lolo/ui/core/organisms/ui-chat-bar.lolo +1 -1
  32. package/behaviors/lolo/ui/core/organisms/ui-dashboard-grid.lolo +1 -1
  33. package/behaviors/lolo/ui/core/organisms/ui-detail-panel.lolo +1 -1
  34. package/behaviors/lolo/ui/core/organisms/ui-entity-cards.lolo +1 -1
  35. package/behaviors/lolo/ui/core/organisms/ui-entity-list.lolo +1 -1
  36. package/behaviors/lolo/ui/core/organisms/ui-entity-table.lolo +1 -1
  37. package/behaviors/lolo/ui/core/organisms/ui-form-section.lolo +9 -3
  38. package/behaviors/lolo/ui/core/organisms/ui-form.lolo +9 -3
  39. package/behaviors/lolo/ui/core/organisms/ui-master-detail.lolo +1 -1
  40. package/behaviors/lolo/ui/core/organisms/ui-media-gallery.lolo +1 -1
  41. package/behaviors/lolo/ui/core/organisms/ui-segment-renderer.lolo +7 -1
  42. package/behaviors/lolo/ui/core/organisms/ui-stat-card.lolo +1 -1
  43. package/behaviors/lolo/ui/core/organisms/ui-subagent-trace-panel.lolo +1 -1
  44. package/behaviors/lolo/ui/game/atoms/ui-action-tile.lolo +1 -1
  45. package/behaviors/lolo/ui/game/atoms/ui-draw-shape-layer.lolo +41 -9
  46. package/behaviors/lolo/ui/game/atoms/ui-draw-shape.lolo +40 -8
  47. package/behaviors/lolo/ui/game/atoms/ui-draw-sprite-layer.lolo +13 -3
  48. package/behaviors/lolo/ui/game/atoms/ui-draw-sprite.lolo +12 -2
  49. package/behaviors/lolo/ui/game/atoms/ui-draw-text-layer.lolo +3 -3
  50. package/behaviors/lolo/ui/game/atoms/ui-draw-text.lolo +2 -2
  51. package/behaviors/lolo/ui/game/organisms/std-tactics-board-3d.lolo +32 -2
  52. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +1 -1
  53. package/behaviors/lolo/ui/learning/atoms/std-learn-compare.lolo +397 -0
  54. package/behaviors/lolo/ui/learning/atoms/std-learn-coupled-sim.lolo +537 -0
  55. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +419 -0
  56. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +591 -0
  57. package/behaviors/lolo/ui/learning/atoms/std-learn-grid-sim.lolo +425 -0
  58. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +398 -0
  59. package/behaviors/lolo/ui/learning/atoms/std-learn-measure.lolo +325 -0
  60. package/behaviors/lolo/ui/learning/atoms/std-learn-perturb.lolo +494 -0
  61. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +408 -0
  62. package/behaviors/lolo/ui/learning/atoms/std-learn-scenario.lolo +307 -0
  63. package/behaviors/lolo/ui/learning/atoms/std-learn-sim-3d.lolo +318 -0
  64. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +336 -0
  65. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +380 -0
  66. package/behaviors/lolo/ui/learning/atoms/ui-biology-canvas.lolo +43 -4
  67. package/behaviors/lolo/ui/learning/atoms/ui-chemistry-canvas.lolo +41 -4
  68. package/behaviors/lolo/ui/learning/atoms/ui-physics-canvas.lolo +43 -4
  69. package/behaviors/lolo/ui/marketing/organisms/ui-book-viewer.lolo +1 -1
  70. package/behaviors/lolo/ui/marketing/organisms/ui-case-study-organism.lolo +1 -1
  71. package/behaviors/lolo/ui/marketing/organisms/ui-feature-grid-organism.lolo +1 -1
  72. package/behaviors/lolo/ui/marketing/organisms/ui-hero-organism.lolo +1 -1
  73. package/behaviors/lolo/ui/marketing/organisms/ui-pricing-organism.lolo +1 -1
  74. package/behaviors/lolo/ui/marketing/organisms/ui-showcase-organism.lolo +1 -1
  75. package/behaviors/lolo/ui/marketing/organisms/ui-stats-organism.lolo +1 -1
  76. package/behaviors/lolo/ui/marketing/organisms/ui-step-flow-organism.lolo +1 -1
  77. package/behaviors/lolo/ui/marketing/organisms/ui-team-organism.lolo +1 -1
  78. package/behaviors/registry/agent/atoms/std-agent-session.orb +22 -1
  79. package/behaviors/registry/agent/atoms/std-agent-tool-call.orb +38 -2
  80. package/behaviors/registry/agent/atoms/std-agent-tool-loop.orb +38 -2
  81. package/behaviors/registry/agent/organisms/std-agent-rabit.orb +23 -1
  82. package/behaviors/registry/infra/atoms/std-approval-gate.orb +8 -16
  83. package/behaviors/registry/infra/atoms/std-cache-aside.orb +31 -62
  84. package/behaviors/registry/infra/atoms/std-circuit-breaker.orb +16 -32
  85. package/behaviors/registry/infra/atoms/std-cross-reference.orb +32 -4
  86. package/behaviors/registry/infra/atoms/std-data-erasure.orb +8 -16
  87. package/behaviors/registry/infra/atoms/std-mod-queue.orb +8 -16
  88. package/behaviors/registry/infra/atoms/std-rate-limiter.orb +14 -28
  89. package/behaviors/registry/infra/atoms/std-row-access-control.orb +30 -2
  90. package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +1 -2
  91. package/behaviors/registry/ui/core/atoms/std-board.orb +19 -5
  92. package/behaviors/registry/ui/core/atoms/std-browse.orb +29 -7
  93. package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +1 -2
  94. package/behaviors/registry/ui/core/atoms/std-esign-flow.orb +3 -5
  95. package/behaviors/registry/ui/core/atoms/std-esign-request.orb +3 -6
  96. package/behaviors/registry/ui/core/atoms/std-event-log.orb +17 -3
  97. package/behaviors/registry/ui/core/atoms/std-export.orb +2 -4
  98. package/behaviors/registry/ui/core/atoms/std-form-advanced.orb +1 -1
  99. package/behaviors/registry/ui/core/atoms/std-gallery.orb +9 -18
  100. package/behaviors/registry/ui/core/atoms/std-graphs.orb +15 -1
  101. package/behaviors/registry/ui/core/atoms/std-import.orb +2 -4
  102. package/behaviors/registry/ui/core/atoms/std-list.orb +4 -6
  103. package/behaviors/registry/ui/core/atoms/std-modal.orb +2 -2
  104. package/behaviors/registry/ui/core/atoms/std-multi-party-flow.orb +16 -2
  105. package/behaviors/registry/ui/core/atoms/std-pagination.orb +15 -1
  106. package/behaviors/registry/ui/core/atoms/std-rating-review.orb +19 -5
  107. package/behaviors/registry/ui/core/atoms/std-recurrence.orb +17 -3
  108. package/behaviors/registry/ui/core/atoms/std-rich-editor.orb +15 -1
  109. package/behaviors/registry/ui/core/atoms/std-scatter.orb +15 -1
  110. package/behaviors/registry/ui/core/atoms/std-signature-capture.orb +2 -2
  111. package/behaviors/registry/ui/core/atoms/std-stats.orb +15 -1
  112. package/behaviors/registry/ui/core/atoms/std-step-flow.orb +16 -2
  113. package/behaviors/registry/ui/core/atoms/std-thread.orb +2 -2
  114. package/behaviors/registry/ui/core/atoms/std-vote.orb +2 -2
  115. package/behaviors/registry/ui/core/atoms/std-wizard.orb +17 -3
  116. package/behaviors/registry/ui/core/atoms/ui-atlas-image.orb +4 -1
  117. package/behaviors/registry/ui/core/atoms/ui-atlas-panel.orb +4 -1
  118. package/behaviors/registry/ui/core/atoms/ui-center.orb +4 -1
  119. package/behaviors/registry/ui/core/atoms/ui-hstack.orb +4 -1
  120. package/behaviors/registry/ui/core/atoms/ui-icon.orb +4 -1
  121. package/behaviors/registry/ui/core/atoms/ui-stack.orb +4 -1
  122. package/behaviors/registry/ui/core/atoms/ui-typography.orb +4 -1
  123. package/behaviors/registry/ui/core/atoms/ui-vstack.orb +4 -1
  124. package/behaviors/registry/ui/core/molecules/ui-accordion.orb +3 -3
  125. package/behaviors/registry/ui/core/molecules/ui-content-renderer.orb +4 -1
  126. package/behaviors/registry/ui/core/molecules/ui-form-layout.orb +4 -1
  127. package/behaviors/registry/ui/core/molecules/ui-grid.orb +4 -1
  128. package/behaviors/registry/ui/core/molecules/ui-tabs.orb +2 -2
  129. package/behaviors/registry/ui/core/molecules/ui-wizard-container.orb +1 -1
  130. package/behaviors/registry/ui/core/organisms/ui-chat-bar.orb +4 -1
  131. package/behaviors/registry/ui/core/organisms/ui-dashboard-grid.orb +5 -2
  132. package/behaviors/registry/ui/core/organisms/ui-detail-panel.orb +5 -2
  133. package/behaviors/registry/ui/core/organisms/ui-entity-cards.orb +4 -1
  134. package/behaviors/registry/ui/core/organisms/ui-entity-list.orb +4 -1
  135. package/behaviors/registry/ui/core/organisms/ui-entity-table.orb +4 -1
  136. package/behaviors/registry/ui/core/organisms/ui-form-section.orb +34 -2
  137. package/behaviors/registry/ui/core/organisms/ui-form.orb +34 -2
  138. package/behaviors/registry/ui/core/organisms/ui-master-detail.orb +4 -1
  139. package/behaviors/registry/ui/core/organisms/ui-media-gallery.orb +4 -1
  140. package/behaviors/registry/ui/core/organisms/ui-segment-renderer.orb +24 -2
  141. package/behaviors/registry/ui/core/organisms/ui-stat-card.orb +4 -1
  142. package/behaviors/registry/ui/core/organisms/ui-subagent-trace-panel.orb +4 -1
  143. package/behaviors/registry/ui/core/organisms/ui-tabbed-container.orb +1 -1
  144. package/behaviors/registry/ui/core/organisms/ui-ui-slot-renderer.orb +1 -1
  145. package/behaviors/registry/ui/game/atoms/std-fx-particles.orb +35 -1
  146. package/behaviors/registry/ui/game/atoms/ui-action-tile.orb +4 -1
  147. package/behaviors/registry/ui/game/atoms/ui-draw-shape-layer.orb +183 -13
  148. package/behaviors/registry/ui/game/atoms/ui-draw-shape.orb +104 -9
  149. package/behaviors/registry/ui/game/atoms/ui-draw-sprite-layer.orb +56 -2
  150. package/behaviors/registry/ui/game/atoms/ui-draw-sprite.orb +36 -4
  151. package/behaviors/registry/ui/game/atoms/ui-draw-text-layer.orb +21 -2
  152. package/behaviors/registry/ui/game/atoms/ui-draw-text.orb +19 -4
  153. package/behaviors/registry/ui/game/organisms/std-tactics-board-3d.orb +173 -2
  154. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1 -1
  155. package/behaviors/registry/ui/learning/atoms/std-learn-compare.orb +2814 -0
  156. package/behaviors/registry/ui/learning/atoms/std-learn-coupled-sim.orb +3183 -0
  157. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +2394 -0
  158. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +3951 -0
  159. package/behaviors/registry/ui/learning/atoms/std-learn-grid-sim.orb +3458 -0
  160. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +2646 -0
  161. package/behaviors/registry/ui/learning/atoms/std-learn-measure.orb +1979 -0
  162. package/behaviors/registry/ui/learning/atoms/std-learn-perturb.orb +2994 -0
  163. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +2682 -0
  164. package/behaviors/registry/ui/learning/atoms/std-learn-scenario.orb +1842 -0
  165. package/behaviors/registry/ui/learning/atoms/std-learn-sim-3d.orb +1648 -0
  166. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +2307 -0
  167. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +2947 -0
  168. package/behaviors/registry/ui/learning/atoms/ui-algorithm-canvas.orb +173 -4
  169. package/behaviors/registry/ui/learning/atoms/ui-biology-canvas.orb +267 -7
  170. package/behaviors/registry/ui/learning/atoms/ui-chemistry-canvas.orb +263 -8
  171. package/behaviors/registry/ui/learning/atoms/ui-learning-canvas.orb +116 -1
  172. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +200 -4
  173. package/behaviors/registry/ui/learning/atoms/ui-physics-canvas.orb +264 -7
  174. package/behaviors/registry/ui/marketing/atoms/std-marketing-article.orb +1 -2
  175. package/behaviors/registry/ui/marketing/atoms/std-marketing-footer.orb +1 -2
  176. package/behaviors/registry/ui/marketing/atoms/std-marketing-split.orb +1 -2
  177. package/behaviors/registry/ui/marketing/atoms/std-marketing-stats.orb +1 -2
  178. package/behaviors/registry/ui/marketing/atoms/std-marketing-steps.orb +1 -2
  179. package/behaviors/registry/ui/marketing/atoms/std-marketing-team.orb +1 -2
  180. package/behaviors/registry/ui/marketing/organisms/ui-book-viewer.orb +4 -1
  181. package/behaviors/registry/ui/marketing/organisms/ui-case-study-organism.orb +4 -1
  182. package/behaviors/registry/ui/marketing/organisms/ui-feature-grid-organism.orb +4 -1
  183. package/behaviors/registry/ui/marketing/organisms/ui-hero-organism.orb +4 -1
  184. package/behaviors/registry/ui/marketing/organisms/ui-pricing-organism.orb +4 -1
  185. package/behaviors/registry/ui/marketing/organisms/ui-showcase-organism.orb +4 -1
  186. package/behaviors/registry/ui/marketing/organisms/ui-stats-organism.orb +4 -1
  187. package/behaviors/registry/ui/marketing/organisms/ui-step-flow-organism.orb +4 -1
  188. package/behaviors/registry/ui/marketing/organisms/ui-team-organism.orb +4 -1
  189. package/dist/behaviors/exports-reader.js +3440 -2189
  190. package/dist/behaviors/functions/index.d.ts +1751 -154
  191. package/dist/behaviors/functions/index.js +3402 -2190
  192. package/dist/behaviors/index.d.ts +1 -1
  193. package/dist/behaviors/index.js +3441 -2190
  194. package/dist/behaviors/query.js +3440 -2189
  195. package/dist/behaviors-embeddings.hash.json +3 -3
  196. package/dist/behaviors-embeddings.json +230 -178
  197. package/dist/behaviors-registry.json +4397 -171
  198. package/dist/index.d.ts +1 -1
  199. package/dist/index.js +3441 -2190
  200. package/dist/knob-embeddings.hash.json +3 -3
  201. package/dist/knob-embeddings.json +1 -1
  202. package/dist/registry/factory-signatures.json +1 -1
  203. package/package.json +2 -2
@@ -0,0 +1,398 @@
1
+ app std-learn-hotspot "1.0.0"
2
+ "Generic spatial tap-to-identify instrument — a config-declared roster of labeled hotspots (id, label, x, y, radius, color) renders on a learning-canvas, and a config-declared prompt roster asks the learner to tap the right part directly on the canvas: a first-try hit scores, a miss names what was actually tapped and invites another try, and a hit reveals the part's explanation with a highlight ring before advancing. All vocabulary is config-declared, so the same mechanism serves any labeled scene: organs on a cell, countries on a sketch map, components on a circuit, bones on a skeleton. Standalone by default; behind a lab router set matchMode and it renders only when MODE_SELECTED matches."
3
+ orbital HotspotOrbital {
4
+ type HotspotCanvasShape = {
5
+ type : "circle" | "text"!
6
+ id : string!
7
+ x : number!
8
+ y : number!
9
+ radius : number
10
+ color : string!
11
+ fill : string
12
+ text : string
13
+ fontSize : number
14
+ align : "left" | "center" | "right"
15
+ lineWidth : number
16
+ }
17
+ type HotspotShape = {
18
+ id : string!
19
+ label : string!
20
+ x : number!
21
+ y : number!
22
+ radius : number
23
+ color : string
24
+ }
25
+ type HotspotPrompt = {
26
+ prompt : string!
27
+ targetId : string!
28
+ explanation : string
29
+ }
30
+
31
+ entity HotspotScene [runtime] {
32
+ id : string!
33
+ active : boolean = false
34
+ title : string
35
+ promptIndex : number = 0
36
+ totalPrompts : number = 0
37
+ score : number = 0
38
+ tries : number = 0
39
+ found : boolean = false
40
+ done : boolean = false
41
+ promptText : string
42
+ targetId : string
43
+ explanation : string
44
+ feedback : string
45
+ canvasShapes : [HotspotCanvasShape]
46
+ view : [node]
47
+ }
48
+
49
+ trait HotspotSim -> @rebindable HotspotScene [interaction, instance] {
50
+ initial: idle
51
+
52
+ state idle {
53
+ INIT -> idle when (== @config.matchMode "")
54
+ (let (
55
+ (first (array/nth @config.prompts 0))
56
+ (circles (array/map @config.shapes (fn s { type: circle, id: (object/get @s id), x: (object/get @s x), y: (object/get @s y), radius: (object/get @s radius 22), color: (object/get @s color "#3b82f6"), fill: (object/get @s color "#3b82f6") })))
57
+ (labels (array/map @config.shapes (fn s { type: text, id: (str/concat "label-" (object/get @s id)), x: (object/get @s x), y: (+ (object/get @s y) (object/get @s radius 22) 14), text: (object/get @s label), fontSize: 12, align: "center", color: "#0f172a" })))
58
+ )
59
+ (do
60
+ (set @entity.active true)
61
+ (set @entity.title @config.title)
62
+ (set @entity.promptIndex 0)
63
+ (set @entity.totalPrompts (array/len @config.prompts))
64
+ (set @entity.score 0)
65
+ (set @entity.tries 0)
66
+ (set @entity.found false)
67
+ (set @entity.done false)
68
+ (set @entity.promptText (object/get @first prompt))
69
+ (set @entity.targetId (object/get @first targetId))
70
+ (set @entity.explanation (object/get @first explanation ""))
71
+ (set @entity.feedback "")
72
+ (set @entity.canvasShapes (array/concat @circles @labels))
73
+ (set @entity.view [
74
+ { type: card, variant: bordered, padding: md, children: [
75
+ { type: typography, variant: body, content: @entity.promptText }
76
+ ] },
77
+ { type: learning-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, shapes: @entity.canvasShapes, interactive: true, animate: false, onShapeClick: "TAP_SHAPE" },
78
+ { type: stack, direction: horizontal, gap: xs, children: [
79
+ { type: typography, variant: caption, content: @entity.feedback }
80
+ ] },
81
+ { type: stack, direction: horizontal, gap: sm, children: [
82
+ { type: button, action: NEXT, label: @config.nextLabel, variant: primary, disabled: (if @entity.found false true) }
83
+ ] },
84
+ { type: stat-display, label: "First-try score", value: @entity.score, max: @entity.totalPrompts }
85
+ ])))
86
+ (render-ui main {
87
+ type: box
88
+ padding: md
89
+ children: [
90
+ { type: stack, direction: vertical, gap: md, children: [
91
+ { type: typography, variant: h4, content: @entity.title },
92
+ { type: progress-bar, value: (+ @entity.promptIndex 1), max: @entity.totalPrompts, showPercentage: false, label: (str/concat "Prompt " (str/concat "" (+ @entity.promptIndex 1)) " of " (str/concat "" @entity.totalPrompts)) },
93
+ { type: stack, direction: vertical, gap: md, children: @entity.view }
94
+ ] }
95
+ ]
96
+ })
97
+
98
+ MODE_SELECTED -> idle when (== ?mode @config.matchMode)
99
+ (let (
100
+ (first (array/nth @config.prompts 0))
101
+ (circles (array/map @config.shapes (fn s { type: circle, id: (object/get @s id), x: (object/get @s x), y: (object/get @s y), radius: (object/get @s radius 22), color: (object/get @s color "#3b82f6"), fill: (object/get @s color "#3b82f6") })))
102
+ (labels (array/map @config.shapes (fn s { type: text, id: (str/concat "label-" (object/get @s id)), x: (object/get @s x), y: (+ (object/get @s y) (object/get @s radius 22) 14), text: (object/get @s label), fontSize: 12, align: "center", color: "#0f172a" })))
103
+ )
104
+ (do
105
+ (set @entity.active true)
106
+ (set @entity.title @config.title)
107
+ (set @entity.promptIndex 0)
108
+ (set @entity.totalPrompts (array/len @config.prompts))
109
+ (set @entity.score 0)
110
+ (set @entity.tries 0)
111
+ (set @entity.found false)
112
+ (set @entity.done false)
113
+ (set @entity.promptText (object/get @first prompt))
114
+ (set @entity.targetId (object/get @first targetId))
115
+ (set @entity.explanation (object/get @first explanation ""))
116
+ (set @entity.feedback "")
117
+ (set @entity.canvasShapes (array/concat @circles @labels))
118
+ (set @entity.view [
119
+ { type: card, variant: bordered, padding: md, children: [
120
+ { type: typography, variant: body, content: @entity.promptText }
121
+ ] },
122
+ { type: learning-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, shapes: @entity.canvasShapes, interactive: true, animate: false, onShapeClick: "TAP_SHAPE" },
123
+ { type: stack, direction: horizontal, gap: xs, children: [
124
+ { type: typography, variant: caption, content: @entity.feedback }
125
+ ] },
126
+ { type: stack, direction: horizontal, gap: sm, children: [
127
+ { type: button, action: NEXT, label: @config.nextLabel, variant: primary, disabled: (if @entity.found false true) }
128
+ ] },
129
+ { type: stat-display, label: "First-try score", value: @entity.score, max: @entity.totalPrompts }
130
+ ])))
131
+ (render-ui main {
132
+ type: box
133
+ padding: md
134
+ children: [
135
+ { type: stack, direction: vertical, gap: md, children: [
136
+ { type: typography, variant: h4, content: @entity.title },
137
+ { type: progress-bar, value: (+ @entity.promptIndex 1), max: @entity.totalPrompts, showPercentage: false, label: (str/concat "Prompt " (str/concat "" (+ @entity.promptIndex 1)) " of " (str/concat "" @entity.totalPrompts)) },
138
+ { type: stack, direction: vertical, gap: md, children: @entity.view }
139
+ ] }
140
+ ]
141
+ })
142
+
143
+ TAP_SHAPE -> idle when (and (== @entity.active true) (and (== @entity.found false) (== @entity.done false)))
144
+ (let (
145
+ (tappedId (str/replace (object/get ?payload id "") "label-" ""))
146
+ (hit (== @tappedId @entity.targetId))
147
+ (tapped (array/find @config.shapes (fn s (== (object/get @s id) @tappedId))))
148
+ (tappedLabel (object/get @tapped label ""))
149
+ (target (array/find @config.shapes (fn s (== (object/get @s id) @entity.targetId))))
150
+ (circles (array/map @config.shapes (fn s { type: circle, id: (object/get @s id), x: (object/get @s x), y: (object/get @s y), radius: (object/get @s radius 22), color: (object/get @s color "#3b82f6"), fill: (object/get @s color "#3b82f6") })))
151
+ (labels (array/map @config.shapes (fn s { type: text, id: (str/concat "label-" (object/get @s id)), x: (object/get @s x), y: (+ (object/get @s y) (object/get @s radius 22) 14), text: (object/get @s label), fontSize: 12, align: "center", color: "#0f172a" })))
152
+ (ring (if @hit
153
+ [ { type: circle, id: "hit-ring", x: (object/get @target x), y: (object/get @target y), radius: (+ (object/get @target radius 22) 6), color: @config.hitColor, fill: "transparent", lineWidth: 4 } ]
154
+ []))
155
+ (newFound (if @hit true false))
156
+ (newScore (if (and @hit (== @entity.tries 0)) (+ @entity.score 1) @entity.score))
157
+ (newFeedback (if @hit
158
+ (if (!= @entity.explanation "") (str/concat "Correct! " @entity.explanation) "Correct!")
159
+ (if (!= @tappedLabel "") (str/concat "That's " @tappedLabel " — try again.") "Not a labeled part — try again.")))
160
+ (newCanvasShapes (array/concat (array/concat @circles @labels) @ring))
161
+ (foundView [
162
+ { type: card, variant: bordered, padding: md, children: [
163
+ { type: typography, variant: body, content: @entity.promptText }
164
+ ] },
165
+ { type: learning-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, shapes: @newCanvasShapes, interactive: true, animate: false, onShapeClick: "TAP_SHAPE" },
166
+ { type: stack, direction: horizontal, gap: xs, children: [
167
+ { type: badge, variant: success, label: "Found" },
168
+ { type: typography, variant: caption, content: @newFeedback }
169
+ ] },
170
+ { type: stack, direction: horizontal, gap: sm, children: [
171
+ { type: button, action: NEXT, label: @config.nextLabel, variant: primary, disabled: false }
172
+ ] },
173
+ { type: stat-display, label: "First-try score", value: @newScore, max: @entity.totalPrompts }
174
+ ])
175
+ (notFoundView [
176
+ { type: card, variant: bordered, padding: md, children: [
177
+ { type: typography, variant: body, content: @entity.promptText }
178
+ ] },
179
+ { type: learning-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, shapes: @newCanvasShapes, interactive: true, animate: false, onShapeClick: "TAP_SHAPE" },
180
+ { type: stack, direction: horizontal, gap: xs, children: [
181
+ { type: typography, variant: caption, content: @newFeedback }
182
+ ] },
183
+ { type: stack, direction: horizontal, gap: sm, children: [
184
+ { type: button, action: NEXT, label: @config.nextLabel, variant: primary, disabled: true }
185
+ ] },
186
+ { type: stat-display, label: "First-try score", value: @newScore, max: @entity.totalPrompts }
187
+ ])
188
+ )
189
+ (do
190
+ (set @entity.tries (if @hit @entity.tries (+ @entity.tries 1)))
191
+ (set @entity.score @newScore)
192
+ (set @entity.found @newFound)
193
+ (set @entity.feedback @newFeedback)
194
+ (set @entity.canvasShapes @newCanvasShapes)
195
+ (set @entity.view (if @newFound @foundView @notFoundView))))
196
+ (render-ui main {
197
+ type: box
198
+ padding: md
199
+ children: [
200
+ { type: stack, direction: vertical, gap: md, children: [
201
+ { type: typography, variant: h4, content: @entity.title },
202
+ { type: progress-bar, value: (+ @entity.promptIndex 1), max: @entity.totalPrompts, showPercentage: false, label: (str/concat "Prompt " (str/concat "" (+ @entity.promptIndex 1)) " of " (str/concat "" @entity.totalPrompts)) },
203
+ { type: stack, direction: vertical, gap: md, children: @entity.view }
204
+ ] }
205
+ ]
206
+ })
207
+ (emit TAP_JUDGED { correct: @entity.found, promptIndex: @entity.promptIndex, tries: @entity.tries })
208
+
209
+ NEXT -> idle when (and (== @entity.active true) (and (== @entity.found true) (== @entity.done false)))
210
+ (let (
211
+ (nextIndex (+ @entity.promptIndex 1))
212
+ (finished (>= @nextIndex (array/len @config.prompts)))
213
+ (next (array/nth @config.prompts (math/min @nextIndex (- (array/len @config.prompts) 1))))
214
+ (circles (array/map @config.shapes (fn s { type: circle, id: (object/get @s id), x: (object/get @s x), y: (object/get @s y), radius: (object/get @s radius 22), color: (object/get @s color "#3b82f6"), fill: (object/get @s color "#3b82f6") })))
215
+ (labels (array/map @config.shapes (fn s { type: text, id: (str/concat "label-" (object/get @s id)), x: (object/get @s x), y: (+ (object/get @s y) (object/get @s radius 22) 14), text: (object/get @s label), fontSize: 12, align: "center", color: "#0f172a" })))
216
+ )
217
+ (do
218
+ (set @entity.promptIndex (if @finished @entity.promptIndex @nextIndex))
219
+ (set @entity.done (if @finished true false))
220
+ (set @entity.tries 0)
221
+ (set @entity.found false)
222
+ (set @entity.promptText (if @finished @entity.promptText (object/get @next prompt)))
223
+ (set @entity.targetId (if @finished @entity.targetId (object/get @next targetId)))
224
+ (set @entity.explanation (if @finished @entity.explanation (object/get @next explanation "")))
225
+ (set @entity.feedback "")
226
+ (set @entity.canvasShapes (array/concat @circles @labels))
227
+ (set @entity.view (if @finished
228
+ [
229
+ { type: typography, variant: h4, content: "Round Complete" },
230
+ { type: stat-display, label: "First-try score", value: @entity.score, max: @entity.totalPrompts },
231
+ { type: button, action: RETRY, label: @config.retryLabel, variant: primary }
232
+ ]
233
+ [
234
+ { type: card, variant: bordered, padding: md, children: [
235
+ { type: typography, variant: body, content: @entity.promptText }
236
+ ] },
237
+ { type: learning-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, shapes: @entity.canvasShapes, interactive: true, animate: false, onShapeClick: "TAP_SHAPE" },
238
+ { type: stack, direction: horizontal, gap: xs, children: [
239
+ { type: typography, variant: caption, content: @entity.feedback }
240
+ ] },
241
+ { type: stack, direction: horizontal, gap: sm, children: [
242
+ { type: button, action: NEXT, label: @config.nextLabel, variant: primary, disabled: (if @entity.found false true) }
243
+ ] },
244
+ { type: stat-display, label: "First-try score", value: @entity.score, max: @entity.totalPrompts }
245
+ ])))
246
+ )
247
+ (render-ui main {
248
+ type: box
249
+ padding: md
250
+ children: [
251
+ { type: stack, direction: vertical, gap: md, children: [
252
+ { type: typography, variant: h4, content: @entity.title },
253
+ { type: progress-bar, value: (+ @entity.promptIndex 1), max: @entity.totalPrompts, showPercentage: false, label: (str/concat "Prompt " (str/concat "" (+ @entity.promptIndex 1)) " of " (str/concat "" @entity.totalPrompts)) },
254
+ { type: stack, direction: vertical, gap: md, children: @entity.view }
255
+ ] }
256
+ ]
257
+ })
258
+ (emit PROMPT_ADVANCED { promptIndex: @entity.promptIndex, done: @entity.done, score: @entity.score })
259
+
260
+ RETRY -> idle when (and (== @entity.active true) (== @entity.done true))
261
+ (let (
262
+ (first (array/nth @config.prompts 0))
263
+ (circles (array/map @config.shapes (fn s { type: circle, id: (object/get @s id), x: (object/get @s x), y: (object/get @s y), radius: (object/get @s radius 22), color: (object/get @s color "#3b82f6"), fill: (object/get @s color "#3b82f6") })))
264
+ (labels (array/map @config.shapes (fn s { type: text, id: (str/concat "label-" (object/get @s id)), x: (object/get @s x), y: (+ (object/get @s y) (object/get @s radius 22) 14), text: (object/get @s label), fontSize: 12, align: "center", color: "#0f172a" })))
265
+ )
266
+ (do
267
+ (set @entity.promptIndex 0)
268
+ (set @entity.score 0)
269
+ (set @entity.tries 0)
270
+ (set @entity.found false)
271
+ (set @entity.done false)
272
+ (set @entity.promptText (object/get @first prompt))
273
+ (set @entity.targetId (object/get @first targetId))
274
+ (set @entity.explanation (object/get @first explanation ""))
275
+ (set @entity.feedback "")
276
+ (set @entity.canvasShapes (array/concat @circles @labels))
277
+ (set @entity.view [
278
+ { type: card, variant: bordered, padding: md, children: [
279
+ { type: typography, variant: body, content: @entity.promptText }
280
+ ] },
281
+ { type: learning-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, shapes: @entity.canvasShapes, interactive: true, animate: false, onShapeClick: "TAP_SHAPE" },
282
+ { type: stack, direction: horizontal, gap: xs, children: [
283
+ { type: typography, variant: caption, content: @entity.feedback }
284
+ ] },
285
+ { type: stack, direction: horizontal, gap: sm, children: [
286
+ { type: button, action: NEXT, label: @config.nextLabel, variant: primary, disabled: (if @entity.found false true) }
287
+ ] },
288
+ { type: stat-display, label: "First-try score", value: @entity.score, max: @entity.totalPrompts }
289
+ ])))
290
+ (render-ui main {
291
+ type: box
292
+ padding: md
293
+ children: [
294
+ { type: stack, direction: vertical, gap: md, children: [
295
+ { type: typography, variant: h4, content: @entity.title },
296
+ { type: progress-bar, value: (+ @entity.promptIndex 1), max: @entity.totalPrompts, showPercentage: false, label: (str/concat "Prompt " (str/concat "" (+ @entity.promptIndex 1)) " of " (str/concat "" @entity.totalPrompts)) },
297
+ { type: stack, direction: vertical, gap: md, children: @entity.view }
298
+ ] }
299
+ ]
300
+ })
301
+ (emit ROUND_RESTARTED { totalPrompts: @entity.totalPrompts })
302
+ }
303
+
304
+ listens {
305
+ MODE_SELECTED {
306
+ mode : string
307
+ }
308
+ @description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
309
+ @tier "domain"
310
+ TAP_SHAPE {
311
+ payload : { id : string type : string index : number! }
312
+ }
313
+ @description "Learner tapped a shape on the canvas — the canvas callback wraps its click info as payload { id, type, index }; the id names the hotspot (or its label) and the tap is judged against the current prompt's target."
314
+ @tier "domain"
315
+ NEXT {
316
+ }
317
+ @description "Learner advanced to the next prompt after finding the current target; past the last prompt this completes the round."
318
+ @tier "domain"
319
+ RETRY {
320
+ }
321
+ @description "Learner restarted the round from the first prompt with a cleared score."
322
+ @tier "domain"
323
+ }
324
+
325
+ emits {
326
+ TAP_JUDGED -> external {
327
+ correct : boolean
328
+ promptIndex : number
329
+ tries : number
330
+ }
331
+ @description "The verdict of a canvas tap: whether the right part was hit, which prompt it was, and how many misses preceded it — a composed listener (coach, grader) can subscribe; a harmless no-op when the atom runs standalone."
332
+ @synonyms "hotspot, tap the part, spatial identification, point at, label the diagram"
333
+ @tier "domain"
334
+ PROMPT_ADVANCED -> external {
335
+ promptIndex : number
336
+ done : boolean
337
+ score : number
338
+ }
339
+ @description "The learner moved to the next prompt (or completed the round) with the running first-try score."
340
+ @tier "domain"
341
+ ROUND_RESTARTED -> external {
342
+ totalPrompts : number
343
+ }
344
+ @description "The learner restarted the round from scratch."
345
+ @tier "domain"
346
+ }
347
+
348
+ config {
349
+ matchMode : string = ""
350
+ @label "Match Mode"
351
+ @description "When composed behind a lab router, the MODE_SELECTED mode value this instrument responds to. Empty string means standalone — render immediately on INIT."
352
+ @tier "presentation"
353
+ title : string = "Tap the Part"
354
+ @label "Title"
355
+ @description "Title shown above the canvas."
356
+ @tier "presentation"
357
+ shapes : [HotspotShape] = [
358
+ { id: "nucleus", label: "Nucleus", x: 300, y: 170, radius: 34, color: "#8b5cf6" },
359
+ { id: "membrane", label: "Membrane", x: 130, y: 120, radius: 26, color: "#3b82f6" },
360
+ { id: "mitochondrion", label: "Mitochondrion", x: 460, y: 120, radius: 24, color: "#f59e0b" },
361
+ { id: "ribosome", label: "Ribosome", x: 200, y: 280, radius: 18, color: "#16a34a" },
362
+ { id: "vacuole", label: "Vacuole", x: 420, y: 280, radius: 28, color: "#06b6d4" }
363
+ ]
364
+ @label "Hotspots"
365
+ @description "The labeled parts of the scene; each carries an id, display label, canvas position, radius, and color. Realizations declare their own subject's parts here."
366
+ @tier "domain"
367
+ prompts : [HotspotPrompt] = [
368
+ { prompt: "Tap the part that stores the cell's genetic material.", targetId: "nucleus", explanation: "The nucleus holds the DNA." },
369
+ { prompt: "Tap the part that produces the cell's energy.", targetId: "mitochondrion", explanation: "Mitochondria run cellular respiration." },
370
+ { prompt: "Tap the part that builds proteins.", targetId: "ribosome", explanation: "Ribosomes translate mRNA into protein." }
371
+ ]
372
+ @label "Prompts"
373
+ @description "The question roster; each prompt names a target hotspot id and an explanation revealed on a hit."
374
+ @tier "domain"
375
+ hitColor : string = "#16a34a"
376
+ @label "Hit Ring Color"
377
+ @description "Color of the highlight ring drawn around the target once found."
378
+ @tier "presentation"
379
+ nextLabel : string = "Next"
380
+ @label "Next Label"
381
+ @tier "presentation"
382
+ retryLabel : string = "Try Again"
383
+ @label "Retry Label"
384
+ @tier "presentation"
385
+ width : number = 600
386
+ @label "Width"
387
+ @tier "presentation"
388
+ height : number = 380
389
+ @label "Height"
390
+ @tier "presentation"
391
+ backgroundColor : string = "#f8fafc"
392
+ @label "Background Color"
393
+ @tier "presentation"
394
+ }
395
+ }
396
+
397
+ page "/learn-hotspot" as HotspotPage -> HotspotSim
398
+ }