@almadar/std 16.186.0 → 16.188.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/behaviors/lolo/ui/core/.hand-authored.json +4 -0
  2. package/behaviors/lolo/ui/core/atoms/std-board.lolo +138 -4
  3. package/behaviors/lolo/ui/core/atoms/std-browse.lolo +148 -8
  4. package/behaviors/lolo/ui/core/atoms/std-calendar.lolo +141 -3
  5. package/behaviors/lolo/ui/core/atoms/std-event-log.lolo +255 -2
  6. package/behaviors/lolo/ui/core/atoms/std-gallery.lolo +148 -1
  7. package/behaviors/lolo/ui/core/atoms/std-list.lolo +193 -1
  8. package/behaviors/lolo/ui/core/atoms/std-record-detail.lolo +329 -0
  9. package/behaviors/lolo/ui/core/atoms/std-selection.lolo +182 -1
  10. package/behaviors/lolo/ui/core/atoms/std-tree.lolo +113 -3
  11. package/behaviors/lolo/ui/game/atoms/std-area-fill.lolo +93 -0
  12. package/behaviors/lolo/ui/game/atoms/std-loop-lock.lolo +79 -0
  13. package/behaviors/lolo/ui/game/atoms/std-pad-lock.lolo +93 -0
  14. package/behaviors/lolo/ui/game/atoms/std-patrol-hazard.lolo +14 -7
  15. package/behaviors/lolo/ui/game/atoms/std-platformer-body.lolo +21 -4
  16. package/behaviors/lolo/ui/game/atoms/std-velocity-gate.lolo +101 -0
  17. package/behaviors/registry/infra/atoms/std-approval-gate.orb +16 -8
  18. package/behaviors/registry/infra/atoms/std-cache-aside.orb +62 -31
  19. package/behaviors/registry/infra/atoms/std-circuit-breaker.orb +32 -16
  20. package/behaviors/registry/infra/atoms/std-data-erasure.orb +16 -8
  21. package/behaviors/registry/infra/atoms/std-mod-queue.orb +16 -8
  22. package/behaviors/registry/infra/atoms/std-rate-limiter.orb +28 -14
  23. package/behaviors/registry/infra/organisms/std-status-lifecycle-control.orb +2 -1
  24. package/behaviors/registry/ml/organisms/std-escalating-decision.orb +10 -5
  25. package/behaviors/registry/ml/organisms/std-knowledge-tracing.orb +4 -2
  26. package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +2 -1
  27. package/behaviors/registry/ui/core/atoms/std-board.orb +355 -2
  28. package/behaviors/registry/ui/core/atoms/std-browse.orb +304 -31
  29. package/behaviors/registry/ui/core/atoms/std-calendar.orb +344 -0
  30. package/behaviors/registry/ui/core/atoms/std-dashboard-grid-embedded.orb +2 -1
  31. package/behaviors/registry/ui/core/atoms/std-esign-flow.orb +4 -2
  32. package/behaviors/registry/ui/core/atoms/std-esign-request.orb +6 -3
  33. package/behaviors/registry/ui/core/atoms/std-event-log.orb +773 -0
  34. package/behaviors/registry/ui/core/atoms/std-export.orb +4 -2
  35. package/behaviors/registry/ui/core/atoms/std-gallery.orb +390 -0
  36. package/behaviors/registry/ui/core/atoms/std-import.orb +4 -2
  37. package/behaviors/registry/ui/core/atoms/std-list.orb +451 -0
  38. package/behaviors/registry/ui/core/atoms/std-record-detail.orb +1141 -0
  39. package/behaviors/registry/ui/core/atoms/std-selection.orb +436 -0
  40. package/behaviors/registry/ui/core/atoms/std-tree.orb +292 -1
  41. package/behaviors/registry/ui/game/atoms/std-area-fill.orb +955 -0
  42. package/behaviors/registry/ui/game/atoms/std-fx-trail.orb +1 -1
  43. package/behaviors/registry/ui/game/atoms/std-gate-lock.orb +1 -1
  44. package/behaviors/registry/ui/game/atoms/std-lives.orb +1 -1
  45. package/behaviors/registry/ui/game/atoms/std-loop-lock.orb +785 -0
  46. package/behaviors/registry/ui/game/atoms/std-pad-lock.orb +903 -0
  47. package/behaviors/registry/ui/game/atoms/std-patrol-hazard.orb +190 -44
  48. package/behaviors/registry/ui/game/atoms/std-platformer-body.orb +868 -637
  49. package/behaviors/registry/ui/game/atoms/std-velocity-gate.orb +1040 -0
  50. package/behaviors/registry/ui/game/organisms/std-arcade-board-3d.orb +8 -4
  51. package/behaviors/registry/ui/game/organisms/std-crew-board-2d.orb +6 -3
  52. package/behaviors/registry/ui/game/organisms/std-dungeon-board-2d.orb +10 -5
  53. package/behaviors/registry/ui/game/organisms/std-market-board-3d.orb +8 -4
  54. package/behaviors/registry/ui/game/organisms/std-objective-board-3d.orb +8 -4
  55. package/behaviors/registry/ui/game/organisms/std-platformer-board-2d.orb +7 -4
  56. package/behaviors/registry/ui/game/organisms/std-survival-board-3d.orb +8 -4
  57. package/behaviors/registry/ui/game/organisms/std-tactics-board-2d.orb +12 -6
  58. package/behaviors/registry/ui/game/organisms/std-tactics-board-3d.orb +8 -4
  59. package/behaviors/registry/ui/game/organisms/std-worldmap-board-2d.orb +6 -3
  60. package/behaviors/registry/ui/learning/atoms/std-learn-slope-probe.orb +1 -1
  61. package/behaviors/registry/ui/marketing/atoms/std-marketing-article.orb +2 -1
  62. package/behaviors/registry/ui/marketing/atoms/std-marketing-footer.orb +2 -1
  63. package/behaviors/registry/ui/marketing/atoms/std-marketing-split.orb +2 -1
  64. package/behaviors/registry/ui/marketing/atoms/std-marketing-stats.orb +2 -1
  65. package/behaviors/registry/ui/marketing/atoms/std-marketing-steps.orb +2 -1
  66. package/behaviors/registry/ui/marketing/atoms/std-marketing-team.orb +2 -1
  67. package/dist/behaviors/exports-reader.js +7822 -3996
  68. package/dist/behaviors/functions/index.d.ts +598 -270
  69. package/dist/behaviors/functions/index.js +7805 -3989
  70. package/dist/behaviors/index.d.ts +1 -1
  71. package/dist/behaviors/index.js +7823 -3997
  72. package/dist/behaviors/query.js +7822 -3996
  73. package/dist/behaviors-embeddings.hash.json +3 -3
  74. package/dist/behaviors-embeddings.json +535 -519
  75. package/dist/behaviors-registry.json +2877 -2268
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.js +7823 -3997
  78. package/dist/knob-embeddings.hash.json +3 -3
  79. package/dist/knob-embeddings.json +1 -1
  80. package/dist/registry/factory-signatures.json +1 -1
  81. package/package.json +1 -1
  82. package/behaviors/lolo/ui/core/organisms/ui-ui-slot-renderer.lolo +0 -71
  83. package/behaviors/registry/ui/core/organisms/ui-ui-slot-renderer.orb +0 -234
@@ -99,6 +99,10 @@
99
99
  "signature": "33fcd927eb0bb9b7",
100
100
  "board": "packages/almadar-std/behaviors/lolo/ui/core/atoms/std-rating-review.lolo"
101
101
  },
102
+ "std-record-detail": {
103
+ "signature": "2d1e4a89d0de6e94",
104
+ "board": "packages/almadar-std/behaviors/lolo/ui/core/atoms/std-record-detail.lolo"
105
+ },
102
106
  "std-recurrence": {
103
107
  "signature": "56693212f7a2f8bc",
104
108
  "board": "packages/almadar-std/behaviors/lolo/ui/core/atoms/std-recurrence.lolo"
@@ -1,7 +1,8 @@
1
- app std-board "1.2.0"
2
- "std-board — generic N-column board primitive. `config.columns` is an array of column definitions ({ key, label, icon, variant }); the trait `array/map`s them on `BoardItemsLoaded` into `@entity.boards` where each board carries its filtered items + a count. The render is a `data-grid` over `@entity.boards` (one card per column) whose `renderItem` lambda nests a `data-list` over `@col.items` (the cards in that stage). N is config-driven — pass any number of columns; the outer `data-grid`'s `cols` is bound to `@config.gridCols` so the grid wraps if you exceed it. Topology = loading / viewing_board / viewing_card / adding / error — distinct from the cookie-cutter `loading/browsing/viewing_single/error` CRUD shape. App-level atoms (e.g. std-pipeline) compose std-board via `uses` and add a sibling persistor + extended source entity. `config.boardLook` switches the board body between the default N-column grid ('columns') and a Trello-style wide-column kanban ('kanban-classic')."
1
+ app std-board "1.3.0"
2
+ "std-board — generic N-column board primitive. `config.columns` is an array of column definitions ({ key, label, icon, variant }); the trait `array/map`s them on `BoardItemsLoaded` into `@entity.boards` where each board carries its filtered items + a count. The render is a `data-grid` over `@entity.boards` (one card per column) whose `renderItem` lambda nests a `data-list` over `@col.items` (the cards in that stage). N is config-driven — pass any number of columns; the outer `data-grid`'s `cols` is bound to `@config.gridCols` so the grid wraps if you exceed it. Topology = loading / viewing_board / viewing_card / adding / error — distinct from the cookie-cutter `loading/browsing/viewing_single/error` CRUD shape. App-level atoms (e.g. std-pipeline) compose std-board via `uses` and add a sibling persistor + extended source entity. `config.boardLook` switches the board body between the default N-column grid ('columns') and a Trello-style wide-column kanban ('kanban-classic'). `viewing_board` listens for REFETCH_QUERY/REFETCH_FILTER/REFETCH_PAGE (the std-search/std-filter/std-pagination wiring targets, no render on refetch — @entity.boards is bound reactively) and can optionally render its own search box (config.bodySearch, default false) and filter bar (config.filters, default []); no pagination control is rendered — a board's columns are not a paged list."
3
3
  type ObjectSpec = { id : string, label : string, value : string }
4
4
  type ColumnSpec = { key : string!, label : string!, icon : string, variant : string }
5
+ type FilterSpec = { field : string!, label : string, options : [string] }
5
6
  orbital BoardOrbital {
6
7
  type BoardItem = { id : string!, title : string, description : string, stage : string, notes : string, position : number }
7
8
 
@@ -219,6 +220,33 @@ orbital BoardOrbital {
219
220
  (fetch BoardView {
220
221
  emit: { failure: BoardItemsLoadFailed, success: BoardItemsLoaded }
221
222
  })
223
+ ;; REFETCH_* stay in viewing_board and issue no render-ui, matching
224
+ ;; std-browse's REFETCH_QUERY/REFETCH_FILTER/REFETCH_PAGE: a render here
225
+ ;; would swap the whole board tree on every keystroke. The fetch runs in
226
+ ;; the background; the BoardItemsLoaded arm below already recomputes
227
+ ;; @entity.boards, which the mounted data-grid is bound to, so the grid
228
+ ;; updates in place with no unmount/flicker.
229
+ REFETCH_QUERY -> viewing_board
230
+ (fetch BoardView {
231
+ emit: { failure: BoardItemsLoadFailed, success: BoardItemsLoaded }
232
+ filter: (or ["=", ?searchTerm, ""] (str/includes (object/get @entity title) ?searchTerm))
233
+ })
234
+ REFETCH_FILTER -> viewing_board
235
+ (fetch BoardView {
236
+ emit: { failure: BoardItemsLoadFailed, success: BoardItemsLoaded }
237
+ filter: (or ["=", ?value, ""] ["=", (object/get @entity ?field), ?value])
238
+ })
239
+ ;; A board's columns are not a paged list — paging the underlying fetch
240
+ ;; would show only one page's worth of items spread across all columns,
241
+ ;; which is wrong kanban UX. This arm exists so a consumer CAN wire
242
+ ;; std-pagination if they have a reason to; std-board itself never
243
+ ;; renders a pagination control (see bodyContent/kanbanClassicBodyContent).
244
+ REFETCH_PAGE -> viewing_board
245
+ (fetch BoardView {
246
+ emit: { failure: BoardItemsLoadFailed, success: BoardItemsLoaded }
247
+ limit: @config.pageSize
248
+ offset: ["*", ["-", ?page, 1], @config.pageSize]
249
+ })
222
250
  BoardItemsLoaded -> viewing_board
223
251
  (set @entity.boards (array/map @config.columns (fn col {
224
252
  count: (array/len (array/filter ?data (fn item ["=", (object/get @item @config.groupByField), (object/get @col key)])))
@@ -422,6 +450,25 @@ orbital BoardOrbital {
422
450
  @description "Indicates a board card has been removed."
423
451
  @synonyms "remove, discard, delete"
424
452
  @tier "presentation"
453
+ REFETCH_QUERY {
454
+ searchTerm : string!
455
+ }
456
+ @description "Triggers a board refresh based on the current search query."
457
+ @synonyms "refresh, reload, update, re-query"
458
+ @tier "domain"
459
+ REFETCH_FILTER {
460
+ field : string!
461
+ value : string!
462
+ }
463
+ @description "Triggers a board refresh based on current filter criteria."
464
+ @synonyms "refresh, filter, update, reload"
465
+ @tier "domain"
466
+ REFETCH_PAGE {
467
+ page : number!
468
+ }
469
+ @description "Triggers a board refresh for a given page. std-board itself renders no pagination control; this exists so a composed std-pagination can drive one."
470
+ @synonyms "reload, refresh, update, paginate"
471
+ @tier "domain"
425
472
  BoardItemsLoaded {
426
473
  data : [ObjectSpec]
427
474
  }
@@ -508,6 +555,31 @@ orbital BoardOrbital {
508
555
  @label "Board look"
509
556
  @description "Layer 3 body layout: default N-column data-grid ('columns'), or a Trello-style wide-column kanban with sticky column headers ('kanban-classic')."
510
557
  @tier "presentation"
558
+ bodySearch : boolean = false
559
+ @label "Show the board's own search box?"
560
+ @description "Renders an inline search box above the columns. Defaults false: unlike std-browse, std-board has never had a search box, so defaulting this true would silently add one to every existing consumer. The wired path — composing std-search beside the board and routing its FILTER/SEARCH event to REFETCH_QUERY — works regardless of this knob; set true only for a standalone board with no page-level search affordance."
561
+ @synonyms "inline search, board search, built-in search, show search, duplicate search"
562
+ @tier "presentation"
563
+ searchPlaceholder : string = "Search…"
564
+ @label "Search box placeholder text"
565
+ @description "Hint text inside the board's embedded search box (e.g. 'Search cards…'). Only shown when bodySearch is true."
566
+ @synonyms "search hint, placeholder, search box text, search label"
567
+ @tier "presentation"
568
+ filters : [FilterSpec] = []
569
+ @label "Which filters should appear above the board?"
570
+ @description "Dropdown filter facets by field, rendered as an embedded filter bar above the columns; each selection fires REFETCH_FILTER on this trait. Empty (the default) = no filter bar, which is the right setting whenever the page already composes std-filter beside this board and routes its FILTER event to REFETCH_FILTER — that is the wired owner and remains the primary path. Use this knob only for a standalone board with no page-level filter affordance."
571
+ @synonyms "filter bar, faceted filters, field filters, column filters, dropdowns"
572
+ @tier "presentation"
573
+ filterBarLook : "toolbar" | "chips" | "pills" | "popover-trigger" | "inline-column-header" = toolbar
574
+ @label "Filter bar look"
575
+ @description "Visual treatment for the embedded filter bar, mirroring std-filter's own enum. Only applies when `filters` is non-empty."
576
+ @synonyms "filter bar style, facet style, chips, pills"
577
+ @tier "presentation"
578
+ pageSize : number = 10
579
+ @label "Fetch page size"
580
+ @description "Records fetched per REFETCH_PAGE request. std-board renders no pagination control itself — this only feeds the fetch limit/offset when a consumer wires a composed std-pagination to REFETCH_PAGE."
581
+ @synonyms "fetch limit, records per page, batch size, page limit"
582
+ @tier "presentation"
511
583
  cardActionsContent : render-ui = {
512
584
  align: center
513
585
  children: [{
@@ -536,7 +608,37 @@ orbital BoardOrbital {
536
608
  direction: horizontal
537
609
  gap: sm
538
610
  type: stack
539
- }, { type: divider }, {
611
+ }, { type: divider }, (if @config.bodySearch {
612
+ children: [{
613
+ className: "w-full max-w-md"
614
+ clearable: true
615
+ event: REFETCH_QUERY
616
+ placeholder: @config.searchPlaceholder
617
+ type: search-input
618
+ }]
619
+ direction: horizontal
620
+ gap: sm
621
+ type: stack
622
+ } {
623
+ children: []
624
+ gap: none
625
+ type: stack
626
+ }), (if [">", (array/len @config.filters), 0] {
627
+ children: [{
628
+ entity: BoardView
629
+ event: REFETCH_FILTER
630
+ filters: @config.filters
631
+ look: @config.filterBarLook
632
+ type: filter-group
633
+ }]
634
+ direction: horizontal
635
+ gap: sm
636
+ type: stack
637
+ } {
638
+ children: []
639
+ gap: none
640
+ type: stack
641
+ }), {
540
642
  cols: @config.gridCols
541
643
  dndRoot: true
542
644
  entity: @entity.boards
@@ -636,7 +738,39 @@ orbital BoardOrbital {
636
738
  direction: horizontal
637
739
  gap: sm
638
740
  type: stack
639
- }, { type: divider }, {
741
+ }, { type: divider }, (if @config.bodySearch {
742
+ children: [{
743
+ className: "w-full max-w-md"
744
+ clearable: true
745
+ event: REFETCH_QUERY
746
+ placeholder: @config.searchPlaceholder
747
+ type: search-input
748
+ }]
749
+ className: px-card-md
750
+ direction: horizontal
751
+ gap: sm
752
+ type: stack
753
+ } {
754
+ children: []
755
+ gap: none
756
+ type: stack
757
+ }), (if [">", (array/len @config.filters), 0] {
758
+ children: [{
759
+ entity: BoardView
760
+ event: REFETCH_FILTER
761
+ filters: @config.filters
762
+ look: @config.filterBarLook
763
+ type: filter-group
764
+ }]
765
+ className: px-card-md
766
+ direction: horizontal
767
+ gap: sm
768
+ type: stack
769
+ } {
770
+ children: []
771
+ gap: none
772
+ type: stack
773
+ }), {
640
774
  className: "w-full pb-2"
641
775
  dndRoot: true
642
776
  entity: @entity.boards
@@ -359,7 +359,7 @@ orbital BrowseItemOrbital {
359
359
  @tier "internal"
360
360
  filters : [FilterSpec] = []
361
361
  @label "Which filters should appear above the list?"
362
- @description "Dropdown filter facets by field. Each fires a refetch when the user changes the selection. Empty = no filter bar."
362
+ @description "Dropdown filter facets by field, rendered as an embedded filter bar above the rows; each selection fires REFETCH_FILTER on this trait. Empty (the default) = no filter bar, which is the right setting whenever the page already composes std-filter beside this list and routes `XFilter.FILTER -> REFETCH_FILTER` — that is the wired owner and remains the primary path. Use this knob only for a standalone list with no page-level filter affordance, so the surface shows one filter bar instead of two."
363
363
  @synonyms "filter bar, faceted filters, field filters, column filters, dropdowns"
364
364
  @tier "presentation"
365
365
  maxInlineActions : number = 3
@@ -402,6 +402,11 @@ orbital BrowseItemOrbital {
402
402
  @description "Hint text inside the list search box (e.g. 'Search orders…')."
403
403
  @synonyms "search hint, placeholder, search box text, search label"
404
404
  @tier "presentation"
405
+ filterBarLook : "toolbar" | "chips" | "pills" | "popover-trigger" | "inline-column-header" = toolbar
406
+ @label "Filter bar look"
407
+ @description "Visual treatment for the embedded filter bar, mirroring std-filter's own enum. Only applies when `filters` is non-empty."
408
+ @synonyms "filter bar style, facet style, chips, pills"
409
+ @tier "presentation"
405
410
  viewPattern : pattern = data-grid
406
411
  @label "Render pattern"
407
412
  @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."
@@ -421,12 +426,43 @@ orbital BrowseItemOrbital {
421
426
  @synonyms "record actions, detail actions, pane actions"
422
427
  @tier "presentation"
423
428
  bodyContent : render-ui = {
424
- children: [@trait.DataGrid1]
429
+ children: [(if @config.bodySearch {
430
+ children: [{
431
+ className: "w-full max-w-md"
432
+ clearable: true
433
+ event: REFETCH_QUERY
434
+ placeholder: @config.searchPlaceholder
435
+ type: search-input
436
+ }]
437
+ direction: horizontal
438
+ gap: sm
439
+ type: stack
440
+ } {
441
+ children: []
442
+ gap: none
443
+ type: stack
444
+ }), (if [">", (array/len @config.filters), 0] {
445
+ children: [{
446
+ entity: BrowseItem
447
+ event: REFETCH_FILTER
448
+ filters: @config.filters
449
+ look: @config.filterBarLook
450
+ type: filter-group
451
+ }]
452
+ direction: horizontal
453
+ gap: sm
454
+ type: stack
455
+ } {
456
+ children: []
457
+ gap: none
458
+ type: stack
459
+ }), @trait.DataGrid1]
425
460
  direction: vertical
461
+ gap: md
426
462
  type: stack
427
463
  }
428
464
  @label "Body content tree"
429
- @description "Render-ui SExpr rendered after rows load when browseLook = table (the default). Composes the canonical data-grid call wrapped in a stack. See denseBodyContent/feedBodyContent/galleryBodyContent for the other browseLook bodies; any may be overridden directly while inheriting the same trait, state machine, emits, and listens."
465
+ @description "Render-ui SExpr rendered after rows load when browseLook = table (the default). Toolbar (search when bodySearch, filter bar when filters is non-empty) above the canonical data-grid call. Until 2026-08-27 this was the only one of the six looks with no search affordance at all. See denseBodyContent/feedBodyContent/galleryBodyContent for the other browseLook bodies; any may be overridden directly while inheriting the same trait, state machine, emits, and listens."
430
466
  @tier "internal"
431
467
  denseBodyContent : render-ui = {
432
468
  children: [(if @config.bodySearch {
@@ -445,6 +481,22 @@ orbital BrowseItemOrbital {
445
481
  children: []
446
482
  gap: none
447
483
  type: stack
484
+ }), (if [">", (array/len @config.filters), 0] {
485
+ children: [{
486
+ entity: BrowseItem
487
+ event: REFETCH_FILTER
488
+ filters: @config.filters
489
+ look: @config.filterBarLook
490
+ type: filter-group
491
+ }]
492
+ className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
493
+ direction: horizontal
494
+ gap: sm
495
+ type: stack
496
+ } {
497
+ children: []
498
+ gap: none
499
+ type: stack
448
500
  }), {
449
501
  children: [@trait.DenseTableView]
450
502
  className: overflow-x-auto
@@ -478,6 +530,22 @@ orbital BrowseItemOrbital {
478
530
  children: []
479
531
  gap: none
480
532
  type: stack
533
+ }), (if [">", (array/len @config.filters), 0] {
534
+ children: [{
535
+ entity: BrowseItem
536
+ event: REFETCH_FILTER
537
+ filters: @config.filters
538
+ look: @config.filterBarLook
539
+ type: filter-group
540
+ }]
541
+ className: w-full
542
+ direction: horizontal
543
+ gap: sm
544
+ type: stack
545
+ } {
546
+ children: []
547
+ gap: none
548
+ type: stack
481
549
  }), {
482
550
  entity: ?data
483
551
  fields: []
@@ -579,6 +647,22 @@ orbital BrowseItemOrbital {
579
647
  children: []
580
648
  gap: none
581
649
  type: stack
650
+ }), (if [">", (array/len @config.filters), 0] {
651
+ children: [{
652
+ entity: BrowseItem
653
+ event: REFETCH_FILTER
654
+ filters: @config.filters
655
+ look: @config.filterBarLook
656
+ type: filter-group
657
+ }]
658
+ className: w-full
659
+ direction: horizontal
660
+ gap: sm
661
+ type: stack
662
+ } {
663
+ children: []
664
+ gap: none
665
+ type: stack
582
666
  }), {
583
667
  className: w-full
584
668
  entity: ?data
@@ -615,6 +699,22 @@ orbital BrowseItemOrbital {
615
699
  children: []
616
700
  gap: none
617
701
  type: stack
702
+ }), (if [">", (array/len @config.filters), 0] {
703
+ children: [{
704
+ entity: BrowseItem
705
+ event: REFETCH_FILTER
706
+ filters: @config.filters
707
+ look: @config.filterBarLook
708
+ type: filter-group
709
+ }]
710
+ className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
711
+ direction: horizontal
712
+ gap: sm
713
+ type: stack
714
+ } {
715
+ children: []
716
+ gap: none
717
+ type: stack
618
718
  }), {
619
719
  children: [{
620
720
  entity: ?data
@@ -638,7 +738,7 @@ orbital BrowseItemOrbital {
638
738
  @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."
639
739
  @tier "internal"
640
740
  masterDetailBodyContent : render-ui = {
641
- children: [{
741
+ children: [(if @config.bodySearch {
642
742
  children: [{
643
743
  className: "w-full max-w-md"
644
744
  clearable: true
@@ -650,7 +750,27 @@ orbital BrowseItemOrbital {
650
750
  direction: horizontal
651
751
  gap: sm
652
752
  type: stack
653
- }, {
753
+ } {
754
+ children: []
755
+ gap: none
756
+ type: stack
757
+ }), (if [">", (array/len @config.filters), 0] {
758
+ children: [{
759
+ entity: BrowseItem
760
+ event: REFETCH_FILTER
761
+ filters: @config.filters
762
+ look: @config.filterBarLook
763
+ type: filter-group
764
+ }]
765
+ className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
766
+ direction: horizontal
767
+ gap: sm
768
+ type: stack
769
+ } {
770
+ children: []
771
+ gap: none
772
+ type: stack
773
+ }), {
654
774
  detail: {
655
775
  children: []
656
776
  gap: none
@@ -678,10 +798,10 @@ orbital BrowseItemOrbital {
678
798
  type: stack
679
799
  }
680
800
  @label "Master-detail look body (no selection)"
681
- @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)."
801
+ @description "Render-ui SExpr rendered after rows load when browseLook = master-detail: record-work split view — toolbar, dense list left, empty-state pane right until a row is opened. Opening a row renders masterDetailRecordBodyContent. The toolbar honours bodySearch and filters like every other look; it used to render its search box unconditionally, which put a second search box on any page that already composed std-search (observed in std-crm ContactOrbital)."
682
802
  @tier "internal"
683
803
  masterDetailRecordBodyContent : render-ui = {
684
- children: [{
804
+ children: [(if @config.bodySearch {
685
805
  children: [{
686
806
  className: "w-full max-w-md"
687
807
  clearable: true
@@ -693,7 +813,27 @@ orbital BrowseItemOrbital {
693
813
  direction: horizontal
694
814
  gap: sm
695
815
  type: stack
696
- }, {
816
+ } {
817
+ children: []
818
+ gap: none
819
+ type: stack
820
+ }), (if [">", (array/len @config.filters), 0] {
821
+ children: [{
822
+ entity: BrowseItem
823
+ event: REFETCH_FILTER
824
+ filters: @config.filters
825
+ look: @config.filterBarLook
826
+ type: filter-group
827
+ }]
828
+ className: "px-card-md py-card-sm border-b border-[var(--color-border)]"
829
+ direction: horizontal
830
+ gap: sm
831
+ type: stack
832
+ } {
833
+ children: []
834
+ gap: none
835
+ type: stack
836
+ }), {
697
837
  detail: {
698
838
  actions: @config.detailActions
699
839
  fieldNames: @config.detailFields
@@ -1,6 +1,7 @@
1
1
  app std-calendar "1.4.0"
2
2
  "std-calendar as a Function — owns the fetch + SELECT_DAY state machine; default bodyContent renders the month-grid + upcoming-events tree. `config.calendarLook` switches the body between the default month-grid ('month'), a day-grouped agenda list ('agenda-list'), and a 7-column week timeline ('week-timeline') while inheriting the same trait, state machine, emits, and listens."
3
3
  type ItemAction = { label : string!, event : string, navigatesTo : string, variant : primary | secondary | ghost | danger, icon : string }
4
+ type FilterSpec = { field : string!, label : string, options : [string] }
4
5
  orbital CalendarEventOrbital {
5
6
  type CalendarEventLoaded = Event { data : [CalendarEvent] } "Fired when CalendarEvent finishes loading; payload.data holds the events"
6
7
 
@@ -101,6 +102,20 @@ orbital CalendarEventOrbital {
101
102
  (fetch CalendarEvent {
102
103
  emit: { failure: CalendarEventLoadFailed, success: CalendarEventLoaded }
103
104
  })
105
+ ;; Same no-render-ui shape as SELECT_DAY/CALENDAR_*_WEEK above: transition
106
+ ;; to `loading` without repainting, so the current grid/agenda/timeline
107
+ ;; stays mounted until CalendarEventLoaded's own render-ui fires — a
108
+ ;; repaint here would flicker the toolbar's search-input every keystroke.
109
+ REFETCH_QUERY -> loading
110
+ (fetch CalendarEvent {
111
+ emit: { failure: CalendarEventLoadFailed, success: CalendarEventLoaded }
112
+ filter: (or ["=", ?searchTerm, ""] (str/includes (object/get @entity name) ?searchTerm))
113
+ })
114
+ REFETCH_FILTER -> loading
115
+ (fetch CalendarEvent {
116
+ emit: { failure: CalendarEventLoadFailed, success: CalendarEventLoaded }
117
+ filter: (or ["=", ?value, ""] ["=", (object/get @entity ?field), ?value])
118
+ })
104
119
  }
105
120
 
106
121
  state error {
@@ -162,6 +177,19 @@ orbital CalendarEventOrbital {
162
177
  @description "Move the week timeline one week later."
163
178
  @synonyms "next week, forward a week, later week, following week"
164
179
  @tier "domain"
180
+ REFETCH_QUERY {
181
+ searchTerm : string!
182
+ }
183
+ @description "Triggers a data refresh based on the current query."
184
+ @synonyms "refresh, reload, update, re-query"
185
+ @tier "domain"
186
+ REFETCH_FILTER {
187
+ field : string!
188
+ value : string!
189
+ }
190
+ @description "Triggers a data refresh based on current filter criteria."
191
+ @synonyms "refresh, filter, update, reload"
192
+ @tier "domain"
165
193
  }
166
194
 
167
195
  config {
@@ -213,6 +241,26 @@ orbital CalendarEventOrbital {
213
241
  @description "Row field the agenda list groups its day sections by. Rebound hosts point this at their own timestamp column (e.g. startTime, scheduledAt)."
214
242
  @synonyms "group field, section field, day field"
215
243
  @tier "presentation"
244
+ bodySearch : boolean = false
245
+ @label "Show the calendar's own search box?"
246
+ @description "Embeds a title search box in the toolbar above the calendar body (all three calendarLook trees). Defaults false: std-calendar has never had a search box, so an untouched consumer's surface is unchanged; the primary path is composing std-search beside this atom and wiring XSearch.SEARCH -> REFETCH_QUERY. Set true only for a standalone calendar with no page-level search affordance."
247
+ @synonyms "inline search, calendar search, built-in search, show search, duplicate search"
248
+ @tier "presentation"
249
+ searchPlaceholder : string = "Search…"
250
+ @label "Search box placeholder text"
251
+ @description "Hint text inside the calendar's search box (e.g. 'Search events…')."
252
+ @synonyms "search hint, placeholder, search box text, search label"
253
+ @tier "presentation"
254
+ filters : [FilterSpec] = []
255
+ @label "Which filters should appear above the calendar?"
256
+ @description "Dropdown filter facets by field, rendered as an embedded filter bar above the calendar body (all three calendarLook trees); each selection fires REFETCH_FILTER on this trait. Empty (the default) = no filter bar, which is the right setting whenever the page already composes std-filter beside this calendar and routes XFilter.FILTER -> REFETCH_FILTER — that is the wired owner and remains the primary path. Use this knob only for a standalone calendar with no page-level filter affordance."
257
+ @synonyms "filter bar, faceted filters, field filters, dropdowns"
258
+ @tier "presentation"
259
+ filterBarLook : "toolbar" | "chips" | "pills" | "popover-trigger" | "inline-column-header" = toolbar
260
+ @label "Filter bar look"
261
+ @description "Visual treatment for the embedded filter bar, mirroring std-filter's own enum. Only applies when `filters` is non-empty."
262
+ @synonyms "filter bar style, facet style, chips, pills"
263
+ @tier "presentation"
216
264
  bodyContent : render-ui = {
217
265
  children: [{
218
266
  align: center
@@ -235,7 +283,37 @@ orbital CalendarEventOrbital {
235
283
  gap: md
236
284
  justify: between
237
285
  type: stack
238
- }, { type: divider }, {
286
+ }, (if @config.bodySearch {
287
+ children: [{
288
+ className: "w-full max-w-md"
289
+ clearable: true
290
+ event: REFETCH_QUERY
291
+ placeholder: @config.searchPlaceholder
292
+ type: search-input
293
+ }]
294
+ direction: horizontal
295
+ gap: sm
296
+ type: stack
297
+ } {
298
+ children: []
299
+ gap: none
300
+ type: stack
301
+ }), (if [">", (array/len @config.filters), 0] {
302
+ children: [{
303
+ entity: CalendarEvent
304
+ event: REFETCH_FILTER
305
+ filters: @config.filters
306
+ look: @config.filterBarLook
307
+ type: filter-group
308
+ }]
309
+ direction: horizontal
310
+ gap: sm
311
+ type: stack
312
+ } {
313
+ children: []
314
+ gap: none
315
+ type: stack
316
+ }), { type: divider }, {
239
317
  events: ?data
240
318
  onEventClick: @config.onEventClick
241
319
  renderItem: (fn item {
@@ -307,7 +385,37 @@ orbital CalendarEventOrbital {
307
385
  gap: md
308
386
  justify: between
309
387
  type: stack
310
- }, { type: divider }, {
388
+ }, (if @config.bodySearch {
389
+ children: [{
390
+ className: "w-full max-w-md"
391
+ clearable: true
392
+ event: REFETCH_QUERY
393
+ placeholder: @config.searchPlaceholder
394
+ type: search-input
395
+ }]
396
+ direction: horizontal
397
+ gap: sm
398
+ type: stack
399
+ } {
400
+ children: []
401
+ gap: none
402
+ type: stack
403
+ }), (if [">", (array/len @config.filters), 0] {
404
+ children: [{
405
+ entity: CalendarEvent
406
+ event: REFETCH_FILTER
407
+ filters: @config.filters
408
+ look: @config.filterBarLook
409
+ type: filter-group
410
+ }]
411
+ direction: horizontal
412
+ gap: sm
413
+ type: stack
414
+ } {
415
+ children: []
416
+ gap: none
417
+ type: stack
418
+ }), { type: divider }, {
311
419
  entity: ?data
312
420
  fields: []
313
421
  gap: md
@@ -429,7 +537,37 @@ orbital CalendarEventOrbital {
429
537
  gap: md
430
538
  justify: between
431
539
  type: stack
432
- }, { type: divider }, {
540
+ }, (if @config.bodySearch {
541
+ children: [{
542
+ className: "w-full max-w-md"
543
+ clearable: true
544
+ event: REFETCH_QUERY
545
+ placeholder: @config.searchPlaceholder
546
+ type: search-input
547
+ }]
548
+ direction: horizontal
549
+ gap: sm
550
+ type: stack
551
+ } {
552
+ children: []
553
+ gap: none
554
+ type: stack
555
+ }), (if [">", (array/len @config.filters), 0] {
556
+ children: [{
557
+ entity: CalendarEvent
558
+ event: REFETCH_FILTER
559
+ filters: @config.filters
560
+ look: @config.filterBarLook
561
+ type: filter-group
562
+ }]
563
+ direction: horizontal
564
+ gap: sm
565
+ type: stack
566
+ } {
567
+ children: []
568
+ gap: none
569
+ type: stack
570
+ }), { type: divider }, {
433
571
  className: w-full
434
572
  dayWindow: 7
435
573
  events: ?data