@almadar/std 16.183.0 → 16.185.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 (44) hide show
  1. package/behaviors/lolo/ui/core/.hand-authored.json +4 -0
  2. package/behaviors/lolo/ui/core/atoms/std-browse.lolo +11 -0
  3. package/behaviors/lolo/ui/core/atoms/std-related.lolo +86 -9
  4. package/behaviors/lolo/ui/core/atoms/std-rich-editor.lolo +368 -1
  5. package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +62 -2
  6. package/behaviors/lolo/ui/core/atoms/std-thread.lolo +43 -2
  7. package/behaviors/lolo/ui/core/atoms/std-tree.lolo +247 -0
  8. package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +110 -2
  9. package/behaviors/lolo/ui/core/molecules/ui-file-tree.lolo +18 -2
  10. package/behaviors/lolo/ui/core/organisms/ui-detail-panel.lolo +12 -2
  11. package/behaviors/lolo/ui/core/organisms/ui-form-section.lolo +2 -2
  12. package/behaviors/lolo/ui/core/organisms/ui-form.lolo +2 -2
  13. package/behaviors/lolo/ui/game/atoms/std-tutorial-autodemo.lolo +136 -0
  14. package/behaviors/lolo/ui/game/atoms/std-tutorial-overlay.lolo +41 -8
  15. package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +3 -3
  16. package/behaviors/registry/ui/core/atoms/std-browse.orb +17 -0
  17. package/behaviors/registry/ui/core/atoms/std-related.orb +256 -3
  18. package/behaviors/registry/ui/core/atoms/std-rich-editor.orb +898 -1
  19. package/behaviors/registry/ui/core/atoms/std-tag-taxonomy.orb +240 -8
  20. package/behaviors/registry/ui/core/atoms/std-thread.orb +254 -4
  21. package/behaviors/registry/ui/core/atoms/std-tree.orb +656 -0
  22. package/behaviors/registry/ui/core/atoms/std-version-history.orb +479 -6
  23. package/behaviors/registry/ui/core/molecules/ui-file-tree.orb +43 -19
  24. package/behaviors/registry/ui/core/organisms/ui-detail-panel.orb +37 -3
  25. package/behaviors/registry/ui/core/organisms/ui-form-section.orb +8 -2
  26. package/behaviors/registry/ui/core/organisms/ui-form.orb +8 -2
  27. package/behaviors/registry/ui/game/atoms/std-tutorial-autodemo.orb +732 -0
  28. package/behaviors/registry/ui/game/atoms/std-tutorial-overlay.orb +305 -153
  29. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +26 -0
  30. package/dist/behaviors/exports-reader.js +5132 -2398
  31. package/dist/behaviors/functions/index.d.ts +261 -44
  32. package/dist/behaviors/functions/index.js +5118 -2396
  33. package/dist/behaviors/index.d.ts +1 -1
  34. package/dist/behaviors/index.js +5133 -2399
  35. package/dist/behaviors/query.js +5132 -2398
  36. package/dist/behaviors-embeddings.hash.json +3 -3
  37. package/dist/behaviors-embeddings.json +310 -302
  38. package/dist/behaviors-registry.json +820 -35
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +5133 -2399
  41. package/dist/knob-embeddings.hash.json +3 -3
  42. package/dist/knob-embeddings.json +1 -1
  43. package/dist/registry/factory-signatures.json +1 -1
  44. package/package.json +2 -2
@@ -151,6 +151,10 @@
151
151
  "signature": "a923f3042436663f",
152
152
  "board": "packages/almadar-std/behaviors/lolo/ui/core/atoms/std-thread.lolo"
153
153
  },
154
+ "std-tree": {
155
+ "signature": "",
156
+ "board": "packages/almadar-std/behaviors/lolo/ui/core/atoms/std-tree.lolo"
157
+ },
154
158
  "std-version-history": {
155
159
  "signature": "1f1eb4fcabd6cead",
156
160
  "board": "packages/almadar-std/behaviors/lolo/ui/core/atoms/std-version-history.lolo"
@@ -84,6 +84,7 @@ orbital BrowseItemOrbital {
84
84
  (fetch BrowseItem {
85
85
  emit: { failure: BrowseItemLoadFailed, success: BrowseItemLoaded }
86
86
  filter: (and (or ["=", @config.scopeField, ""] ["=", (object/get @entity @config.scopeField), @user.id]) (or ["=", @config.initialFilterField, ""] ["=", (object/get @entity @config.initialFilterField), @config.initialFilterValue]))
87
+ include: @config.include
87
88
  limit: @config.pageSize
88
89
  offset: 0
89
90
  })
@@ -113,6 +114,7 @@ orbital BrowseItemOrbital {
113
114
  (fetch BrowseItem {
114
115
  emit: { failure: BrowseItemLoadFailed, success: BrowseItemLoaded }
115
116
  filter: (and (or ["=", @config.scopeField, ""] ["=", (object/get @entity @config.scopeField), @user.id]) (or ["=", @config.initialFilterField, ""] ["=", (object/get @entity @config.initialFilterField), @config.initialFilterValue]))
117
+ include: @config.include
116
118
  limit: @config.pageSize
117
119
  offset: 0
118
120
  })
@@ -126,6 +128,7 @@ orbital BrowseItemOrbital {
126
128
  (fetch BrowseItem {
127
129
  emit: { failure: BrowseItemLoadFailed, success: BrowseItemLoaded }
128
130
  filter: (and (or ["=", @config.scopeField, ""] ["=", (object/get @entity @config.scopeField), @user.id]) (or ["=", ?searchTerm, ""] (str/includes (object/get @entity name) ?searchTerm)))
131
+ include: @config.include
129
132
  limit: @config.pageSize
130
133
  offset: 0
131
134
  })
@@ -133,6 +136,7 @@ orbital BrowseItemOrbital {
133
136
  (fetch BrowseItem {
134
137
  emit: { failure: BrowseItemLoadFailed, success: BrowseItemLoaded }
135
138
  filter: (and (or ["=", @config.scopeField, ""] ["=", (object/get @entity @config.scopeField), @user.id]) (or ["=", ?value, ""] ["=", (object/get @entity ?field), ?value]))
139
+ include: @config.include
136
140
  limit: @config.pageSize
137
141
  offset: 0
138
142
  })
@@ -140,6 +144,7 @@ orbital BrowseItemOrbital {
140
144
  (fetch BrowseItem {
141
145
  emit: { failure: BrowseItemLoadFailed, success: BrowseItemLoaded }
142
146
  filter: (or ["=", @config.scopeField, ""] ["=", (object/get @entity @config.scopeField), @user.id])
147
+ include: @config.include
143
148
  limit: @config.pageSize
144
149
  offset: ["*", ["-", ?page, 1], @config.pageSize]
145
150
  })
@@ -170,6 +175,7 @@ orbital BrowseItemOrbital {
170
175
  (fetch BrowseItem {
171
176
  emit: { failure: BrowseItemLoadFailed, success: BrowseItemLoaded }
172
177
  filter: (and (or ["=", @config.scopeField, ""] ["=", (object/get @entity @config.scopeField), @user.id]) (or ["=", @config.initialFilterField, ""] ["=", (object/get @entity @config.initialFilterField), @config.initialFilterValue]))
178
+ include: @config.include
173
179
  limit: @config.pageSize
174
180
  offset: 0
175
181
  })
@@ -346,6 +352,11 @@ orbital BrowseItemOrbital {
346
352
  @description "Rows visible per page before the pagination control appears."
347
353
  @synonyms "visible rows, items per page, display limit, show per page"
348
354
  @tier "presentation"
355
+ include : [string] = []
356
+ @label "Which relation fields to hydrate?"
357
+ @description "Entity relation field names to hydrate on fetch (e.g. a `tag : Category` field named `tag`), so grid columns can use dot-paths like tag.name. Empty = no hydration, the default."
358
+ @synonyms "relation hydration, populate relations, joined fields, hydrate relations"
359
+ @tier "internal"
349
360
  filters : [FilterSpec] = []
350
361
  @label "Which filters should appear above the list?"
351
362
  @description "Dropdown filter facets by field. Each fires a refetch when the user changes the selection. Empty = no filter bar."
@@ -1,18 +1,24 @@
1
- app std-related "1.0.0"
1
+ app std-related "1.1.0"
2
2
  "std-related as a Function"
3
3
  ;;
4
- ;; Relation-rendering atom. Assumes the linked entity's relation field has
5
- ;; already been hydrated by an upstream fetch with include; this atom only
6
- ;; owns the rendering of the related children.
4
+ ;; Relation-rendering atom. INIT loads the full RelatedItem collection
5
+ ;; unfiltered (unchanged default). A consumer that wants the list scoped to
6
+ ;; one parent document sets `relationField` (the RelatedItem column that
7
+ ;; holds the parent key) and redirects its own "document opened" signal to
8
+ ;; SHOW_RELATED { key } in a `listens` override — the atom then fetches only
9
+ ;; rows whose relationField equals key. Each row is clickable: it emits
10
+ ;; SELECT_RELATED { id, name } for a parent molecule (e.g.
11
+ ;; std-related-master-detail) to drive a detail panel.
7
12
  ;;
8
13
  ;; Paired with std-browse via @trait.X this gives a one-line master-detail
9
- ;; layout: hstack [ @trait.ParentList, @trait.ParentRelated ]. The molecule
10
- ;; std-related-master-detail is the canonical showcase.
14
+ ;; layout: hstack [ @trait.ParentList, @trait.ParentRelated ].
11
15
  orbital RelatedItemOrbital {
12
16
  type RelatedItemLoaded = Event { data : [RelatedItem] } "Fired when RelatedItem finishes loading; payload.data holds the list"
13
17
 
14
18
  type RelatedItemLoadFailed = Event { error : string, code : string } "Fired when RelatedItem fails to load"
15
19
 
20
+ type ShowRelated = Event { key : string! } "Fired to scope the related fetch to rows whose relationField equals key"
21
+
16
22
  entity RelatedItem [persistent: relateditems] {
17
23
  id : string!
18
24
  name : string @description "The identifier or designation for the related item." @synonyms "title, label, identifier"
@@ -39,12 +45,30 @@ orbital RelatedItemOrbital {
39
45
  gap: sm
40
46
  type: stack
41
47
  })
48
+ SHOW_RELATED -> loading
49
+ (fetch RelatedItem {
50
+ emit: { failure: RelatedItemLoadFailed, success: RelatedItemLoaded }
51
+ filter: (or ["=", @config.relationField, ""] ["=", (object/get @entity @config.relationField), ?key])
52
+ })
53
+ (render-ui main {
54
+ align: center
55
+ children: [{ type: spinner }, {
56
+ color: muted
57
+ content: "Loading related…"
58
+ type: typography
59
+ variant: caption
60
+ }]
61
+ className: py-8
62
+ direction: vertical
63
+ gap: sm
64
+ type: stack
65
+ })
42
66
  RelatedItemLoaded -> loaded
43
67
  (render-ui main {
44
68
  children: [{
45
69
  align: center
46
70
  children: [{ name: link, type: icon }, {
47
- content: Related
71
+ content: @config.title
48
72
  type: typography
49
73
  variant: h3
50
74
  }]
@@ -56,13 +80,23 @@ orbital RelatedItemOrbital {
56
80
  fields: []
57
81
  look: @config.tableLook
58
82
  renderItem: (fn item {
83
+ align: center
59
84
  children: [{
60
85
  content: @item.name
61
86
  type: typography
62
87
  variant: body
88
+ }, {
89
+ action: SELECT_RELATED
90
+ actionPayload: { id: @item.id, name: @item.name }
91
+ icon: chevron-right
92
+ label: Open
93
+ size: sm
94
+ type: button
95
+ variant: ghost
63
96
  }]
64
- direction: vertical
97
+ direction: horizontal
65
98
  gap: xs
99
+ justify: between
66
100
  type: stack
67
101
  })
68
102
  type: data-list
@@ -107,6 +141,14 @@ orbital RelatedItemOrbital {
107
141
  emit: { failure: RelatedItemLoadFailed, success: RelatedItemLoaded }
108
142
  })
109
143
  (render-ui main { type: spinner })
144
+ SELECT_RELATED -> loaded
145
+ (set @entity.id ?id)
146
+ SHOW_RELATED -> loading
147
+ (fetch RelatedItem {
148
+ emit: { failure: RelatedItemLoadFailed, success: RelatedItemLoaded }
149
+ filter: (or ["=", @config.relationField, ""] ["=", (object/get @entity @config.relationField), ?key])
150
+ })
151
+ (render-ui main { type: spinner })
110
152
  }
111
153
 
112
154
  state error {
@@ -122,6 +164,12 @@ orbital RelatedItemOrbital {
122
164
  emit: { failure: RelatedItemLoadFailed, success: RelatedItemLoaded }
123
165
  })
124
166
  (render-ui main { type: spinner })
167
+ SHOW_RELATED -> loading
168
+ (fetch RelatedItem {
169
+ emit: { failure: RelatedItemLoadFailed, success: RelatedItemLoaded }
170
+ filter: (or ["=", @config.relationField, ""] ["=", (object/get @entity @config.relationField), ?key])
171
+ })
172
+ (render-ui main { type: spinner })
125
173
  }
126
174
 
127
175
  emits {
@@ -133,9 +181,23 @@ orbital RelatedItemOrbital {
133
181
  @description "Indicates failure to load related items."
134
182
  @synonyms "error, failure, problem, unsuccessful"
135
183
  @tier "internal"
184
+ SELECT_RELATED -> external {
185
+ id : string!
186
+ name : string
187
+ }
188
+ @description "Signals a related item was selected to drive a detail view."
189
+ @synonyms "select, choose, open, view"
190
+ @tier "domain"
136
191
  }
137
192
 
138
193
  listens {
194
+ SELECT_RELATED {
195
+ id : string!
196
+ name : string
197
+ }
198
+ @description "A related item was selected; the atom records it as the active row."
199
+ @synonyms "select, choose, open, view"
200
+ @tier "domain"
139
201
  RelatedItemLoadFailed {
140
202
  error : string
141
203
  code : string
@@ -143,9 +205,23 @@ orbital RelatedItemOrbital {
143
205
  @description "Indicates failure to load related items."
144
206
  @synonyms "error, failure, problem, unsuccessful"
145
207
  @tier "internal"
208
+ SHOW_RELATED {
209
+ key : string!
210
+ }
211
+ @description "Scopes the related fetch to rows whose relationField equals key."
212
+ @synonyms "scope, filter, focus, target"
213
+ @tier "domain"
146
214
  }
147
215
 
148
216
  config {
217
+ title : string = Related
218
+ @label "Section title"
219
+ @description "Heading shown above the related list."
220
+ @tier "presentation"
221
+ relationField : string = ""
222
+ @label "Relation field"
223
+ @description "RelatedItem field compared against SHOW_RELATED's key to scope the fetch to one parent document; empty renders the full unfiltered collection (default)."
224
+ @tier "domain"
149
225
  tableLook : "dense" | "spacious" | "striped" | "borderless" | "card-rows" = dense
150
226
  @label "Table look"
151
227
  @description "Layer 2 visual treatment for the data table rendered by this atom."
@@ -153,6 +229,7 @@ orbital RelatedItemOrbital {
153
229
  }
154
230
  }
155
231
 
156
- ;; consumer: parent molecule (e.g. std-related-master-detail) listens for SELECT_RELATED to drive a detail panel
232
+ ;; consumer: parent molecule (e.g. std-related-master-detail) listens for SELECT_RELATED to drive a detail panel; a
233
+ ;; per-document scope forwards its "opened" event to SHOW_RELATED { key } via a listens override on relationField
157
234
  page "/related" as Related -> RelatedItemList
158
235
  }
@@ -1,5 +1,5 @@
1
1
  app std-rich-editor "1.0.0"
2
- "std-rich-editor — wiki/CMS Notion-style block editor. Browses documents in `browsing`; opens the rich-block-editor on a selected row; persists block edits + publish status."
2
+ "std-rich-editor — wiki/CMS Notion-style block editor. Browses documents in `browsing`; opens the rich-block-editor on a selected row; persists block edits + publish status. `BlockEdit` is a rebindable single-row IN-PLACE body editor: an organism embeds it where the content belongs, rebinds it to its own entity (WikiPage, Note, …), hands it the row's id + blocksJson via OPEN, and routes the emitted BLOCKS_SAVED into its own persistor — this atom never fetches or persists. The read-only body is itself the edit affordance; an empty row paints an empty state that starts the first block; and OPEN_DRAFT opens straight into the authoring editor for a row that was just minted blank (document-first creation)."
3
3
  ;; @capabilities "notes app, note-taking, journal, personal journal entries, document editor, write and organize notes"
4
4
  type BlockSpec = { type : string!, content : string, id : string }
5
5
  type ObjectSpec = { id : string, label : string, value : string }
@@ -419,5 +419,372 @@ orbital DocumentOrbital {
419
419
  }
420
420
  }
421
421
 
422
+ ;; In-place body editor for one row. The composing organism embeds this
423
+ ;; trait where the content belongs (`<trait.X />` inside its detail
424
+ ;; render) and feeds it the loaded row through OPEN; the body paints
425
+ ;; read-only AND is its own edit affordance — clicking it enters editing
426
+ ;; with the authoring toolbar, and a row with no blocks yet paints an
427
+ ;; empty state whose button starts the first one. OPEN_DRAFT is the
428
+ ;; document-first entry: same payload, but it lands in the editor. SAVE
429
+ ;; emits BLOCKS_SAVED for the host's persistor; this atom never fetches or
430
+ ;; persists.
431
+ trait BlockEdit -> @rebindable Document [interaction, instance] {
432
+ initial: idle
433
+
434
+ state idle {
435
+ INIT -> idle
436
+ OPEN -> viewing when [">", (array/len ?blocksJson), 0]
437
+ (set @entity.id ?id)
438
+ (set @entity.blocksJson ?blocksJson)
439
+ (render-ui main {
440
+ children: [{
441
+ children: [{
442
+ action: EDIT_BLOCKS
443
+ icon: edit
444
+ label: @config.editLabel
445
+ type: button
446
+ variant: secondary
447
+ }]
448
+ direction: horizontal
449
+ gap: sm
450
+ justify: end
451
+ type: stack
452
+ }, {
453
+ action: EDIT_BLOCKS
454
+ children: [{
455
+ initialBlocks: @entity.blocksJson
456
+ readOnly: true
457
+ type: rich-block-editor
458
+ }]
459
+ className: "cursor-text rounded-md transition-colors hover:bg-muted/40"
460
+ type: box
461
+ }]
462
+ direction: vertical
463
+ gap: sm
464
+ type: stack
465
+ })
466
+ OPEN -> viewing when ["=", (array/len ?blocksJson), 0]
467
+ (set @entity.id ?id)
468
+ (set @entity.blocksJson ?blocksJson)
469
+ (render-ui main {
470
+ actionEvent: EDIT_BLOCKS
471
+ actionLabel: @config.emptyActionLabel
472
+ description: @config.emptyDescription
473
+ icon: file-text
474
+ title: @config.emptyTitle
475
+ type: empty-state
476
+ })
477
+ ;; Document-first entry: the row was just minted as a blank draft, so
478
+ ;; the body opens ALREADY authoring instead of painting an empty state
479
+ ;; the author has to click through. A distinct trigger, not a flag on
480
+ ;; OPEN — the guard surface stays decidable.
481
+ OPEN_DRAFT -> editing
482
+ (set @entity.id ?id)
483
+ (set @entity.blocksJson ?blocksJson)
484
+ (render-ui main {
485
+ children: [{
486
+ align: center
487
+ children: [{ name: file-text, type: icon }, {
488
+ content: @config.title
489
+ type: typography
490
+ variant: h4
491
+ }]
492
+ direction: horizontal
493
+ gap: sm
494
+ type: stack
495
+ }, {
496
+ changeEvent: BLOCKS_CHANGE
497
+ initialBlocks: @entity.blocksJson
498
+ placeholder: @config.placeholder
499
+ showToolbar: true
500
+ type: rich-block-editor
501
+ }, {
502
+ children: [{
503
+ action: SAVE_BLOCKS
504
+ label: Save
505
+ type: button
506
+ variant: primary
507
+ }, {
508
+ action: CLOSE
509
+ label: Cancel
510
+ type: button
511
+ variant: ghost
512
+ }]
513
+ direction: horizontal
514
+ gap: sm
515
+ justify: end
516
+ type: stack
517
+ }]
518
+ direction: vertical
519
+ gap: md
520
+ type: stack
521
+ })
522
+ ;; self-loop on the external emit so the validator sees an in-atom
523
+ ;; listener — the real consumer is the composing organism's persistor.
524
+ BLOCKS_SAVED -> idle
525
+ }
526
+
527
+ state viewing {
528
+ OPEN_DRAFT -> editing
529
+ (set @entity.id ?id)
530
+ (set @entity.blocksJson ?blocksJson)
531
+ (render-ui main {
532
+ children: [{
533
+ align: center
534
+ children: [{ name: file-text, type: icon }, {
535
+ content: @config.title
536
+ type: typography
537
+ variant: h4
538
+ }]
539
+ direction: horizontal
540
+ gap: sm
541
+ type: stack
542
+ }, {
543
+ changeEvent: BLOCKS_CHANGE
544
+ initialBlocks: @entity.blocksJson
545
+ placeholder: @config.placeholder
546
+ showToolbar: true
547
+ type: rich-block-editor
548
+ }, {
549
+ children: [{
550
+ action: SAVE_BLOCKS
551
+ label: Save
552
+ type: button
553
+ variant: primary
554
+ }, {
555
+ action: CLOSE
556
+ label: Cancel
557
+ type: button
558
+ variant: ghost
559
+ }]
560
+ direction: horizontal
561
+ gap: sm
562
+ justify: end
563
+ type: stack
564
+ }]
565
+ direction: vertical
566
+ gap: md
567
+ type: stack
568
+ })
569
+ OPEN -> viewing when [">", (array/len ?blocksJson), 0]
570
+ (set @entity.id ?id)
571
+ (set @entity.blocksJson ?blocksJson)
572
+ (render-ui main {
573
+ children: [{
574
+ children: [{
575
+ action: EDIT_BLOCKS
576
+ icon: edit
577
+ label: @config.editLabel
578
+ type: button
579
+ variant: secondary
580
+ }]
581
+ direction: horizontal
582
+ gap: sm
583
+ justify: end
584
+ type: stack
585
+ }, {
586
+ action: EDIT_BLOCKS
587
+ children: [{
588
+ initialBlocks: @entity.blocksJson
589
+ readOnly: true
590
+ type: rich-block-editor
591
+ }]
592
+ className: "cursor-text rounded-md transition-colors hover:bg-muted/40"
593
+ type: box
594
+ }]
595
+ direction: vertical
596
+ gap: sm
597
+ type: stack
598
+ })
599
+ OPEN -> viewing when ["=", (array/len ?blocksJson), 0]
600
+ (set @entity.id ?id)
601
+ (set @entity.blocksJson ?blocksJson)
602
+ (render-ui main {
603
+ actionEvent: EDIT_BLOCKS
604
+ actionLabel: @config.emptyActionLabel
605
+ description: @config.emptyDescription
606
+ icon: file-text
607
+ title: @config.emptyTitle
608
+ type: empty-state
609
+ })
610
+ EDIT_BLOCKS -> editing
611
+ (render-ui main {
612
+ children: [{
613
+ align: center
614
+ children: [{ name: file-text, type: icon }, {
615
+ content: @config.title
616
+ type: typography
617
+ variant: h4
618
+ }]
619
+ direction: horizontal
620
+ gap: sm
621
+ type: stack
622
+ }, {
623
+ changeEvent: BLOCKS_CHANGE
624
+ initialBlocks: @entity.blocksJson
625
+ placeholder: @config.placeholder
626
+ showToolbar: true
627
+ type: rich-block-editor
628
+ }, {
629
+ children: [{
630
+ action: SAVE_BLOCKS
631
+ label: Save
632
+ type: button
633
+ variant: primary
634
+ }, {
635
+ action: CLOSE
636
+ label: Cancel
637
+ type: button
638
+ variant: ghost
639
+ }]
640
+ direction: horizontal
641
+ gap: sm
642
+ justify: end
643
+ type: stack
644
+ }]
645
+ direction: vertical
646
+ gap: md
647
+ type: stack
648
+ })
649
+ }
650
+
651
+ state editing {
652
+ BLOCKS_CHANGE -> editing
653
+ (set @entity.blocksJson ?blocks)
654
+ SAVE_BLOCKS -> viewing
655
+ (emit BLOCKS_SAVED { id: @entity.id, blocksJson: @entity.blocksJson })
656
+ (render-ui main {
657
+ children: [{
658
+ children: [{
659
+ action: EDIT_BLOCKS
660
+ icon: edit
661
+ label: @config.editLabel
662
+ type: button
663
+ variant: secondary
664
+ }]
665
+ direction: horizontal
666
+ gap: sm
667
+ justify: end
668
+ type: stack
669
+ }, {
670
+ action: EDIT_BLOCKS
671
+ children: [{
672
+ initialBlocks: @entity.blocksJson
673
+ readOnly: true
674
+ type: rich-block-editor
675
+ }]
676
+ className: "cursor-text rounded-md transition-colors hover:bg-muted/40"
677
+ type: box
678
+ }]
679
+ direction: vertical
680
+ gap: sm
681
+ type: stack
682
+ })
683
+ CLOSE -> viewing
684
+ (render-ui main {
685
+ children: [{
686
+ children: [{
687
+ action: EDIT_BLOCKS
688
+ icon: edit
689
+ label: @config.editLabel
690
+ type: button
691
+ variant: secondary
692
+ }]
693
+ direction: horizontal
694
+ gap: sm
695
+ justify: end
696
+ type: stack
697
+ }, {
698
+ action: EDIT_BLOCKS
699
+ children: [{
700
+ initialBlocks: @entity.blocksJson
701
+ readOnly: true
702
+ type: rich-block-editor
703
+ }]
704
+ className: "cursor-text rounded-md transition-colors hover:bg-muted/40"
705
+ type: box
706
+ }]
707
+ direction: vertical
708
+ gap: sm
709
+ type: stack
710
+ })
711
+ }
712
+
713
+ emits {
714
+ BLOCKS_SAVED -> external {
715
+ id : string!
716
+ blocksJson : [BlockSpec]
717
+ }
718
+ @description "Signals a row's block content is ready to persist. This atom never persists — the composing organism routes BLOCKS_SAVED into its own persistor's update event."
719
+ @synonyms "saved, persist, update, commit"
720
+ @tier "domain"
721
+ CLOSE
722
+ @description "Leaves the editor without saving; the body returns to its read-only render."
723
+ @synonyms "dismiss, hide, cancel, close"
724
+ @tier "domain"
725
+ BLOCKS_CHANGE {
726
+ blocks : [BlockSpec]
727
+ }
728
+ @description "Signals that the content of a block has been modified while editing."
729
+ @synonyms "update, modify, changed, edit"
730
+ @tier "domain"
731
+ }
732
+
733
+ listens {
734
+ OPEN {
735
+ id : string!
736
+ blocksJson : [BlockSpec]
737
+ }
738
+ @description "Hands the editor the row whose body it renders — the caller supplies the row's id and current blocks, since this trait never fetches them itself. Re-firing it repaints the body (e.g. after the host refetches a saved row)."
739
+ @synonyms "show, launch, reveal, edit"
740
+ @tier "domain"
741
+ OPEN_DRAFT {
742
+ id : string!
743
+ blocksJson : [BlockSpec]
744
+ }
745
+ @description "Hands the editor a row that was just created as a blank draft: same payload as OPEN, but the body opens straight in the authoring editor instead of the read-only render, so a document-first 'New page' lands the author on a cursor rather than an empty state."
746
+ @synonyms "new document, start writing, blank draft, compose"
747
+ @tier "domain"
748
+ BLOCKS_CHANGE {
749
+ blocks : [BlockSpec]
750
+ }
751
+ @description "Indicates changes to the content of editable blocks while editing."
752
+ @synonyms "update, modify, edit"
753
+ @tier "domain"
754
+ CLOSE
755
+ @description "Leaves the editor without saving; the body returns to its read-only render."
756
+ @synonyms "dismiss, hide, cancel, close"
757
+ @tier "domain"
758
+ }
759
+
760
+ config {
761
+ title : string = "Edit content"
762
+ @label "Editor heading"
763
+ @description "Heading shown above the editor while the body is being edited."
764
+ @tier "presentation"
765
+ placeholder : string = "Start writing…"
766
+ @label "Placeholder text"
767
+ @description "Placeholder shown in the block editor when it is empty."
768
+ @tier "presentation"
769
+ editLabel : string = "Edit content"
770
+ @label "Edit button label"
771
+ @description "Label of the button above the read-only body that opens the editor."
772
+ @tier "presentation"
773
+ emptyTitle : string = "Nothing written yet"
774
+ @label "Empty heading"
775
+ @description "Heading shown in place of the body when the row has no blocks yet."
776
+ @tier "presentation"
777
+ emptyDescription : string = "This page has no content yet. Start writing to fill it in."
778
+ @label "Empty description"
779
+ @description "Supporting line shown with the empty body state."
780
+ @tier "presentation"
781
+ emptyActionLabel : string = "Start writing"
782
+ @label "Empty action label"
783
+ @description "Label of the button that opens the editor from the empty body state."
784
+ @tier "presentation"
785
+ }
786
+ }
787
+
422
788
  page "/documents/edit" as DocumentEditPage -> DocumentEdit
789
+ page "/documents/block-edit" as BlockEditPage -> BlockEdit
423
790
  }