@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.
Files changed (45) hide show
  1. package/behaviors/lolo/ui/core/molecules/ui-table-view.lolo +4 -9
  2. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +132 -22
  3. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +428 -0
  4. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +520 -0
  5. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +805 -0
  6. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +31 -2
  7. package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +186 -0
  8. package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +522 -0
  9. package/behaviors/lolo/ui/learning/atoms/ui-algo-graph-canvas.lolo +192 -0
  10. package/behaviors/lolo/ui/learning/atoms/ui-algorithm-canvas.lolo +111 -2
  11. package/behaviors/lolo/ui/learning/atoms/ui-biology-canvas.lolo +150 -1
  12. package/behaviors/lolo/ui/learning/atoms/ui-chemistry-canvas.lolo +132 -1
  13. package/behaviors/lolo/ui/learning/atoms/ui-learning-canvas.lolo +48 -1
  14. package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +141 -1
  15. package/behaviors/lolo/ui/learning/atoms/ui-physics-canvas.lolo +199 -1
  16. package/behaviors/registry/ui/core/molecules/ui-table-view.orb +4 -39
  17. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1288 -197
  18. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +2512 -0
  19. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +3678 -0
  20. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +6539 -0
  21. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +173 -2
  22. package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +986 -0
  23. package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +3333 -0
  24. package/behaviors/registry/ui/learning/atoms/ui-algo-graph-canvas.orb +716 -0
  25. package/behaviors/registry/ui/learning/atoms/ui-algorithm-canvas.orb +506 -1
  26. package/behaviors/registry/ui/learning/atoms/ui-biology-canvas.orb +684 -0
  27. package/behaviors/registry/ui/learning/atoms/ui-chemistry-canvas.orb +634 -0
  28. package/behaviors/registry/ui/learning/atoms/ui-learning-canvas.orb +256 -0
  29. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +736 -12
  30. package/behaviors/registry/ui/learning/atoms/ui-physics-canvas.orb +1061 -33
  31. package/dist/behaviors/exports-reader.js +2448 -2184
  32. package/dist/behaviors/functions/index.d.ts +800 -13
  33. package/dist/behaviors/functions/index.js +2431 -2185
  34. package/dist/behaviors/index.d.ts +1 -1
  35. package/dist/behaviors/index.js +2449 -2185
  36. package/dist/behaviors/query.js +2448 -2184
  37. package/dist/behaviors-embeddings.hash.json +3 -3
  38. package/dist/behaviors-embeddings.json +318 -294
  39. package/dist/behaviors-registry.json +4391 -1298
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.js +2449 -2185
  42. package/dist/knob-embeddings.hash.json +3 -3
  43. package/dist/knob-embeddings.json +1 -1
  44. package/dist/registry/factory-signatures.json +1 -1
  45. package/package.json +2 -2
@@ -0,0 +1,428 @@
1
+ app std-learn-derivation "1.0.0"
2
+ "Generic goal-directed derivation/proof-builder instrument — config-declared starting lines (facts and two-sided links) sit in a scrollable derivation panel with per-line badges, a roster of config-declared candidate steps render as buttons, and picking one dynamically matches it against the CURRENT lines (a left/right side of some link plus a same/opposite negation sense) to derive a new line, appending it with config-declared badge styling on success or surfacing a config-declared feedback string on failure; an inline clock can auto-apply the first applicable candidate every autoApplyIntervalMs while running, a Reset restores the starting lines, and the goal is reached when a derived line's label matches the config-declared goal label. Left/right and same/opposite are structural roles, not domain vocabulary, so the same matching mechanism serves propositional inference, chained implications, precursor/product chemistry, or any other two-sided-relation derivation on a biology-canvas-free text panel. Standalone by default; behind a lab router set matchMode and it renders only when MODE_SELECTED matches."
3
+ orbital LearnDerivationOrbital {
4
+ type LearnDerivationLine = {
5
+ id : string!
6
+ label : string!
7
+ kind : "fact" | "link"!
8
+ subject : string
9
+ negated : boolean
10
+ leftSubject : string
11
+ leftNegated : boolean
12
+ rightSubject : string
13
+ rightNegated : boolean
14
+ badgeLabel : string
15
+ badgeVariant : string
16
+ }
17
+ type LearnDerivationCandidate = {
18
+ key : string!
19
+ label : string!
20
+ matchSide : "left" | "right"!
21
+ matchNegationMode : "same" | "opposite"!
22
+ deriveSide : "left" | "right"!
23
+ deriveNegationMode : "same" | "invert"!
24
+ resultBadgeLabel : string
25
+ resultBadgeVariant : string
26
+ noMatchFeedback : string
27
+ }
28
+
29
+ entity LearnDerivationScene [runtime] {
30
+ id : string!
31
+ active : boolean = false
32
+ title : string
33
+ lines : [LearnDerivationLine]
34
+ feedback : string
35
+ goalReached : boolean = false
36
+ running : boolean = false
37
+ elapsedMs : number = 0
38
+ narration : string
39
+ renderChildren : [node]
40
+ }
41
+
42
+ trait LearnDerivationSim -> @rebindable LearnDerivationScene [interaction, instance] {
43
+ initial: idle
44
+
45
+ state idle {
46
+ INIT -> idle when (== @config.matchMode "")
47
+ (set @entity.active true)
48
+ (set @entity.title @config.title)
49
+ (set @entity.feedback "")
50
+ (set @entity.narration "")
51
+ (set @entity.goalReached false)
52
+ (set @entity.running @config.autoRun)
53
+ (set @entity.elapsedMs 0)
54
+ (set @entity.lines @config.premises)
55
+ (set @entity.renderChildren [
56
+ { type: typography, variant: h4, content: @entity.title },
57
+ { type: typography, variant: body, content: (str/concat @config.goalPrefix @config.goalLabel) },
58
+ { type: stack, direction: vertical, gap: xs, style: { maxHeight: (str/concat @config.height "px"), overflowY: "auto" }, children: (array/map @entity.lines (fn line
59
+ { type: stack, direction: horizontal, gap: sm, children: [
60
+ { type: badge, label: @line.badgeLabel, variant: @line.badgeVariant },
61
+ { type: typography, variant: body, content: @line.label }
62
+ ] }
63
+ )) },
64
+ { type: stack, direction: horizontal, gap: sm, children: (array/map @config.candidates (fn c { type: button, action: APPLY_STEP, actionPayload: { candidateKey: @c.key }, label: @c.label, variant: primary })) },
65
+ { type: stack, direction: horizontal, gap: sm, children: [
66
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
67
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
68
+ ] },
69
+ { type: typography, variant: caption, content: @entity.narration },
70
+ { type: typography, variant: caption, content: (if @entity.goalReached @config.goalReachedMessage "") }
71
+ ])
72
+ (render-ui main {
73
+ type: box
74
+ padding: md
75
+ maxWidth: (str/concat @config.width "px")
76
+ children: [
77
+ { type: stack, direction: vertical, gap: md, children: @entity.renderChildren }
78
+ ]
79
+ })
80
+
81
+ MODE_SELECTED -> idle when (== ?mode @config.matchMode)
82
+ (set @entity.active true)
83
+ (set @entity.title @config.title)
84
+ (set @entity.feedback "")
85
+ (set @entity.narration "")
86
+ (set @entity.goalReached false)
87
+ (set @entity.running @config.autoRun)
88
+ (set @entity.elapsedMs 0)
89
+ (set @entity.lines @config.premises)
90
+ (set @entity.renderChildren [
91
+ { type: typography, variant: h4, content: @entity.title },
92
+ { type: typography, variant: body, content: (str/concat @config.goalPrefix @config.goalLabel) },
93
+ { type: stack, direction: vertical, gap: xs, style: { maxHeight: (str/concat @config.height "px"), overflowY: "auto" }, children: (array/map @entity.lines (fn line
94
+ { type: stack, direction: horizontal, gap: sm, children: [
95
+ { type: badge, label: @line.badgeLabel, variant: @line.badgeVariant },
96
+ { type: typography, variant: body, content: @line.label }
97
+ ] }
98
+ )) },
99
+ { type: stack, direction: horizontal, gap: sm, children: (array/map @config.candidates (fn c { type: button, action: APPLY_STEP, actionPayload: { candidateKey: @c.key }, label: @c.label, variant: primary })) },
100
+ { type: stack, direction: horizontal, gap: sm, children: [
101
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
102
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
103
+ ] },
104
+ { type: typography, variant: caption, content: @entity.narration },
105
+ { type: typography, variant: caption, content: (if @entity.goalReached @config.goalReachedMessage "") }
106
+ ])
107
+ (render-ui main {
108
+ type: box
109
+ padding: md
110
+ maxWidth: (str/concat @config.width "px")
111
+ children: [
112
+ { type: stack, direction: vertical, gap: md, children: @entity.renderChildren }
113
+ ]
114
+ })
115
+
116
+ APPLY_STEP -> idle when (== @entity.active true)
117
+ (let (
118
+ (candidate (array/first (array/filter @config.candidates (fn c (== @c.key ?candidateKey)))))
119
+ (links (array/filter @entity.lines (fn l (== @l.kind "link"))))
120
+ (facts (array/filter @entity.lines (fn l (== @l.kind "fact"))))
121
+ (wantLeft (== @candidate.matchSide "left"))
122
+ (matchSense (== @candidate.matchNegationMode "same"))
123
+ (activeLink (array/first (array/filter @links (fn l (let (
124
+ (linkSubject (if @wantLeft @l.leftSubject @l.rightSubject))
125
+ (linkNegated (if @wantLeft @l.leftNegated @l.rightNegated))
126
+ (requiredNegated (if @matchSense @linkNegated (not @linkNegated)))
127
+ )
128
+ (array/some @facts (fn f (and (== @f.subject @linkSubject) (== @f.negated @requiredNegated)))))))))
129
+ (deriveLeft (== @candidate.deriveSide "left"))
130
+ (deriveSense (== @candidate.deriveNegationMode "same"))
131
+ (derivedSubject (if (!= @activeLink null) (if @deriveLeft @activeLink.leftSubject @activeLink.rightSubject) ""))
132
+ (derivedSideNegated (if (!= @activeLink null) (if @deriveLeft @activeLink.leftNegated @activeLink.rightNegated) false))
133
+ (derivedNegated (if @deriveSense @derivedSideNegated (not @derivedSideNegated)))
134
+ (derivedLabel (if (!= @activeLink null) (if @derivedNegated (str/concat @config.negationSymbol @derivedSubject) @derivedSubject) ""))
135
+ (alreadyPresent (array/some @entity.lines (fn l (== @l.label @derivedLabel))))
136
+ (canDerive (and (!= @activeLink null) (not @alreadyPresent)))
137
+ )
138
+ (do
139
+ (set @entity.lines (if @canDerive
140
+ (array/concat @entity.lines [{ id: (str/concat "line-" (array/len @entity.lines)), label: @derivedLabel, kind: "fact", subject: @derivedSubject, negated: @derivedNegated, badgeLabel: @candidate.resultBadgeLabel, badgeVariant: @candidate.resultBadgeVariant }])
141
+ @entity.lines))
142
+ (set @entity.feedback (if @canDerive
143
+ (str/concat "Derived " @derivedLabel " via " @candidate.label ".")
144
+ (if @alreadyPresent
145
+ (str/concat @derivedLabel " is already in the derivation.")
146
+ @candidate.noMatchFeedback)))
147
+ (set @entity.goalReached (if @canDerive (== @derivedLabel @config.goalLabel) @entity.goalReached))
148
+ (set @entity.narration @entity.feedback)
149
+ )
150
+ )
151
+ (set @entity.renderChildren [
152
+ { type: typography, variant: h4, content: @entity.title },
153
+ { type: typography, variant: body, content: (str/concat @config.goalPrefix @config.goalLabel) },
154
+ { type: stack, direction: vertical, gap: xs, style: { maxHeight: (str/concat @config.height "px"), overflowY: "auto" }, children: (array/map @entity.lines (fn line
155
+ { type: stack, direction: horizontal, gap: sm, children: [
156
+ { type: badge, label: @line.badgeLabel, variant: @line.badgeVariant },
157
+ { type: typography, variant: body, content: @line.label }
158
+ ] }
159
+ )) },
160
+ { type: stack, direction: horizontal, gap: sm, children: (array/map @config.candidates (fn c { type: button, action: APPLY_STEP, actionPayload: { candidateKey: @c.key }, label: @c.label, variant: primary })) },
161
+ { type: stack, direction: horizontal, gap: sm, children: [
162
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
163
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
164
+ ] },
165
+ { type: typography, variant: caption, content: @entity.narration },
166
+ { type: typography, variant: caption, content: (if @entity.goalReached @config.goalReachedMessage "") }
167
+ ])
168
+ (render-ui main {
169
+ type: box
170
+ padding: md
171
+ maxWidth: (str/concat @config.width "px")
172
+ children: [
173
+ { type: stack, direction: vertical, gap: md, children: @entity.renderChildren }
174
+ ]
175
+ })
176
+ (emit DERIVATION_UPDATED { feedback: @entity.feedback, goalReached: @entity.goalReached, lineCount: (array/len @entity.lines), narration: @entity.narration })
177
+
178
+ TOGGLE_RUN -> idle when (== @entity.active true)
179
+ (set @entity.running (not @entity.running))
180
+ (set @entity.feedback (if @entity.running @config.autoRunOnMessage @config.autoRunOffMessage))
181
+ (set @entity.narration @entity.feedback)
182
+ (set @entity.renderChildren [
183
+ { type: typography, variant: h4, content: @entity.title },
184
+ { type: typography, variant: body, content: (str/concat @config.goalPrefix @config.goalLabel) },
185
+ { type: stack, direction: vertical, gap: xs, style: { maxHeight: (str/concat @config.height "px"), overflowY: "auto" }, children: (array/map @entity.lines (fn line
186
+ { type: stack, direction: horizontal, gap: sm, children: [
187
+ { type: badge, label: @line.badgeLabel, variant: @line.badgeVariant },
188
+ { type: typography, variant: body, content: @line.label }
189
+ ] }
190
+ )) },
191
+ { type: stack, direction: horizontal, gap: sm, children: (array/map @config.candidates (fn c { type: button, action: APPLY_STEP, actionPayload: { candidateKey: @c.key }, label: @c.label, variant: primary })) },
192
+ { type: stack, direction: horizontal, gap: sm, children: [
193
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
194
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
195
+ ] },
196
+ { type: typography, variant: caption, content: @entity.narration },
197
+ { type: typography, variant: caption, content: (if @entity.goalReached @config.goalReachedMessage "") }
198
+ ])
199
+ (render-ui main {
200
+ type: box
201
+ padding: md
202
+ maxWidth: (str/concat @config.width "px")
203
+ children: [
204
+ { type: stack, direction: vertical, gap: md, children: @entity.renderChildren }
205
+ ]
206
+ })
207
+ (emit DERIVATION_UPDATED { feedback: @entity.feedback, goalReached: @entity.goalReached, lineCount: (array/len @entity.lines), narration: @entity.narration })
208
+
209
+ RESET -> idle when (== @entity.active true)
210
+ (set @entity.lines @config.premises)
211
+ (set @entity.feedback "")
212
+ (set @entity.narration "")
213
+ (set @entity.goalReached false)
214
+ (set @entity.running @config.autoRun)
215
+ (set @entity.elapsedMs 0)
216
+ (set @entity.renderChildren [
217
+ { type: typography, variant: h4, content: @entity.title },
218
+ { type: typography, variant: body, content: (str/concat @config.goalPrefix @config.goalLabel) },
219
+ { type: stack, direction: vertical, gap: xs, style: { maxHeight: (str/concat @config.height "px"), overflowY: "auto" }, children: (array/map @entity.lines (fn line
220
+ { type: stack, direction: horizontal, gap: sm, children: [
221
+ { type: badge, label: @line.badgeLabel, variant: @line.badgeVariant },
222
+ { type: typography, variant: body, content: @line.label }
223
+ ] }
224
+ )) },
225
+ { type: stack, direction: horizontal, gap: sm, children: (array/map @config.candidates (fn c { type: button, action: APPLY_STEP, actionPayload: { candidateKey: @c.key }, label: @c.label, variant: primary })) },
226
+ { type: stack, direction: horizontal, gap: sm, children: [
227
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
228
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
229
+ ] },
230
+ { type: typography, variant: caption, content: @entity.narration },
231
+ { type: typography, variant: caption, content: (if @entity.goalReached @config.goalReachedMessage "") }
232
+ ])
233
+ (render-ui main {
234
+ type: box
235
+ padding: md
236
+ maxWidth: (str/concat @config.width "px")
237
+ children: [
238
+ { type: stack, direction: vertical, gap: md, children: @entity.renderChildren }
239
+ ]
240
+ })
241
+ (emit DERIVATION_UPDATED { feedback: @entity.feedback, goalReached: @entity.goalReached, lineCount: (array/len @entity.lines), narration: @entity.narration })
242
+ }
243
+
244
+ ticks {
245
+ derivationTick every 32ms
246
+ when (and (== @entity.active true) (and (== @entity.running true) (not (== @entity.goalReached true))))
247
+ (set @entity.elapsedMs (+ @entity.elapsedMs 32))
248
+ (when (>= @entity.elapsedMs @config.autoApplyIntervalMs)
249
+ (do
250
+ (set @entity.elapsedMs 0)
251
+ (let (
252
+ (links (array/filter @entity.lines (fn l (== @l.kind "link"))))
253
+ (facts (array/filter @entity.lines (fn l (== @l.kind "fact"))))
254
+ (evaluated (array/map @config.candidates (fn c (let (
255
+ (wantLeft (== @c.matchSide "left"))
256
+ (matchSense (== @c.matchNegationMode "same"))
257
+ (activeLink (array/first (array/filter @links (fn l (let (
258
+ (linkSubject (if @wantLeft @l.leftSubject @l.rightSubject))
259
+ (linkNegated (if @wantLeft @l.leftNegated @l.rightNegated))
260
+ (requiredNegated (if @matchSense @linkNegated (not @linkNegated)))
261
+ )
262
+ (array/some @facts (fn f (and (== @f.subject @linkSubject) (== @f.negated @requiredNegated)))))))))
263
+ (deriveLeft (== @c.deriveSide "left"))
264
+ (deriveSense (== @c.deriveNegationMode "same"))
265
+ (derivedSubject (if (!= @activeLink null) (if @deriveLeft @activeLink.leftSubject @activeLink.rightSubject) ""))
266
+ (derivedSideNegated (if (!= @activeLink null) (if @deriveLeft @activeLink.leftNegated @activeLink.rightNegated) false))
267
+ (derivedNegated (if @deriveSense @derivedSideNegated (not @derivedSideNegated)))
268
+ (derivedLabel (if (!= @activeLink null) (if @derivedNegated (str/concat @config.negationSymbol @derivedSubject) @derivedSubject) ""))
269
+ (alreadyPresent (array/some @entity.lines (fn l (== @l.label @derivedLabel))))
270
+ (applicable (and (!= @activeLink null) (not @alreadyPresent)))
271
+ )
272
+ { label: @c.label, resultBadgeLabel: @c.resultBadgeLabel, resultBadgeVariant: @c.resultBadgeVariant, derivedSubject: @derivedSubject, derivedNegated: @derivedNegated, derivedLabel: @derivedLabel, applicable: @applicable }
273
+ ))))
274
+ (match (array/first (array/filter @evaluated (fn e (== @e.applicable true)))))
275
+ (found (!= @match null))
276
+ )
277
+ (do
278
+ (set @entity.lines (if @found
279
+ (array/concat @entity.lines [{ id: (str/concat "line-" (array/len @entity.lines)), label: @match.derivedLabel, kind: "fact", subject: @match.derivedSubject, negated: @match.derivedNegated, badgeLabel: @match.resultBadgeLabel, badgeVariant: @match.resultBadgeVariant }])
280
+ @entity.lines))
281
+ (set @entity.feedback (if @found
282
+ (str/concat "Derived " @match.derivedLabel " via " @match.label ".")
283
+ @config.noStepAvailableMessage))
284
+ (set @entity.goalReached (if @found (== @match.derivedLabel @config.goalLabel) @entity.goalReached))
285
+ (set @entity.narration @entity.feedback)
286
+ )
287
+ )
288
+ (set @entity.renderChildren [
289
+ { type: typography, variant: h4, content: @entity.title },
290
+ { type: typography, variant: body, content: (str/concat @config.goalPrefix @config.goalLabel) },
291
+ { type: stack, direction: vertical, gap: xs, style: { maxHeight: (str/concat @config.height "px"), overflowY: "auto" }, children: (array/map @entity.lines (fn line
292
+ { type: stack, direction: horizontal, gap: sm, children: [
293
+ { type: badge, label: @line.badgeLabel, variant: @line.badgeVariant },
294
+ { type: typography, variant: body, content: @line.label }
295
+ ] }
296
+ )) },
297
+ { type: stack, direction: horizontal, gap: sm, children: (array/map @config.candidates (fn c { type: button, action: APPLY_STEP, actionPayload: { candidateKey: @c.key }, label: @c.label, variant: primary })) },
298
+ { type: stack, direction: horizontal, gap: sm, children: [
299
+ { type: button, action: TOGGLE_RUN, label: (if @entity.running "Pause" "Run"), variant: secondary },
300
+ { type: button, action: RESET, label: @config.resetLabel, variant: ghost }
301
+ ] },
302
+ { type: typography, variant: caption, content: @entity.narration },
303
+ { type: typography, variant: caption, content: (if @entity.goalReached @config.goalReachedMessage "") }
304
+ ])
305
+ (render-ui main {
306
+ type: box
307
+ padding: md
308
+ maxWidth: (str/concat @config.width "px")
309
+ children: [
310
+ { type: stack, direction: vertical, gap: md, children: @entity.renderChildren }
311
+ ]
312
+ })
313
+ (emit DERIVATION_UPDATED { feedback: @entity.feedback, goalReached: @entity.goalReached, lineCount: (array/len @entity.lines), narration: @entity.narration })
314
+ ))
315
+ }
316
+
317
+ listens {
318
+ MODE_SELECTED {
319
+ mode : string
320
+ }
321
+ @description "Lab router broadcast naming the active mode; this instrument renders when the mode equals matchMode, otherwise renders null."
322
+ @tier "domain"
323
+ APPLY_STEP {
324
+ candidateKey : string
325
+ }
326
+ @description "Learner clicked a candidate-step button to try to derive the next line: the candidate's matchSide/matchNegationMode is matched against the current lines, and on success its deriveSide/deriveNegationMode determines the appended line."
327
+ @tier "domain"
328
+ TOGGLE_RUN {
329
+ }
330
+ @description "Learner toggled auto-derive: while running, the tick applies the first applicable candidate automatically every autoApplyIntervalMs."
331
+ @tier "domain"
332
+ RESET {
333
+ }
334
+ @description "Learner reset the derivation: lines restore to the config-declared starting premises, feedback/narration clear, and goalReached/running reset."
335
+ @tier "domain"
336
+ }
337
+
338
+ emits {
339
+ DERIVATION_UPDATED -> external {
340
+ feedback : string
341
+ goalReached : boolean
342
+ lineCount : number
343
+ narration : string
344
+ }
345
+ @description "The derivation state after each learner action — a composed listener (readout, grader) can subscribe; a harmless no-op when the atom runs standalone."
346
+ @synonyms "derivation update, proof step, derived line, candidate applied, goal reached"
347
+ @tier "domain"
348
+ }
349
+
350
+ config {
351
+ matchMode : string = ""
352
+ @label "Match Mode"
353
+ @description "When composed behind a lab router, the MODE_SELECTED mode value this instrument responds to. Empty string means standalone — render immediately on INIT."
354
+ @tier "presentation"
355
+ title : string = "Derivation Lab"
356
+ @label "Title"
357
+ @description "Title shown above the derivation panel."
358
+ @tier "presentation"
359
+ autoRun : boolean = false
360
+ @label "Auto Run"
361
+ @description "When true, INIT/MODE_SELECTED/Reset start the tick auto-applying candidates; toggle live with the Run/Pause button."
362
+ @tier "domain"
363
+ autoApplyIntervalMs : number = 800
364
+ @label "Auto-Apply Interval (ms)"
365
+ @description "Milliseconds of running clock per automatic candidate application — the 32ms base tick accumulates elapsed time and applies the first applicable candidate each time this interval elapses."
366
+ @tier "domain"
367
+ premises : [LearnDerivationLine] = [
368
+ { id: "given-a", label: "A", kind: "fact", subject: "A", negated: false, badgeLabel: "GIVEN", badgeVariant: "neutral" },
369
+ { id: "given-a-implies-b", label: "A → B", kind: "link", leftSubject: "A", leftNegated: false, rightSubject: "B", rightNegated: false, badgeLabel: "RULE", badgeVariant: "info" }
370
+ ]
371
+ @label "Premises"
372
+ @description "The starting lines of the derivation: plain facts (subject/negated) and two-sided links (leftSubject/leftNegated/rightSubject/rightNegated) the learner combines with candidate steps. Each line carries its own badgeLabel/badgeVariant."
373
+ @synonyms "premise, given, starting line, hypothesis, starting statement"
374
+ @tier "domain"
375
+ candidates : [LearnDerivationCandidate] = [
376
+ { key: "FORWARD", label: "Forward Rule", matchSide: "left", matchNegationMode: "same", deriveSide: "right", deriveNegationMode: "same", resultBadgeLabel: "DERIVED", resultBadgeVariant: "success", noMatchFeedback: "No matching premises yet for the forward rule." },
377
+ { key: "BACKWARD", label: "Backward Rule", matchSide: "right", matchNegationMode: "opposite", deriveSide: "left", deriveNegationMode: "invert", resultBadgeLabel: "DERIVED", resultBadgeVariant: "success", noMatchFeedback: "No matching premises yet for the backward rule." }
378
+ ]
379
+ @label "Candidates"
380
+ @description "Candidate steps offered as buttons. Each declares which side of a link it matches against a current fact (matchSide/matchNegationMode) and which side it derives (deriveSide/deriveNegationMode) — left/right and same/opposite/invert are structural roles, not domain vocabulary, so the same two candidates can encode modus ponens/modus tollens, a forward/backward chemical rule, or any other two-sided derivation pair."
381
+ @synonyms "candidate, rule, step, inference rule, derivation rule, deduction rule"
382
+ @tier "domain"
383
+ goalLabel : string = "B"
384
+ @label "Goal"
385
+ @description "The line label the learner is trying to derive; goalReached fires when a newly derived line's label equals this."
386
+ @synonyms "target, goal, derivation target, conclusion"
387
+ @tier "domain"
388
+ goalPrefix : string = "Goal: derive "
389
+ @label "Goal Caption Prefix"
390
+ @description "Text prepended to goalLabel to form the goal caption shown above the derivation panel."
391
+ @tier "presentation"
392
+ goalReachedMessage : string = "Goal reached."
393
+ @label "Goal Reached Message"
394
+ @description "Caption shown once goalReached is true."
395
+ @tier "presentation"
396
+ noStepAvailableMessage : string = "No further steps are available yet."
397
+ @label "No Step Available Message"
398
+ @description "Feedback shown when the auto-apply tick finds no applicable candidate."
399
+ @tier "presentation"
400
+ autoRunOnMessage : string = "Auto-stepping — tick applies the next available step."
401
+ @label "Auto Run On Message"
402
+ @description "Feedback shown when Run is toggled on."
403
+ @tier "presentation"
404
+ autoRunOffMessage : string = "Paused — apply steps manually."
405
+ @label "Auto Run Off Message"
406
+ @description "Feedback shown when Run is toggled off (Pause)."
407
+ @tier "presentation"
408
+ negationSymbol : string = "¬"
409
+ @label "Negation Symbol"
410
+ @description "Prefix prepended to a derived line's label when it is negated."
411
+ @tier "presentation"
412
+ resetLabel : string = "Reset"
413
+ @label "Reset Button Label"
414
+ @description "Label text for the button that restores the starting premises."
415
+ @tier "presentation"
416
+ width : number = 600
417
+ @label "Width"
418
+ @description "Maximum width of the panel, in pixels."
419
+ @tier "presentation"
420
+ height : number = 400
421
+ @label "Height"
422
+ @description "Maximum height of the scrollable derivation panel, in pixels."
423
+ @tier "presentation"
424
+ }
425
+ }
426
+
427
+ page "/learn-derivation" as LearnDerivationPage -> LearnDerivationSim
428
+ }