@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.
Files changed (34) hide show
  1. package/behaviors/lolo/ui/core/atoms/std-app-search.lolo +1 -1
  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 +189 -87
  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/registry/factory-signatures.json +9 -13
  10. package/behaviors/registry/ui/core/atoms/std-app-search.orb +9 -1
  11. package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +242 -50
  12. package/behaviors/registry/ui/core/atoms/std-board.orb +86 -20
  13. package/behaviors/registry/ui/core/atoms/std-browse.orb +379 -123
  14. package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +41 -29
  15. package/behaviors/registry/ui/core/atoms/std-file-store.orb +0 -3
  16. package/behaviors/registry/ui/core/atoms/std-stats.orb +267 -238
  17. package/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -38
  18. package/dist/behaviors/behaviors-embeddings.json +475 -475
  19. package/dist/behaviors/behaviors-registry.json +10 -12
  20. package/dist/behaviors/knob-embeddings.json +1 -1
  21. package/dist/behaviors/registry/factory-signatures.json +9 -13
  22. package/dist/behaviors/registry/ui/core/atoms/std-app-search.orb +9 -1
  23. package/dist/behaviors/registry/ui/core/atoms/std-billable-hour.orb +242 -50
  24. package/dist/behaviors/registry/ui/core/atoms/std-board.orb +86 -20
  25. package/dist/behaviors/registry/ui/core/atoms/std-browse.orb +379 -123
  26. package/dist/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +41 -29
  27. package/dist/behaviors/registry/ui/core/atoms/std-file-store.orb +0 -3
  28. package/dist/behaviors/registry/ui/core/atoms/std-stats.orb +267 -238
  29. package/dist/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -38
  30. package/dist/behaviors-embeddings.json +475 -475
  31. package/dist/behaviors-registry.json +10 -12
  32. package/dist/knob-embeddings.json +1 -1
  33. package/dist/registry/factory-signatures.json +9 -13
  34. 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 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."
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: 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]
@@ -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-2xl"
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: "w-full max-w-2xl"
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: "rounded-lg border border-[var(--color-border)] overflow-x-auto bg-[var(--color-card)] shadow-sm"
395
+ className: overflow-x-auto
355
396
  direction: vertical
356
397
  gap: none
357
398
  type: stack
358
399
  }]
359
- className: p-card-md
400
+ className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] shadow-sm overflow-hidden"
360
401
  direction: vertical
361
- gap: sm
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: "w-full p-card-md"
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 -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"
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: "w-full px-card-md py-section"
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 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."
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 }, { 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