@almadar/std 16.174.0 → 16.176.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.
- package/behaviors/lolo/ui/core/molecules/ui-table-view.lolo +4 -9
- package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +132 -22
- package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +428 -0
- package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +520 -0
- package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +805 -0
- package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +31 -2
- package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +186 -0
- package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +522 -0
- package/behaviors/lolo/ui/learning/atoms/ui-algo-graph-canvas.lolo +192 -0
- package/behaviors/lolo/ui/learning/atoms/ui-algorithm-canvas.lolo +111 -2
- package/behaviors/lolo/ui/learning/atoms/ui-biology-canvas.lolo +150 -1
- package/behaviors/lolo/ui/learning/atoms/ui-chemistry-canvas.lolo +132 -1
- package/behaviors/lolo/ui/learning/atoms/ui-learning-canvas.lolo +48 -1
- package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +141 -1
- package/behaviors/lolo/ui/learning/atoms/ui-physics-canvas.lolo +199 -1
- package/behaviors/registry/ui/core/molecules/ui-table-view.orb +4 -39
- package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1288 -197
- package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +2512 -0
- package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +3678 -0
- package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +6539 -0
- package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +173 -2
- package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +986 -0
- package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +3333 -0
- package/behaviors/registry/ui/learning/atoms/ui-algo-graph-canvas.orb +716 -0
- package/behaviors/registry/ui/learning/atoms/ui-algorithm-canvas.orb +506 -1
- package/behaviors/registry/ui/learning/atoms/ui-biology-canvas.orb +684 -0
- package/behaviors/registry/ui/learning/atoms/ui-chemistry-canvas.orb +634 -0
- package/behaviors/registry/ui/learning/atoms/ui-learning-canvas.orb +256 -0
- package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +736 -12
- package/behaviors/registry/ui/learning/atoms/ui-physics-canvas.orb +1061 -33
- package/dist/behaviors/exports-reader.js +2448 -2184
- package/dist/behaviors/functions/index.d.ts +800 -13
- package/dist/behaviors/functions/index.js +2431 -2185
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +2449 -2185
- package/dist/behaviors/query.js +2448 -2184
- package/dist/behaviors-embeddings.hash.json +3 -3
- package/dist/behaviors-embeddings.json +318 -294
- package/dist/behaviors-registry.json +4391 -1298
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2449 -2185
- package/dist/knob-embeddings.hash.json +3 -3
- package/dist/knob-embeddings.json +1 -1
- package/dist/registry/factory-signatures.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
app std-learn-tree "1.0.0"
|
|
2
|
+
"Generic ordered-tree builder instrument — inserts values one by one from a config-declared sequence, each value descends left/right by comparison against the current node and attaches as a leaf; the tree renders on algo-graph-canvas (layout: tree, auto-positioned from the parent/child edges), settled nodes green, the descent cursor red, the comparison trail amber. Traverse sweeps the tree to the chosen order (inorder/preorder/levelorder) one node per press or on auto-run, narrating the visit order so far. Vocabulary, sample values, colors, and labels are entirely config-declared, so the same mechanism serves any ordered-tree domain. Standalone by default; behind a lab router set matchMode and it renders only when MODE_SELECTED matches."
|
|
3
|
+
orbital LearnTreeOrbital {
|
|
4
|
+
type LearnTreeNode = {
|
|
5
|
+
id : string!
|
|
6
|
+
label : string
|
|
7
|
+
state : "unvisited" | "frontier" | "current" | "visited" | "goal" | "path"
|
|
8
|
+
}
|
|
9
|
+
type LearnTreeEdge = {
|
|
10
|
+
from : string!
|
|
11
|
+
to : string!
|
|
12
|
+
directed : boolean
|
|
13
|
+
label : string
|
|
14
|
+
state : "default" | "tree" | "relaxed" | "candidate" | "path"
|
|
15
|
+
}
|
|
16
|
+
type LearnTreeEntry = {
|
|
17
|
+
id : string!
|
|
18
|
+
value : number!
|
|
19
|
+
rank : number!
|
|
20
|
+
path : string
|
|
21
|
+
levelKey : number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
entity LearnTreeScene [runtime] {
|
|
25
|
+
id : string!
|
|
26
|
+
active : boolean = false
|
|
27
|
+
title : string
|
|
28
|
+
nodes : [LearnTreeNode]
|
|
29
|
+
edges : [LearnTreeEdge]
|
|
30
|
+
entries : [LearnTreeEntry]
|
|
31
|
+
cursor : number = 0
|
|
32
|
+
pending : number = 0
|
|
33
|
+
descending : boolean = false
|
|
34
|
+
trail : [string]
|
|
35
|
+
curId : string = ""
|
|
36
|
+
curVal : number = 0
|
|
37
|
+
curRank : number = 0
|
|
38
|
+
curRankOffset : number = 0
|
|
39
|
+
depth : number = 0
|
|
40
|
+
maxDepth : number = 0
|
|
41
|
+
count : number = 0
|
|
42
|
+
traversing : boolean = false
|
|
43
|
+
traversalOrder : [string]
|
|
44
|
+
sweptIds : [string]
|
|
45
|
+
done : boolean = false
|
|
46
|
+
running : boolean = false
|
|
47
|
+
narration : string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
trait LearnTreeSim -> @rebindable LearnTreeScene [interaction, instance] {
|
|
51
|
+
initial: idle
|
|
52
|
+
|
|
53
|
+
state idle {
|
|
54
|
+
INIT -> idle when (== @config.matchMode "")
|
|
55
|
+
(set @entity.active true)
|
|
56
|
+
(set @entity.title @config.title)
|
|
57
|
+
(set @entity.nodes [])
|
|
58
|
+
(set @entity.edges [])
|
|
59
|
+
(set @entity.entries [])
|
|
60
|
+
(set @entity.cursor 0)
|
|
61
|
+
(set @entity.descending false)
|
|
62
|
+
(set @entity.trail [])
|
|
63
|
+
(set @entity.depth 0)
|
|
64
|
+
(set @entity.maxDepth 0)
|
|
65
|
+
(set @entity.count 0)
|
|
66
|
+
(set @entity.traversing false)
|
|
67
|
+
(set @entity.traversalOrder [])
|
|
68
|
+
(set @entity.sweptIds [])
|
|
69
|
+
(set @entity.done false)
|
|
70
|
+
(set @entity.running @config.autoRun)
|
|
71
|
+
(set @entity.narration "Press Insert to add the next value; keep pressing to watch it descend.")
|
|
72
|
+
(render-ui main {
|
|
73
|
+
type: box
|
|
74
|
+
padding: md
|
|
75
|
+
children: [
|
|
76
|
+
{ type: stack, direction: vertical, gap: md, children: [
|
|
77
|
+
{ type: typography, variant: h4, content: @entity.title },
|
|
78
|
+
{ type: stack, direction: horizontal, gap: sm, children: [
|
|
79
|
+
{ type: button, action: INSERT_VALUE, label: "Insert", variant: primary },
|
|
80
|
+
{ type: button, action: DELETE_VALUE, label: "Delete", variant: secondary },
|
|
81
|
+
{ type: button, action: RUN_TRAVERSAL, label: "Traverse", variant: secondary },
|
|
82
|
+
{ type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
|
|
83
|
+
{ type: button, action: RESET, label: "Reset", variant: ghost }
|
|
84
|
+
] },
|
|
85
|
+
{ type: algo-graph-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, layout: tree, root: "n0", nodes: @entity.nodes, edges: @entity.edges },
|
|
86
|
+
{ type: typography, variant: body, content: @entity.narration }
|
|
87
|
+
] }
|
|
88
|
+
]
|
|
89
|
+
})
|
|
90
|
+
(emit TREE_UPDATED { nodes: @entity.nodes, edges: @entity.edges, count: @entity.count, narration: @entity.narration })
|
|
91
|
+
|
|
92
|
+
MODE_SELECTED -> idle when (== ?mode @config.matchMode)
|
|
93
|
+
(set @entity.active true)
|
|
94
|
+
(set @entity.title @config.title)
|
|
95
|
+
(set @entity.nodes [])
|
|
96
|
+
(set @entity.edges [])
|
|
97
|
+
(set @entity.entries [])
|
|
98
|
+
(set @entity.cursor 0)
|
|
99
|
+
(set @entity.descending false)
|
|
100
|
+
(set @entity.trail [])
|
|
101
|
+
(set @entity.depth 0)
|
|
102
|
+
(set @entity.maxDepth 0)
|
|
103
|
+
(set @entity.count 0)
|
|
104
|
+
(set @entity.traversing false)
|
|
105
|
+
(set @entity.traversalOrder [])
|
|
106
|
+
(set @entity.sweptIds [])
|
|
107
|
+
(set @entity.done false)
|
|
108
|
+
(set @entity.running @config.autoRun)
|
|
109
|
+
(set @entity.narration "Press Insert to add the next value; keep pressing to watch it descend.")
|
|
110
|
+
(render-ui main {
|
|
111
|
+
type: box
|
|
112
|
+
padding: md
|
|
113
|
+
children: [
|
|
114
|
+
{ type: stack, direction: vertical, gap: md, children: [
|
|
115
|
+
{ type: typography, variant: h4, content: @entity.title },
|
|
116
|
+
{ type: stack, direction: horizontal, gap: sm, children: [
|
|
117
|
+
{ type: button, action: INSERT_VALUE, label: "Insert", variant: primary },
|
|
118
|
+
{ type: button, action: DELETE_VALUE, label: "Delete", variant: secondary },
|
|
119
|
+
{ type: button, action: RUN_TRAVERSAL, label: "Traverse", variant: secondary },
|
|
120
|
+
{ type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
|
|
121
|
+
{ type: button, action: RESET, label: "Reset", variant: ghost }
|
|
122
|
+
] },
|
|
123
|
+
{ type: algo-graph-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, layout: tree, root: "n0", nodes: @entity.nodes, edges: @entity.edges },
|
|
124
|
+
{ type: typography, variant: body, content: @entity.narration }
|
|
125
|
+
] }
|
|
126
|
+
]
|
|
127
|
+
})
|
|
128
|
+
(emit TREE_UPDATED { nodes: @entity.nodes, edges: @entity.edges, count: @entity.count, narration: @entity.narration })
|
|
129
|
+
|
|
130
|
+
INSERT_VALUE -> idle when (and (== @entity.active true) (or @entity.descending (< @entity.cursor (array/len @config.values))))
|
|
131
|
+
(set @entity.traversing false)
|
|
132
|
+
(set @entity.traversalOrder [])
|
|
133
|
+
(set @entity.sweptIds [])
|
|
134
|
+
(set @entity.done false)
|
|
135
|
+
(if (not @entity.descending)
|
|
136
|
+
(let (
|
|
137
|
+
(v (array/nth @config.values @entity.cursor))
|
|
138
|
+
)
|
|
139
|
+
(if (== @entity.count 0)
|
|
140
|
+
(do
|
|
141
|
+
(set @entity.pending @v)
|
|
142
|
+
(set @entity.nodes (array/append @entity.nodes { id: "n0", label: (str/concat "" @v), state: "visited" }))
|
|
143
|
+
(set @entity.entries (array/append @entity.entries { id: "n0", value: @v, rank: 300, path: "", levelKey: 300 }))
|
|
144
|
+
(set @entity.count 1)
|
|
145
|
+
(set @entity.cursor 1)
|
|
146
|
+
(set @entity.narration (str/concat "insert " (str/concat "" @v) " — tree empty, becomes root" (if @config.balanced " · height 1 for 1 node" ""))))
|
|
147
|
+
(do
|
|
148
|
+
(set @entity.pending @v)
|
|
149
|
+
(set @entity.descending true)
|
|
150
|
+
(set @entity.trail [])
|
|
151
|
+
(set @entity.curId (object/get (array/first @entity.entries) id))
|
|
152
|
+
(set @entity.curVal (object/get (array/first @entity.entries) value))
|
|
153
|
+
(set @entity.curRank (object/get (array/first @entity.entries) rank))
|
|
154
|
+
(set @entity.curRankOffset 150)
|
|
155
|
+
(set @entity.depth 0)
|
|
156
|
+
(set @entity.cursor (+ @entity.cursor 1))
|
|
157
|
+
(set @entity.narration (str/concat "insert " (str/concat "" @v) (if @config.showComparisons " — compare at the root" ""))))))
|
|
158
|
+
(let (
|
|
159
|
+
(side (if (< @entity.pending @entity.curVal) "L" "R"))
|
|
160
|
+
(child (array/find @entity.edges (fn e (and (== (object/get @e from) @entity.curId) (== (object/get @e label) @side)))))
|
|
161
|
+
(newRank (if (== @side "L") (- @entity.curRank @entity.curRankOffset) (+ @entity.curRank @entity.curRankOffset)))
|
|
162
|
+
(newId (str/concat "n" (str/concat "" @entity.count)))
|
|
163
|
+
(newDepth (+ @entity.depth 1))
|
|
164
|
+
(newCount (+ @entity.count 1))
|
|
165
|
+
(newMax (math/max @entity.maxDepth @newDepth))
|
|
166
|
+
(cmpText (str/concat (str/concat "" @entity.pending) (if (== @side "L") " < " " >= ") (str/concat "" @entity.curVal)))
|
|
167
|
+
)
|
|
168
|
+
(if (== @child null)
|
|
169
|
+
(do
|
|
170
|
+
(set @entity.narration (str/concat (if @config.showComparisons (str/concat @cmpText " — ") "") "attach " (str/concat "" @entity.pending) " as a leaf" (if @config.balanced (str/concat " · height " (str/concat "" (+ @newMax 1)) " for " (str/concat "" @newCount) " nodes") "")))
|
|
171
|
+
(set @entity.nodes (array/append @entity.nodes { id: @newId, label: (str/concat "" @entity.pending), state: "visited" }))
|
|
172
|
+
(set @entity.edges (array/append @entity.edges { from: @entity.curId, to: @newId, directed: true, label: @side, state: "tree" }))
|
|
173
|
+
(set @entity.entries (array/append @entity.entries { id: @newId, value: @entity.pending, rank: @newRank, path: (str/concat (object/get (array/find @entity.entries (fn t (== (object/get @t id) @entity.curId))) path) @side), levelKey: (+ (* @newDepth 10000) @newRank) }))
|
|
174
|
+
(set @entity.count @newCount)
|
|
175
|
+
(set @entity.maxDepth @newMax)
|
|
176
|
+
(set @entity.descending false)
|
|
177
|
+
(set @entity.trail []))
|
|
178
|
+
(do
|
|
179
|
+
(set @entity.narration (str/concat (if @config.showComparisons (str/concat @cmpText " — ") "") "descend " (if (== @side "L") "left" "right")))
|
|
180
|
+
(set @entity.trail (array/append @entity.trail @entity.curId))
|
|
181
|
+
(set @entity.curId (object/get @child to))
|
|
182
|
+
(set @entity.curVal (object/get (array/find @entity.entries (fn t (== (object/get @t id) (object/get @child to)))) value))
|
|
183
|
+
(set @entity.curRank (object/get (array/find @entity.entries (fn t (== (object/get @t id) (object/get @child to)))) rank))
|
|
184
|
+
(set @entity.curRankOffset (/ @entity.curRankOffset 2))
|
|
185
|
+
(set @entity.depth @newDepth)))))
|
|
186
|
+
(set @entity.nodes (array/map @entity.nodes (fn n
|
|
187
|
+
(object/merge @n {
|
|
188
|
+
state: (if (and (== @entity.descending true) (== (object/get @n id) @entity.curId)) "current"
|
|
189
|
+
(if (and (== @entity.descending true) (array/includes @entity.trail (object/get @n id))) "frontier"
|
|
190
|
+
"visited"))
|
|
191
|
+
}))))
|
|
192
|
+
(set @entity.edges (array/map @entity.edges (fn e
|
|
193
|
+
(object/merge @e {
|
|
194
|
+
state: (if (and (== @entity.descending true) (== (object/get @e from) @entity.curId)) "candidate" "tree")
|
|
195
|
+
}))))
|
|
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: stack, direction: horizontal, gap: sm, children: [
|
|
203
|
+
{ type: button, action: INSERT_VALUE, label: "Insert", variant: primary },
|
|
204
|
+
{ type: button, action: DELETE_VALUE, label: "Delete", variant: secondary },
|
|
205
|
+
{ type: button, action: RUN_TRAVERSAL, label: "Traverse", variant: secondary },
|
|
206
|
+
{ type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
|
|
207
|
+
{ type: button, action: RESET, label: "Reset", variant: ghost }
|
|
208
|
+
] },
|
|
209
|
+
{ type: algo-graph-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, layout: tree, root: "n0", nodes: @entity.nodes, edges: @entity.edges },
|
|
210
|
+
{ type: typography, variant: body, content: @entity.narration }
|
|
211
|
+
] }
|
|
212
|
+
]
|
|
213
|
+
})
|
|
214
|
+
(emit TREE_UPDATED { nodes: @entity.nodes, edges: @entity.edges, count: @entity.count, narration: @entity.narration })
|
|
215
|
+
|
|
216
|
+
DELETE_VALUE -> idle when (and (== @entity.active true) (> @entity.count 0))
|
|
217
|
+
(let (
|
|
218
|
+
(lastId (object/get (array/last @entity.entries) id))
|
|
219
|
+
(lastVal (object/get (array/last @entity.entries) value))
|
|
220
|
+
)
|
|
221
|
+
(do
|
|
222
|
+
(set @entity.nodes (array/filter @entity.nodes (fn n (!= (object/get @n id) @lastId))))
|
|
223
|
+
(set @entity.edges (array/filter @entity.edges (fn e (and (!= (object/get @e to) @lastId) (!= (object/get @e from) @lastId)))))
|
|
224
|
+
(set @entity.entries (array/filter @entity.entries (fn t (!= (object/get @t id) @lastId))))
|
|
225
|
+
(set @entity.count (- @entity.count 1))
|
|
226
|
+
(set @entity.cursor (math/max (- @entity.cursor 1) 0))
|
|
227
|
+
(set @entity.descending false)
|
|
228
|
+
(set @entity.trail [])
|
|
229
|
+
(set @entity.traversing false)
|
|
230
|
+
(set @entity.traversalOrder [])
|
|
231
|
+
(set @entity.sweptIds [])
|
|
232
|
+
(set @entity.done false)
|
|
233
|
+
(set @entity.narration (str/concat "remove " (str/concat "" @lastVal) " — the most recently inserted value (undo); press Insert to re-insert"))))
|
|
234
|
+
(set @entity.nodes (array/map @entity.nodes (fn n (object/merge @n { state: "visited" }))))
|
|
235
|
+
(set @entity.edges (array/map @entity.edges (fn e (object/merge @e { state: "tree" }))))
|
|
236
|
+
(render-ui main {
|
|
237
|
+
type: box
|
|
238
|
+
padding: md
|
|
239
|
+
children: [
|
|
240
|
+
{ type: stack, direction: vertical, gap: md, children: [
|
|
241
|
+
{ type: typography, variant: h4, content: @entity.title },
|
|
242
|
+
{ type: stack, direction: horizontal, gap: sm, children: [
|
|
243
|
+
{ type: button, action: INSERT_VALUE, label: "Insert", variant: primary },
|
|
244
|
+
{ type: button, action: DELETE_VALUE, label: "Delete", variant: secondary },
|
|
245
|
+
{ type: button, action: RUN_TRAVERSAL, label: "Traverse", variant: secondary },
|
|
246
|
+
{ type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
|
|
247
|
+
{ type: button, action: RESET, label: "Reset", variant: ghost }
|
|
248
|
+
] },
|
|
249
|
+
{ type: algo-graph-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, layout: tree, root: "n0", nodes: @entity.nodes, edges: @entity.edges },
|
|
250
|
+
{ type: typography, variant: body, content: @entity.narration }
|
|
251
|
+
] }
|
|
252
|
+
]
|
|
253
|
+
})
|
|
254
|
+
(emit TREE_UPDATED { nodes: @entity.nodes, edges: @entity.edges, count: @entity.count, narration: @entity.narration })
|
|
255
|
+
|
|
256
|
+
RUN_TRAVERSAL -> idle when (and (== @entity.active true) (> @entity.count 0))
|
|
257
|
+
(if (or (== @entity.traversing false) (== @entity.done true))
|
|
258
|
+
(let (
|
|
259
|
+
(order (if (== @config.traversalKind "preorder")
|
|
260
|
+
(array/map (array/sort @entity.entries "path" "asc") (fn t (object/get @t id)))
|
|
261
|
+
(if (== @config.traversalKind "levelorder")
|
|
262
|
+
(array/map (array/sort @entity.entries "levelKey" "asc") (fn t (object/get @t id)))
|
|
263
|
+
(array/map (array/sort @entity.entries "value" "asc") (fn t (object/get @t id))))))
|
|
264
|
+
(firstId (array/first @order))
|
|
265
|
+
)
|
|
266
|
+
(let (
|
|
267
|
+
(firstVal (object/get (array/find @entity.entries (fn t (== (object/get @t id) @firstId))) value))
|
|
268
|
+
)
|
|
269
|
+
(do
|
|
270
|
+
(set @entity.traversalOrder @order)
|
|
271
|
+
(set @entity.sweptIds [@firstId])
|
|
272
|
+
(set @entity.traversing true)
|
|
273
|
+
(set @entity.done (== (array/len @order) 1))
|
|
274
|
+
(set @entity.narration (str/concat @config.traversalKind " traversal — visiting order so far: " (str/concat "" @firstVal))))))
|
|
275
|
+
(let (
|
|
276
|
+
(nextId (array/nth @entity.traversalOrder (array/len @entity.sweptIds)))
|
|
277
|
+
)
|
|
278
|
+
(let (
|
|
279
|
+
(newSwept (array/append @entity.sweptIds @nextId))
|
|
280
|
+
)
|
|
281
|
+
(do
|
|
282
|
+
(set @entity.sweptIds @newSwept)
|
|
283
|
+
(set @entity.done (>= (array/len @newSwept) (array/len @entity.traversalOrder)))
|
|
284
|
+
(set @entity.narration (str/concat @config.traversalKind " traversal — visiting order so far: " (str/join (array/map @newSwept (fn sid (str/concat "" (object/get (array/find @entity.entries (fn t (== (object/get @t id) @sid))) value)))) ", ")))))))
|
|
285
|
+
(set @entity.nodes (array/map @entity.nodes (fn n
|
|
286
|
+
(object/merge @n {
|
|
287
|
+
state: (if (and (== @entity.traversing true) (array/includes @entity.sweptIds (object/get @n id))) "path" "visited")
|
|
288
|
+
}))))
|
|
289
|
+
(set @entity.edges (array/map @entity.edges (fn e (object/merge @e { state: "tree" }))))
|
|
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: stack, direction: horizontal, gap: sm, children: [
|
|
297
|
+
{ type: button, action: INSERT_VALUE, label: "Insert", variant: primary },
|
|
298
|
+
{ type: button, action: DELETE_VALUE, label: "Delete", variant: secondary },
|
|
299
|
+
{ type: button, action: RUN_TRAVERSAL, label: "Traverse", variant: secondary },
|
|
300
|
+
{ type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
|
|
301
|
+
{ type: button, action: RESET, label: "Reset", variant: ghost }
|
|
302
|
+
] },
|
|
303
|
+
{ type: algo-graph-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, layout: tree, root: "n0", nodes: @entity.nodes, edges: @entity.edges },
|
|
304
|
+
{ type: typography, variant: body, content: @entity.narration }
|
|
305
|
+
] }
|
|
306
|
+
]
|
|
307
|
+
})
|
|
308
|
+
(emit TREE_UPDATED { nodes: @entity.nodes, edges: @entity.edges, count: @entity.count, narration: @entity.narration })
|
|
309
|
+
|
|
310
|
+
TOGGLE_RUN -> idle when (== @entity.active true)
|
|
311
|
+
(set @entity.running (not @entity.running))
|
|
312
|
+
(set @entity.narration (if @entity.running "Auto-stepping the traversal — tick sweeps one node at a time." "Paused — step the traversal manually with Traverse."))
|
|
313
|
+
(render-ui main {
|
|
314
|
+
type: box
|
|
315
|
+
padding: md
|
|
316
|
+
children: [
|
|
317
|
+
{ type: stack, direction: vertical, gap: md, children: [
|
|
318
|
+
{ type: typography, variant: h4, content: @entity.title },
|
|
319
|
+
{ type: stack, direction: horizontal, gap: sm, children: [
|
|
320
|
+
{ type: button, action: INSERT_VALUE, label: "Insert", variant: primary },
|
|
321
|
+
{ type: button, action: DELETE_VALUE, label: "Delete", variant: secondary },
|
|
322
|
+
{ type: button, action: RUN_TRAVERSAL, label: "Traverse", variant: secondary },
|
|
323
|
+
{ type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
|
|
324
|
+
{ type: button, action: RESET, label: "Reset", variant: ghost }
|
|
325
|
+
] },
|
|
326
|
+
{ type: algo-graph-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, layout: tree, root: "n0", nodes: @entity.nodes, edges: @entity.edges },
|
|
327
|
+
{ type: typography, variant: body, content: @entity.narration }
|
|
328
|
+
] }
|
|
329
|
+
]
|
|
330
|
+
})
|
|
331
|
+
(emit TREE_UPDATED { nodes: @entity.nodes, edges: @entity.edges, count: @entity.count, narration: @entity.narration })
|
|
332
|
+
|
|
333
|
+
RESET -> idle when (== @entity.active true)
|
|
334
|
+
(set @entity.nodes [])
|
|
335
|
+
(set @entity.edges [])
|
|
336
|
+
(set @entity.entries [])
|
|
337
|
+
(set @entity.cursor 0)
|
|
338
|
+
(set @entity.descending false)
|
|
339
|
+
(set @entity.trail [])
|
|
340
|
+
(set @entity.depth 0)
|
|
341
|
+
(set @entity.maxDepth 0)
|
|
342
|
+
(set @entity.count 0)
|
|
343
|
+
(set @entity.traversing false)
|
|
344
|
+
(set @entity.traversalOrder [])
|
|
345
|
+
(set @entity.sweptIds [])
|
|
346
|
+
(set @entity.done false)
|
|
347
|
+
(set @entity.running @config.autoRun)
|
|
348
|
+
(set @entity.narration "Press Insert to add the next value; keep pressing to watch it descend.")
|
|
349
|
+
(render-ui main {
|
|
350
|
+
type: box
|
|
351
|
+
padding: md
|
|
352
|
+
children: [
|
|
353
|
+
{ type: stack, direction: vertical, gap: md, children: [
|
|
354
|
+
{ type: typography, variant: h4, content: @entity.title },
|
|
355
|
+
{ type: stack, direction: horizontal, gap: sm, children: [
|
|
356
|
+
{ type: button, action: INSERT_VALUE, label: "Insert", variant: primary },
|
|
357
|
+
{ type: button, action: DELETE_VALUE, label: "Delete", variant: secondary },
|
|
358
|
+
{ type: button, action: RUN_TRAVERSAL, label: "Traverse", variant: secondary },
|
|
359
|
+
{ type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
|
|
360
|
+
{ type: button, action: RESET, label: "Reset", variant: ghost }
|
|
361
|
+
] },
|
|
362
|
+
{ type: algo-graph-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, layout: tree, root: "n0", nodes: @entity.nodes, edges: @entity.edges },
|
|
363
|
+
{ type: typography, variant: body, content: @entity.narration }
|
|
364
|
+
] }
|
|
365
|
+
]
|
|
366
|
+
})
|
|
367
|
+
(emit TREE_UPDATED { nodes: @entity.nodes, edges: @entity.edges, count: @entity.count, narration: @entity.narration })
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
ticks {
|
|
371
|
+
treeTick every 500ms
|
|
372
|
+
when (and (== @entity.active true) (and (== @entity.running true) (not (== @entity.done true))))
|
|
373
|
+
(if (or (== @entity.traversing false) (== @entity.done true))
|
|
374
|
+
(let (
|
|
375
|
+
(order (if (== @config.traversalKind "preorder")
|
|
376
|
+
(array/map (array/sort @entity.entries "path" "asc") (fn t (object/get @t id)))
|
|
377
|
+
(if (== @config.traversalKind "levelorder")
|
|
378
|
+
(array/map (array/sort @entity.entries "levelKey" "asc") (fn t (object/get @t id)))
|
|
379
|
+
(array/map (array/sort @entity.entries "value" "asc") (fn t (object/get @t id))))))
|
|
380
|
+
(firstId (array/first @order))
|
|
381
|
+
)
|
|
382
|
+
(let (
|
|
383
|
+
(firstVal (object/get (array/find @entity.entries (fn t (== (object/get @t id) @firstId))) value))
|
|
384
|
+
)
|
|
385
|
+
(do
|
|
386
|
+
(set @entity.traversalOrder @order)
|
|
387
|
+
(set @entity.sweptIds [@firstId])
|
|
388
|
+
(set @entity.traversing true)
|
|
389
|
+
(set @entity.done (== (array/len @order) 1))
|
|
390
|
+
(set @entity.narration (str/concat @config.traversalKind " traversal — visiting order so far: " (str/concat "" @firstVal))))))
|
|
391
|
+
(let (
|
|
392
|
+
(nextId (array/nth @entity.traversalOrder (array/len @entity.sweptIds)))
|
|
393
|
+
)
|
|
394
|
+
(let (
|
|
395
|
+
(newSwept (array/append @entity.sweptIds @nextId))
|
|
396
|
+
)
|
|
397
|
+
(do
|
|
398
|
+
(set @entity.sweptIds @newSwept)
|
|
399
|
+
(set @entity.done (>= (array/len @newSwept) (array/len @entity.traversalOrder)))
|
|
400
|
+
(set @entity.narration (str/concat @config.traversalKind " traversal — visiting order so far: " (str/join (array/map @newSwept (fn sid (str/concat "" (object/get (array/find @entity.entries (fn t (== (object/get @t id) @sid))) value)))) ", ")))))))
|
|
401
|
+
(set @entity.nodes (array/map @entity.nodes (fn n
|
|
402
|
+
(object/merge @n {
|
|
403
|
+
state: (if (and (== @entity.traversing true) (array/includes @entity.sweptIds (object/get @n id))) "path" "visited")
|
|
404
|
+
}))))
|
|
405
|
+
(set @entity.edges (array/map @entity.edges (fn e (object/merge @e { state: "tree" }))))
|
|
406
|
+
(render-ui main {
|
|
407
|
+
type: box
|
|
408
|
+
padding: md
|
|
409
|
+
children: [
|
|
410
|
+
{ type: stack, direction: vertical, gap: md, children: [
|
|
411
|
+
{ type: typography, variant: h4, content: @entity.title },
|
|
412
|
+
{ type: stack, direction: horizontal, gap: sm, children: [
|
|
413
|
+
{ type: button, action: INSERT_VALUE, label: "Insert", variant: primary },
|
|
414
|
+
{ type: button, action: DELETE_VALUE, label: "Delete", variant: secondary },
|
|
415
|
+
{ type: button, action: RUN_TRAVERSAL, label: "Traverse", variant: secondary },
|
|
416
|
+
{ type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
|
|
417
|
+
{ type: button, action: RESET, label: "Reset", variant: ghost }
|
|
418
|
+
] },
|
|
419
|
+
{ type: algo-graph-canvas, width: @config.width, height: @config.height, backgroundColor: @config.backgroundColor, layout: tree, root: "n0", nodes: @entity.nodes, edges: @entity.edges },
|
|
420
|
+
{ type: typography, variant: body, content: @entity.narration }
|
|
421
|
+
] }
|
|
422
|
+
]
|
|
423
|
+
})
|
|
424
|
+
(emit TREE_UPDATED { nodes: @entity.nodes, edges: @entity.edges, count: @entity.count, narration: @entity.narration })
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
listens {
|
|
428
|
+
MODE_SELECTED {
|
|
429
|
+
mode : string
|
|
430
|
+
}
|
|
431
|
+
@description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
|
|
432
|
+
@tier "domain"
|
|
433
|
+
INSERT_VALUE {
|
|
434
|
+
}
|
|
435
|
+
@description "Insert the next config-declared value: descends one comparison step per press from the root, coloring the descent cursor and comparison trail, until it attaches as a leaf. Pressing again while a descent is in flight continues that same descent."
|
|
436
|
+
@synonyms "insert, add, next value, descend, attach"
|
|
437
|
+
@tier "domain"
|
|
438
|
+
DELETE_VALUE {
|
|
439
|
+
}
|
|
440
|
+
@description "Undo the most recently inserted value — removes its node/edge/entry and rewinds the insert cursor by one so the same value can be re-inserted."
|
|
441
|
+
@synonyms "delete, remove, undo, rewind"
|
|
442
|
+
@tier "domain"
|
|
443
|
+
RUN_TRAVERSAL {
|
|
444
|
+
}
|
|
445
|
+
@description "Advance the traversal sweep one node at a time in the configured order (inorder/preorder/levelorder); pressing after the sweep completes starts a fresh sweep."
|
|
446
|
+
@synonyms "traverse, sweep, visit, walk, in-order, pre-order, level-order"
|
|
447
|
+
@tier "domain"
|
|
448
|
+
TOGGLE_RUN {
|
|
449
|
+
}
|
|
450
|
+
@description "Toggle auto-stepping: while running, the tick sweeps the traversal one node forward every step. Has no effect on Insert/Delete, which stay manual."
|
|
451
|
+
@synonyms "run, pause, auto, auto-step"
|
|
452
|
+
@tier "domain"
|
|
453
|
+
RESET {
|
|
454
|
+
}
|
|
455
|
+
@description "Clear the tree back to empty and restore the insert cursor and running flag from config, ready to insert from the first value again."
|
|
456
|
+
@synonyms "reset, clear, restart, start over"
|
|
457
|
+
@tier "domain"
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
emits {
|
|
461
|
+
TREE_UPDATED -> external {
|
|
462
|
+
nodes : [LearnTreeNode]
|
|
463
|
+
edges : [LearnTreeEdge]
|
|
464
|
+
count : number
|
|
465
|
+
narration : string
|
|
466
|
+
}
|
|
467
|
+
@description "The tree after each insert/delete/traversal step — a composed listener (readout, grader) can subscribe; a harmless no-op when the atom runs standalone."
|
|
468
|
+
@synonyms "tree update, insert result, traversal result, node count"
|
|
469
|
+
@tier "domain"
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
config {
|
|
473
|
+
matchMode : string = ""
|
|
474
|
+
@label "Match Mode"
|
|
475
|
+
@description "When composed behind a lab router, the MODE_SELECTED mode value this instrument responds to. Empty string means standalone — render immediately on INIT."
|
|
476
|
+
@tier "presentation"
|
|
477
|
+
autoRun : boolean = false
|
|
478
|
+
@label "Auto Run"
|
|
479
|
+
@description "When true, the tick auto-steps the traversal sweep started by Traverse; toggle live with the Run/Pause button. Has no effect on Insert/Delete, which stay manual."
|
|
480
|
+
@synonyms "auto run, auto step, auto play"
|
|
481
|
+
@tier "domain"
|
|
482
|
+
values : [number] = [50, 30, 70, 20, 40, 60, 80]
|
|
483
|
+
@label "Values"
|
|
484
|
+
@description "The values Insert feeds into the tree, one per descent, in order."
|
|
485
|
+
@synonyms "keys, values to insert, dataset, sequence"
|
|
486
|
+
@tier "domain"
|
|
487
|
+
traversalKind : string = "inorder"
|
|
488
|
+
@label "Traversal Kind"
|
|
489
|
+
@description "Which sweep Traverse replays — inorder (sorted order, left to right), preorder (root before children), or levelorder (breadth first, top to bottom)."
|
|
490
|
+
@synonyms "traversal, in-order, pre-order, level-order, sweep"
|
|
491
|
+
@tier "domain"
|
|
492
|
+
showComparisons : boolean = true
|
|
493
|
+
@label "Show Comparisons"
|
|
494
|
+
@description "Include each left/right comparison in the narration while a value descends; turn off for a terse attach-only narration."
|
|
495
|
+
@synonyms "comparisons, narration detail"
|
|
496
|
+
@tier "presentation"
|
|
497
|
+
balanced : boolean = false
|
|
498
|
+
@label "Balance Readout"
|
|
499
|
+
@description "When true, each insert's narration reports the tree's current height against its node count so the learner can judge balance; no auto-rotation is performed."
|
|
500
|
+
@synonyms "balance, height, balance readout"
|
|
501
|
+
@tier "domain"
|
|
502
|
+
title : string = "Ordered Tree"
|
|
503
|
+
@label "Title"
|
|
504
|
+
@description "Title shown above the canvas."
|
|
505
|
+
@tier "presentation"
|
|
506
|
+
width : number = 600
|
|
507
|
+
@label "Width"
|
|
508
|
+
@description "Canvas width in pixels."
|
|
509
|
+
@tier "presentation"
|
|
510
|
+
height : number = 400
|
|
511
|
+
@label "Height"
|
|
512
|
+
@description "Canvas height in pixels."
|
|
513
|
+
@tier "presentation"
|
|
514
|
+
backgroundColor : string = "#f8fafc"
|
|
515
|
+
@label "Background Color"
|
|
516
|
+
@description "Canvas background color."
|
|
517
|
+
@tier "presentation"
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
page "/learn-tree" as LearnTreePage -> LearnTreeSim
|
|
522
|
+
}
|