@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
@@ -1,5 +1,6 @@
1
1
  app std-list "1.0.0"
2
2
  "std-list as a Function"
3
+ type FilterSpec = { field : string!, label : string, options : [string] }
3
4
  ;;
4
5
  ;; Daisy-chain composition showcase: no `on EVENT { ... }` trait-ref overrides.
5
6
  ;; Modal + Confirmation trait refs are configured via `config: { ... }` at the
@@ -71,7 +72,37 @@ orbital ListItemOrbital {
71
72
  gap: md
72
73
  justify: between
73
74
  type: stack
74
- }, { type: divider }, {
75
+ }, { type: divider }, (if @config.bodySearch {
76
+ children: [{
77
+ className: "w-full max-w-md"
78
+ clearable: true
79
+ event: REFETCH_QUERY
80
+ placeholder: @config.searchPlaceholder
81
+ type: search-input
82
+ }]
83
+ direction: horizontal
84
+ gap: sm
85
+ type: stack
86
+ } {
87
+ children: []
88
+ gap: none
89
+ type: stack
90
+ }), (if [">", (array/len @config.filters), 0] {
91
+ children: [{
92
+ entity: ListItem
93
+ event: REFETCH_FILTER
94
+ filters: @config.filters
95
+ look: @config.filterBarLook
96
+ type: filter-group
97
+ }]
98
+ direction: horizontal
99
+ gap: sm
100
+ type: stack
101
+ } {
102
+ children: []
103
+ gap: none
104
+ type: stack
105
+ }), {
75
106
  entity: ?data
76
107
  fields: [{
77
108
  icon: list
@@ -96,6 +127,7 @@ orbital ListItemOrbital {
96
127
  label: Delete
97
128
  variant: danger
98
129
  }]
130
+ pageSize: @config.displayPageSize
99
131
  type: data-grid
100
132
  }]
101
133
  className: "max-w-5xl mx-auto w-full"
@@ -139,6 +171,114 @@ orbital ListItemOrbital {
139
171
  emit: { failure: ListItemLoadFailed, success: ListItemLoaded }
140
172
  })
141
173
  (render-ui main { type: spinner })
174
+ ;; REFETCH_* stays in `browsing` and DOES NOT replace the grid with a
175
+ ;; spinner — same reasoning as std-browse's REFETCH_* arms: a skeleton
176
+ ;; on refetch unmounts the grid and flickers every keystroke. The fetch
177
+ ;; fires in the background; the ListItemLoaded arm below re-renders in
178
+ ;; place once it lands.
179
+ REFETCH_QUERY -> browsing
180
+ (fetch ListItem {
181
+ emit: { failure: ListItemLoadFailed, success: ListItemLoaded }
182
+ filter: (or ["=", ?searchTerm, ""] (str/includes (object/get @entity name) ?searchTerm))
183
+ })
184
+ REFETCH_FILTER -> browsing
185
+ (fetch ListItem {
186
+ emit: { failure: ListItemLoadFailed, success: ListItemLoaded }
187
+ filter: (or ["=", ?value, ""] ["=", (object/get @entity ?field), ?value])
188
+ })
189
+ ;; No filter carried over on REFETCH_PAGE, mirroring std-browse exactly.
190
+ REFETCH_PAGE -> browsing
191
+ (fetch ListItem {
192
+ emit: { failure: ListItemLoadFailed, success: ListItemLoaded }
193
+ limit: @config.pageSize
194
+ offset: ["*", ["-", ?page, 1], @config.pageSize]
195
+ })
196
+ ListItemLoaded -> browsing
197
+ (render-ui main {
198
+ children: [{
199
+ children: [{
200
+ children: [{ name: list, type: icon }, {
201
+ content: ListItems
202
+ type: typography
203
+ variant: h2
204
+ }]
205
+ direction: horizontal
206
+ gap: md
207
+ type: stack
208
+ }, {
209
+ action: CREATE
210
+ icon: plus
211
+ label: Create
212
+ type: button
213
+ variant: primary
214
+ }]
215
+ direction: horizontal
216
+ gap: md
217
+ justify: between
218
+ type: stack
219
+ }, { type: divider }, (if @config.bodySearch {
220
+ children: [{
221
+ className: "w-full max-w-md"
222
+ clearable: true
223
+ event: REFETCH_QUERY
224
+ placeholder: @config.searchPlaceholder
225
+ type: search-input
226
+ }]
227
+ direction: horizontal
228
+ gap: sm
229
+ type: stack
230
+ } {
231
+ children: []
232
+ gap: none
233
+ type: stack
234
+ }), (if [">", (array/len @config.filters), 0] {
235
+ children: [{
236
+ entity: ListItem
237
+ event: REFETCH_FILTER
238
+ filters: @config.filters
239
+ look: @config.filterBarLook
240
+ type: filter-group
241
+ }]
242
+ direction: horizontal
243
+ gap: sm
244
+ type: stack
245
+ } {
246
+ children: []
247
+ gap: none
248
+ type: stack
249
+ }), {
250
+ entity: ?data
251
+ fields: [{
252
+ icon: list
253
+ label: Name
254
+ name: name
255
+ variant: h4
256
+ }, {
257
+ label: Description
258
+ name: description
259
+ variant: caption
260
+ }, {
261
+ label: Status
262
+ name: status
263
+ variant: badge
264
+ }]
265
+ itemActions: [{
266
+ event: EDIT
267
+ label: Edit
268
+ variant: ghost
269
+ }, {
270
+ event: DELETE
271
+ label: Delete
272
+ variant: danger
273
+ }]
274
+ pageSize: @config.displayPageSize
275
+ type: data-grid
276
+ }]
277
+ className: "max-w-5xl mx-auto w-full"
278
+ direction: vertical
279
+ gap: lg
280
+ type: stack
281
+ })
142
282
  }
143
283
 
144
284
  state error {
@@ -190,6 +330,58 @@ orbital ListItemOrbital {
190
330
  ListItemPersistor.ITEM_CREATED -> INIT
191
331
  ListItemPersistor.ITEM_UPDATED -> INIT
192
332
  ListItemPersistor.ITEM_DELETED -> INIT
333
+ REFETCH_QUERY {
334
+ searchTerm : string!
335
+ }
336
+ @description "Triggers a list refresh based on the current search term."
337
+ @synonyms "refresh, reload, update, re-query, search"
338
+ @tier "domain"
339
+ REFETCH_FILTER {
340
+ field : string!
341
+ value : string!
342
+ }
343
+ @description "Triggers a list refresh based on current filter criteria."
344
+ @synonyms "refresh, filter, update, reload"
345
+ @tier "domain"
346
+ REFETCH_PAGE {
347
+ page : number!
348
+ }
349
+ @description "Triggers a list page refresh."
350
+ @synonyms "reload, refresh, update, paginate"
351
+ @tier "domain"
352
+ }
353
+
354
+ config {
355
+ bodySearch : boolean = false
356
+ @label "Show the list's own search box?"
357
+ @description "Embeds a search box above the grid. 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 — this atom has never had a search box (0 consumers today)."
358
+ @synonyms "inline search, list search, built-in search, show search"
359
+ @tier "presentation"
360
+ searchPlaceholder : string = "Search…"
361
+ @label "Search box placeholder text"
362
+ @description "Hint text inside the list search box."
363
+ @synonyms "search hint, placeholder, search box text"
364
+ @tier "presentation"
365
+ filters : [FilterSpec] = []
366
+ @label "Which filters should appear above the grid?"
367
+ @description "Dropdown filter facets by field, rendered as an embedded filter bar above the rows; each selection fires REFETCH_FILTER on this trait. Empty (the default) = no filter bar, the right setting whenever the page already composes std-filter beside this list and routes XFilter.FILTER -> REFETCH_FILTER — that is the wired owner and remains the primary path. Use this knob only for a standalone list with no page-level filter affordance."
368
+ @synonyms "filter bar, faceted filters, field filters, dropdowns"
369
+ @tier "presentation"
370
+ filterBarLook : "toolbar" | "chips" | "pills" | "popover-trigger" | "inline-column-header" = toolbar
371
+ @label "Filter bar look"
372
+ @description "Visual treatment for the embedded filter bar, mirroring std-filter's own enum. Only applies when `filters` is non-empty."
373
+ @synonyms "filter bar style, facet style, chips, pills"
374
+ @tier "presentation"
375
+ pageSize : number = 20
376
+ @label "How many rows per page when paging?"
377
+ @description "Records fetched from the server per REFETCH_PAGE request. The initial load and search/filter refetches remain unpaginated (load everything that matches) unless a consumer wires REFETCH_PAGE, so the default list surface is unchanged."
378
+ @synonyms "fetch limit, records per page, page limit"
379
+ @tier "presentation"
380
+ displayPageSize : number = 20
381
+ @label "How many rows to show at once?"
382
+ @description "Rows visible per page before the data-grid's own pagination control appears — 20 matches the CRUD-list density sweet spot (Miller's law, 10-25 rows)."
383
+ @synonyms "visible rows, items per page, display limit, show per page"
384
+ @tier "presentation"
193
385
  }
194
386
  }
195
387
 
@@ -0,0 +1,329 @@
1
+ app std-record-detail "1.0.0"
2
+ "std-record-detail — single primitive: detail-panel. Owns the fetch-one-record state machine (loading → viewing → error, with RETRY), the record counterpart to std-browse's collection machine. Consumers configure fields/sections/actions and the title/subtitle/status bindings; the atom fetches by the id carried on INIT and renders the panel. `detailLook` (panel default / slide-over / page) selects one of three render bodies. Pair with std-detail-layout for the back + breadcrumb chrome — that atom owns navigation, this one owns the record — exactly as a list page pairs std-browse with std-app-layout."
3
+ ;; @capabilities "record detail, single record view, entity detail panel, drill-down record, show one record, record profile, detail page, view details"
4
+ orbital RecordItemOrbital {
5
+ type RecordAction = { label : string!, event : string!, icon : string, variant : primary | secondary | ghost | danger }
6
+ type RecordFieldSpec = { key : string!, header : string, format : date | currency | number | boolean | percent }
7
+ type RecordSection = { title : string!, fields : [string] }
8
+
9
+ type RecordItemLoaded = Event { data : RecordItem, totalCount : number } "Fired when the single record finishes loading; payload.data holds the record. totalCount is accepted so a fetch emitter shared with collection atoms still validates."
10
+
11
+ type RecordItemLoadFailed = Event { error : string, code : string } "Fired when the record fails to load; drives the error state so a failed load shows an alert with a retry instead of an empty page."
12
+
13
+ entity RecordItem [runtime] {
14
+ id : string!
15
+ name : string! @description "The record's title or identifier." @synonyms "title, label, heading"
16
+ description : string @description "A textual explanation or summary of the record." @synonyms "summary, details, notes"
17
+ status : "active" | "inactive" | "pending" @description "The record's current state." @synonyms "state, condition, stage" = active
18
+ createdAt : string
19
+ }
20
+
21
+ trait RecordItemDetail -> @rebindable RecordItem [interaction, instance] {
22
+ ;; One machine, two shapes. A page detail (`openOn: init`, the default)
23
+ ;; falls straight through idle on INIT and behaves exactly as before. An
24
+ ;; overlay (`openOn: event`) stays blank until a row-click OPEN_VIEW hands
25
+ ;; it an id, which is the topology 23 hand-authored viewer traits use and
26
+ ;; the reason they could not compose an `initial: loading` atom: mounting
27
+ ;; one on a list page would fetch a record it has no id for.
28
+ initial: idle
29
+
30
+ state idle {
31
+ INIT -> loading when ["=", @config.openOn, "init"]
32
+ (set @entity.id ?id)
33
+ (fetch RecordItem {
34
+ emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
35
+ id: ?id
36
+ include: @config.include
37
+ })
38
+ (render-ui main { rows: 4, type: skeleton })
39
+ ;; ONE unguarded arm on purpose. This briefly carried a second,
40
+ ;; `when @config.preloaded`-guarded arm that rendered the OPEN_VIEW row
41
+ ;; directly to skip a round-trip. Both execution paths mishandled it:
42
+ ;; the live runtime PASSED the guard with `preloaded: false`
43
+ ;; (StateMachineCore's permissive mode turns an unresolved config guard
44
+ ;; into true), so overlays rendered from `?row` and never fetched —
45
+ ;; silently skipping `include` relation hydration; and the canonical
46
+ ;; evaluator FAILED the guard and then did not fall through to this arm,
47
+ ;; reporting the event as not firing at all. A guarded arm is not
48
+ ;; currently a trustworthy correctness boundary, so the atom no longer
49
+ ;; depends on one. Zero consumers had opted into the skip.
50
+ OPEN_VIEW -> loading
51
+ (set @entity.id ?id)
52
+ (fetch RecordItem {
53
+ emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
54
+ id: ?id
55
+ include: @config.include
56
+ })
57
+ (render-ui main { rows: 4, type: skeleton })
58
+ }
59
+
60
+ state loading {
61
+ INIT -> loading
62
+ (set @entity.id ?id)
63
+ (fetch RecordItem {
64
+ emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
65
+ id: ?id
66
+ include: @config.include
67
+ })
68
+ ;; Skeleton, not a blank frame: the hand-authored detail traits this
69
+ ;; atom replaces rendered nothing until the record arrived.
70
+ (render-ui main { rows: 4, type: skeleton })
71
+ RecordItemLoaded -> viewing
72
+ (set @entity.name (object/get ?data @config.titleField))
73
+ (set @entity.description (object/get ?data @config.subtitleField))
74
+ (if ["=", @config.detailLook, slide-over]
75
+ (render-ui main @config.slideOverBodyContent)
76
+ (if ["=", @config.detailLook, page]
77
+ (render-ui main @config.pageBodyContent)
78
+ (render-ui main @config.bodyContent)))
79
+ RecordItemLoadFailed -> error
80
+ (render-ui main { message: ?error, title: "Couldn't load this record", type: alert, variant: error })
81
+ }
82
+
83
+ state viewing {
84
+ ;; No skeleton on re-entry: from a populated panel a skeleton unmounts the
85
+ ;; record and flickers. First mount is unaffected — `initial: loading` and
86
+ ;; `state loading`'s own INIT still paint it.
87
+ INIT -> loading
88
+ (set @entity.id ?id)
89
+ (fetch RecordItem {
90
+ emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
91
+ id: ?id
92
+ include: @config.include
93
+ })
94
+ RecordItemLoaded -> viewing
95
+ (set @entity.name (object/get ?data @config.titleField))
96
+ (set @entity.description (object/get ?data @config.subtitleField))
97
+ (if ["=", @config.detailLook, slide-over]
98
+ (render-ui main @config.slideOverBodyContent)
99
+ (if ["=", @config.detailLook, page]
100
+ (render-ui main @config.pageBodyContent)
101
+ (render-ui main @config.bodyContent)))
102
+ RecordItemLoadFailed -> error
103
+ (render-ui main { message: ?error, title: "Couldn't load this record", type: alert, variant: error })
104
+ CLOSE_VIEW -> idle
105
+ (render-ui main null)
106
+ }
107
+
108
+ state error {
109
+ INIT -> loading
110
+ (set @entity.id ?id)
111
+ (fetch RecordItem {
112
+ emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
113
+ id: ?id
114
+ include: @config.include
115
+ })
116
+ (render-ui main { rows: 4, type: skeleton })
117
+ ;; The hand-authored traits sent a failed load back to `loading` with no
118
+ ;; effect, so the page stayed blank forever with no way out. RETRY is the
119
+ ;; way out.
120
+ RETRY -> loading
121
+ (fetch RecordItem {
122
+ emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
123
+ id: @entity.id
124
+ include: @config.include
125
+ })
126
+ (render-ui main { rows: 4, type: skeleton })
127
+ }
128
+
129
+ emits {
130
+ RecordItemLoaded
131
+ @description "Signals that the record has been successfully loaded."
132
+ @synonyms "loaded, received, ready, fetched"
133
+ @tier "domain"
134
+ RecordItemLoadFailed
135
+ @description "Indicates a failure to load the record."
136
+ @synonyms "error, failed, problem, unsuccessful"
137
+ @tier "internal"
138
+ EDIT -> external {
139
+ id : string!
140
+ row : RecordItem
141
+ }
142
+ @description "Signals a request to edit the record being viewed."
143
+ @synonyms "modify, update, change"
144
+ @tier "domain"
145
+ DELETE -> external {
146
+ id : string!
147
+ row : RecordItem
148
+ }
149
+ @description "Signals a request to remove the record being viewed."
150
+ @synonyms "remove, discard, delete, archive"
151
+ @tier "domain"
152
+ }
153
+
154
+ listens {
155
+ INIT {
156
+ id : string
157
+ }
158
+ @description "Opens the detail surface for the record with this id."
159
+ @synonyms "open record, view record, show details, drill in"
160
+ @tier "domain"
161
+ OPEN_VIEW {
162
+ id : string!
163
+ row : RecordItem
164
+ }
165
+ @description "A row was opened from a list; shows that record in this surface."
166
+ @synonyms "open record, view row, show details, drill in, expand"
167
+ @tier "domain"
168
+ CLOSE_VIEW
169
+ @description "Closes the record surface and returns it to blank."
170
+ @synonyms "close, dismiss, back to list"
171
+ @tier "domain"
172
+ RETRY
173
+ @description "Re-runs the fetch after a failed load."
174
+ @synonyms "try again, reload, refresh"
175
+ @tier "domain"
176
+ RecordItemLoadFailed {
177
+ error : string
178
+ code : string
179
+ }
180
+ @description "Indicates a failure to load the record."
181
+ @synonyms "error, failure, problem, unsuccessful"
182
+ @tier "internal"
183
+ }
184
+
185
+ config {
186
+ fields : [RecordFieldSpec] = [{
187
+ header: Name
188
+ key: name
189
+ }, {
190
+ header: Description
191
+ key: description
192
+ }, {
193
+ header: Status
194
+ key: status
195
+ }, {
196
+ format: date
197
+ header: Created
198
+ key: createdAt
199
+ }]
200
+ @label "Which fields appear in the record panel?"
201
+ @description "Each entry maps one entity field to a labelled row in the panel. Unlike a list row, a detail panel SHOULD label every field — the value stands alone here with no column header or repeated row position to name it."
202
+ @synonyms "detail fields, record fields, panel fields, shown fields"
203
+ @tier "presentation"
204
+ sections : [RecordSection] = []
205
+ @label "How should the fields be grouped?"
206
+ @description "Optional titled groups over `fields`, e.g. [{title: Contact, fields: [email, phone]}]. Group into sections of 3-5 once a record carries more than about seven fields — a flat list past that is the cognitive-load problem Almadar_UX.md §1.4 (Miller) describes. Empty = one ungrouped list, the default."
207
+ @synonyms "field groups, grouped fields, panel sections, fieldsets"
208
+ @tier "presentation"
209
+ actions : [RecordAction] = [{
210
+ event: EDIT
211
+ label: Edit
212
+ variant: secondary
213
+ }, {
214
+ event: DELETE
215
+ label: Delete
216
+ variant: danger
217
+ }]
218
+ @label "What actions can users take on this record?"
219
+ @description "Buttons on the panel header. One primary at most; the rest collapse past maxInlineActions."
220
+ @synonyms "record actions, detail actions, panel buttons"
221
+ @tier "presentation"
222
+ maxInlineActions : number = 3
223
+ @label "How many actions before collapsing?"
224
+ @description "Actions beyond this count collapse into a '⋯' overflow menu."
225
+ @synonyms "inline actions limit, overflow threshold"
226
+ @tier "presentation"
227
+ showActions : boolean = true
228
+ @label "Show the action buttons?"
229
+ @description "Set false for a read-only record view; a panel with actions nothing listens to is a dead affordance."
230
+ @synonyms "read only, hide actions, view only"
231
+ @tier "presentation"
232
+ ;; Field NAMES, not binding paths. A path knob (`subtitleBinding:
233
+ ;; @entity.company`) makes the consumer responsible for an @entity field
234
+ ;; this atom never sets — an ORB_BINDING_ENTITY_FIELD_NEVER_SET and an
235
+ ;; empty subtitle, which is exactly what the first migration of std-crm
236
+ ;; ContactDetail hit. Naming the source field instead lets the atom do the
237
+ ;; (set) itself off the payload, so the trap cannot be reintroduced.
238
+ titleField : string = name
239
+ @label "Which field is the record's title?"
240
+ @description "Name of the field on the loaded record shown as the panel title (e.g. contactName, subject, title)."
241
+ @synonyms "title field, heading field, name field, label field"
242
+ @tier "presentation"
243
+ subtitleField : string = description
244
+ @label "Which field is the subtitle?"
245
+ @description "Name of the field shown under the title (e.g. company, category). Leave at the default when the record has no meaningful second line."
246
+ @synonyms "subtitle field, secondary field, subheading"
247
+ @tier "presentation"
248
+ include : [string] = []
249
+ @label "Which relation fields to hydrate?"
250
+ @description "Entity relation field names to hydrate on fetch, so panel fields can use dot-paths like owner.name. Empty = no hydration, the default."
251
+ @synonyms "relation hydration, populate relations, joined fields"
252
+ @tier "internal"
253
+ openOn : "init" | "event" = init
254
+ @label "When does this surface load?"
255
+ @description "init = a routed detail page; the record loads as soon as the page mounts (the default). event = an overlay that stays blank until a row-click OPEN_VIEW hands it an id, which is what a list-embedded slide-over needs — mounting an init-loading surface on a list page would fetch a record it has no id for."
256
+ @synonyms "open on click, slide over, row click, blank until opened, list embedded"
257
+ @tier "policy"
258
+ detailLook : "panel" | "slide-over" | "page" = panel
259
+ @label "Detail look"
260
+ @description "Visual treatment for the record surface. panel = an inline detail-panel in the page flow (bodyContent); slide-over = the same panel as a right-hand drawer over the list behind it (slideOverBodyContent); page = a full-width record page with roomier spacing (pageBodyContent). Selecting a non-panel look swaps which body tree renders after the record loads; any body may still be overridden directly at the call site."
261
+ @synonyms "detail style, drawer, slide over, full page"
262
+ @tier "presentation"
263
+ bodyContent : render-ui = {
264
+ children: [{
265
+ actions: @config.actions
266
+ entity: ?data
267
+ fields: @config.fields
268
+ initialData: ?data
269
+ maxInlineActions: @config.maxInlineActions
270
+ sections: @config.sections
271
+ showActions: @config.showActions
272
+ subtitle: @entity.description
273
+ title: @entity.name
274
+ type: detail-panel
275
+ }]
276
+ direction: vertical
277
+ gap: lg
278
+ type: stack
279
+ }
280
+ @label "Body content tree"
281
+ @description "Render-ui SExpr rendered after the record loads when detailLook = panel (the default). See slideOverBodyContent/pageBodyContent for the other looks; any may be overridden directly while inheriting the same trait, state machine, emits, and listens."
282
+ @tier "internal"
283
+ slideOverBodyContent : render-ui = {
284
+ children: [{
285
+ actions: @config.actions
286
+ entity: ?data
287
+ fields: @config.fields
288
+ initialData: ?data
289
+ maxInlineActions: @config.maxInlineActions
290
+ sections: @config.sections
291
+ showActions: @config.showActions
292
+ slideOver: true
293
+ subtitle: @entity.description
294
+ title: @entity.name
295
+ type: detail-panel
296
+ }]
297
+ direction: vertical
298
+ gap: none
299
+ type: stack
300
+ }
301
+ @label "Slide-over look body"
302
+ @description "Render-ui SExpr rendered after the record loads when detailLook = slide-over: the same panel as a right-hand drawer, so the list behind stays in place. Use when the record is a glance, not a workspace."
303
+ @tier "internal"
304
+ pageBodyContent : render-ui = {
305
+ children: [{
306
+ actions: @config.actions
307
+ entity: ?data
308
+ fields: @config.fields
309
+ initialData: ?data
310
+ maxInlineActions: @config.maxInlineActions
311
+ sections: @config.sections
312
+ showActions: @config.showActions
313
+ subtitle: @entity.description
314
+ title: @entity.name
315
+ type: detail-panel
316
+ }]
317
+ className: "w-full max-w-4xl"
318
+ direction: vertical
319
+ gap: lg
320
+ type: stack
321
+ }
322
+ @label "Page look body"
323
+ @description "Render-ui SExpr rendered after the record loads when detailLook = page: a full record page, width-capped so field rows do not stretch past a readable measure (Almadar_UI_Beauty.md §3)."
324
+ @tier "internal"
325
+ }
326
+ }
327
+
328
+ page "/recorditems" as RecordItemDetailPage -> RecordItemDetail
329
+ }