@almadar/std 16.187.0 → 16.188.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 (81) hide show
  1. package/behaviors/lolo/ui/core/.hand-authored.json +4 -0
  2. package/behaviors/lolo/ui/core/atoms/std-board.lolo +138 -4
  3. package/behaviors/lolo/ui/core/atoms/std-browse.lolo +148 -8
  4. package/behaviors/lolo/ui/core/atoms/std-calendar.lolo +141 -3
  5. package/behaviors/lolo/ui/core/atoms/std-event-log.lolo +255 -2
  6. package/behaviors/lolo/ui/core/atoms/std-gallery.lolo +148 -1
  7. package/behaviors/lolo/ui/core/atoms/std-list.lolo +193 -1
  8. package/behaviors/lolo/ui/core/atoms/std-record-detail.lolo +329 -0
  9. package/behaviors/lolo/ui/core/atoms/std-selection.lolo +182 -1
  10. package/behaviors/lolo/ui/core/atoms/std-tree.lolo +113 -3
  11. package/behaviors/lolo/ui/game/atoms/std-area-fill.lolo +93 -0
  12. package/behaviors/lolo/ui/game/atoms/std-loop-lock.lolo +79 -0
  13. package/behaviors/lolo/ui/game/atoms/std-pad-lock.lolo +93 -0
  14. package/behaviors/lolo/ui/game/atoms/std-patrol-hazard.lolo +14 -7
  15. package/behaviors/lolo/ui/game/atoms/std-platformer-body.lolo +21 -4
  16. package/behaviors/lolo/ui/game/atoms/std-velocity-gate.lolo +101 -0
  17. package/behaviors/registry/infra/atoms/std-approval-gate.orb +16 -8
  18. package/behaviors/registry/infra/atoms/std-cache-aside.orb +62 -31
  19. package/behaviors/registry/infra/atoms/std-circuit-breaker.orb +32 -16
  20. package/behaviors/registry/infra/atoms/std-data-erasure.orb +16 -8
  21. package/behaviors/registry/infra/atoms/std-mod-queue.orb +16 -8
  22. package/behaviors/registry/infra/atoms/std-rate-limiter.orb +28 -14
  23. package/behaviors/registry/infra/organisms/std-status-lifecycle-control.orb +2 -1
  24. package/behaviors/registry/ml/organisms/std-escalating-decision.orb +10 -5
  25. package/behaviors/registry/ml/organisms/std-knowledge-tracing.orb +4 -2
  26. package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +2 -1
  27. package/behaviors/registry/ui/core/atoms/std-board.orb +355 -2
  28. package/behaviors/registry/ui/core/atoms/std-browse.orb +304 -31
  29. package/behaviors/registry/ui/core/atoms/std-calendar.orb +344 -0
  30. package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +2 -1
  31. package/behaviors/registry/ui/core/atoms/std-esign-flow.orb +4 -2
  32. package/behaviors/registry/ui/core/atoms/std-esign-request.orb +6 -3
  33. package/behaviors/registry/ui/core/atoms/std-event-log.orb +773 -0
  34. package/behaviors/registry/ui/core/atoms/std-export.orb +4 -2
  35. package/behaviors/registry/ui/core/atoms/std-gallery.orb +390 -0
  36. package/behaviors/registry/ui/core/atoms/std-import.orb +4 -2
  37. package/behaviors/registry/ui/core/atoms/std-list.orb +451 -0
  38. package/behaviors/registry/ui/core/atoms/std-record-detail.orb +1141 -0
  39. package/behaviors/registry/ui/core/atoms/std-selection.orb +436 -0
  40. package/behaviors/registry/ui/core/atoms/std-tree.orb +292 -1
  41. package/behaviors/registry/ui/game/atoms/std-area-fill.orb +955 -0
  42. package/behaviors/registry/ui/game/atoms/std-fx-trail.orb +1 -1
  43. package/behaviors/registry/ui/game/atoms/std-gate-lock.orb +1 -1
  44. package/behaviors/registry/ui/game/atoms/std-lives.orb +1 -1
  45. package/behaviors/registry/ui/game/atoms/std-loop-lock.orb +785 -0
  46. package/behaviors/registry/ui/game/atoms/std-pad-lock.orb +903 -0
  47. package/behaviors/registry/ui/game/atoms/std-patrol-hazard.orb +190 -44
  48. package/behaviors/registry/ui/game/atoms/std-platformer-body.orb +868 -637
  49. package/behaviors/registry/ui/game/atoms/std-velocity-gate.orb +1040 -0
  50. package/behaviors/registry/ui/game/organisms/std-arcade-board-3d.orb +8 -4
  51. package/behaviors/registry/ui/game/organisms/std-crew-board-2d.orb +6 -3
  52. package/behaviors/registry/ui/game/organisms/std-dungeon-board-2d.orb +10 -5
  53. package/behaviors/registry/ui/game/organisms/std-market-board-3d.orb +8 -4
  54. package/behaviors/registry/ui/game/organisms/std-objective-board-3d.orb +8 -4
  55. package/behaviors/registry/ui/game/organisms/std-platformer-board-2d.orb +7 -4
  56. package/behaviors/registry/ui/game/organisms/std-survival-board-3d.orb +8 -4
  57. package/behaviors/registry/ui/game/organisms/std-tactics-board-2d.orb +12 -6
  58. package/behaviors/registry/ui/game/organisms/std-tactics-board-3d.orb +8 -4
  59. package/behaviors/registry/ui/game/organisms/std-worldmap-board-2d.orb +6 -3
  60. package/behaviors/registry/ui/learning/atoms/std-learn-slope-probe.orb +1 -1
  61. package/behaviors/registry/ui/marketing/atoms/std-marketing-article.orb +2 -1
  62. package/behaviors/registry/ui/marketing/atoms/std-marketing-footer.orb +2 -1
  63. package/behaviors/registry/ui/marketing/atoms/std-marketing-split.orb +2 -1
  64. package/behaviors/registry/ui/marketing/atoms/std-marketing-stats.orb +2 -1
  65. package/behaviors/registry/ui/marketing/atoms/std-marketing-steps.orb +2 -1
  66. package/behaviors/registry/ui/marketing/atoms/std-marketing-team.orb +2 -1
  67. package/dist/behaviors/exports-reader.js +7541 -3153
  68. package/dist/behaviors/functions/index.d.ts +598 -18
  69. package/dist/behaviors/functions/index.js +7524 -3154
  70. package/dist/behaviors/index.d.ts +1 -1
  71. package/dist/behaviors/index.js +7542 -3154
  72. package/dist/behaviors/query.js +7541 -3153
  73. package/dist/behaviors-embeddings.hash.json +3 -3
  74. package/dist/behaviors-embeddings.json +485 -461
  75. package/dist/behaviors-registry.json +2612 -934
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.js +7542 -3154
  78. package/dist/knob-embeddings.hash.json +3 -3
  79. package/dist/knob-embeddings.json +1 -1
  80. package/dist/registry/factory-signatures.json +1 -1
  81. package/package.json +1 -1
@@ -2,6 +2,7 @@ app std-event-log "2.1.0"
2
2
  "std-event-log — generic chronological event feed (Stripe activity log / GitHub PR timeline / Linear event feed style). Renders a vertical `timeline` with type-coded dots, a single horizontal chip-bar of kind filters (All + per-kind), reverse-chronological sort, and an inline form for logging events that weren't auto-captured. `config.kindOptions` maps each kind → status/icon/label so consuming molecules/organisms get type-coded visuals without re-implementing topology. Topology = loading / viewing / backfilling / error."
3
3
  type ObjectSpec = { id : string, label : string, value : string }
4
4
  type KindOption = { label : string!, value : string! }
5
+ type FilterSpec = { field : string!, label : string, options : [string] }
5
6
  orbital EventLogOrbital {
6
7
  type LogEntry = { id : string!, title : string!, description : string, date : string, status : "complete" | "active" | "pending" | "error", kind : "created" | "updated" | "approved" | "rejected" }
7
8
 
@@ -89,7 +90,37 @@ orbital EventLogOrbital {
89
90
  direction: horizontal
90
91
  gap: sm
91
92
  type: stack
92
- }, {
93
+ }, (if @config.bodySearch {
94
+ children: [{
95
+ className: "w-full max-w-md"
96
+ clearable: true
97
+ event: REFETCH_QUERY
98
+ placeholder: @config.searchPlaceholder
99
+ type: search-input
100
+ }]
101
+ direction: horizontal
102
+ gap: sm
103
+ type: stack
104
+ } {
105
+ children: []
106
+ gap: none
107
+ type: stack
108
+ }), (if [">", (array/len @config.filters), 0] {
109
+ children: [{
110
+ entity: EventLogView
111
+ event: REFETCH_FILTER
112
+ filters: @config.filters
113
+ look: @config.filterBarLook
114
+ type: filter-group
115
+ }]
116
+ direction: horizontal
117
+ gap: sm
118
+ type: stack
119
+ } {
120
+ children: []
121
+ gap: none
122
+ type: stack
123
+ }), {
93
124
  activeTab: @entity.filterKind
94
125
  tabChangeEvent: APPLY_FILTER
95
126
  tabs: @entity.filterChips
@@ -155,7 +186,150 @@ orbital EventLogOrbital {
155
186
  direction: horizontal
156
187
  gap: sm
157
188
  type: stack
189
+ }, (if @config.bodySearch {
190
+ children: [{
191
+ className: "w-full max-w-md"
192
+ clearable: true
193
+ event: REFETCH_QUERY
194
+ placeholder: @config.searchPlaceholder
195
+ type: search-input
196
+ }]
197
+ direction: horizontal
198
+ gap: sm
199
+ type: stack
200
+ } {
201
+ children: []
202
+ gap: none
203
+ type: stack
204
+ }), (if [">", (array/len @config.filters), 0] {
205
+ children: [{
206
+ entity: EventLogView
207
+ event: REFETCH_FILTER
208
+ filters: @config.filters
209
+ look: @config.filterBarLook
210
+ type: filter-group
211
+ }]
212
+ direction: horizontal
213
+ gap: sm
214
+ type: stack
215
+ } {
216
+ children: []
217
+ gap: none
218
+ type: stack
219
+ }), {
220
+ activeTab: @entity.filterKind
221
+ tabChangeEvent: APPLY_FILTER
222
+ tabs: @entity.filterChips
223
+ type: tabs
158
224
  }, {
225
+ entity: @entity.entries
226
+ fields: (title description date status)
227
+ look: @config.timelineLook
228
+ type: timeline
229
+ }]
230
+ direction: vertical
231
+ gap: md
232
+ type: stack
233
+ })
234
+ ;; REFETCH_* stays in `viewing` and DOES NOT replace the timeline with a
235
+ ;; skeleton — same reasoning as std-browse's REFETCH_* arms: a skeleton
236
+ ;; on refetch unmounts the timeline and flickers every keystroke. The
237
+ ;; fetch fires in the background; the EventLogLoaded arm below re-renders
238
+ ;; in place once it lands. Each refetch also honours the current kind
239
+ ;; tab (@entity.filterKind) so search/generic-filter compound with it
240
+ ;; instead of resetting it.
241
+ REFETCH_QUERY -> viewing
242
+ (fetch EventLogView {
243
+ emit: { failure: EventLogLoadFailed, success: EventLogLoaded }
244
+ filter: (and (or ["=", @entity.filterKind, ""] ["=", (object/get @entity kind), @entity.filterKind]) (or ["=", ?searchTerm, ""] (str/includes (object/get @entity title) ?searchTerm)))
245
+ })
246
+ REFETCH_FILTER -> viewing
247
+ (fetch EventLogView {
248
+ emit: { failure: EventLogLoadFailed, success: EventLogLoaded }
249
+ filter: (and (or ["=", @entity.filterKind, ""] ["=", (object/get @entity kind), @entity.filterKind]) (or ["=", ?value, ""] ["=", (object/get @entity ?field), ?value]))
250
+ })
251
+ ;; No filter carried over on REFETCH_PAGE, mirroring std-browse exactly —
252
+ ;; the reference page arm does not preserve query/filter either.
253
+ REFETCH_PAGE -> viewing
254
+ (fetch EventLogView {
255
+ emit: { failure: EventLogLoadFailed, success: EventLogLoaded }
256
+ limit: @config.pageSize
257
+ offset: ["*", ["-", ?page, 1], @config.pageSize]
258
+ })
259
+ EventLogLoaded -> viewing
260
+ (set @entity.filterChips (array/prepend (array/map @config.kindOptions (fn k {
261
+ icon: (object/get @k icon circle)
262
+ id: (object/get @k key)
263
+ label: (object/get @k label)
264
+ })) {
265
+ icon: list
266
+ id: ""
267
+ label: All
268
+ }))
269
+ (set @entity.allEntries (array/sort (array/map ?data (fn row {
270
+ date: (object/get @row createdAt (object/get @row date ""))
271
+ description: (object/get @row description "")
272
+ id: (object/get @row id "")
273
+ kind: (object/get @row kind "")
274
+ status: (object/get (or (array/find @config.kindOptions (fn k ["=", (object/get @k key), (object/get @row kind "")])) {}) status pending)
275
+ title: (object/get @row title "")
276
+ })) date desc))
277
+ (set @entity.entries (array/sort (array/map ?data (fn row {
278
+ date: (object/get @row createdAt (object/get @row date ""))
279
+ description: (object/get @row description "")
280
+ id: (object/get @row id "")
281
+ kind: (object/get @row kind "")
282
+ status: (object/get (or (array/find @config.kindOptions (fn k ["=", (object/get @k key), (object/get @row kind "")])) {}) status pending)
283
+ title: (object/get @row title "")
284
+ })) date desc))
285
+ (render-ui main {
286
+ children: [{
287
+ align: center
288
+ children: [{ name: history, type: icon }, {
289
+ content: @config.title
290
+ type: typography
291
+ variant: h3
292
+ }, {
293
+ action: OPEN_BACKFILL
294
+ icon: plus
295
+ label: "Log event"
296
+ type: button
297
+ variant: primary
298
+ }]
299
+ direction: horizontal
300
+ gap: sm
301
+ type: stack
302
+ }, (if @config.bodySearch {
303
+ children: [{
304
+ className: "w-full max-w-md"
305
+ clearable: true
306
+ event: REFETCH_QUERY
307
+ placeholder: @config.searchPlaceholder
308
+ type: search-input
309
+ }]
310
+ direction: horizontal
311
+ gap: sm
312
+ type: stack
313
+ } {
314
+ children: []
315
+ gap: none
316
+ type: stack
317
+ }), (if [">", (array/len @config.filters), 0] {
318
+ children: [{
319
+ entity: EventLogView
320
+ event: REFETCH_FILTER
321
+ filters: @config.filters
322
+ look: @config.filterBarLook
323
+ type: filter-group
324
+ }]
325
+ direction: horizontal
326
+ gap: sm
327
+ type: stack
328
+ } {
329
+ children: []
330
+ gap: none
331
+ type: stack
332
+ }), {
159
333
  activeTab: @entity.filterKind
160
334
  tabChangeEvent: APPLY_FILTER
161
335
  tabs: @entity.filterChips
@@ -237,7 +411,37 @@ orbital EventLogOrbital {
237
411
  direction: horizontal
238
412
  gap: sm
239
413
  type: stack
240
- }, {
414
+ }, (if @config.bodySearch {
415
+ children: [{
416
+ className: "w-full max-w-md"
417
+ clearable: true
418
+ event: REFETCH_QUERY
419
+ placeholder: @config.searchPlaceholder
420
+ type: search-input
421
+ }]
422
+ direction: horizontal
423
+ gap: sm
424
+ type: stack
425
+ } {
426
+ children: []
427
+ gap: none
428
+ type: stack
429
+ }), (if [">", (array/len @config.filters), 0] {
430
+ children: [{
431
+ entity: EventLogView
432
+ event: REFETCH_FILTER
433
+ filters: @config.filters
434
+ look: @config.filterBarLook
435
+ type: filter-group
436
+ }]
437
+ direction: horizontal
438
+ gap: sm
439
+ type: stack
440
+ } {
441
+ children: []
442
+ gap: none
443
+ type: stack
444
+ }), {
241
445
  activeTab: @entity.filterKind
242
446
  tabChangeEvent: APPLY_FILTER
243
447
  tabs: @entity.filterChips
@@ -354,6 +558,25 @@ orbital EventLogOrbital {
354
558
  @description "Indicates an attempt to save an event failed."
355
559
  @synonyms "error, failure, save failed"
356
560
  @tier "internal"
561
+ REFETCH_QUERY {
562
+ searchTerm : string!
563
+ }
564
+ @description "Triggers an activity feed refresh based on the current search term."
565
+ @synonyms "refresh, reload, update, re-query, search"
566
+ @tier "domain"
567
+ REFETCH_FILTER {
568
+ field : string!
569
+ value : string!
570
+ }
571
+ @description "Triggers an activity feed refresh based on a field/value filter, in addition to the built-in kind tabs."
572
+ @synonyms "refresh, filter, update, reload"
573
+ @tier "domain"
574
+ REFETCH_PAGE {
575
+ page : number!
576
+ }
577
+ @description "Triggers an activity feed page refresh."
578
+ @synonyms "reload, refresh, update, paginate"
579
+ @tier "domain"
357
580
  }
358
581
 
359
582
  config {
@@ -397,6 +620,36 @@ orbital EventLogOrbital {
397
620
  @label "Timeline look"
398
621
  @description "Layer 2 visual treatment for the timeline."
399
622
  @tier "presentation"
623
+ bodySearch : boolean = false
624
+ @label "Show the log's own search box?"
625
+ @description "Embeds a search box above the activity feed. Set true only when the page has no other search affordance (e.g. no page-level std-search), so the surface shows one search box instead of two. False is the default — no consumer of this atom has ever had a search box."
626
+ @synonyms "inline search, log search, built-in search, show search"
627
+ @tier "presentation"
628
+ searchPlaceholder : string = "Search activity…"
629
+ @label "Search box placeholder text"
630
+ @description "Hint text inside the log's search box."
631
+ @synonyms "search hint, placeholder, search box text"
632
+ @tier "presentation"
633
+ filters : [FilterSpec] = []
634
+ @label "Which filters should appear above the feed?"
635
+ @description "Dropdown filter facets by field, rendered as an embedded filter bar above the timeline; each selection fires REFETCH_FILTER on this trait, ANDed with the current kind tab. Empty (the default) = no filter bar — the right setting whenever the page already composes std-filter beside this atom and routes XFilter.FILTER -> REFETCH_FILTER, or relies solely on the built-in kind-filter chips (driven by kindOptions, which this knob does not duplicate or replace). Use this knob only for an extra facet beyond kind (e.g. actor, priority)."
636
+ @synonyms "filter bar, faceted filters, field filters, dropdowns"
637
+ @tier "presentation"
638
+ filterBarLook : "toolbar" | "chips" | "pills" | "popover-trigger" | "inline-column-header" = toolbar
639
+ @label "Filter bar look"
640
+ @description "Visual treatment for the embedded filter bar, mirroring std-filter's own enum. Only applies when `filters` is non-empty."
641
+ @synonyms "filter bar style, facet style, chips, pills"
642
+ @tier "presentation"
643
+ pageSize : number = 20
644
+ @label "How many entries per page when paging?"
645
+ @description "Records fetched from the server per REFETCH_PAGE request. The initial load and every search/kind/filter refetch stay unpaginated (load everything that matches) so the default unlimited activity feed is unchanged; only an explicit REFETCH_PAGE from a composed std-pagination control applies this limit."
646
+ @synonyms "fetch limit, records per page, page limit"
647
+ @tier "presentation"
648
+ displayPageSize : number = 20
649
+ @label "How many entries per page should a pager show?"
650
+ @description "Page size a consumer-composed std-pagination control should use to compute total pages (e.g. from totalCount). This atom's own timeline pattern has no built-in pager UI, so this knob is informational for the composed control, not consumed by the timeline itself."
651
+ @synonyms "visible rows, items per page, display limit"
652
+ @tier "presentation"
400
653
  }
401
654
  }
402
655
 
@@ -1,5 +1,6 @@
1
1
  app std-gallery "1.0.0"
2
2
  "std-gallery — image gallery primitive. Browses GalleryItems in a media-gallery grid; opens a lightbox modal for full-size viewing."
3
+ type FilterSpec = { field : string!, label : string, options : [string] }
3
4
  orbital GalleryItemOrbital {
4
5
  uses Spinner from "std/behaviors/ui-spinner"
5
6
  uses Typography from "std/behaviors/ui-typography"
@@ -103,7 +104,37 @@ orbital GalleryItemOrbital {
103
104
  direction: horizontal
104
105
  gap: sm
105
106
  type: stack
106
- }, @trait.TitleDivider, @trait.MediaGalleryGrid]
107
+ }, @trait.TitleDivider, (if @config.bodySearch {
108
+ children: [{
109
+ className: "w-full max-w-md"
110
+ clearable: true
111
+ event: REFETCH_QUERY
112
+ placeholder: @config.searchPlaceholder
113
+ type: search-input
114
+ }]
115
+ direction: horizontal
116
+ gap: sm
117
+ type: stack
118
+ } {
119
+ children: []
120
+ gap: none
121
+ type: stack
122
+ }), (if [">", (array/len @config.filters), 0] {
123
+ children: [{
124
+ entity: GalleryItem
125
+ event: REFETCH_FILTER
126
+ filters: @config.filters
127
+ look: @config.filterBarLook
128
+ type: filter-group
129
+ }]
130
+ direction: horizontal
131
+ gap: sm
132
+ type: stack
133
+ } {
134
+ children: []
135
+ gap: none
136
+ type: stack
137
+ }), @trait.MediaGalleryGrid]
107
138
  direction: vertical
108
139
  gap: md
109
140
  type: stack
@@ -121,6 +152,78 @@ orbital GalleryItemOrbital {
121
152
  (fetch GalleryItem {
122
153
  emit: { failure: GalleryItemLoadFailed, success: GalleryItemLoaded }
123
154
  })
155
+ ;; REFETCH_* stays in `browsing` and DOES NOT replace the gallery grid
156
+ ;; with a spinner — search-as-you-type would otherwise unmount the
157
+ ;; media-gallery every keystroke and flicker. The fetch fires in the
158
+ ;; background; when GalleryItemLoaded arrives below, React reconciles
159
+ ;; the grid in place instead of unmounting it.
160
+ REFETCH_QUERY -> browsing
161
+ (fetch GalleryItem {
162
+ emit: { failure: GalleryItemLoadFailed, success: GalleryItemLoaded }
163
+ filter: (or ["=", ?searchTerm, ""] (str/includes (object/get @entity name) ?searchTerm))
164
+ limit: @config.pageSize
165
+ offset: 0
166
+ })
167
+ REFETCH_FILTER -> browsing
168
+ (fetch GalleryItem {
169
+ emit: { failure: GalleryItemLoadFailed, success: GalleryItemLoaded }
170
+ filter: (or ["=", ?value, ""] ["=", (object/get @entity ?field), ?value])
171
+ limit: @config.pageSize
172
+ offset: 0
173
+ })
174
+ REFETCH_PAGE -> browsing
175
+ (fetch GalleryItem {
176
+ emit: { failure: GalleryItemLoadFailed, success: GalleryItemLoaded }
177
+ limit: @config.pageSize
178
+ offset: ["*", ["-", ?page, 1], @config.pageSize]
179
+ })
180
+ GalleryItemLoaded -> browsing
181
+ (render-ui main {
182
+ children: [{
183
+ align: center
184
+ children: [@trait.GalleryIcon, {
185
+ content: @config.title
186
+ type: typography
187
+ variant: h3
188
+ }]
189
+ direction: horizontal
190
+ gap: sm
191
+ type: stack
192
+ }, @trait.TitleDivider, (if @config.bodySearch {
193
+ children: [{
194
+ className: "w-full max-w-md"
195
+ clearable: true
196
+ event: REFETCH_QUERY
197
+ placeholder: @config.searchPlaceholder
198
+ type: search-input
199
+ }]
200
+ direction: horizontal
201
+ gap: sm
202
+ type: stack
203
+ } {
204
+ children: []
205
+ gap: none
206
+ type: stack
207
+ }), (if [">", (array/len @config.filters), 0] {
208
+ children: [{
209
+ entity: GalleryItem
210
+ event: REFETCH_FILTER
211
+ filters: @config.filters
212
+ look: @config.filterBarLook
213
+ type: filter-group
214
+ }]
215
+ direction: horizontal
216
+ gap: sm
217
+ type: stack
218
+ } {
219
+ children: []
220
+ gap: none
221
+ type: stack
222
+ }), @trait.MediaGalleryGrid]
223
+ direction: vertical
224
+ gap: md
225
+ type: stack
226
+ })
124
227
  VIEW -> viewing
125
228
  (set @entity.id ?id)
126
229
  (render-ui modal {
@@ -195,6 +298,25 @@ orbital GalleryItemOrbital {
195
298
  }
196
299
 
197
300
  listens {
301
+ REFETCH_QUERY {
302
+ searchTerm : string!
303
+ }
304
+ @description "Triggers a gallery refresh based on the current search query."
305
+ @synonyms "refresh, reload, update, re-query"
306
+ @tier "domain"
307
+ REFETCH_FILTER {
308
+ field : string!
309
+ value : string!
310
+ }
311
+ @description "Triggers a gallery refresh based on current filter criteria."
312
+ @synonyms "refresh, filter, update, reload"
313
+ @tier "domain"
314
+ REFETCH_PAGE {
315
+ page : number!
316
+ }
317
+ @description "Triggers a gallery page refresh."
318
+ @synonyms "reload, refresh, update, paginate"
319
+ @tier "domain"
198
320
  VIEW {
199
321
  id : string!
200
322
  row : GalleryItem
@@ -236,6 +358,31 @@ orbital GalleryItemOrbital {
236
358
  @description "Entity field holding the image URL shown in the lightbox for the selected item"
237
359
  @synonyms "image url field, thumbnail field, src field"
238
360
  @tier "presentation"
361
+ bodySearch : boolean = false
362
+ @label "Show the gallery's own search box?"
363
+ @description "Embeds a search box above the gallery grid. Set false (the default) when the page already provides a search affordance (a toolbar Search molecule), so the surface shows one search box instead of two."
364
+ @synonyms "inline search, list search, built-in search, show search, duplicate search"
365
+ @tier "presentation"
366
+ searchPlaceholder : string = "Search…"
367
+ @label "Search box placeholder text"
368
+ @description "Hint text inside the gallery search box (e.g. 'Search photos…')."
369
+ @synonyms "search hint, placeholder, search box text, search label"
370
+ @tier "presentation"
371
+ filters : [FilterSpec] = []
372
+ @label "Which filters should appear above the gallery?"
373
+ @description "Dropdown filter facets by field, rendered as an embedded filter bar above the grid; each selection fires REFETCH_FILTER on this trait. Empty (the default) = no filter bar, which is the right setting whenever the page already composes std-filter beside this gallery and routes `XFilter.FILTER -> REFETCH_FILTER` — that is the wired owner and remains the primary path. Use this knob only for a standalone gallery with no page-level filter affordance."
374
+ @synonyms "filter bar, faceted filters, field filters, dropdowns"
375
+ @tier "presentation"
376
+ filterBarLook : "toolbar" | "chips" | "pills" | "popover-trigger" | "inline-column-header" = toolbar
377
+ @label "Filter bar look"
378
+ @description "Visual treatment for the embedded filter bar, mirroring std-filter's own enum. Only applies when `filters` is non-empty."
379
+ @synonyms "filter bar style, facet style, chips, pills"
380
+ @tier "presentation"
381
+ pageSize : number = 20
382
+ @label "How many images to fetch per page?"
383
+ @description "Records fetched from the server when REFETCH_PAGE fires (a composed std-pagination driving this trait). The initial load and revisit fetches stay unpaged (whole collection), unchanged from before this knob existed."
384
+ @synonyms "fetch limit, records per page, batch size, page limit"
385
+ @tier "presentation"
239
386
  }
240
387
  }
241
388