@almadar/std 16.148.0 → 16.150.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 (45) hide show
  1. package/behaviors/lolo/ui/core/atoms/std-app-search.lolo +3 -3
  2. package/behaviors/lolo/ui/core/atoms/std-billable-hour.lolo +86 -41
  3. package/behaviors/lolo/ui/core/atoms/std-board.lolo +29 -16
  4. package/behaviors/lolo/ui/core/atoms/std-browse.lolo +190 -86
  5. package/behaviors/lolo/ui/core/atoms/std-dashboard-grid-embedded.lolo +8 -0
  6. package/behaviors/lolo/ui/core/atoms/std-file-store.lolo +1 -1
  7. package/behaviors/lolo/ui/core/atoms/std-stats.lolo +14 -10
  8. package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +1 -29
  9. package/behaviors/lolo/ui/core/organisms/ui-entity-table.lolo +1 -0
  10. package/behaviors/registry/factory-signatures.json +9 -13
  11. package/behaviors/registry/ui/core/atoms/std-app-search.orb +39 -4
  12. package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +242 -50
  13. package/behaviors/registry/ui/core/atoms/std-board.orb +86 -20
  14. package/behaviors/registry/ui/core/atoms/std-browse.orb +423 -125
  15. package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +41 -29
  16. package/behaviors/registry/ui/core/atoms/std-file-store.orb +0 -3
  17. package/behaviors/registry/ui/core/atoms/std-stats.orb +267 -238
  18. package/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -38
  19. package/behaviors/registry/ui/core/organisms/ui-entity-table.orb +5 -0
  20. package/dist/behaviors/behaviors-embeddings.json +200 -200
  21. package/dist/behaviors/behaviors-registry.json +34 -18
  22. package/dist/behaviors/exports-reader.js +33 -3
  23. package/dist/behaviors/functions/index.d.ts +41 -54
  24. package/dist/behaviors/functions/index.js +31 -3
  25. package/dist/behaviors/index.d.ts +1 -1
  26. package/dist/behaviors/index.js +34 -4
  27. package/dist/behaviors/knob-embeddings.json +1 -1
  28. package/dist/behaviors/query.js +33 -3
  29. package/dist/behaviors/registry/factory-signatures.json +9 -13
  30. package/dist/behaviors/registry/ui/core/atoms/std-app-search.orb +39 -4
  31. package/dist/behaviors/registry/ui/core/atoms/std-billable-hour.orb +242 -50
  32. package/dist/behaviors/registry/ui/core/atoms/std-board.orb +86 -20
  33. package/dist/behaviors/registry/ui/core/atoms/std-browse.orb +423 -125
  34. package/dist/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +41 -29
  35. package/dist/behaviors/registry/ui/core/atoms/std-file-store.orb +0 -3
  36. package/dist/behaviors/registry/ui/core/atoms/std-stats.orb +267 -238
  37. package/dist/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -38
  38. package/dist/behaviors/registry/ui/core/organisms/ui-entity-table.orb +5 -0
  39. package/dist/behaviors-embeddings.json +200 -200
  40. package/dist/behaviors-registry.json +34 -18
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +34 -4
  43. package/dist/knob-embeddings.json +1 -1
  44. package/dist/registry/factory-signatures.json +9 -13
  45. package/package.json +9 -2
@@ -1,8 +1,8 @@
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 four render bodies; dense/feed/gallery were folded in from std-browse-dense/-feed/-gallery (Phase 5.B) and their consumers retarget by setting browseLook."
3
- type FieldSpec = { name : string!, label : string, variant : string, icon : string, format : string, key : string, header : string }
4
- type ItemAction = { label : string!, event : string, navigatesTo : string, variant : string, icon : string }
5
- type ColumnSpec = { key : string!, field : string, header : string, width : string, align : string, className : string, weight : string, format : string, icon : string }
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
+ type FieldSpec = { name : string!, label : string, variant : h3 | h4 | body | caption | badge | small | progress, icon : string, format : date | currency | number | boolean | percent, key : string, header : string }
4
+ type ItemAction = { label : string!, event : string, navigatesTo : string, variant : primary | secondary | ghost | danger, icon : string }
5
+ type ColumnSpec = { key : string!, field : string, header : string, width : string, align : left | center | right, className : string, weight : normal | medium | semibold, format : badge | date | currency | number | percent | boolean, icon : string }
6
6
  type FilterSpec = { field : string!, label : string, options : [string] }
7
7
  orbital BrowseItemOrbital {
8
8
  type BrowseItemLoaded = Event { data : [BrowseItem], totalCount : number } "Fired when the BrowseItem collection finishes loading; payload.data holds the (possibly paginated) slice and totalCount holds the unsliced filter total. totalCount is optional so compiled-path emitters that haven't been migrated yet still validate"
@@ -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: true
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 { size: sm, type: spinner })
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
- (render-ui main @config.bodyContent))))
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 { children: [{ color: error, content: ?error, type: typography, variant: caption }], direction: vertical, type: stack })
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 { size: sm, type: spinner })
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
- (render-ui main @config.bodyContent))))
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 { size: sm, type: spinner })
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 = 99
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 sticky-header table with search bar (denseBodyContent); feed = chronological card stream (feedBodyContent); gallery = image-first responsive card grid (galleryBodyContent). Selecting a non-table look swaps which body tree renders after rows load; any of the four bodies may still be overridden directly at the call site for a one-off layout."
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]
@@ -334,13 +376,13 @@ orbital BrowseItemOrbital {
334
376
  denseBodyContent : render-ui = {
335
377
  children: [(if @config.bodySearch {
336
378
  children: [{
337
- className: w-full
379
+ className: "w-full max-w-md"
338
380
  clearable: true
339
381
  event: REFETCH_QUERY
340
382
  placeholder: @config.searchPlaceholder
341
383
  type: search-input
342
384
  }]
343
- className: w-full
385
+ className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
344
386
  direction: horizontal
345
387
  gap: sm
346
388
  type: stack
@@ -350,13 +392,14 @@ orbital BrowseItemOrbital {
350
392
  type: stack
351
393
  }), {
352
394
  children: [@trait.DenseTableView]
353
- className: "rounded-lg border border-[var(--color-border)] overflow-x-auto bg-[var(--color-card)] shadow-sm"
395
+ className: overflow-x-auto
354
396
  direction: vertical
355
397
  gap: none
356
398
  type: stack
357
399
  }]
400
+ className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
358
401
  direction: vertical
359
- gap: sm
402
+ gap: none
360
403
  type: stack
361
404
  }
362
405
  @label "Dense look body"
@@ -448,6 +491,7 @@ orbital BrowseItemOrbital {
448
491
  }]
449
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"
450
493
  look: flat-bordered
494
+ padding: none
451
495
  type: card
452
496
  })
453
497
  type: data-list
@@ -471,7 +515,7 @@ orbital BrowseItemOrbital {
471
515
  placeholder: @config.searchPlaceholder
472
516
  type: search-input
473
517
  }]
474
- className: "sticky top-0 z-10 -mx-6 px-6 py-3 bg-[var(--color-surface)]/95 backdrop-blur border-b border-[var(--color-border)]"
518
+ className: "sticky top-0 z-10 py-3 bg-[var(--color-surface)]/95 backdrop-blur"
475
519
  direction: horizontal
476
520
  gap: sm
477
521
  type: stack
@@ -482,76 +526,12 @@ orbital BrowseItemOrbital {
482
526
  }), {
483
527
  className: w-full
484
528
  entity: ?data
485
- fields: []
529
+ fields: @config.fields
486
530
  gap: lg
531
+ imageField: @config.imageField
487
532
  itemActions: @config.itemActions
488
533
  maxInlineActions: @config.maxInlineActions
489
534
  minCardWidth: 260
490
- renderItem: (fn item {
491
- children: [{
492
- align: center
493
- children: [{
494
- className: "text-muted-foreground opacity-50 transition-transform duration-normal group-hover:scale-105"
495
- name: image
496
- size: lg
497
- type: icon
498
- }, {
499
- className: "px-2 text-center truncate max-w-full"
500
- color: muted
501
- content: @config.imageLabelBinding
502
- type: typography
503
- variant: caption
504
- }, {
505
- align: center
506
- children: [{
507
- label: @config.priceBinding
508
- type: badge
509
- variant: primary
510
- }]
511
- className: "absolute top-2 right-2"
512
- direction: horizontal
513
- gap: xs
514
- type: stack
515
- }, {
516
- align: center
517
- children: [{
518
- label: @config.badgeBinding
519
- type: badge
520
- variant: neutral
521
- }]
522
- className: "absolute top-2 left-2"
523
- direction: horizontal
524
- gap: xs
525
- type: stack
526
- }]
527
- className: "relative aspect-square w-full bg-muted overflow-hidden"
528
- direction: vertical
529
- gap: xs
530
- justify: center
531
- type: stack
532
- }, {
533
- children: [{
534
- className: truncate
535
- content: @config.titleBinding
536
- type: typography
537
- variant: h4
538
- }, {
539
- className: line-clamp-2
540
- color: muted
541
- content: @config.descriptionBinding
542
- type: typography
543
- variant: caption
544
- }]
545
- className: p-card-md
546
- direction: vertical
547
- gap: xs
548
- type: stack
549
- }]
550
- 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"
551
- direction: vertical
552
- gap: sm
553
- type: stack
554
- })
555
535
  type: data-grid
556
536
  }]
557
537
  className: w-full
@@ -560,7 +540,131 @@ orbital BrowseItemOrbital {
560
540
  type: stack
561
541
  }
562
542
  @label "Gallery look body"
563
- @description "Render-ui SExpr rendered after rows load when browseLook = gallery. Image-first responsive card grid with auto-fit minCardWidth; composes titleBinding/descriptionBinding/badgeBinding/imageLabelBinding/priceBinding/searchPlaceholder. Folded from std-browse-gallery (Phase 5.B)."
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."
564
668
  @tier "internal"
565
669
  }
566
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 }, { type: divider }, {
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 0)
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 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))
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: primary
83
+ variant: ghost
112
84
  }, {
113
85
  event: ROLLBACK
114
86
  icon: rotate-ccw
@@ -47,6 +47,7 @@ orbital EntityTableOrbital {
47
47
  label : string!
48
48
  icon : icon
49
49
  variant : "default" | "danger"
50
+ event : string
50
51
  }
51
52
 
52
53
  entity EntityTableItem [runtime] {