@almadar/std 16.183.0 → 16.184.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 (44) hide show
  1. package/behaviors/lolo/ui/core/.hand-authored.json +4 -0
  2. package/behaviors/lolo/ui/core/atoms/std-browse.lolo +11 -0
  3. package/behaviors/lolo/ui/core/atoms/std-related.lolo +86 -9
  4. package/behaviors/lolo/ui/core/atoms/std-rich-editor.lolo +115 -1
  5. package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +62 -2
  6. package/behaviors/lolo/ui/core/atoms/std-thread.lolo +43 -2
  7. package/behaviors/lolo/ui/core/atoms/std-tree.lolo +247 -0
  8. package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +110 -2
  9. package/behaviors/lolo/ui/core/molecules/ui-file-tree.lolo +18 -2
  10. package/behaviors/lolo/ui/core/organisms/ui-detail-panel.lolo +6 -2
  11. package/behaviors/lolo/ui/core/organisms/ui-form-section.lolo +2 -2
  12. package/behaviors/lolo/ui/core/organisms/ui-form.lolo +2 -2
  13. package/behaviors/lolo/ui/game/atoms/std-tutorial-autodemo.lolo +136 -0
  14. package/behaviors/lolo/ui/game/atoms/std-tutorial-overlay.lolo +41 -8
  15. package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +3 -3
  16. package/behaviors/registry/ui/core/atoms/std-browse.orb +17 -0
  17. package/behaviors/registry/ui/core/atoms/std-related.orb +256 -3
  18. package/behaviors/registry/ui/core/atoms/std-rich-editor.orb +407 -1
  19. package/behaviors/registry/ui/core/atoms/std-tag-taxonomy.orb +240 -8
  20. package/behaviors/registry/ui/core/atoms/std-thread.orb +254 -4
  21. package/behaviors/registry/ui/core/atoms/std-tree.orb +656 -0
  22. package/behaviors/registry/ui/core/atoms/std-version-history.orb +479 -6
  23. package/behaviors/registry/ui/core/molecules/ui-file-tree.orb +43 -19
  24. package/behaviors/registry/ui/core/organisms/ui-detail-panel.orb +28 -3
  25. package/behaviors/registry/ui/core/organisms/ui-form-section.orb +8 -2
  26. package/behaviors/registry/ui/core/organisms/ui-form.orb +8 -2
  27. package/behaviors/registry/ui/game/atoms/std-tutorial-autodemo.orb +732 -0
  28. package/behaviors/registry/ui/game/atoms/std-tutorial-overlay.orb +305 -153
  29. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +26 -0
  30. package/dist/behaviors/exports-reader.js +4320 -2060
  31. package/dist/behaviors/functions/index.d.ts +259 -44
  32. package/dist/behaviors/functions/index.js +4306 -2058
  33. package/dist/behaviors/index.d.ts +1 -1
  34. package/dist/behaviors/index.js +4321 -2061
  35. package/dist/behaviors/query.js +4320 -2060
  36. package/dist/behaviors-embeddings.hash.json +3 -3
  37. package/dist/behaviors-embeddings.json +274 -266
  38. package/dist/behaviors-registry.json +820 -33
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +4321 -2061
  41. package/dist/knob-embeddings.hash.json +3 -3
  42. package/dist/knob-embeddings.json +1 -1
  43. package/dist/registry/factory-signatures.json +1 -1
  44. package/package.json +2 -2
@@ -0,0 +1,247 @@
1
+ app std-tree "1.0.0"
2
+ "std-tree — generic hierarchical node browser. Fetches a flat TreeNode collection and renders it through file-tree's flat `items` mode (parentId-nested at render time by the component, not the .lolo); OPEN_NODE is the external handoff for a selected node's id. Rebindable: pass `fields` at the call site to retarget it at any parent-pointer entity (categories, org units, comment threads)."
3
+ orbital TreeNodeOrbital {
4
+ type TreeNodeLoaded = Event { data : [TreeNode] } "Fired when the tree node collection finishes loading"
5
+
6
+ type TreeNodeLoadFailed = Event { error : string, code : string } "Fired when the tree node collection fails to load"
7
+
8
+ entity TreeNode [persistent: treenodes] {
9
+ id : string!
10
+ name : string! @description "The descriptive label for this node." @synonyms "label, title, text"
11
+ parentId : string @description "Reference to the parent node in the hierarchy; empty marks a root." @synonyms "parent, superior, ancestor" = ""
12
+ icon : string @description "Icon name shown beside the node." @synonyms "glyph, symbol, pictogram" = ""
13
+ createdAt : string
14
+ }
15
+
16
+ trait TreeBrowse -> @rebindable TreeNode [interaction, collection] {
17
+ initial: loading
18
+
19
+ state loading {
20
+ INIT -> loading
21
+ (fetch TreeNode {
22
+ emit: { failure: TreeNodeLoadFailed, success: TreeNodeLoaded }
23
+ })
24
+ (render-ui main {
25
+ align: center
26
+ children: [{ type: spinner }, {
27
+ color: muted
28
+ content: "Loading tree…"
29
+ type: typography
30
+ variant: caption
31
+ }]
32
+ className: py-12
33
+ direction: vertical
34
+ gap: md
35
+ type: stack
36
+ })
37
+ TreeNodeLoaded -> browsing
38
+ (render-ui main {
39
+ children: [{
40
+ align: center
41
+ children: [{ name: list-tree, type: icon }, {
42
+ content: @config.title
43
+ type: typography
44
+ variant: h3
45
+ }, {
46
+ label: (array/len ?data)
47
+ size: sm
48
+ type: badge
49
+ variant: neutral
50
+ }]
51
+ direction: horizontal
52
+ gap: sm
53
+ type: stack
54
+ }, { type: divider }, {
55
+ items: (array/map ?data (fn row {
56
+ icon: @row.icon
57
+ id: @row.id
58
+ label: @row.name
59
+ parentId: @row.parentId
60
+ }))
61
+ onNodeSelect: OPEN_NODE
62
+ type: file-tree
63
+ }]
64
+ direction: vertical
65
+ gap: md
66
+ type: stack
67
+ })
68
+ TreeNodeLoadFailed -> error
69
+ (render-ui main {
70
+ align: center
71
+ children: [{
72
+ color: error
73
+ name: alert-triangle
74
+ type: icon
75
+ }, {
76
+ content: "Failed to load tree"
77
+ type: typography
78
+ variant: body
79
+ }, {
80
+ color: muted
81
+ content: ?error
82
+ type: typography
83
+ variant: caption
84
+ }, {
85
+ action: RETRY
86
+ icon: rotate-ccw
87
+ label: Retry
88
+ type: button
89
+ variant: ghost
90
+ }]
91
+ className: py-8
92
+ direction: vertical
93
+ gap: sm
94
+ type: stack
95
+ })
96
+ }
97
+
98
+ state browsing {
99
+ INIT -> loading
100
+ (fetch TreeNode {
101
+ emit: { failure: TreeNodeLoadFailed, success: TreeNodeLoaded }
102
+ })
103
+ (render-ui main {
104
+ align: center
105
+ children: [{ type: spinner }, {
106
+ color: muted
107
+ content: "Loading tree…"
108
+ type: typography
109
+ variant: caption
110
+ }]
111
+ className: py-12
112
+ direction: vertical
113
+ gap: md
114
+ type: stack
115
+ })
116
+ OPEN_NODE -> browsing
117
+ (set @entity.id ?id)
118
+ TreeNodeLoaded -> browsing
119
+ (render-ui main {
120
+ children: [{
121
+ align: center
122
+ children: [{ name: list-tree, type: icon }, {
123
+ content: @config.title
124
+ type: typography
125
+ variant: h3
126
+ }, {
127
+ label: (array/len ?data)
128
+ size: sm
129
+ type: badge
130
+ variant: neutral
131
+ }]
132
+ direction: horizontal
133
+ gap: sm
134
+ type: stack
135
+ }, { type: divider }, {
136
+ items: (array/map ?data (fn row {
137
+ icon: @row.icon
138
+ id: @row.id
139
+ label: @row.name
140
+ parentId: @row.parentId
141
+ }))
142
+ onNodeSelect: OPEN_NODE
143
+ type: file-tree
144
+ }]
145
+ direction: vertical
146
+ gap: md
147
+ type: stack
148
+ })
149
+ TreeNodeLoadFailed -> error
150
+ (render-ui main {
151
+ align: center
152
+ children: [{
153
+ color: error
154
+ name: alert-triangle
155
+ type: icon
156
+ }, {
157
+ content: "Failed to load tree"
158
+ type: typography
159
+ variant: body
160
+ }, {
161
+ color: muted
162
+ content: ?error
163
+ type: typography
164
+ variant: caption
165
+ }, {
166
+ action: RETRY
167
+ icon: rotate-ccw
168
+ label: Retry
169
+ type: button
170
+ variant: ghost
171
+ }]
172
+ className: py-8
173
+ direction: vertical
174
+ gap: sm
175
+ type: stack
176
+ })
177
+ }
178
+
179
+ state error {
180
+ INIT -> loading
181
+ (fetch TreeNode {
182
+ emit: { failure: TreeNodeLoadFailed, success: TreeNodeLoaded }
183
+ })
184
+ (render-ui main { type: spinner })
185
+ ;; the error screen's Retry button: INIT is lifecycle-only and the bus never
186
+ ;; delivers it, so the affordance must target a first-class user event
187
+ RETRY -> loading
188
+ (fetch TreeNode {
189
+ emit: { failure: TreeNodeLoadFailed, success: TreeNodeLoaded }
190
+ })
191
+ (render-ui main { type: spinner })
192
+ }
193
+
194
+ emits {
195
+ OPEN_NODE {
196
+ id : string!
197
+ }
198
+ @description "Signals a tree node has been selected for external handoff."
199
+ @synonyms "select, choose, open, activate"
200
+ @tier "domain"
201
+ TreeNodeLoaded {
202
+ data : [TreeNode]
203
+ }
204
+ @description "Indicates the tree node collection has been successfully retrieved."
205
+ @synonyms "loaded, fetched, retrieved, ready"
206
+ @tier "domain"
207
+ TreeNodeLoadFailed {
208
+ error : string
209
+ code : string
210
+ }
211
+ @description "Indicates a failure to load the tree node collection."
212
+ @synonyms "error, failed, problem, unsuccessful"
213
+ @tier "internal"
214
+ }
215
+
216
+ listens {
217
+ OPEN_NODE {
218
+ id : string!
219
+ }
220
+ @description "A tree node was selected for external handoff."
221
+ @synonyms "select, choose, open, activate"
222
+ @tier "domain"
223
+ TreeNodeLoaded {
224
+ data : [TreeNode]
225
+ }
226
+ @description "Indicates the tree node collection has been successfully retrieved."
227
+ @synonyms "loaded, fetched, retrieved, ready"
228
+ @tier "domain"
229
+ TreeNodeLoadFailed {
230
+ error : string
231
+ code : string
232
+ }
233
+ @description "Indicates a failure to load the tree node collection."
234
+ @synonyms "error, failed, problem, unsuccessful"
235
+ @tier "internal"
236
+ }
237
+
238
+ config {
239
+ title : string = Tree
240
+ @label "Section title"
241
+ @description "Heading shown above the tree"
242
+ @tier "presentation"
243
+ }
244
+ }
245
+
246
+ page "/tree" as TreePage -> TreeBrowse
247
+ }
@@ -1,5 +1,5 @@
1
1
  app std-version-history "1.0.0"
2
- "std-version-history — wiki/CMS revision history with diff + rollback. Browses revisions; opens detail in `viewing_single`; compares two revisions in `comparing` with intrinsic version-diff; persists rollback."
2
+ "std-version-history — wiki/CMS revision history with diff + rollback. Browses revisions; opens detail in `viewing_single`; compares two revisions in `comparing` with intrinsic version-diff; persists rollback. `RevisionManage` is a rebindable single-entity trait: a consumer rebinds it to its own entity via `linkedEntity`. `config.scopeField` names the Revision field holding the parent document id; a page-mounted `{id}` route param scopes INIT's own fetch, and a consumer forwards SHOW_HISTORY { key } (via a `listens` override) to rescope the feed to one document — empty scopeField (default) or no id keeps today's unfiltered global feed. Rollback and retry refetches stay scoped."
3
3
  orbital RevisionOrbital {
4
4
  type RevisionLoaded = Event { data : [Revision] } "Fired when the revision collection finishes loading"
5
5
 
@@ -20,14 +20,17 @@ orbital RevisionOrbital {
20
20
  summary : string @description "A brief explanation of the revision's changes." @synonyms "description, note, comment, explanation"
21
21
  searchTerm : string @description "The current search query the user submitted." @synonyms "query, term, q" = ""
22
22
  createdAt : string
23
+ scopeKey : string @intrinsic @description "The parent document id the feed is currently scoped to, set by SHOW_HISTORY. Empty when browsing the unfiltered global feed." @synonyms "scope, filter key, document scope" = ""
23
24
  }
24
25
 
25
- trait RevisionManage -> Revision [interaction, collection] {
26
+ trait RevisionManage -> @rebindable Revision [interaction, collection] {
26
27
  initial: loading
27
28
 
28
29
  state loading {
29
30
  INIT -> loading
31
+ (set @entity.scopeKey (str/default ?id ""))
30
32
  (fetch Revision {
33
+ filter: (or ["=", @config.scopeField, ""] ["=", (str/default ?id ""), ""] ["=", (object/get @entity @config.scopeField), ?id])
31
34
  emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
32
35
  })
33
36
  (render-ui main {
@@ -95,7 +98,9 @@ orbital RevisionOrbital {
95
98
  type: stack
96
99
  })
97
100
  RevisionRolledBack -> loading
101
+ (set @entity.scopeKey (object/get ?row @config.scopeField ""))
98
102
  (fetch Revision {
103
+ filter: (or ["=", @config.scopeField, ""] ["=", (object/get ?row @config.scopeField ""), ""] ["=", (object/get @entity @config.scopeField), (object/get ?row @config.scopeField "")])
99
104
  emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
100
105
  })
101
106
  RevisionRollbackFailed -> error
@@ -110,11 +115,32 @@ orbital RevisionOrbital {
110
115
  type: alert
111
116
  variant: error
112
117
  })
118
+ SHOW_HISTORY -> loading
119
+ (set @entity.scopeKey ?key)
120
+ (fetch Revision {
121
+ filter: ["=", (object/get @entity @config.scopeField), ?key]
122
+ emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
123
+ })
124
+ (render-ui main {
125
+ align: center
126
+ children: [{ type: spinner }, {
127
+ color: muted
128
+ content: "Loading version history…"
129
+ type: typography
130
+ variant: caption
131
+ }]
132
+ className: py-12
133
+ direction: vertical
134
+ gap: md
135
+ type: stack
136
+ })
113
137
  }
114
138
 
115
139
  state browsing {
116
140
  INIT -> loading
141
+ (set @entity.scopeKey (str/default ?id ""))
117
142
  (fetch Revision {
143
+ filter: (or ["=", @config.scopeField, ""] ["=", (str/default ?id ""), ""] ["=", (object/get @entity @config.scopeField), ?id])
118
144
  emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
119
145
  })
120
146
  (render-ui main {
@@ -270,6 +296,25 @@ orbital RevisionOrbital {
270
296
  gap: md
271
297
  type: stack
272
298
  })
299
+ SHOW_HISTORY -> loading
300
+ (set @entity.scopeKey ?key)
301
+ (fetch Revision {
302
+ filter: ["=", (object/get @entity @config.scopeField), ?key]
303
+ emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
304
+ })
305
+ (render-ui main {
306
+ align: center
307
+ children: [{ type: spinner }, {
308
+ color: muted
309
+ content: "Loading version history…"
310
+ type: typography
311
+ variant: caption
312
+ }]
313
+ className: py-12
314
+ direction: vertical
315
+ gap: md
316
+ type: stack
317
+ })
273
318
  }
274
319
 
275
320
  state viewing_single {
@@ -350,6 +395,25 @@ orbital RevisionOrbital {
350
395
  gap: md
351
396
  type: stack
352
397
  })
398
+ SHOW_HISTORY -> loading
399
+ (set @entity.scopeKey ?key)
400
+ (fetch Revision {
401
+ filter: ["=", (object/get @entity @config.scopeField), ?key]
402
+ emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
403
+ })
404
+ (render-ui main {
405
+ align: center
406
+ children: [{ type: spinner }, {
407
+ color: muted
408
+ content: "Loading version history…"
409
+ type: typography
410
+ variant: caption
411
+ }]
412
+ className: py-12
413
+ direction: vertical
414
+ gap: md
415
+ type: stack
416
+ })
353
417
  }
354
418
 
355
419
  state comparing {
@@ -388,11 +452,39 @@ orbital RevisionOrbital {
388
452
  gap: md
389
453
  type: stack
390
454
  })
455
+ SHOW_HISTORY -> loading
456
+ (set @entity.scopeKey ?key)
457
+ (fetch Revision {
458
+ filter: ["=", (object/get @entity @config.scopeField), ?key]
459
+ emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
460
+ })
461
+ (render-ui main {
462
+ align: center
463
+ children: [{ type: spinner }, {
464
+ color: muted
465
+ content: "Loading version history…"
466
+ type: typography
467
+ variant: caption
468
+ }]
469
+ className: py-12
470
+ direction: vertical
471
+ gap: md
472
+ type: stack
473
+ })
391
474
  }
392
475
 
393
476
  state error {
394
477
  INIT -> loading
478
+ (set @entity.scopeKey (str/default ?id ""))
395
479
  (fetch Revision {
480
+ filter: (or ["=", @config.scopeField, ""] ["=", (str/default ?id ""), ""] ["=", (object/get @entity @config.scopeField), ?id])
481
+ emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
482
+ })
483
+ (render-ui main { size: sm, type: spinner })
484
+ SHOW_HISTORY -> loading
485
+ (set @entity.scopeKey ?key)
486
+ (fetch Revision {
487
+ filter: ["=", (object/get @entity @config.scopeField), ?key]
396
488
  emit: { failure: RevisionLoadFailed, success: RevisionLoaded }
397
489
  })
398
490
  (render-ui main { size: sm, type: spinner })
@@ -454,6 +546,12 @@ orbital RevisionOrbital {
454
546
  }
455
547
 
456
548
  listens {
549
+ INIT {
550
+ id : string
551
+ }
552
+ @description "Optional route param delivered on mount (e.g. an :id page segment); scopes INIT's own fetch to rows whose scopeField equals id. Absent id or empty scopeField config keeps the unfiltered global feed."
553
+ @synonyms "route id, deep link, initial scope"
554
+ @tier "domain"
457
555
  OPEN_REVISION {
458
556
  id : string!
459
557
  row : Revision
@@ -506,6 +604,12 @@ orbital RevisionOrbital {
506
604
  @description "Indicates that a revision rollback operation has failed."
507
605
  @synonyms "rollback error, revert failure, undo failed"
508
606
  @tier "internal"
607
+ SHOW_HISTORY {
608
+ key : string!
609
+ }
610
+ @description "Requests the revision feed scoped to one parent document; stores the key and refetches filtered by config.scopeField."
611
+ @synonyms "view history, doc history, filter revisions"
612
+ @tier "domain"
509
613
  }
510
614
 
511
615
  config {
@@ -521,6 +625,10 @@ orbital RevisionOrbital {
521
625
  @label "Stat display look"
522
626
  @description "Layer 2 visual treatment for stat / KPI cards."
523
627
  @tier "presentation"
628
+ scopeField : string = ""
629
+ @label "Scope field"
630
+ @description "Revision entity field holding the parent document's id. When set, a page-mounted {id} route param scopes INIT's fetch, and a SHOW_HISTORY { key } event rescopes the feed, to rows whose scopeField equals the id/key; rollback and retry refetches stay scoped too. Empty (default) or no id = unfiltered global feed, unchanged from prior behavior."
631
+ @tier "domain"
524
632
  }
525
633
  }
526
634
 
@@ -9,6 +9,12 @@ orbital FileTreeOrbital {
9
9
  size : number
10
10
  language : string
11
11
  }
12
+ type FileTreeItemsItem = {
13
+ id : string!
14
+ label : string!
15
+ parentId : string
16
+ icon : string
17
+ }
12
18
 
13
19
  entity FileTreeItem [runtime] {
14
20
  id : string!
@@ -21,8 +27,10 @@ orbital FileTreeOrbital {
21
27
  INIT -> idle
22
28
  (render-ui main {
23
29
  tree: @config.tree
30
+ items: @config.items
24
31
  selectedPath: @config.selectedPath
25
32
  onFileSelect: @config.onFileSelect
33
+ onNodeSelect: @config.onNodeSelect
26
34
  className: @config.className
27
35
  indent: @config.indent
28
36
  type: file-tree
@@ -38,9 +46,13 @@ orbital FileTreeOrbital {
38
46
  }
39
47
 
40
48
  config {
41
- tree : [FileTreeTreeItem] = [{ name: "Name", path: "Path", type: file, children: [], size: 1, language: "Language" }, { name: "Name 2", path: "Path 2", type: dir, children: [], size: 2, language: "Language 2" }]
49
+ tree : [FileTreeTreeItem] = []
42
50
  @label "Tree"
43
- @description "The tree data"
51
+ @description "The tree data (pre-nested). Ignored when `items` is provided."
52
+ @tier "presentation"
53
+ items : [FileTreeItemsItem] = []
54
+ @label "Items"
55
+ @description "Flat node list, nested by `parentId` at render time — takes precedence over `tree` when provided. A `parentId` with no matching row renders that node as a root rather than dropping it."
44
56
  @tier "presentation"
45
57
  selectedPath : string
46
58
  @label "Selected Path"
@@ -50,6 +62,10 @@ orbital FileTreeOrbital {
50
62
  @label "On File Select"
51
63
  @description "Called when a file is clicked"
52
64
  @tier "presentation"
65
+ onNodeSelect : string = ""
66
+ @label "On Node Select"
67
+ @description "Called when a node is clicked, in flat `items` mode; carries the selected node's id. Presence-gated: binding this prop is what turns on the click-to-select affordance, mirroring Badge's `onRemove`."
68
+ @tier "presentation"
53
69
  className : string
54
70
  @label "Class Name"
55
71
  @description "CSS class"
@@ -41,7 +41,11 @@ orbital DetailPanelOrbital {
41
41
  header : string
42
42
  type : string
43
43
  values : [string]
44
- relation : string
44
+ relation : DetailPanelFieldsItemRelation
45
+ }
46
+ type DetailPanelFieldsItemRelation = {
47
+ entity : string!
48
+ cardinality : "one" | "many" | "one-to-many" | "many-to-one" | "many-to-many"
45
49
  }
46
50
  type DetailPanelRelationsDataValueItem = {
47
51
  value : string!
@@ -224,7 +228,7 @@ orbital DetailPanelOrbital {
224
228
  @label "Slide Over"
225
229
  @description "slideOver prop"
226
230
  @tier "presentation"
227
- fields : [DetailPanelFieldsItem] = [{ key: "Key", header: "Header", type: "Type", values: ["Item", "Item 2"], relation: "Relation" }, { key: "Key 2", header: "Header 2", type: "Type 2", values: ["Item", "Item 2"], relation: "Relation 2" }]
231
+ fields : [DetailPanelFieldsItem] = [{ key: "Key", header: "Header", type: "Type", values: ["Item", "Item 2"], relation: { entity: "Entity", cardinality: one } }, { key: "Key 2", header: "Header 2", type: "Type 2", values: ["Item", "Item 2"], relation: { entity: "Entity 2", cardinality: many } }]
228
232
  @label "Fields"
229
233
  @description "Fields to display - accepts string[], {key, header}[], or DetailField[]"
230
234
  @tier "presentation"
@@ -29,7 +29,7 @@ orbital FormSectionOrbital {
29
29
  type FormSectionFieldsItemRelation = {
30
30
  entity : string!
31
31
  displayField : string
32
- cardinality : "one" | "many"
32
+ cardinality : "one" | "many" | "one-to-many" | "many-to-one" | "many-to-many"
33
33
  }
34
34
  type FormSectionFieldsItemValidation = {
35
35
  enum : [string]
@@ -108,7 +108,7 @@ orbital FormSectionOrbital {
108
108
  type FormSectionSectionsItemFieldsItemRelation = {
109
109
  entity : string!
110
110
  displayField : string
111
- cardinality : "one" | "many"
111
+ cardinality : "one" | "many" | "one-to-many" | "many-to-one" | "many-to-many"
112
112
  }
113
113
  type FormSectionSectionsItemFieldsItemValidation = {
114
114
  enum : [string]
@@ -29,7 +29,7 @@ orbital FormOrbital {
29
29
  type FormFieldsItemRelation = {
30
30
  entity : string!
31
31
  displayField : string
32
- cardinality : "one" | "many"
32
+ cardinality : "one" | "many" | "one-to-many" | "many-to-one" | "many-to-many"
33
33
  }
34
34
  type FormFieldsItemValidation = {
35
35
  enum : [string]
@@ -108,7 +108,7 @@ orbital FormOrbital {
108
108
  type FormSectionsItemFieldsItemRelation = {
109
109
  entity : string!
110
110
  displayField : string
111
- cardinality : "one" | "many"
111
+ cardinality : "one" | "many" | "one-to-many" | "many-to-one" | "many-to-many"
112
112
  }
113
113
  type FormSectionsItemFieldsItemValidation = {
114
114
  enum : [string]
@@ -0,0 +1,136 @@
1
+ app std-tutorial-autodemo "1.0.0"
2
+ "Generic scripted input-replay atom: the demo half of a tutorial. Holds one or more named cue scripts (each an ordered list of { at, event, arg } rows where at is a millisecond offset and event is LEFT/RIGHT/JUMP/STOP); listens for TUTORIAL_DEMO_START { demoId } (from std-tutorial-overlay's demoId step) and plays the matching script on a 32ms clock, emitting the semantic DEMO_LEFT / DEMO_RIGHT / DEMO_JUMP / DEMO_STOP events the host board maps onto its own movement. TUTORIAL_DEMO_STOP, DEMO_CANCEL (real user input), or exhausting the cue list end the demo and emit DEMO_STOP so the character stops moving. Renders a small Demo badge while playing. Domain-agnostic: the demo never emits board events (OW_*, etc.) — the organism maps DEMO_* to its own steering, so a demo cannot self-cancel through the board's input path."
3
+ orbital TutorialAutoDemoOrbital {
4
+ type DemoCue = {
5
+ at : number!
6
+ event : string!
7
+ arg : number
8
+ }
9
+ type DemoScript = {
10
+ id : string!
11
+ cues : [DemoCue]!
12
+ }
13
+
14
+ entity AutoDemoData [runtime] {
15
+ id : string!
16
+ playing : boolean = false
17
+ scriptId : string = ""
18
+ elapsed : number = 0
19
+ cueIndex : number = 0
20
+ }
21
+
22
+ trait AutoDemo -> @rebindable AutoDemoData [interaction, instance] {
23
+ initial: idle
24
+
25
+ state idle {
26
+ INIT -> idle
27
+ (render-ui hud-top null)
28
+
29
+ TUTORIAL_DEMO_START -> playing
30
+ (set @entity.scriptId @payload.demoId)
31
+ (set @entity.elapsed 0)
32
+ (set @entity.cueIndex 0)
33
+ (set @entity.playing true)
34
+ (render-ui hud-top {
35
+ type: badge
36
+ label: "Demo"
37
+ variant: "info"
38
+ })
39
+
40
+ TUTORIAL_DEMO_STOP -> idle
41
+ (render-ui hud-top null)
42
+
43
+ DEMO_CANCEL -> idle
44
+ (render-ui hud-top null)
45
+ }
46
+
47
+ state playing {
48
+ TUTORIAL_DEMO_START -> playing
49
+ (set @entity.scriptId @payload.demoId)
50
+ (set @entity.elapsed 0)
51
+ (set @entity.cueIndex 0)
52
+ (render-ui hud-top {
53
+ type: badge
54
+ label: "Demo"
55
+ variant: "info"
56
+ })
57
+
58
+ TUTORIAL_DEMO_STOP -> idle
59
+ (set @entity.playing false)
60
+ (render-ui hud-top null)
61
+ (emit DEMO_STOP {})
62
+
63
+ DEMO_CANCEL -> idle
64
+ (set @entity.playing false)
65
+ (render-ui hud-top null)
66
+ (emit DEMO_STOP {})
67
+ }
68
+
69
+ ticks {
70
+ demoTick every 32ms when (== @entity.playing true)
71
+ (set @entity.elapsed (+ @entity.elapsed 32))
72
+ (let ((scriptMatch (array/filter @config.scripts (fn s (== (object/get @s id) @entity.scriptId)))))
73
+ (let ((cueList (if (> (array/len @scriptMatch) 0) (object/get (array/first @scriptMatch) cues []) [])))
74
+ (let ((cue (if (< @entity.cueIndex (array/len @cueList)) (array/nth @cueList @entity.cueIndex) { at: 999999, event: "" })))
75
+ (do
76
+ (if (<= (object/get @cue at 0) @entity.elapsed)
77
+ (do
78
+ (set @entity.cueIndex (+ @entity.cueIndex 1))
79
+ (if (== (object/get @cue event "") "LEFT") (emit DEMO_LEFT {}) null)
80
+ (if (== (object/get @cue event "") "RIGHT") (emit DEMO_RIGHT {}) null)
81
+ (if (== (object/get @cue event "") "JUMP") (emit DEMO_JUMP {}) null)
82
+ (if (== (object/get @cue event "") "STOP") (emit DEMO_STOP {}) null))
83
+ null)
84
+ (if (>= @entity.cueIndex (array/len @cueList))
85
+ (do
86
+ (set @entity.playing false)
87
+ (render-ui hud-top null)
88
+ (emit DEMO_STOP {}))
89
+ null)))))
90
+ }
91
+
92
+ listens {
93
+ TUTORIAL_DEMO_START {
94
+ demoId : string!
95
+ }
96
+ @description "A tutorial step carrying a demoId was shown (std-tutorial-overlay); begin playing the cue script whose id matches demoId."
97
+ @tier "domain"
98
+ TUTORIAL_DEMO_STOP {
99
+ }
100
+ @description "The tutorial left a demo step or was dismissed; stop the demo and release control."
101
+ @tier "domain"
102
+ DEMO_CANCEL {
103
+ }
104
+ @description "Real user input was received by the host board; stop the demo immediately and hand control back to the player."
105
+ @tier "domain"
106
+ }
107
+
108
+ emits {
109
+ DEMO_LEFT -> external {
110
+ }
111
+ @description "Scripted run-left cue; the host board maps this to its own steer-left."
112
+ @tier "domain"
113
+ DEMO_RIGHT -> external {
114
+ }
115
+ @description "Scripted run-right cue; the host board maps this to its own steer-right."
116
+ @tier "domain"
117
+ DEMO_JUMP -> external {
118
+ }
119
+ @description "Scripted jump cue; the host board maps this to its own jump."
120
+ @tier "domain"
121
+ DEMO_STOP -> external {
122
+ }
123
+ @description "Scripted stop cue (or demo end/cancel); the host board maps this to stop movement."
124
+ @tier "domain"
125
+ }
126
+
127
+ config {
128
+ scripts : [DemoScript] = []
129
+ @label "Scripts"
130
+ @description "Named cue scripts keyed by the tutorial step's demoId; each cue fires its event after `at` milliseconds of the demo clock."
131
+ @tier "domain"
132
+ }
133
+ }
134
+
135
+ page "/tutorial-autodemo" as TutorialAutoDemoPage -> AutoDemo
136
+ }