@almadar/std 16.147.0 → 16.149.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/atoms/std-app-search.lolo +1 -1
- package/behaviors/lolo/ui/core/atoms/std-billable-hour.lolo +86 -41
- package/behaviors/lolo/ui/core/atoms/std-board.lolo +29 -16
- package/behaviors/lolo/ui/core/atoms/std-browse.lolo +189 -87
- package/behaviors/lolo/ui/core/atoms/std-dashboard-grid-embedded.lolo +8 -0
- package/behaviors/lolo/ui/core/atoms/std-file-store.lolo +1 -1
- package/behaviors/lolo/ui/core/atoms/std-stats.lolo +14 -10
- package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +1 -29
- package/behaviors/registry/factory-signatures.json +9 -13
- package/behaviors/registry/ui/core/atoms/std-app-search.orb +9 -1
- package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +242 -50
- package/behaviors/registry/ui/core/atoms/std-board.orb +86 -20
- package/behaviors/registry/ui/core/atoms/std-browse.orb +379 -123
- package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +41 -29
- package/behaviors/registry/ui/core/atoms/std-file-store.orb +0 -3
- package/behaviors/registry/ui/core/atoms/std-stats.orb +267 -238
- package/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -38
- package/dist/behaviors/behaviors-embeddings.json +475 -475
- package/dist/behaviors/behaviors-registry.json +10 -12
- package/dist/behaviors/knob-embeddings.json +1 -1
- package/dist/behaviors/registry/factory-signatures.json +9 -13
- package/dist/behaviors/registry/ui/core/atoms/std-app-search.orb +9 -1
- package/dist/behaviors/registry/ui/core/atoms/std-billable-hour.orb +242 -50
- package/dist/behaviors/registry/ui/core/atoms/std-board.orb +86 -20
- package/dist/behaviors/registry/ui/core/atoms/std-browse.orb +379 -123
- package/dist/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +41 -29
- package/dist/behaviors/registry/ui/core/atoms/std-file-store.orb +0 -3
- package/dist/behaviors/registry/ui/core/atoms/std-stats.orb +267 -238
- package/dist/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -38
- package/dist/behaviors-embeddings.json +475 -475
- package/dist/behaviors-registry.json +10 -12
- package/dist/knob-embeddings.json +1 -1
- package/dist/registry/factory-signatures.json +9 -13
- package/package.json +8 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
app std-browse "2.3.0"
|
|
2
|
-
"std-browse as a Function — single primitive: data-grid. Owns the fetch + REFETCH (query/filter/page) state machine. Consumers configure fields/imageField/itemActions/cols/gap/variant; the atom renders the grid. Each `fields[]` entry accepts `{ name, label, variant }` plus optional `referenceEntity` + `displayField` to render a foreign-key column as the joined entity's display value (data-grid resolves @list[referenceEntity] at render time when both keys are present). `browseLook` (table default / dense / feed / gallery) selects one of
|
|
2
|
+
"std-browse as a Function — single primitive: data-grid. Owns the fetch + REFETCH (query/filter/page) state machine. Consumers configure fields/imageField/itemActions/cols/gap/variant; the atom renders the grid. Each `fields[]` entry accepts `{ name, label, variant }` plus optional `referenceEntity` + `displayField` to render a foreign-key column as the joined entity's display value (data-grid resolves @list[referenceEntity] at render time when both keys are present). `browseLook` (table default / dense / feed / gallery / master-detail / triage) selects one of six render bodies; dense/feed/gallery were folded in from std-browse-dense/-feed/-gallery (Phase 5.B); master-detail (split record view driven by the guarded VIEW/CLOSE self-transitions) and triage (inbox rows) were folded in 2026-07-26. Consumers retarget by setting browseLook."
|
|
3
3
|
type FieldSpec = { name : string!, label : string, variant : string, icon : string, format : string, key : string, header : string }
|
|
4
4
|
type ItemAction = { label : string!, event : string, navigatesTo : string, variant : string, icon : string }
|
|
5
5
|
type ColumnSpec = { key : string!, field : string, header : string, width : string, align : string, className : string, weight : string, format : string, icon : string }
|
|
@@ -20,6 +20,7 @@ orbital BrowseItemOrbital {
|
|
|
20
20
|
uses Typography from "std/behaviors/ui-typography"
|
|
21
21
|
uses DataGrid from "std/behaviors/ui-data-grid"
|
|
22
22
|
uses TableView from "std/behaviors/ui-table-view"
|
|
23
|
+
uses DataList from "std/behaviors/ui-data-list"
|
|
23
24
|
|
|
24
25
|
trait DataGrid1 = DataGrid.traits.DataGridRender -> BrowseItem {
|
|
25
26
|
config {
|
|
@@ -28,6 +29,7 @@ orbital BrowseItemOrbital {
|
|
|
28
29
|
gap: @config.gap
|
|
29
30
|
imageField: @config.imageField
|
|
30
31
|
itemActions: @config.itemActions
|
|
32
|
+
maxInlineActions: @config.maxInlineActions
|
|
31
33
|
pageSize: @config.displayPageSize
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -44,7 +46,7 @@ orbital BrowseItemOrbital {
|
|
|
44
46
|
look: dense
|
|
45
47
|
maxInlineActions: @config.maxInlineActions
|
|
46
48
|
pageSize: @config.displayPageSize
|
|
47
|
-
selectable:
|
|
49
|
+
selectable: @config.selectable
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
listens {
|
|
@@ -52,6 +54,20 @@ orbital BrowseItemOrbital {
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
trait MasterListView = DataList.traits.DataListRender -> BrowseItem {
|
|
58
|
+
config {
|
|
59
|
+
fields: @config.fields
|
|
60
|
+
itemActions: @config.itemActions
|
|
61
|
+
itemClickEvent: VIEW
|
|
62
|
+
maxInlineActions: @config.maxInlineActions
|
|
63
|
+
variant: compact
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
listens {
|
|
67
|
+
BrowseItemBrowse.BrowseItemLoaded -> DataListLoaded
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
trait BrowseItemBrowse -> @rebindable BrowseItem [interaction, collection] {
|
|
56
72
|
initial: loading
|
|
57
73
|
|
|
@@ -62,7 +78,7 @@ orbital BrowseItemOrbital {
|
|
|
62
78
|
limit: @config.pageSize
|
|
63
79
|
offset: 0
|
|
64
80
|
})
|
|
65
|
-
(render-ui main {
|
|
81
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
66
82
|
BrowseItemLoaded -> browsing
|
|
67
83
|
(if ["=", @config.browseLook, dense]
|
|
68
84
|
(render-ui main @config.denseBodyContent)
|
|
@@ -70,9 +86,13 @@ orbital BrowseItemOrbital {
|
|
|
70
86
|
(render-ui main @config.feedBodyContent)
|
|
71
87
|
(if ["=", @config.browseLook, gallery]
|
|
72
88
|
(render-ui main @config.galleryBodyContent)
|
|
73
|
-
(
|
|
89
|
+
(if ["=", @config.browseLook, master-detail]
|
|
90
|
+
(render-ui main @config.masterDetailBodyContent)
|
|
91
|
+
(if ["=", @config.browseLook, triage]
|
|
92
|
+
(render-ui main @config.triageBodyContent)
|
|
93
|
+
(render-ui main @config.bodyContent))))))
|
|
74
94
|
BrowseItemLoadFailed -> error
|
|
75
|
-
(render-ui main {
|
|
95
|
+
(render-ui main { message: ?error, title: "Couldn't load this list", type: alert, variant: error })
|
|
76
96
|
}
|
|
77
97
|
|
|
78
98
|
state browsing {
|
|
@@ -82,7 +102,7 @@ orbital BrowseItemOrbital {
|
|
|
82
102
|
limit: @config.pageSize
|
|
83
103
|
offset: 0
|
|
84
104
|
})
|
|
85
|
-
(render-ui main {
|
|
105
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
86
106
|
;; REFETCH_* stays in `browsing` and DOES NOT replace the data-grid
|
|
87
107
|
;; with a spinner. Replacing during search-as-you-type unmounts the
|
|
88
108
|
;; grid every keystroke and causes a visible flicker. The fetch fires
|
|
@@ -109,6 +129,14 @@ orbital BrowseItemOrbital {
|
|
|
109
129
|
limit: @config.pageSize
|
|
110
130
|
offset: ["*", ["-", ?page, 1], @config.pageSize]
|
|
111
131
|
})
|
|
132
|
+
;; master-detail only: opening a row re-renders the split view with the
|
|
133
|
+
;; clicked row's detail (payload ?row feeds the panel). Guard keeps every
|
|
134
|
+
;; other look's machine untouched — VIEW still reaches external listeners
|
|
135
|
+
;; either way, and no effect here re-emits the trigger.
|
|
136
|
+
VIEW -> browsing when ["=", @config.browseLook, "master-detail"]
|
|
137
|
+
(render-ui main @config.masterDetailRecordBodyContent)
|
|
138
|
+
CLOSE -> browsing when ["=", @config.browseLook, "master-detail"]
|
|
139
|
+
(render-ui main @config.masterDetailBodyContent)
|
|
112
140
|
BrowseItemLoaded -> browsing
|
|
113
141
|
(if ["=", @config.browseLook, dense]
|
|
114
142
|
(render-ui main @config.denseBodyContent)
|
|
@@ -116,7 +144,11 @@ orbital BrowseItemOrbital {
|
|
|
116
144
|
(render-ui main @config.feedBodyContent)
|
|
117
145
|
(if ["=", @config.browseLook, gallery]
|
|
118
146
|
(render-ui main @config.galleryBodyContent)
|
|
119
|
-
(
|
|
147
|
+
(if ["=", @config.browseLook, master-detail]
|
|
148
|
+
(render-ui main @config.masterDetailBodyContent)
|
|
149
|
+
(if ["=", @config.browseLook, triage]
|
|
150
|
+
(render-ui main @config.triageBodyContent)
|
|
151
|
+
(render-ui main @config.bodyContent))))))
|
|
120
152
|
}
|
|
121
153
|
|
|
122
154
|
state error {
|
|
@@ -126,7 +158,7 @@ orbital BrowseItemOrbital {
|
|
|
126
158
|
limit: @config.pageSize
|
|
127
159
|
offset: 0
|
|
128
160
|
})
|
|
129
|
-
(render-ui main {
|
|
161
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
130
162
|
}
|
|
131
163
|
|
|
132
164
|
emits {
|
|
@@ -300,11 +332,16 @@ orbital BrowseItemOrbital {
|
|
|
300
332
|
@description "Dropdown filter facets by field. Each fires a refetch when the user changes the selection. Empty = no filter bar."
|
|
301
333
|
@synonyms "filter bar, faceted filters, field filters, column filters, dropdowns"
|
|
302
334
|
@tier "presentation"
|
|
303
|
-
maxInlineActions : number =
|
|
335
|
+
maxInlineActions : number = 3
|
|
304
336
|
@label "How many row actions to show before collapsing?"
|
|
305
337
|
@description "Actions beyond this count collapse into a '⋯' overflow menu. Set 2–3 for dense tables."
|
|
306
338
|
@synonyms "inline actions limit, overflow threshold, max buttons per row"
|
|
307
339
|
@tier "presentation"
|
|
340
|
+
selectable : boolean = false
|
|
341
|
+
@label "Show row selection checkboxes?"
|
|
342
|
+
@description "Adds a checkbox column with select-all to the dense table. Enable only when the surface offers bulk actions — checkboxes with nothing to act on are a dead affordance."
|
|
343
|
+
@synonyms "row checkboxes, multi-select, bulk select, selection column"
|
|
344
|
+
@tier "presentation"
|
|
308
345
|
bodySearch : boolean = true
|
|
309
346
|
@label "Show the list's own search box?"
|
|
310
347
|
@description "The dense/feed/gallery looks embed their own search box above the rows. Set false when the page already provides a search affordance (a toolbar Search molecule), so the surface shows one search box instead of two."
|
|
@@ -319,9 +356,14 @@ orbital BrowseItemOrbital {
|
|
|
319
356
|
@label "Render pattern"
|
|
320
357
|
@description "UI pattern used to render the collection. Used by the default bodyContent tree; Layer 3 variants override bodyContent directly instead and may ignore this knob."
|
|
321
358
|
@tier "internal"
|
|
322
|
-
browseLook : "table" | "dense" | "feed" | "gallery" = table
|
|
359
|
+
browseLook : "table" | "dense" | "feed" | "gallery" | "master-detail" | "triage" = table
|
|
323
360
|
@label "Browse look"
|
|
324
|
-
@description "Layer 2 visual treatment for the whole browse surface. table = the default data-grid (bodyContent); dense = spreadsheet-style
|
|
361
|
+
@description "Layer 2 visual treatment for the whole browse surface. table = the default data-grid (bodyContent); dense = spreadsheet-style table with toolbar search (denseBodyContent); feed = chronological card stream (feedBodyContent); gallery = image-first responsive card grid (galleryBodyContent); master-detail = record-work split view — dense list left, clicked record's detail right (masterDetailBodyContent); triage = inbox-register compact rows for queue work (triageBodyContent). Selecting a non-table look swaps which body tree renders after rows load; any body may still be overridden directly at the call site for a one-off layout."
|
|
362
|
+
@tier "presentation"
|
|
363
|
+
detailFields : [string] = [name, description, status, createdAt]
|
|
364
|
+
@label "Which fields appear in the master-detail record pane?"
|
|
365
|
+
@description "Field names shown in the detail panel when browseLook = master-detail; the first entry doubles as the pane title. Consumers overriding columns/fields should override this list to match their entity."
|
|
366
|
+
@synonyms "detail columns, record fields, detail pane fields"
|
|
325
367
|
@tier "presentation"
|
|
326
368
|
bodyContent : render-ui = {
|
|
327
369
|
children: [@trait.DataGrid1]
|
|
@@ -333,15 +375,14 @@ orbital BrowseItemOrbital {
|
|
|
333
375
|
@tier "internal"
|
|
334
376
|
denseBodyContent : render-ui = {
|
|
335
377
|
children: [(if @config.bodySearch {
|
|
336
|
-
align: center
|
|
337
378
|
children: [{
|
|
338
|
-
className: "w-full max-w-
|
|
379
|
+
className: "w-full max-w-md"
|
|
339
380
|
clearable: true
|
|
340
381
|
event: REFETCH_QUERY
|
|
341
382
|
placeholder: @config.searchPlaceholder
|
|
342
383
|
type: search-input
|
|
343
384
|
}]
|
|
344
|
-
className: "
|
|
385
|
+
className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
|
|
345
386
|
direction: horizontal
|
|
346
387
|
gap: sm
|
|
347
388
|
type: stack
|
|
@@ -351,14 +392,14 @@ orbital BrowseItemOrbital {
|
|
|
351
392
|
type: stack
|
|
352
393
|
}), {
|
|
353
394
|
children: [@trait.DenseTableView]
|
|
354
|
-
className:
|
|
395
|
+
className: overflow-x-auto
|
|
355
396
|
direction: vertical
|
|
356
397
|
gap: none
|
|
357
398
|
type: stack
|
|
358
399
|
}]
|
|
359
|
-
className:
|
|
400
|
+
className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
|
|
360
401
|
direction: vertical
|
|
361
|
-
gap:
|
|
402
|
+
gap: none
|
|
362
403
|
type: stack
|
|
363
404
|
}
|
|
364
405
|
@label "Dense look body"
|
|
@@ -450,12 +491,13 @@ orbital BrowseItemOrbital {
|
|
|
450
491
|
}]
|
|
451
492
|
className: "rounded-lg bg-[var(--color-card)] border border-[var(--color-border)] shadow-sm transition-all duration-normal hover:shadow-main hover:border-[var(--color-border-strong)] hover:-translate-y-px"
|
|
452
493
|
look: flat-bordered
|
|
494
|
+
padding: none
|
|
453
495
|
type: card
|
|
454
496
|
})
|
|
455
497
|
type: data-list
|
|
456
498
|
variant: card
|
|
457
499
|
}]
|
|
458
|
-
className:
|
|
500
|
+
className: w-full
|
|
459
501
|
direction: vertical
|
|
460
502
|
gap: lg
|
|
461
503
|
type: stack
|
|
@@ -473,7 +515,7 @@ orbital BrowseItemOrbital {
|
|
|
473
515
|
placeholder: @config.searchPlaceholder
|
|
474
516
|
type: search-input
|
|
475
517
|
}]
|
|
476
|
-
className: "sticky top-0 z-10
|
|
518
|
+
className: "sticky top-0 z-10 py-3 bg-[var(--color-surface)]/95 backdrop-blur"
|
|
477
519
|
direction: horizontal
|
|
478
520
|
gap: sm
|
|
479
521
|
type: stack
|
|
@@ -484,85 +526,145 @@ orbital BrowseItemOrbital {
|
|
|
484
526
|
}), {
|
|
485
527
|
className: w-full
|
|
486
528
|
entity: ?data
|
|
487
|
-
fields:
|
|
529
|
+
fields: @config.fields
|
|
488
530
|
gap: lg
|
|
531
|
+
imageField: @config.imageField
|
|
489
532
|
itemActions: @config.itemActions
|
|
490
533
|
maxInlineActions: @config.maxInlineActions
|
|
491
534
|
minCardWidth: 260
|
|
492
|
-
renderItem: (fn item {
|
|
493
|
-
children: [{
|
|
494
|
-
align: center
|
|
495
|
-
children: [{
|
|
496
|
-
className: "text-muted-foreground opacity-50 transition-transform duration-normal group-hover:scale-105"
|
|
497
|
-
name: image
|
|
498
|
-
size: lg
|
|
499
|
-
type: icon
|
|
500
|
-
}, {
|
|
501
|
-
className: "px-2 text-center truncate max-w-full"
|
|
502
|
-
color: muted
|
|
503
|
-
content: @config.imageLabelBinding
|
|
504
|
-
type: typography
|
|
505
|
-
variant: caption
|
|
506
|
-
}, {
|
|
507
|
-
align: center
|
|
508
|
-
children: [{
|
|
509
|
-
label: @config.priceBinding
|
|
510
|
-
type: badge
|
|
511
|
-
variant: primary
|
|
512
|
-
}]
|
|
513
|
-
className: "absolute top-2 right-2"
|
|
514
|
-
direction: horizontal
|
|
515
|
-
gap: xs
|
|
516
|
-
type: stack
|
|
517
|
-
}, {
|
|
518
|
-
align: center
|
|
519
|
-
children: [{
|
|
520
|
-
label: @config.badgeBinding
|
|
521
|
-
type: badge
|
|
522
|
-
variant: neutral
|
|
523
|
-
}]
|
|
524
|
-
className: "absolute top-2 left-2"
|
|
525
|
-
direction: horizontal
|
|
526
|
-
gap: xs
|
|
527
|
-
type: stack
|
|
528
|
-
}]
|
|
529
|
-
className: "relative aspect-square w-full bg-muted overflow-hidden"
|
|
530
|
-
direction: vertical
|
|
531
|
-
gap: xs
|
|
532
|
-
justify: center
|
|
533
|
-
type: stack
|
|
534
|
-
}, {
|
|
535
|
-
children: [{
|
|
536
|
-
className: truncate
|
|
537
|
-
content: @config.titleBinding
|
|
538
|
-
type: typography
|
|
539
|
-
variant: h4
|
|
540
|
-
}, {
|
|
541
|
-
className: line-clamp-2
|
|
542
|
-
color: muted
|
|
543
|
-
content: @config.descriptionBinding
|
|
544
|
-
type: typography
|
|
545
|
-
variant: caption
|
|
546
|
-
}]
|
|
547
|
-
className: p-card-md
|
|
548
|
-
direction: vertical
|
|
549
|
-
gap: xs
|
|
550
|
-
type: stack
|
|
551
|
-
}]
|
|
552
|
-
className: "group rounded-lg overflow-hidden bg-[var(--color-card)] border border-[var(--color-border)] shadow-sm hover:shadow-hover hover:-translate-y-0.5 transition-all duration-normal cursor-pointer"
|
|
553
|
-
direction: vertical
|
|
554
|
-
gap: sm
|
|
555
|
-
type: stack
|
|
556
|
-
})
|
|
557
535
|
type: data-grid
|
|
558
536
|
}]
|
|
559
|
-
className:
|
|
537
|
+
className: w-full
|
|
560
538
|
direction: vertical
|
|
561
539
|
gap: lg
|
|
562
540
|
type: stack
|
|
563
541
|
}
|
|
564
542
|
@label "Gallery look body"
|
|
565
|
-
@description "Render-ui SExpr rendered after rows load when browseLook = gallery. Image-first responsive card grid
|
|
543
|
+
@description "Render-ui SExpr rendered after rows load when browseLook = gallery. Image-first responsive card grid (auto-fit minCardWidth) on the data-grid fields path: imageField renders the card photo when set (falsy-safe when empty), fields drive title/badges/meta. One tree, no renderItem lambda — lambdas do not survive inside (if) arms (2026-07-27)."
|
|
544
|
+
@tier "internal"
|
|
545
|
+
triageBodyContent : render-ui = {
|
|
546
|
+
children: [(if @config.bodySearch {
|
|
547
|
+
children: [{
|
|
548
|
+
className: "w-full max-w-md"
|
|
549
|
+
clearable: true
|
|
550
|
+
event: REFETCH_QUERY
|
|
551
|
+
placeholder: @config.searchPlaceholder
|
|
552
|
+
type: search-input
|
|
553
|
+
}]
|
|
554
|
+
className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
|
|
555
|
+
direction: horizontal
|
|
556
|
+
gap: sm
|
|
557
|
+
type: stack
|
|
558
|
+
} {
|
|
559
|
+
children: []
|
|
560
|
+
gap: none
|
|
561
|
+
type: stack
|
|
562
|
+
}), {
|
|
563
|
+
children: [{
|
|
564
|
+
entity: ?data
|
|
565
|
+
fields: @config.fields
|
|
566
|
+
itemActions: @config.itemActions
|
|
567
|
+
maxInlineActions: @config.maxInlineActions
|
|
568
|
+
type: data-list
|
|
569
|
+
variant: compact
|
|
570
|
+
}]
|
|
571
|
+
className: overflow-x-auto
|
|
572
|
+
direction: vertical
|
|
573
|
+
gap: none
|
|
574
|
+
type: stack
|
|
575
|
+
}]
|
|
576
|
+
className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
|
|
577
|
+
direction: vertical
|
|
578
|
+
gap: none
|
|
579
|
+
type: stack
|
|
580
|
+
}
|
|
581
|
+
@label "Triage look body"
|
|
582
|
+
@description "Render-ui SExpr rendered after rows load when browseLook = triage. Inbox-register queue: one surface with a toolbar search and compact list rows (title + badges + inline meta) built for keyboard-pace queue work; row actions collapse per maxInlineActions. Composes fields/itemActions/searchPlaceholder/bodySearch."
|
|
583
|
+
@tier "internal"
|
|
584
|
+
masterDetailBodyContent : render-ui = {
|
|
585
|
+
children: [{
|
|
586
|
+
children: [{
|
|
587
|
+
className: "w-full max-w-md"
|
|
588
|
+
clearable: true
|
|
589
|
+
event: REFETCH_QUERY
|
|
590
|
+
placeholder: @config.searchPlaceholder
|
|
591
|
+
type: search-input
|
|
592
|
+
}]
|
|
593
|
+
className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
|
|
594
|
+
direction: horizontal
|
|
595
|
+
gap: sm
|
|
596
|
+
type: stack
|
|
597
|
+
}, {
|
|
598
|
+
detail: {
|
|
599
|
+
children: []
|
|
600
|
+
gap: none
|
|
601
|
+
type: stack
|
|
602
|
+
}
|
|
603
|
+
emptyDetail: {
|
|
604
|
+
description: "Select a record from the list to see its details."
|
|
605
|
+
icon: mouse-pointer-click
|
|
606
|
+
title: "Nothing selected"
|
|
607
|
+
type: empty-state
|
|
608
|
+
}
|
|
609
|
+
hasSelection: false
|
|
610
|
+
master: {
|
|
611
|
+
children: [@trait.MasterListView]
|
|
612
|
+
className: "overflow-y-auto"
|
|
613
|
+
direction: vertical
|
|
614
|
+
gap: none
|
|
615
|
+
type: stack
|
|
616
|
+
}
|
|
617
|
+
type: master-detail-layout
|
|
618
|
+
}]
|
|
619
|
+
className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
|
|
620
|
+
direction: vertical
|
|
621
|
+
gap: none
|
|
622
|
+
type: stack
|
|
623
|
+
}
|
|
624
|
+
@label "Master-detail look body (no selection)"
|
|
625
|
+
@description "Render-ui SExpr rendered after rows load when browseLook = master-detail: record-work split view — toolbar search, dense list left, empty-state pane right until a row is opened. Opening a row renders masterDetailRecordBodyContent. The toolbar is always present in this look (bodySearch does not apply)."
|
|
626
|
+
@tier "internal"
|
|
627
|
+
masterDetailRecordBodyContent : render-ui = {
|
|
628
|
+
children: [{
|
|
629
|
+
children: [{
|
|
630
|
+
className: "w-full max-w-md"
|
|
631
|
+
clearable: true
|
|
632
|
+
event: REFETCH_QUERY
|
|
633
|
+
placeholder: @config.searchPlaceholder
|
|
634
|
+
type: search-input
|
|
635
|
+
}]
|
|
636
|
+
className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
|
|
637
|
+
direction: horizontal
|
|
638
|
+
gap: sm
|
|
639
|
+
type: stack
|
|
640
|
+
}, {
|
|
641
|
+
detail: {
|
|
642
|
+
actions: [{
|
|
643
|
+
event: CLOSE
|
|
644
|
+
label: Close
|
|
645
|
+
}]
|
|
646
|
+
fieldNames: @config.detailFields
|
|
647
|
+
initialData: ?row
|
|
648
|
+
showActions: false
|
|
649
|
+
type: detail-panel
|
|
650
|
+
}
|
|
651
|
+
hasSelection: true
|
|
652
|
+
master: {
|
|
653
|
+
children: [@trait.MasterListView]
|
|
654
|
+
className: "overflow-y-auto"
|
|
655
|
+
direction: vertical
|
|
656
|
+
gap: none
|
|
657
|
+
type: stack
|
|
658
|
+
}
|
|
659
|
+
type: master-detail-layout
|
|
660
|
+
}]
|
|
661
|
+
className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
|
|
662
|
+
direction: vertical
|
|
663
|
+
gap: none
|
|
664
|
+
type: stack
|
|
665
|
+
}
|
|
666
|
+
@label "Master-detail look body (record open)"
|
|
667
|
+
@description "Render-ui SExpr rendered by the VIEW transition when browseLook = master-detail: the same split view with the clicked record's detail panel on the right, fed by the VIEW payload row and detailFields."
|
|
566
668
|
@tier "internal"
|
|
567
669
|
}
|
|
568
670
|
}
|
|
@@ -29,6 +29,9 @@ orbital DashboardGridEmbeddedOrbital {
|
|
|
29
29
|
state composing {
|
|
30
30
|
INIT -> composing
|
|
31
31
|
(render-ui main {
|
|
32
|
+
data-theme: @config.theme
|
|
33
|
+
type: box
|
|
34
|
+
children: [{
|
|
32
35
|
children: [{
|
|
33
36
|
align: center
|
|
34
37
|
children: [{
|
|
@@ -54,6 +57,7 @@ orbital DashboardGridEmbeddedOrbital {
|
|
|
54
57
|
direction: vertical
|
|
55
58
|
gap: md
|
|
56
59
|
type: stack
|
|
60
|
+
}]
|
|
57
61
|
})
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -138,6 +142,10 @@ orbital DashboardGridEmbeddedOrbital {
|
|
|
138
142
|
@label "Tile min width"
|
|
139
143
|
@description "Minimum pixel width per tile before the grid re-flows"
|
|
140
144
|
@tier "presentation"
|
|
145
|
+
theme : string = ""
|
|
146
|
+
@label "Theme"
|
|
147
|
+
@description "Design-system skin applied to the embedded grid surface via data-theme (e.g. 'linear-clean-light'). Empty inherits the surrounding shell's theme. Needed because this atom renders without app-shell chrome, so no layout-level theme cascade reaches it."
|
|
148
|
+
@tier "presentation"
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
|
|
@@ -59,7 +59,7 @@ orbital StoredFileOrbital {
|
|
|
59
59
|
direction: horizontal
|
|
60
60
|
gap: sm
|
|
61
61
|
type: stack
|
|
62
|
-
}, { type: divider }, { tree: @config.tree, type: file-tree }, {
|
|
62
|
+
}, { type: divider }, { tree: @config.tree, type: file-tree }, {
|
|
63
63
|
entity: ?data
|
|
64
64
|
fields: @config.fields
|
|
65
65
|
gap: sm
|
|
@@ -33,7 +33,7 @@ orbital StatsItemOrbital {
|
|
|
33
33
|
sparklineData: (object/get @metric sparklineData [])
|
|
34
34
|
suffix: (object/get @metric suffix "")
|
|
35
35
|
target: (object/get @metric target 0)
|
|
36
|
-
trend: (object/get @metric trend
|
|
36
|
+
trend: (object/get @metric trend)
|
|
37
37
|
trendFormat: (object/get @metric trendFormat absolute)
|
|
38
38
|
trendPolarity: (object/get @metric trendPolarity higher-is-better)
|
|
39
39
|
value: (object/get @metric value 0)
|
|
@@ -62,7 +62,7 @@ orbital StatsItemOrbital {
|
|
|
62
62
|
sparklineData: (object/get @metric sparklineData [])
|
|
63
63
|
suffix: (object/get @metric suffix "")
|
|
64
64
|
target: (object/get @metric target 0)
|
|
65
|
-
trend: (if ["=", (object/get @metric aggregation count), variance] ["-", (array/sum (array/filter ?data (object/get @metric filter true)) (object/get @metric field "")), (array/sum (array/filter ?data (object/get @metric baselineFilter true)) (object/get @metric field ""))] (object/get @metric trend
|
|
65
|
+
trend: (if ["=", (object/get @metric aggregation count), variance] ["-", (array/sum (array/filter ?data (object/get @metric filter true)) (object/get @metric field "")), (array/sum (array/filter ?data (object/get @metric baselineFilter true)) (object/get @metric field ""))] (object/get @metric trend))
|
|
66
66
|
trendFormat: (object/get @metric trendFormat absolute)
|
|
67
67
|
trendPolarity: (object/get @metric trendPolarity higher-is-better)
|
|
68
68
|
value: (if ["=", (object/get @metric aggregation count), sum] (array/sum (array/filter ?data (object/get @metric filter true)) (object/get @metric field "")) (if ["=", (object/get @metric aggregation count), avg] (array/avg (array/filter ?data (object/get @metric filter true)) (object/get @metric field "")) (if ["=", (object/get @metric aggregation count), min] (array/min (array/filter ?data (object/get @metric filter true)) (object/get @metric field "")) (if ["=", (object/get @metric aggregation count), max] (array/max (array/filter ?data (object/get @metric filter true)) (object/get @metric field "")) (if ["=", (object/get @metric aggregation count), variance] (array/sum (array/filter ?data (object/get @metric filter true)) (object/get @metric field "")) (array/len (array/filter ?data (object/get @metric filter true))))))))
|
|
@@ -117,6 +117,10 @@ orbital StatsItemOrbital {
|
|
|
117
117
|
@description "Heading shown above the stat-card grid"
|
|
118
118
|
@synonyms "the section header text. user phrases: 'rename section to X' / 'title the KPIs X' / 'change section title to X' -> X"
|
|
119
119
|
@tier "presentation"
|
|
120
|
+
theme : string = ""
|
|
121
|
+
@label "Theme"
|
|
122
|
+
@description "Data-theme skin applied to the stat body root — for chrome-less pages composed without an AppShell."
|
|
123
|
+
@tier "presentation"
|
|
120
124
|
viewPattern : pattern = data-grid
|
|
121
125
|
@label "Render pattern"
|
|
122
126
|
@description "UI pattern used to arrange the stat cards"
|
|
@@ -130,7 +134,7 @@ orbital StatsItemOrbital {
|
|
|
130
134
|
@label "Stats look"
|
|
131
135
|
@description "Layer 3 body layout: default per-metric stat-card grid ('cards'), Stripe-dashboard KPI tile row ('kpi-tiles'), single oversize hero number ('hero-metric'), or chart-first sparkline tile grid ('sparkline-row')."
|
|
132
136
|
@tier "presentation"
|
|
133
|
-
bodyContent : render-ui = {
|
|
137
|
+
bodyContent : render-ui = { data-theme: @config.theme, type: box, children: [{
|
|
134
138
|
entity: @entity.cards
|
|
135
139
|
fields: []
|
|
136
140
|
renderItem: (fn card {
|
|
@@ -152,11 +156,11 @@ orbital StatsItemOrbital {
|
|
|
152
156
|
variant: @card.variant
|
|
153
157
|
})
|
|
154
158
|
type: @config.viewPattern
|
|
155
|
-
}
|
|
159
|
+
}] }
|
|
156
160
|
@label "Body content tree"
|
|
157
161
|
@description "Render-ui SExpr rendered after metrics aggregate for statsLook='cards' — the canonical data-grid call mapping @entity.cards through stat-display. Other statsLook values render kpiTilesBodyContent / heroMetricBodyContent / sparklineRowBodyContent instead; all four share this trait's state machine, aggregation pipeline, and listens."
|
|
158
162
|
@tier "internal"
|
|
159
|
-
kpiTilesBodyContent : render-ui = {
|
|
163
|
+
kpiTilesBodyContent : render-ui = { data-theme: @config.theme, type: box, children: [{
|
|
160
164
|
className: w-full
|
|
161
165
|
entity: @entity.cards
|
|
162
166
|
fields: []
|
|
@@ -228,11 +232,11 @@ orbital StatsItemOrbital {
|
|
|
228
232
|
type: stack
|
|
229
233
|
})
|
|
230
234
|
type: data-grid
|
|
231
|
-
}
|
|
235
|
+
}] }
|
|
232
236
|
@label "KPI tiles body content tree"
|
|
233
237
|
@description "Render-ui SExpr for statsLook='kpi-tiles' — Stripe-dashboard style tile grid: huge typography number, label, trend indicator, sparkline hint."
|
|
234
238
|
@tier "internal"
|
|
235
|
-
heroMetricBodyContent : render-ui = {
|
|
239
|
+
heroMetricBodyContent : render-ui = { data-theme: @config.theme, type: box, children: [{
|
|
236
240
|
entity: @entity.cards
|
|
237
241
|
fields: []
|
|
238
242
|
gap: none
|
|
@@ -313,11 +317,11 @@ orbital StatsItemOrbital {
|
|
|
313
317
|
type: stack
|
|
314
318
|
})
|
|
315
319
|
type: data-list
|
|
316
|
-
}
|
|
320
|
+
}] }
|
|
317
321
|
@label "Hero metric body content tree"
|
|
318
322
|
@description "Render-ui SExpr for statsLook='hero-metric' — one elevated card, one oversize number, background sparkline."
|
|
319
323
|
@tier "internal"
|
|
320
|
-
sparklineRowBodyContent : render-ui = {
|
|
324
|
+
sparklineRowBodyContent : render-ui = { data-theme: @config.theme, type: box, children: [{
|
|
321
325
|
className: w-full
|
|
322
326
|
entity: @entity.cards
|
|
323
327
|
fields: []
|
|
@@ -391,7 +395,7 @@ orbital StatsItemOrbital {
|
|
|
391
395
|
type: stack
|
|
392
396
|
})
|
|
393
397
|
type: data-grid
|
|
394
|
-
}
|
|
398
|
+
}] }
|
|
395
399
|
@label "Sparkline row body content tree"
|
|
396
400
|
@description "Render-ui SExpr for statsLook='sparkline-row' — chart-first tiles where the mini trend line is the visual centerpiece, label above, value+delta footer."
|
|
397
401
|
@tier "internal"
|
|
@@ -56,34 +56,6 @@ orbital RevisionOrbital {
|
|
|
56
56
|
gap: sm
|
|
57
57
|
type: stack
|
|
58
58
|
}, {
|
|
59
|
-
children: [{
|
|
60
|
-
icon: history
|
|
61
|
-
label: "Total revisions"
|
|
62
|
-
look: @config.statLook
|
|
63
|
-
type: stat-display
|
|
64
|
-
value: ?data.length
|
|
65
|
-
}, {
|
|
66
|
-
icon: users
|
|
67
|
-
label: Authors
|
|
68
|
-
look: @config.statLook
|
|
69
|
-
type: stat-display
|
|
70
|
-
value: "6"
|
|
71
|
-
}, {
|
|
72
|
-
icon: save
|
|
73
|
-
label: "Saves this week"
|
|
74
|
-
look: @config.statLook
|
|
75
|
-
type: stat-display
|
|
76
|
-
value: "14"
|
|
77
|
-
}, {
|
|
78
|
-
icon: rotate-ccw
|
|
79
|
-
label: Rollbacks
|
|
80
|
-
look: @config.statLook
|
|
81
|
-
type: stat-display
|
|
82
|
-
value: "2"
|
|
83
|
-
}]
|
|
84
|
-
cols: 4
|
|
85
|
-
type: simple-grid
|
|
86
|
-
}, { type: divider }, {
|
|
87
59
|
cols: 1
|
|
88
60
|
entity: ?data
|
|
89
61
|
fields: [{
|
|
@@ -108,7 +80,7 @@ orbital RevisionOrbital {
|
|
|
108
80
|
event: OPEN_REVISION
|
|
109
81
|
icon: arrow-right
|
|
110
82
|
label: Open
|
|
111
|
-
variant:
|
|
83
|
+
variant: ghost
|
|
112
84
|
}, {
|
|
113
85
|
event: ROLLBACK
|
|
114
86
|
icon: rotate-ccw
|