@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,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
|
+
}
|