@almadar/std 16.183.0 → 16.185.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 +368 -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 +12 -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 +898 -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 +37 -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 +5132 -2398
  31. package/dist/behaviors/functions/index.d.ts +261 -44
  32. package/dist/behaviors/functions/index.js +5118 -2396
  33. package/dist/behaviors/index.d.ts +1 -1
  34. package/dist/behaviors/index.js +5133 -2399
  35. package/dist/behaviors/query.js +5132 -2398
  36. package/dist/behaviors-embeddings.hash.json +3 -3
  37. package/dist/behaviors-embeddings.json +310 -302
  38. package/dist/behaviors-registry.json +820 -35
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +5133 -2399
  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
@@ -1,5 +1,5 @@
1
1
  app std-tag-taxonomy "1.1.0"
2
- "std-tag-taxonomy — hierarchical category tag tree (parent/child). TagBrowse fetches Tag rows on INIT, renders the active level via data-grid, and on SELECT_TAG drills into a tag's children by binding @entity.id to the chosen parent."
2
+ "std-tag-taxonomy — hierarchical category tag tree (parent/child). TagBrowse fetches root-level Tag rows (parentId = \"\") on INIT and renders the active level via data-grid; SELECT_TAG drills into the chosen tag's children by fetching rows whose parentId matches it, binding @entity.id to that parent so the level is remembered; SHOW_ROOT resets back to the root level, surfaced as a back affordance while drilled in. Flat taxonomies (all parentId \"\") render exactly one level, unchanged. TagSelected is emitted on selection for consumers to filter content by tag."
3
3
  orbital TagTaxonomyOrbital {
4
4
  type TagLoaded = Event { data : [Tag] } "Fired when the tag collection finishes loading"
5
5
 
@@ -10,7 +10,7 @@ orbital TagTaxonomyOrbital {
10
10
  entity Tag [persistent: tags] {
11
11
  id : string!
12
12
  name : string! @description "The descriptive label for this item." @synonyms "label, title, description"
13
- parentId : string @description "Reference to the parent tag in the hierarchy." @synonyms "parent, superior, ancestor"
13
+ parentId : Tag @description "Reference to the parent tag in the hierarchy; empty marks a root." @synonyms "parent, superior, ancestor"
14
14
  slug : string @description "A unique, URL-friendly identifier." @synonyms "identifier, key, code"
15
15
  color : string @description "A visual identifier, often used for categorization." @synonyms "hue, shade, tint, category color"
16
16
  count : number @description "The number of items associated with this entry." @synonyms "quantity, total, size, amount" = 0
@@ -22,7 +22,10 @@ orbital TagTaxonomyOrbital {
22
22
 
23
23
  state loading {
24
24
  INIT -> loading
25
+ (set @entity.id "")
26
+ (set @entity.parentId "")
25
27
  (fetch Tag {
28
+ filter: ["=", (object/get @entity parentId), ""]
26
29
  emit: { failure: TagLoadFailed, success: TagLoaded }
27
30
  })
28
31
  (render-ui main {
@@ -41,6 +44,14 @@ orbital TagTaxonomyOrbital {
41
44
  TagLoaded -> browsing
42
45
  (render-ui main {
43
46
  children: [{
47
+ action: SHOW_ROOT
48
+ disabled: (if (!= @entity.id "") false true)
49
+ icon: arrow-left
50
+ label: "All categories"
51
+ size: sm
52
+ type: button
53
+ variant: ghost
54
+ }, {
44
55
  content: @config.title
45
56
  type: typography
46
57
  variant: h3
@@ -115,7 +126,10 @@ orbital TagTaxonomyOrbital {
115
126
 
116
127
  state browsing {
117
128
  INIT -> loading
129
+ (set @entity.id "")
130
+ (set @entity.parentId "")
118
131
  (fetch Tag {
132
+ filter: ["=", (object/get @entity parentId), ""]
119
133
  emit: { failure: TagLoadFailed, success: TagLoaded }
120
134
  })
121
135
  (render-ui main {
@@ -133,8 +147,10 @@ orbital TagTaxonomyOrbital {
133
147
  })
134
148
  SELECT_TAG -> editing
135
149
  (set @entity.id ?tagId)
150
+ (set @entity.parentId ?tagId)
136
151
  (emit TagSelected { tagId: ?tagId })
137
152
  (fetch Tag {
153
+ filter: ["=", (object/get @entity parentId), ?tagId]
138
154
  emit: { failure: TagLoadFailed, success: TagLoaded }
139
155
  })
140
156
  (render-ui main {
@@ -150,9 +166,37 @@ orbital TagTaxonomyOrbital {
150
166
  gap: md
151
167
  type: stack
152
168
  })
169
+ SHOW_ROOT -> loading
170
+ (set @entity.id "")
171
+ (set @entity.parentId "")
172
+ (fetch Tag {
173
+ filter: ["=", (object/get @entity parentId), ""]
174
+ emit: { failure: TagLoadFailed, success: TagLoaded }
175
+ })
176
+ (render-ui main {
177
+ align: center
178
+ children: [{ type: spinner }, {
179
+ color: muted
180
+ content: "Loading categories…"
181
+ type: typography
182
+ variant: caption
183
+ }]
184
+ className: py-12
185
+ direction: vertical
186
+ gap: md
187
+ type: stack
188
+ })
153
189
  TagLoaded -> browsing
154
190
  (render-ui main {
155
191
  children: [{
192
+ action: SHOW_ROOT
193
+ disabled: (if (!= @entity.id "") false true)
194
+ icon: arrow-left
195
+ label: "All categories"
196
+ size: sm
197
+ type: button
198
+ variant: ghost
199
+ }, {
156
200
  content: @config.title
157
201
  type: typography
158
202
  variant: h3
@@ -227,7 +271,10 @@ orbital TagTaxonomyOrbital {
227
271
 
228
272
  state editing {
229
273
  INIT -> loading
274
+ (set @entity.id "")
275
+ (set @entity.parentId "")
230
276
  (fetch Tag {
277
+ filter: ["=", (object/get @entity parentId), ""]
231
278
  emit: { failure: TagLoadFailed, success: TagLoaded }
232
279
  })
233
280
  (render-ui main {
@@ -245,12 +292,22 @@ orbital TagTaxonomyOrbital {
245
292
  })
246
293
  SELECT_TAG -> editing
247
294
  (set @entity.id ?tagId)
295
+ (set @entity.parentId ?tagId)
248
296
  (emit TagSelected { tagId: ?tagId })
249
297
  TagSelected -> editing
250
298
  (set @entity.id ?tagId)
299
+ (set @entity.parentId ?tagId)
251
300
  TagLoaded -> browsing
252
301
  (render-ui main {
253
302
  children: [{
303
+ action: SHOW_ROOT
304
+ disabled: (if (!= @entity.id "") false true)
305
+ icon: arrow-left
306
+ label: "All categories"
307
+ size: sm
308
+ type: button
309
+ variant: ghost
310
+ }, {
254
311
  content: @config.title
255
312
  type: typography
256
313
  variant: h3
@@ -325,7 +382,10 @@ orbital TagTaxonomyOrbital {
325
382
 
326
383
  state error {
327
384
  INIT -> loading
385
+ (set @entity.id "")
386
+ (set @entity.parentId "")
328
387
  (fetch Tag {
388
+ filter: ["=", (object/get @entity parentId), ""]
329
389
  emit: { failure: TagLoadFailed, success: TagLoaded }
330
390
  })
331
391
  (render-ui main {
@@ -1,5 +1,5 @@
1
1
  app std-thread "1.3.0"
2
- "std-thread — forum-style nested replies with collapse + flat-mode toggle. Owns ThreadPost (persistent). flat:true covers 1:1 linear inquiry threads; flat:false renders forum-style nested. INIT fetches, scoped by @config.threadRootId when set (blank = every root, the default). REPLY persists a child post under @payload.parentId, stamped with the real @user identity. Layer 3 variants override `bodyContent` to deliver chat-bubble, email-threaded, or linear-comments UX while inheriting the same trait, state machine, emits, and listens."
2
+ "std-thread — forum-style nested replies with collapse + flat-mode toggle. Owns ThreadPost (persistent). flat:true covers 1:1 linear inquiry threads; flat:false renders forum-style nested. INIT fetches, scoped by @config.threadRootId when set (blank = every root, the default). REPLY persists a child post under @payload.parentId, stamped with the real @user identity. A consumer that mounts this as page comments forwards its own document-opened signal to SET_THREAD_ROOT { rootId }, which stamps the frame and refetches filtered by the payload; the after-create and after-select refetch arms keep filtering by @config.threadRootId, so a mutation re-widens the panel until the consumer re-fires SET_THREAD_ROOT. Layer 3 variants override `bodyContent` to deliver chat-bubble, email-threaded, or linear-comments UX while inheriting the same trait, state machine, emits, and listens."
3
3
  orbital ThreadPostOrbital {
4
4
  ;; Forward relation: array of child ThreadPost IDs whose parentId === this.id.
5
5
  ;; Lowers to `type: "relation", cardinality: "many"` (orbital-lolo lower.rs).
@@ -19,6 +19,8 @@ orbital ThreadPostOrbital {
19
19
 
20
20
  type ThreadPostCreateFailed = Event { error : string, code : string } "Fired when a new thread-post fails to persist"
21
21
 
22
+ type SetThreadRoot = Event { rootId : string! } "Fired to scope every ThreadPost fetch, and new-reply threadRootId stamping, to the descendants of one document root"
23
+
22
24
  entity ThreadPost [persistent: threadposts] {
23
25
  id : string!
24
26
  parentId : string @description "Identifier of the parent ThreadPost in a nested thread." @synonyms "parent, ancestor, source"
@@ -52,6 +54,14 @@ orbital ThreadPostOrbital {
52
54
  type: alert
53
55
  variant: error
54
56
  })
57
+ SET_THREAD_ROOT -> loading
58
+ (set @entity.threadRootId ?rootId)
59
+ (fetch ThreadPost {
60
+ emit: { failure: ThreadPostLoadFailed, success: ThreadPostLoaded }
61
+ filter: (or ["=", ?rootId, ""] ["=", (object/get @entity threadRootId), ?rootId])
62
+ include: (replies)
63
+ })
64
+ (render-ui main { size: sm, type: spinner })
55
65
  }
56
66
 
57
67
  state browsing {
@@ -73,13 +83,14 @@ orbital ThreadPostOrbital {
73
83
  authorName: @user.name
74
84
  content: ?content
75
85
  parentId: (str/default ?parentNodeId ?parentId)
76
- threadRootId: (str/default ?parentNodeId (str/default @config.threadRootId root))
86
+ threadRootId: (str/default ?parentNodeId (str/default @entity.threadRootId (str/default @config.threadRootId root)))
77
87
  } {
78
88
  emit: { failure: ThreadPostCreateFailed, success: ThreadPostCreated }
79
89
  })
80
90
  ;; Reply with no text yet (e.g. an email "Reply" button) opens the composer.
81
91
  REPLY -> replying when ["=", (str/default ?content ""), ""]
82
92
  (set @entity.parentId (str/default ?parentNodeId ?parentId))
93
+ (set @entity.threadRootId (str/default ?parentNodeId (str/default @entity.threadRootId (str/default @config.threadRootId root))))
83
94
  (render-ui main {
84
95
  children: [{
85
96
  content: "Write a reply"
@@ -139,6 +150,14 @@ orbital ThreadPostOrbital {
139
150
  type: alert
140
151
  variant: error
141
152
  })
153
+ SET_THREAD_ROOT -> loading
154
+ (set @entity.threadRootId ?rootId)
155
+ (fetch ThreadPost {
156
+ emit: { failure: ThreadPostLoadFailed, success: ThreadPostLoaded }
157
+ filter: (or ["=", ?rootId, ""] ["=", (object/get @entity threadRootId), ?rootId])
158
+ include: (replies)
159
+ })
160
+ (render-ui main { size: sm, type: spinner })
142
161
  }
143
162
 
144
163
  state replying {
@@ -165,6 +184,14 @@ orbital ThreadPostOrbital {
165
184
  type: alert
166
185
  variant: error
167
186
  })
187
+ SET_THREAD_ROOT -> loading
188
+ (set @entity.threadRootId ?rootId)
189
+ (fetch ThreadPost {
190
+ emit: { failure: ThreadPostLoadFailed, success: ThreadPostLoaded }
191
+ filter: (or ["=", ?rootId, ""] ["=", (object/get @entity threadRootId), ?rootId])
192
+ include: (replies)
193
+ })
194
+ (render-ui main { size: sm, type: spinner })
168
195
  }
169
196
 
170
197
  state error {
@@ -175,6 +202,14 @@ orbital ThreadPostOrbital {
175
202
  include: (replies)
176
203
  })
177
204
  (render-ui main { size: sm, type: spinner })
205
+ SET_THREAD_ROOT -> loading
206
+ (set @entity.threadRootId ?rootId)
207
+ (fetch ThreadPost {
208
+ emit: { failure: ThreadPostLoadFailed, success: ThreadPostLoaded }
209
+ filter: (or ["=", ?rootId, ""] ["=", (object/get @entity threadRootId), ?rootId])
210
+ include: (replies)
211
+ })
212
+ (render-ui main { size: sm, type: spinner })
178
213
  }
179
214
 
180
215
  emits {
@@ -295,6 +330,12 @@ orbital ThreadPostOrbital {
295
330
  @description "Indicates a failure to create a new thread post."
296
331
  @synonyms "error, failure, rejected, invalid"
297
332
  @tier "internal"
333
+ SET_THREAD_ROOT {
334
+ rootId : string!
335
+ }
336
+ @description "Scopes every fetch, and new-reply threadRootId stamping, to the descendants of one document root."
337
+ @synonyms "scope, focus, set root, document scope, thread scope"
338
+ @tier "domain"
298
339
  }
299
340
 
300
341
  config {
@@ -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