@almadar/std 16.192.0 → 16.193.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.
@@ -41,6 +41,7 @@ orbital AppLayoutOrbital {
41
41
  notificationClickEvent: @config.notificationClickEvent
42
42
  notifications: @config.notifications
43
43
  searchEvent: @config.searchEvent
44
+ sidebarContent: @config.sidebarTrait
44
45
  topBarActions: @config.topBarActions
45
46
  type: @config.layoutPattern
46
47
  user: (if @config.viewerName { avatar: @config.viewerAvatar, email: @config.viewerEmail, name: @config.viewerName } null)
@@ -90,6 +91,11 @@ orbital AppLayoutOrbital {
90
91
  @label "Content trait"
91
92
  @description "Trait rendered inside the app layout content area"
92
93
  @tier "presentation"
94
+ sidebarTrait : trait
95
+ @label "Sidebar trait"
96
+ @description "Optional trait rendered in the sidebar between the nav links and the footer — the Notion/Confluence-style always-visible page tree; scrolls independently. Unset renders plain nav only."
97
+ @synonyms "sidebar tree, nav tree, page tree in sidebar, hierarchy rail"
98
+ @tier "presentation"
93
99
  appName : string = App
94
100
  @label "What should this app be called?"
95
101
  @description "Name shown in the top-left of the chrome header."
@@ -0,0 +1,314 @@
1
+ app std-tag-strip "1.0.0"
2
+ "std-tag-strip — inline tag editing ON the document surface: the record's tags render as removable chips with a type-to-add input (a typed name that matches an existing tag attaches it; an unknown name is created in the shared taxonomy first), plus one-click chips for every existing tag not yet attached. Emits TAGS_CHANGED { id, tagIds } — the host organism routes it into its persistor's update; the document's refetch re-scopes the strip. Binds any record carrying a tagIds relation array; the tag store is the shared `tags` collection (std-tag-taxonomy's)."
3
+ ;; @capabilities "inline tags, tag chips on a document, add tag from taxonomy, create tag by typing, remove tag chip, tag editing in place"
4
+ orbital TagStripOrbital {
5
+ type TagStripOptions = Event { data : [StripTag] } "The shared tag store loaded for the strip's display pass"
6
+
7
+ type TagStripAddOptions = Event { data : [StripTag] } "The shared tag store loaded to resolve a typed tag name on add"
8
+
9
+ type TagStripRemoveOptions = Event { data : [StripTag] } "The shared tag store loaded to resolve the remaining names on remove"
10
+
11
+ type TagStripTagCreated = Event { id : string, row : StripTag } "A typed tag name had no existing row; one was created in the shared taxonomy"
12
+
13
+ type TagStripLoadFailed = Event { error : string, code : string } "The tag store fetch or create failed"
14
+
15
+ entity StripTag [persistent: tags] {
16
+ id : string!
17
+ name : string! @description "The descriptive label for this tag." @synonyms "label, title"
18
+ parentId : StripTag @description "Reference to the parent tag in the hierarchy; empty marks a root." @synonyms "parent, ancestor"
19
+ createdAt : string = ""
20
+ pendingId : string = ""
21
+ docId : string @intrinsic @description "The open record's id, stashed at SHOW_TAG_STRIP so commits know which row to update." @synonyms "record id, scope id" = ""
22
+ docTagIds : [string] @intrinsic @description "The open record's current tag ids, stashed at SHOW_TAG_STRIP for membership tests." @synonyms "attached tags, current tags"
23
+ pendingTagName : string @intrinsic @description "The tag name typed or clicked on add, held across the resolve fetch." @synonyms "typed tag, adding tag" = ""
24
+ pendingNames : [string] @intrinsic @description "The chip names remaining after a remove, held across the resolve fetch." @synonyms "remaining tags, kept names"
25
+ }
26
+
27
+ trait TagStrip -> @rebindable StripTag [interaction, collection] {
28
+ initial: idle
29
+
30
+ state idle {
31
+ INIT -> idle
32
+ (fetch StripTag {
33
+ emit: { failure: TagStripLoadFailed, success: TagStripOptions }
34
+ })
35
+ ;; Pre-scope paint: the store is warm but no record is open yet.
36
+ TagStripOptions -> idle
37
+ TagStripLoadFailed -> idle
38
+ SHOW_TAG_STRIP -> loading
39
+ (set @entity.docId ?id)
40
+ (set @entity.docTagIds (array/map ?tagIds (fn t (str/default (object/get @t id) @t))))
41
+ (fetch StripTag {
42
+ emit: { failure: TagStripLoadFailed, success: TagStripOptions }
43
+ })
44
+ }
45
+
46
+ state loading {
47
+ SHOW_TAG_STRIP -> loading
48
+ (set @entity.docId ?id)
49
+ (set @entity.docTagIds (array/map ?tagIds (fn t (str/default (object/get @t id) @t))))
50
+ (fetch StripTag {
51
+ emit: { failure: TagStripLoadFailed, success: TagStripOptions }
52
+ })
53
+ TagStripOptions -> ready
54
+ (render-ui main {
55
+ children: [(if ["!=", @config.title, ""] {
56
+ color: muted
57
+ content: @config.title
58
+ type: typography
59
+ variant: caption
60
+ } null), {
61
+ addEvent: ADD_TAG
62
+ helperText: @config.helperText
63
+ placeholder: @config.placeholder
64
+ removeEvent: REMOVE_TAG
65
+ type: tag-input
66
+ value: (array/map (array/filter ?data (fn t (array/includes @entity.docTagIds (object/get @t id)))) (fn t (object/get @t name)))
67
+ variant: secondary
68
+ }, (if ["=", (array/len (array/filter ?data (fn t (if (array/includes @entity.docTagIds (object/get @t id)) false true)))), 0] null {
69
+ children: (array/map (array/filter ?data (fn t (if (array/includes @entity.docTagIds (object/get @t id)) false true))) (fn t {
70
+ action: ADD_TAG
71
+ actionPayload: { tag: @t.name }
72
+ icon: tag
73
+ label: @t.name
74
+ size: sm
75
+ type: button
76
+ variant: ghost
77
+ }))
78
+ className: flex-wrap
79
+ direction: horizontal
80
+ gap: xs
81
+ type: stack
82
+ })]
83
+ direction: vertical
84
+ gap: xs
85
+ type: stack
86
+ })
87
+ TagStripLoadFailed -> idle
88
+ (render-ui main {
89
+ message: ?error
90
+ type: alert
91
+ variant: error
92
+ })
93
+ }
94
+
95
+ state ready {
96
+ SHOW_TAG_STRIP -> loading
97
+ (set @entity.docId ?id)
98
+ (set @entity.docTagIds (array/map ?tagIds (fn t (str/default (object/get @t id) @t))))
99
+ (fetch StripTag {
100
+ emit: { failure: TagStripLoadFailed, success: TagStripOptions }
101
+ })
102
+ ADD_TAG -> adding
103
+ (set @entity.pendingTagName ?tag)
104
+ (fetch StripTag {
105
+ emit: { failure: TagStripLoadFailed, success: TagStripAddOptions }
106
+ })
107
+ (render-ui main {
108
+ align: center
109
+ children: [{ size: sm, type: spinner }]
110
+ direction: horizontal
111
+ gap: xs
112
+ type: stack
113
+ })
114
+ REMOVE_TAG -> removing
115
+ (set @entity.pendingNames ?value)
116
+ (fetch StripTag {
117
+ emit: { failure: TagStripLoadFailed, success: TagStripRemoveOptions }
118
+ })
119
+ (render-ui main {
120
+ align: center
121
+ children: [{ size: sm, type: spinner }]
122
+ direction: horizontal
123
+ gap: xs
124
+ type: stack
125
+ })
126
+ TagStripTagCreated -> ready
127
+ }
128
+
129
+ state adding {
130
+ SHOW_TAG_STRIP -> loading
131
+ (set @entity.docId ?id)
132
+ (set @entity.docTagIds (array/map ?tagIds (fn t (str/default (object/get @t id) @t))))
133
+ (fetch StripTag {
134
+ emit: { failure: TagStripLoadFailed, success: TagStripOptions }
135
+ })
136
+ ;; The typed name matches an existing tag — attach its id.
137
+ TagStripAddOptions -> ready when (array/includes (array/map ?data (fn t (object/get @t name))) @entity.pendingTagName)
138
+ (emit TAGS_CHANGED {
139
+ id: @entity.docId
140
+ tagIds: (array/append @entity.docTagIds (object/get (array/first (array/filter ?data (fn t ["=", (object/get @t name), @entity.pendingTagName]))) id))
141
+ })
142
+ ;; Unknown name — create it in the shared taxonomy, then attach.
143
+ TagStripAddOptions -> creating
144
+ (persist create StripTag { name: @entity.pendingTagName } {
145
+ emit: { failure: TagStripLoadFailed, success: TagStripTagCreated }
146
+ })
147
+ TagStripLoadFailed -> idle
148
+ (render-ui main {
149
+ message: ?error
150
+ type: alert
151
+ variant: error
152
+ })
153
+ }
154
+
155
+ state creating {
156
+ SHOW_TAG_STRIP -> loading
157
+ (set @entity.docId ?id)
158
+ (set @entity.docTagIds (array/map ?tagIds (fn t (str/default (object/get @t id) @t))))
159
+ (fetch StripTag {
160
+ emit: { failure: TagStripLoadFailed, success: TagStripOptions }
161
+ })
162
+ TagStripTagCreated -> ready when ["!=", (str/default ?id ""), ""]
163
+ (emit TAGS_CHANGED {
164
+ id: @entity.docId
165
+ tagIds: (array/append @entity.docTagIds ?id)
166
+ })
167
+ TagStripLoadFailed -> idle
168
+ (render-ui main {
169
+ message: ?error
170
+ type: alert
171
+ variant: error
172
+ })
173
+ }
174
+
175
+ state removing {
176
+ SHOW_TAG_STRIP -> loading
177
+ (set @entity.docId ?id)
178
+ (set @entity.docTagIds (array/map ?tagIds (fn t (str/default (object/get @t id) @t))))
179
+ (fetch StripTag {
180
+ emit: { failure: TagStripLoadFailed, success: TagStripOptions }
181
+ })
182
+ ;; The remaining chip names map back to ids — every kept name exists
183
+ ;; by construction (chips only render attached, existing tags).
184
+ TagStripRemoveOptions -> ready
185
+ (emit TAGS_CHANGED {
186
+ id: @entity.docId
187
+ tagIds: (array/map (array/filter ?data (fn t (array/includes @entity.pendingNames (object/get @t name)))) (fn t (object/get @t id)))
188
+ })
189
+ TagStripLoadFailed -> idle
190
+ (render-ui main {
191
+ message: ?error
192
+ type: alert
193
+ variant: error
194
+ })
195
+ }
196
+
197
+ emits {
198
+ TAGS_CHANGED -> external {
199
+ id : string
200
+ tagIds : [string]
201
+ }
202
+ @description "The record's tag set changed (chip added or removed); the host routes this into its persistor's update so the write and the refetch stay the organism's."
203
+ @synonyms "tags updated, tag set changed, retagged"
204
+ @tier "domain"
205
+ ADD_TAG {
206
+ tag : string!
207
+ }
208
+ @description "A tag name was typed into the strip or an existing tag's suggestion chip was clicked."
209
+ @synonyms "attach tag, add label"
210
+ @tier "presentation"
211
+ REMOVE_TAG {
212
+ tag : string
213
+ value : [string]
214
+ }
215
+ @description "A chip's remove affordance was clicked; value carries the names that remain."
216
+ @synonyms "detach tag, remove label"
217
+ @tier "presentation"
218
+ TagStripOptions {
219
+ data : [StripTag]
220
+ }
221
+ @description "The shared tag store loaded for the display pass."
222
+ @tier "internal"
223
+ TagStripAddOptions {
224
+ data : [StripTag]
225
+ }
226
+ @description "The shared tag store loaded to resolve a typed name on add."
227
+ @tier "internal"
228
+ TagStripRemoveOptions {
229
+ data : [StripTag]
230
+ }
231
+ @description "The shared tag store loaded to resolve the remaining names on remove."
232
+ @tier "internal"
233
+ TagStripTagCreated {
234
+ id : string
235
+ row : StripTag
236
+ }
237
+ @description "A typed name had no existing tag; a fresh row was created in the shared taxonomy."
238
+ @tier "internal"
239
+ TagStripLoadFailed {
240
+ error : string
241
+ code : string
242
+ }
243
+ @description "The tag store fetch or create failed."
244
+ @tier "internal"
245
+ }
246
+
247
+ listens {
248
+ SHOW_TAG_STRIP {
249
+ id : string!
250
+ tagIds : [string]
251
+ }
252
+ @description "Scope the strip to the open record — routed from the document's load events; carries the record's id and current tag ids (hydrated tag rows are normalized to their ids at stash time, so include-hydrated fetches route straight through)."
253
+ @synonyms "load tags, scope tag strip, show tags"
254
+ @tier "domain"
255
+ ADD_TAG {
256
+ tag : string!
257
+ }
258
+ @description "Attach a tag by name — typed into the input or clicked from the existing-tag chips."
259
+ @synonyms "attach tag, add label"
260
+ @tier "presentation"
261
+ REMOVE_TAG {
262
+ tag : string
263
+ value : [string]
264
+ }
265
+ @description "Detach a chip; value carries the names that remain."
266
+ @synonyms "detach tag, remove label"
267
+ @tier "presentation"
268
+ TagStripOptions {
269
+ data : [StripTag]
270
+ }
271
+ @description "Display-pass fetch resolved."
272
+ @tier "internal"
273
+ TagStripAddOptions {
274
+ data : [StripTag]
275
+ }
276
+ @description "Add-resolve fetch resolved."
277
+ @tier "internal"
278
+ TagStripRemoveOptions {
279
+ data : [StripTag]
280
+ }
281
+ @description "Remove-resolve fetch resolved."
282
+ @tier "internal"
283
+ TagStripTagCreated {
284
+ id : string
285
+ row : StripTag
286
+ }
287
+ @description "The taxonomy row for a typed name persisted; attach its id."
288
+ @tier "internal"
289
+ TagStripLoadFailed {
290
+ error : string
291
+ code : string
292
+ }
293
+ @description "The tag store fetch or create failed."
294
+ @tier "internal"
295
+ }
296
+
297
+ config {
298
+ title : string = Tags
299
+ @label "Strip caption"
300
+ @description "Small caption above the chips; empty hides it"
301
+ @tier "presentation"
302
+ placeholder : string = "Add a tag…"
303
+ @label "Input placeholder"
304
+ @description "Placeholder in the type-to-add input"
305
+ @tier "presentation"
306
+ helperText : string = ""
307
+ @label "Helper text"
308
+ @description "Optional helper line under the input"
309
+ @tier "presentation"
310
+ }
311
+ }
312
+
313
+ page "/tagstrip/demo" as TagStripDemoPage -> TagStrip
314
+ }
@@ -39,7 +39,7 @@ orbital TagTaxonomyOrbital {
39
39
  align: center
40
40
  children: [{ type: spinner }, {
41
41
  color: muted
42
- content: "Loading categories…"
42
+ content: "Loading…"
43
43
  type: typography
44
44
  variant: caption
45
45
  }]
@@ -54,7 +54,7 @@ orbital TagTaxonomyOrbital {
54
54
  action: SHOW_ROOT
55
55
  disabled: (if (!= @entity.id "") false true)
56
56
  icon: arrow-left
57
- label: "All categories"
57
+ label: @config.rootLabel
58
58
  size: sm
59
59
  type: button
60
60
  variant: ghost
@@ -65,7 +65,7 @@ orbital TagTaxonomyOrbital {
65
65
  }, (if @config.allowEdit {
66
66
  action: NEW_TAG
67
67
  icon: plus
68
- label: "New category"
68
+ label: @config.createLabel
69
69
  size: sm
70
70
  type: button
71
71
  variant: secondary
@@ -150,7 +150,7 @@ orbital TagTaxonomyOrbital {
150
150
  align: center
151
151
  children: [{ type: spinner }, {
152
152
  color: muted
153
- content: "Loading categories…"
153
+ content: "Loading…"
154
154
  type: typography
155
155
  variant: caption
156
156
  }]
@@ -171,7 +171,7 @@ orbital TagTaxonomyOrbital {
171
171
  align: center
172
172
  children: [{ type: spinner }, {
173
173
  color: muted
174
- content: "Loading children…"
174
+ content: "Loading…"
175
175
  type: typography
176
176
  variant: caption
177
177
  }]
@@ -191,7 +191,7 @@ orbital TagTaxonomyOrbital {
191
191
  align: center
192
192
  children: [{ type: spinner }, {
193
193
  color: muted
194
- content: "Loading categories…"
194
+ content: "Loading…"
195
195
  type: typography
196
196
  variant: caption
197
197
  }]
@@ -206,7 +206,7 @@ orbital TagTaxonomyOrbital {
206
206
  action: SHOW_ROOT
207
207
  disabled: (if (!= @entity.id "") false true)
208
208
  icon: arrow-left
209
- label: "All categories"
209
+ label: @config.rootLabel
210
210
  size: sm
211
211
  type: button
212
212
  variant: ghost
@@ -217,7 +217,7 @@ orbital TagTaxonomyOrbital {
217
217
  }, (if @config.allowEdit {
218
218
  action: NEW_TAG
219
219
  icon: plus
220
- label: "New category"
220
+ label: @config.createLabel
221
221
  size: sm
222
222
  type: button
223
223
  variant: secondary
@@ -285,7 +285,7 @@ orbital TagTaxonomyOrbital {
285
285
  NEW_TAG -> creating when @config.allowEdit
286
286
  (render-ui modal {
287
287
  children: [{
288
- content: "New category"
288
+ content: @config.createLabel
289
289
  type: typography
290
290
  variant: h3
291
291
  }, {
@@ -330,7 +330,7 @@ orbital TagTaxonomyOrbital {
330
330
  align: center
331
331
  children: [{ type: spinner }, {
332
332
  color: muted
333
- content: "Loading categories…"
333
+ content: "Loading…"
334
334
  type: typography
335
335
  variant: caption
336
336
  }]
@@ -347,7 +347,7 @@ orbital TagTaxonomyOrbital {
347
347
  align: center
348
348
  children: [{ type: spinner }, {
349
349
  color: muted
350
- content: "Loading categories…"
350
+ content: "Loading…"
351
351
  type: typography
352
352
  variant: caption
353
353
  }]
@@ -380,7 +380,7 @@ orbital TagTaxonomyOrbital {
380
380
  align: center
381
381
  children: [{ type: spinner }, {
382
382
  color: muted
383
- content: "Loading categories…"
383
+ content: "Loading…"
384
384
  type: typography
385
385
  variant: caption
386
386
  }]
@@ -402,7 +402,7 @@ orbital TagTaxonomyOrbital {
402
402
  action: SHOW_ROOT
403
403
  disabled: (if (!= @entity.id "") false true)
404
404
  icon: arrow-left
405
- label: "All categories"
405
+ label: @config.rootLabel
406
406
  size: sm
407
407
  type: button
408
408
  variant: ghost
@@ -413,7 +413,7 @@ orbital TagTaxonomyOrbital {
413
413
  }, (if @config.allowEdit {
414
414
  action: NEW_TAG
415
415
  icon: plus
416
- label: "New category"
416
+ label: @config.createLabel
417
417
  size: sm
418
418
  type: button
419
419
  variant: secondary
@@ -498,7 +498,7 @@ orbital TagTaxonomyOrbital {
498
498
  align: center
499
499
  children: [{ type: spinner }, {
500
500
  color: muted
501
- content: "Loading categories…"
501
+ content: "Loading…"
502
502
  type: typography
503
503
  variant: caption
504
504
  }]
@@ -536,7 +536,7 @@ orbital TagTaxonomyOrbital {
536
536
  @synonyms "chosen, picked, activated, highlighted"
537
537
  @tier "domain"
538
538
  NEW_TAG
539
- @description "Opens the new-category form (rendered only when allowEdit is on); the creation lands at the level currently open."
539
+ @description "Opens the create-tag form (rendered only when allowEdit is on); the creation lands at the level currently open."
540
540
  @synonyms "add tag, create category, new category"
541
541
  @tier "domain"
542
542
  COMMIT_TAG {
@@ -638,8 +638,18 @@ orbital TagTaxonomyOrbital {
638
638
  @tier "presentation"
639
639
  allowEdit : boolean = false
640
640
  @label "Allow editing"
641
- @description "Render the New-category affordance: creations land at the level currently open (top level, or inside the drilled-into category). Rename is a follow-up (ledgered)."
641
+ @description "Render the create-tag affordance: creations land at the level currently open (top level, or inside the drilled-into category). Rename is a follow-up (ledgered)."
642
642
  @tier "domain"
643
+ createLabel : string = "New tag"
644
+ @label "Create-button label"
645
+ @description "Label on the create affordance and its form heading — say what this taxonomy calls its entries (New tag, New category, New topic)."
646
+ @synonyms "add label, new tag label, create caption"
647
+ @tier "presentation"
648
+ rootLabel : string = "All tags"
649
+ @label "Back-to-root label"
650
+ @description "Label on the back affordance that returns from a drilled-in level to the root — phrase it in this taxonomy's vocabulary (All tags, All categories)."
651
+ @synonyms "back label, root caption, all label"
652
+ @tier "presentation"
643
653
  tableLook : "dense" | "spacious" | "striped" | "borderless" | "card-rows" = dense
644
654
  @label "Table look"
645
655
  @description "Layer 2 visual treatment for the data table rendered by this atom."