@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.
- package/behaviors/lolo/ui/core/.hand-authored.json +4 -0
- package/behaviors/lolo/ui/core/atoms/std-browse.lolo +11 -0
- package/behaviors/lolo/ui/core/atoms/std-related.lolo +86 -9
- package/behaviors/lolo/ui/core/atoms/std-rich-editor.lolo +368 -1
- package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +62 -2
- package/behaviors/lolo/ui/core/atoms/std-thread.lolo +43 -2
- package/behaviors/lolo/ui/core/atoms/std-tree.lolo +247 -0
- package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +110 -2
- package/behaviors/lolo/ui/core/molecules/ui-file-tree.lolo +18 -2
- package/behaviors/lolo/ui/core/organisms/ui-detail-panel.lolo +12 -2
- package/behaviors/lolo/ui/core/organisms/ui-form-section.lolo +2 -2
- package/behaviors/lolo/ui/core/organisms/ui-form.lolo +2 -2
- package/behaviors/lolo/ui/game/atoms/std-tutorial-autodemo.lolo +136 -0
- package/behaviors/lolo/ui/game/atoms/std-tutorial-overlay.lolo +41 -8
- package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +3 -3
- package/behaviors/registry/ui/core/atoms/std-browse.orb +17 -0
- package/behaviors/registry/ui/core/atoms/std-related.orb +256 -3
- package/behaviors/registry/ui/core/atoms/std-rich-editor.orb +898 -1
- package/behaviors/registry/ui/core/atoms/std-tag-taxonomy.orb +240 -8
- package/behaviors/registry/ui/core/atoms/std-thread.orb +254 -4
- package/behaviors/registry/ui/core/atoms/std-tree.orb +656 -0
- package/behaviors/registry/ui/core/atoms/std-version-history.orb +479 -6
- package/behaviors/registry/ui/core/molecules/ui-file-tree.orb +43 -19
- package/behaviors/registry/ui/core/organisms/ui-detail-panel.orb +37 -3
- package/behaviors/registry/ui/core/organisms/ui-form-section.orb +8 -2
- package/behaviors/registry/ui/core/organisms/ui-form.orb +8 -2
- package/behaviors/registry/ui/game/atoms/std-tutorial-autodemo.orb +732 -0
- package/behaviors/registry/ui/game/atoms/std-tutorial-overlay.orb +305 -153
- package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +26 -0
- package/dist/behaviors/exports-reader.js +5132 -2398
- package/dist/behaviors/functions/index.d.ts +261 -44
- package/dist/behaviors/functions/index.js +5118 -2396
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +5133 -2399
- package/dist/behaviors/query.js +5132 -2398
- package/dist/behaviors-embeddings.hash.json +3 -3
- package/dist/behaviors-embeddings.json +310 -302
- package/dist/behaviors-registry.json +820 -35
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5133 -2399
- 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
|
@@ -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
|
|
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 :
|
|
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
|
|