@almadar/std 16.188.0 → 16.190.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/behaviors/lolo/ui/core/.hand-authored.json +0 -4
- package/behaviors/lolo/ui/core/atoms/std-calendar.lolo +20 -8
- package/behaviors/lolo/ui/core/atoms/std-record-detail.lolo +428 -26
- package/behaviors/lolo/ui/core/atoms/std-related.lolo +5 -1
- package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +1 -1
- package/behaviors/lolo/ui/core/molecules/ui-document-details.lolo +80 -0
- package/behaviors/lolo/ui/core/molecules/ui-document-panel.lolo +213 -0
- package/behaviors/lolo/ui/core/molecules/{ui-rich-block-editor.lolo → ui-rich-text-editor.lolo} +25 -25
- package/behaviors/lolo/ui/game/atoms/std-choice-gate.lolo +100 -0
- package/behaviors/lolo/ui/game/atoms/std-coef-pad.lolo +114 -0
- package/behaviors/lolo/ui/game/atoms/std-curve-fog.lolo +50 -0
- package/behaviors/lolo/ui/game/atoms/std-pad-lock.lolo +5 -1
- package/behaviors/lolo/ui/game/atoms/std-platformer-body.lolo +29 -3
- package/behaviors/lolo/ui/game/atoms/std-zone-card.lolo +95 -0
- package/behaviors/lolo/ui/game/organisms/std-platformer-board-2d.lolo +9 -0
- package/behaviors/registry/ui/core/atoms/std-calendar.orb +51 -27
- package/behaviors/registry/ui/core/atoms/std-record-detail.orb +2399 -180
- package/behaviors/registry/ui/core/atoms/std-related.orb +23 -1
- package/behaviors/registry/ui/core/atoms/std-version-history.orb +1 -1
- package/behaviors/registry/ui/core/molecules/ui-document-details.orb +342 -0
- package/behaviors/registry/ui/core/molecules/ui-document-panel.orb +655 -0
- package/behaviors/registry/ui/core/molecules/{ui-rich-block-editor.orb → ui-rich-text-editor.orb} +84 -97
- package/behaviors/registry/ui/game/atoms/std-choice-gate.orb +855 -0
- package/behaviors/registry/ui/game/atoms/std-coef-pad.orb +928 -0
- package/behaviors/registry/ui/game/atoms/std-curve-fog.orb +337 -0
- package/behaviors/registry/ui/game/atoms/std-pad-lock.orb +31 -1
- package/behaviors/registry/ui/game/atoms/std-platformer-body.orb +847 -401
- package/behaviors/registry/ui/game/atoms/std-zone-card.orb +642 -0
- package/behaviors/registry/ui/game/organisms/std-platformer-board-2d.orb +38 -6
- package/dist/behaviors/exports-reader.js +6491 -6163
- package/dist/behaviors/functions/index.d.ts +669 -300
- package/dist/behaviors/functions/index.js +6469 -6152
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +6492 -6164
- package/dist/behaviors/query.js +6491 -6163
- package/dist/behaviors-embeddings.hash.json +3 -3
- package/dist/behaviors-embeddings.json +367 -375
- package/dist/behaviors-registry.json +4104 -2597
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6492 -6164
- package/dist/knob-embeddings.hash.json +3 -3
- package/dist/knob-embeddings.json +1 -1
- package/dist/registry/factory-signatures.json +1 -1
- package/package.json +4 -4
- package/behaviors/lolo/ui/core/atoms/std-rich-editor.lolo +0 -790
- package/behaviors/registry/ui/core/atoms/std-rich-editor.orb +0 -2225
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
app std-record-detail "1.0.0"
|
|
2
|
-
"std-record-detail — single primitive: detail-panel. Owns the fetch-one-record state machine (loading → viewing → error, with RETRY), the record counterpart to std-browse's collection machine. Consumers configure fields/sections/actions and the title/subtitle/status bindings; the atom fetches by the id carried on INIT and renders the panel. `detailLook` (panel default / slide-over / page) selects one
|
|
3
|
-
;; @capabilities "record detail, single record view, entity detail panel, drill-down record, show one record, record profile, detail page, view details"
|
|
2
|
+
"std-record-detail — single primitive: detail-panel. Owns the fetch-one-record state machine (loading → viewing → error, with RETRY), the record counterpart to std-browse's collection machine. Consumers configure fields/sections/actions and the title/subtitle/status bindings; the atom fetches by the id (or, for slug-routed records, the slug) carried on INIT and renders the panel. `detailLook` (panel default / slide-over / page / document) selects the render body, exactly as std-browse's browseLook selects a collection shape. The `document` look is for records whose shape IS a document (wiki page, note, article): the pure writing surface — a big inline-editable title with the rich-text body DIRECTLY beneath it, Edit/Done and the actions menu tucked into the header corner — where clicking the body drops a cursor in, every typing pause autosaves (CONTENT_SAVED, which the host persists QUIETLY — no refetch, or the editor unmounts mid-keystroke), and Done emits CONTENT_COMMITTED (the host refetches and snapshots version history on this one). Metadata never sits between the title and the words: compose the sibling RecordItemDetails trait beside the doc for a Gutenberg-style settings rail. A row minted blank by a document-first create arrives with draft=1 on INIT and opens straight in the editor. Pair with std-detail-layout for the back + breadcrumb chrome — that atom owns navigation, this one owns the record — exactly as a list page pairs std-browse with std-app-layout."
|
|
3
|
+
;; @capabilities "record detail, single record view, entity detail panel, drill-down record, show one record, record profile, detail page, view details, document editor, wiki page, note taking, article body, rich text editing, write and edit content"
|
|
4
4
|
orbital RecordItemOrbital {
|
|
5
5
|
type RecordAction = { label : string!, event : string!, icon : string, variant : primary | secondary | ghost | danger }
|
|
6
6
|
type RecordFieldSpec = { key : string!, header : string, format : date | currency | number | boolean | percent }
|
|
7
|
+
type MetaFieldSpec = { key : string!, label : string, kind : text | boolean | select | readonly, options : [string], icon : string, format : date | currency | number | boolean | percent }
|
|
7
8
|
type RecordSection = { title : string!, fields : [string] }
|
|
8
9
|
|
|
9
10
|
type RecordItemLoaded = Event { data : RecordItem, totalCount : number } "Fired when the single record finishes loading; payload.data holds the record. totalCount is accepted so a fetch emitter shared with collection atoms still validates."
|
|
10
11
|
|
|
12
|
+
type RecordItemSlugLoaded = Event { data : [RecordItem], totalCount : number } "Fired when a slug-routed record finishes loading. The fetch filters on the slug field, so the payload is a collection — (array/first ?data) is the record, and an empty array means no record matches the address."
|
|
13
|
+
|
|
11
14
|
type RecordItemLoadFailed = Event { error : string, code : string } "Fired when the record fails to load; drives the error state so a failed load shows an alert with a retry instead of an empty page."
|
|
12
15
|
|
|
13
16
|
entity RecordItem [runtime] {
|
|
@@ -15,6 +18,10 @@ orbital RecordItemOrbital {
|
|
|
15
18
|
name : string! @description "The record's title or identifier." @synonyms "title, label, heading"
|
|
16
19
|
description : string @description "A textual explanation or summary of the record." @synonyms "summary, details, notes"
|
|
17
20
|
status : "active" | "inactive" | "pending" @description "The record's current state." @synonyms "state, condition, stage" = active
|
|
21
|
+
slug : string @description "URL-friendly identifier, for records opened by address instead of id." @synonyms "permalink, url key, address" = ""
|
|
22
|
+
content : string @description "The record's rich-text body as HTML, edited in place by the document look." @synonyms "body, text, document, article body" = ""
|
|
23
|
+
loadedRow : object @intrinsic @description "The full row exactly as last fetched (relations hydrated when config.include asks). The document look binds it as the panel's entity and hands it to header actions as ?row, so a settings modal seeds from the real record instead of the atom's four mapped columns." = {}
|
|
24
|
+
openDraft : string @intrinsic @description "Set from the navigate payload when the surface was opened on a record that was JUST minted blank; read once the row loads to decide whether the document body opens authoring (draft) or read-only. Absent on a refresh or a normal open, so the draft entry never repeats." @synonyms "new document, just created, draft open" = ""
|
|
18
25
|
createdAt : string
|
|
19
26
|
}
|
|
20
27
|
|
|
@@ -28,14 +35,46 @@ orbital RecordItemOrbital {
|
|
|
28
35
|
initial: idle
|
|
29
36
|
|
|
30
37
|
state idle {
|
|
31
|
-
INIT -> loading when ["=", @config.openOn, "init"]
|
|
38
|
+
INIT -> loading when (and ["=", @config.openOn, "init"] ["!=", (str/default ?id ""), ""])
|
|
32
39
|
(set @entity.id ?id)
|
|
40
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
33
41
|
(fetch RecordItem {
|
|
34
42
|
emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
|
|
35
43
|
id: ?id
|
|
36
44
|
include: @config.include
|
|
37
45
|
})
|
|
38
46
|
(render-ui main { rows: 4, type: skeleton })
|
|
47
|
+
;; Slug-routed open (/things/:slug): no id on the payload, so fetch by
|
|
48
|
+
;; the slug field instead. Filtered rather than by-id, so the success
|
|
49
|
+
;; payload is a collection — its own event keeps the two shapes honest.
|
|
50
|
+
INIT -> loading when (and ["=", @config.openOn, "init"] ["=", (str/default ?id ""), ""] ["!=", (str/default ?slug ""), ""])
|
|
51
|
+
(set @entity.slug ?slug)
|
|
52
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
53
|
+
(fetch RecordItem {
|
|
54
|
+
filter: ["=", (object/get @entity slug), ?slug]
|
|
55
|
+
emit: { failure: RecordItemLoadFailed, success: RecordItemSlugLoaded }
|
|
56
|
+
include: @config.include
|
|
57
|
+
})
|
|
58
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
59
|
+
;; Fallback: an init-opened surface whose INIT carried neither key —
|
|
60
|
+
;; fetch by the (missing) id so the failure surfaces as the error
|
|
61
|
+
;; state's alert + Retry instead of a silent blank page. This is the
|
|
62
|
+
;; pre-slug atom's exact behavior, kept as the explicit last arm.
|
|
63
|
+
INIT -> loading when (and ["=", @config.openOn, "init"] ["=", (str/default ?id ""), ""] ["=", (str/default ?slug ""), ""])
|
|
64
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
65
|
+
(fetch RecordItem {
|
|
66
|
+
emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
|
|
67
|
+
id: ?id
|
|
68
|
+
include: @config.include
|
|
69
|
+
})
|
|
70
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
71
|
+
;; self-loops on the external emits so the validator sees in-atom
|
|
72
|
+
;; listeners — the real consumers are the composing organism's
|
|
73
|
+
;; persistor (CONTENT_SAVED, TITLE_COMMITTED) and its refetch /
|
|
74
|
+
;; revision path (CONTENT_COMMITTED).
|
|
75
|
+
CONTENT_SAVED -> idle
|
|
76
|
+
CONTENT_COMMITTED -> idle
|
|
77
|
+
TITLE_COMMITTED -> idle
|
|
39
78
|
;; ONE unguarded arm on purpose. This briefly carried a second,
|
|
40
79
|
;; `when @config.preloaded`-guarded arm that rendered the OPEN_VIEW row
|
|
41
80
|
;; directly to skip a round-trip. Both execution paths mishandled it:
|
|
@@ -58,8 +97,9 @@ orbital RecordItemOrbital {
|
|
|
58
97
|
}
|
|
59
98
|
|
|
60
99
|
state loading {
|
|
61
|
-
INIT -> loading
|
|
100
|
+
INIT -> loading when ["!=", (str/default ?id ""), ""]
|
|
62
101
|
(set @entity.id ?id)
|
|
102
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
63
103
|
(fetch RecordItem {
|
|
64
104
|
emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
|
|
65
105
|
id: ?id
|
|
@@ -68,37 +108,173 @@ orbital RecordItemOrbital {
|
|
|
68
108
|
;; Skeleton, not a blank frame: the hand-authored detail traits this
|
|
69
109
|
;; atom replaces rendered nothing until the record arrived.
|
|
70
110
|
(render-ui main { rows: 4, type: skeleton })
|
|
71
|
-
|
|
111
|
+
INIT -> loading when (and ["=", (str/default ?id ""), ""] ["!=", (str/default ?slug ""), ""])
|
|
112
|
+
(set @entity.slug ?slug)
|
|
113
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
114
|
+
(fetch RecordItem {
|
|
115
|
+
filter: ["=", (object/get @entity slug), ?slug]
|
|
116
|
+
emit: { failure: RecordItemLoadFailed, success: RecordItemSlugLoaded }
|
|
117
|
+
include: @config.include
|
|
118
|
+
})
|
|
119
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
120
|
+
;; A record minted blank by a document-first create arrives with
|
|
121
|
+
;; draft=1 and lands straight in the editor — a distinct target state,
|
|
122
|
+
;; not a flag on the render, so the machine stays honest about being
|
|
123
|
+
;; in an editing session.
|
|
124
|
+
RecordItemLoaded -> editing when ["=", (str/default (object/get @entity openDraft) ""), "1"]
|
|
125
|
+
;; (object/merge … {}) is a type adapter only: type_compat lacks "declared object accepts a named row type" (ledgered)
|
|
126
|
+
(set @entity.loadedRow (object/merge ?data {}))
|
|
127
|
+
(set @entity.id (object/get ?data id))
|
|
128
|
+
(set @entity.name (object/get ?data @config.titleField))
|
|
129
|
+
(set @entity.description (str/default (object/get ?data @config.subtitleField) ""))
|
|
130
|
+
(set @entity.content (object/get ?data @config.contentField))
|
|
131
|
+
(render-ui main @config.documentEditingContent)
|
|
132
|
+
RecordItemLoaded -> viewing when ["!=", (str/default (object/get @entity openDraft) ""), "1"]
|
|
133
|
+
;; (object/merge … {}) is a type adapter only: type_compat lacks "declared object accepts a named row type" (ledgered)
|
|
134
|
+
(set @entity.loadedRow (object/merge ?data {}))
|
|
135
|
+
(set @entity.id (object/get ?data id))
|
|
72
136
|
(set @entity.name (object/get ?data @config.titleField))
|
|
73
|
-
(set @entity.description (object/get ?data @config.subtitleField))
|
|
74
|
-
(
|
|
75
|
-
|
|
76
|
-
(
|
|
77
|
-
|
|
78
|
-
(render-ui main @config.
|
|
137
|
+
(set @entity.description (str/default (object/get ?data @config.subtitleField) ""))
|
|
138
|
+
(set @entity.content (object/get ?data @config.contentField))
|
|
139
|
+
(if ["=", @config.detailLook, document]
|
|
140
|
+
(render-ui main @config.documentBodyContent)
|
|
141
|
+
(if ["=", @config.detailLook, slide-over]
|
|
142
|
+
(render-ui main @config.slideOverBodyContent)
|
|
143
|
+
(if ["=", @config.detailLook, page]
|
|
144
|
+
(render-ui main @config.pageBodyContent)
|
|
145
|
+
(render-ui main @config.bodyContent))))
|
|
146
|
+
RecordItemSlugLoaded -> editing when (and [">", (array/len ?data), 0] ["=", (str/default (object/get @entity openDraft) ""), "1"])
|
|
147
|
+
(set @entity.loadedRow (array/first ?data))
|
|
148
|
+
(set @entity.id (object/get (array/first ?data) id))
|
|
149
|
+
(set @entity.name (object/get (array/first ?data) @config.titleField))
|
|
150
|
+
(set @entity.description (str/default (object/get (array/first ?data) @config.subtitleField) ""))
|
|
151
|
+
(set @entity.content (object/get (array/first ?data) @config.contentField))
|
|
152
|
+
(render-ui main @config.documentEditingContent)
|
|
153
|
+
RecordItemSlugLoaded -> viewing when (and [">", (array/len ?data), 0] ["!=", (str/default (object/get @entity openDraft) ""), "1"])
|
|
154
|
+
(set @entity.loadedRow (array/first ?data))
|
|
155
|
+
(set @entity.id (object/get (array/first ?data) id))
|
|
156
|
+
(set @entity.name (object/get (array/first ?data) @config.titleField))
|
|
157
|
+
(set @entity.description (str/default (object/get (array/first ?data) @config.subtitleField) ""))
|
|
158
|
+
(set @entity.content (object/get (array/first ?data) @config.contentField))
|
|
159
|
+
(if ["=", @config.detailLook, document]
|
|
160
|
+
(render-ui main @config.documentBodyContent)
|
|
161
|
+
(if ["=", @config.detailLook, slide-over]
|
|
162
|
+
(render-ui main @config.slideOverBodyContent)
|
|
163
|
+
(if ["=", @config.detailLook, page]
|
|
164
|
+
(render-ui main @config.pageBodyContent)
|
|
165
|
+
(render-ui main @config.bodyContent))))
|
|
166
|
+
RecordItemSlugLoaded -> error when ["=", (array/len ?data), 0]
|
|
167
|
+
(render-ui main { description: "No record matches this address.", icon: search-x, title: "Not found", type: empty-state })
|
|
79
168
|
RecordItemLoadFailed -> error
|
|
80
169
|
(render-ui main { message: ?error, title: "Couldn't load this record", type: alert, variant: error })
|
|
81
170
|
}
|
|
82
171
|
|
|
172
|
+
;; The document look's editing session. Reached from a draft landing or
|
|
173
|
+
;; the read view's EDIT_CONTENT; every typing pause autosaves through
|
|
174
|
+
;; CONTENT_SAVED, and Done commits. INIT is handled ONLY for a
|
|
175
|
+
;; DIFFERENT record: a page navigation (open another note, open a
|
|
176
|
+
;; just-minted subpage) delivers INIT and must not be swallowed — the
|
|
177
|
+
;; session's server-side trait state survives navigation, so an
|
|
178
|
+
;; INIT-less editing state left every later record page blank. The
|
|
179
|
+
;; SAME record's INIT (the refetch echo after an update) stays
|
|
180
|
+
;; unhandled on purpose: a repaint would unmount the editor under the
|
|
181
|
+
;; author mid-keystroke. Work is already autosaved when we leave.
|
|
182
|
+
state editing {
|
|
183
|
+
INIT -> loading when (and ["!=", (str/default ?id ""), ""] ["!=", ?id, @entity.id])
|
|
184
|
+
(set @entity.id ?id)
|
|
185
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
186
|
+
(fetch RecordItem {
|
|
187
|
+
emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
|
|
188
|
+
id: ?id
|
|
189
|
+
include: @config.include
|
|
190
|
+
})
|
|
191
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
192
|
+
INIT -> loading when (and ["=", (str/default ?id ""), ""] ["!=", (str/default ?slug ""), ""] ["!=", ?slug, @entity.slug])
|
|
193
|
+
(set @entity.slug ?slug)
|
|
194
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
195
|
+
(fetch RecordItem {
|
|
196
|
+
filter: ["=", (object/get @entity slug), ?slug]
|
|
197
|
+
emit: { failure: RecordItemLoadFailed, success: RecordItemSlugLoaded }
|
|
198
|
+
include: @config.include
|
|
199
|
+
})
|
|
200
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
201
|
+
CONTENT_CHANGE -> editing
|
|
202
|
+
(set @entity.content ?value)
|
|
203
|
+
(set @entity.loadedRow (object/set @entity.loadedRow @config.contentField ?value))
|
|
204
|
+
(emit CONTENT_SAVED { id: @entity.id, content: @entity.content })
|
|
205
|
+
TITLE_COMMITTED -> editing
|
|
206
|
+
(set @entity.name ?title)
|
|
207
|
+
(set @entity.loadedRow (object/set @entity.loadedRow @config.titleField ?title))
|
|
208
|
+
DONE_EDITING -> viewing
|
|
209
|
+
(emit CONTENT_SAVED { id: @entity.id, content: @entity.content })
|
|
210
|
+
(emit CONTENT_COMMITTED { id: @entity.id, content: @entity.content })
|
|
211
|
+
(render-ui main @config.documentBodyContent)
|
|
212
|
+
;; The editing card renders the same header actions as the read view,
|
|
213
|
+
;; so a consumer-configured Close must not be a dead button here:
|
|
214
|
+
;; flush the autosave stream and close, same contract as Done minus
|
|
215
|
+
;; the commit.
|
|
216
|
+
CLOSE_VIEW -> idle
|
|
217
|
+
(emit CONTENT_SAVED { id: @entity.id, content: @entity.content })
|
|
218
|
+
(render-ui main null)
|
|
219
|
+
}
|
|
220
|
+
|
|
83
221
|
state viewing {
|
|
84
222
|
;; No skeleton on re-entry: from a populated panel a skeleton unmounts the
|
|
85
223
|
;; record and flickers. First mount is unaffected — `initial: loading` and
|
|
86
224
|
;; `state loading`'s own INIT still paint it.
|
|
87
|
-
INIT -> loading
|
|
225
|
+
INIT -> loading when ["!=", (str/default ?id ""), ""]
|
|
88
226
|
(set @entity.id ?id)
|
|
227
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
89
228
|
(fetch RecordItem {
|
|
90
229
|
emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
|
|
91
230
|
id: ?id
|
|
92
231
|
include: @config.include
|
|
93
232
|
})
|
|
233
|
+
INIT -> loading when (and ["=", (str/default ?id ""), ""] ["!=", (str/default ?slug ""), ""])
|
|
234
|
+
(set @entity.slug ?slug)
|
|
235
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
236
|
+
(fetch RecordItem {
|
|
237
|
+
filter: ["=", (object/get @entity slug), ?slug]
|
|
238
|
+
emit: { failure: RecordItemLoadFailed, success: RecordItemSlugLoaded }
|
|
239
|
+
include: @config.include
|
|
240
|
+
})
|
|
94
241
|
RecordItemLoaded -> viewing
|
|
242
|
+
;; (object/merge … {}) is a type adapter only: type_compat lacks "declared object accepts a named row type" (ledgered)
|
|
243
|
+
(set @entity.loadedRow (object/merge ?data {}))
|
|
244
|
+
(set @entity.id (object/get ?data id))
|
|
95
245
|
(set @entity.name (object/get ?data @config.titleField))
|
|
96
|
-
(set @entity.description (object/get ?data @config.subtitleField))
|
|
97
|
-
(
|
|
98
|
-
|
|
99
|
-
(
|
|
100
|
-
|
|
101
|
-
(render-ui main @config.
|
|
246
|
+
(set @entity.description (str/default (object/get ?data @config.subtitleField) ""))
|
|
247
|
+
(set @entity.content (object/get ?data @config.contentField))
|
|
248
|
+
(if ["=", @config.detailLook, document]
|
|
249
|
+
(render-ui main @config.documentBodyContent)
|
|
250
|
+
(if ["=", @config.detailLook, slide-over]
|
|
251
|
+
(render-ui main @config.slideOverBodyContent)
|
|
252
|
+
(if ["=", @config.detailLook, page]
|
|
253
|
+
(render-ui main @config.pageBodyContent)
|
|
254
|
+
(render-ui main @config.bodyContent))))
|
|
255
|
+
RecordItemSlugLoaded -> viewing when [">", (array/len ?data), 0]
|
|
256
|
+
(set @entity.loadedRow (array/first ?data))
|
|
257
|
+
(set @entity.id (object/get (array/first ?data) id))
|
|
258
|
+
(set @entity.name (object/get (array/first ?data) @config.titleField))
|
|
259
|
+
(set @entity.description (str/default (object/get (array/first ?data) @config.subtitleField) ""))
|
|
260
|
+
(set @entity.content (object/get (array/first ?data) @config.contentField))
|
|
261
|
+
(if ["=", @config.detailLook, document]
|
|
262
|
+
(render-ui main @config.documentBodyContent)
|
|
263
|
+
(if ["=", @config.detailLook, slide-over]
|
|
264
|
+
(render-ui main @config.slideOverBodyContent)
|
|
265
|
+
(if ["=", @config.detailLook, page]
|
|
266
|
+
(render-ui main @config.pageBodyContent)
|
|
267
|
+
(render-ui main @config.bodyContent))))
|
|
268
|
+
RecordItemSlugLoaded -> error when ["=", (array/len ?data), 0]
|
|
269
|
+
(render-ui main { description: "No record matches this address.", icon: search-x, title: "Not found", type: empty-state })
|
|
270
|
+
;; The document body is its own edit affordance: clicking it (or its
|
|
271
|
+
;; Edit button) starts the editing session with a cursor in the text.
|
|
272
|
+
EDIT_CONTENT -> editing
|
|
273
|
+
(render-ui main @config.documentEditingContent)
|
|
274
|
+
;; The title renames from the read view too, without an editing session.
|
|
275
|
+
TITLE_COMMITTED -> viewing
|
|
276
|
+
(set @entity.name ?title)
|
|
277
|
+
(set @entity.loadedRow (object/set @entity.loadedRow @config.titleField ?title))
|
|
102
278
|
RecordItemLoadFailed -> error
|
|
103
279
|
(render-ui main { message: ?error, title: "Couldn't load this record", type: alert, variant: error })
|
|
104
280
|
CLOSE_VIEW -> idle
|
|
@@ -106,14 +282,24 @@ orbital RecordItemOrbital {
|
|
|
106
282
|
}
|
|
107
283
|
|
|
108
284
|
state error {
|
|
109
|
-
INIT -> loading
|
|
285
|
+
INIT -> loading when ["!=", (str/default ?id ""), ""]
|
|
110
286
|
(set @entity.id ?id)
|
|
287
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
111
288
|
(fetch RecordItem {
|
|
112
289
|
emit: { failure: RecordItemLoadFailed, success: RecordItemLoaded }
|
|
113
290
|
id: ?id
|
|
114
291
|
include: @config.include
|
|
115
292
|
})
|
|
116
293
|
(render-ui main { rows: 4, type: skeleton })
|
|
294
|
+
INIT -> loading when (and ["=", (str/default ?id ""), ""] ["!=", (str/default ?slug ""), ""])
|
|
295
|
+
(set @entity.slug ?slug)
|
|
296
|
+
(set @entity.openDraft (str/default ?draft ""))
|
|
297
|
+
(fetch RecordItem {
|
|
298
|
+
filter: ["=", (object/get @entity slug), ?slug]
|
|
299
|
+
emit: { failure: RecordItemLoadFailed, success: RecordItemSlugLoaded }
|
|
300
|
+
include: @config.include
|
|
301
|
+
})
|
|
302
|
+
(render-ui main { rows: 4, type: skeleton })
|
|
117
303
|
;; The hand-authored traits sent a failed load back to `loading` with no
|
|
118
304
|
;; effect, so the page stayed blank forever with no way out. RETRY is the
|
|
119
305
|
;; way out.
|
|
@@ -131,6 +317,10 @@ orbital RecordItemOrbital {
|
|
|
131
317
|
@description "Signals that the record has been successfully loaded."
|
|
132
318
|
@synonyms "loaded, received, ready, fetched"
|
|
133
319
|
@tier "domain"
|
|
320
|
+
RecordItemSlugLoaded
|
|
321
|
+
@description "A slug-routed record finished loading; the payload is a collection whose first element is the record. Composing traits that need the loaded row (comments scoping, status controls) listen to this."
|
|
322
|
+
@synonyms "loaded by address, slug resolved, record found"
|
|
323
|
+
@tier "domain"
|
|
134
324
|
RecordItemLoadFailed
|
|
135
325
|
@description "Indicates a failure to load the record."
|
|
136
326
|
@synonyms "error, failed, problem, unsuccessful"
|
|
@@ -149,15 +339,67 @@ orbital RecordItemOrbital {
|
|
|
149
339
|
@description "Signals a request to remove the record being viewed."
|
|
150
340
|
@synonyms "remove, discard, delete, archive"
|
|
151
341
|
@tier "domain"
|
|
342
|
+
CONTENT_SAVED -> external {
|
|
343
|
+
id : string!
|
|
344
|
+
content : string
|
|
345
|
+
}
|
|
346
|
+
@description "The document look's autosave stream: fired at every typing pause while editing, and once more on Done. This atom never persists — the composing organism routes CONTENT_SAVED into a QUIET persist (one that does not refetch or repaint this surface, or the editor unmounts mid-keystroke)."
|
|
347
|
+
@synonyms "autosave, saved, persist content, draft saved"
|
|
348
|
+
@tier "domain"
|
|
349
|
+
CONTENT_COMMITTED -> external {
|
|
350
|
+
id : string!
|
|
351
|
+
content : string
|
|
352
|
+
}
|
|
353
|
+
@description "The author finished the document editing session (pressed Done). Hosts refetch the row, repaint, and snapshot version history on THIS event — never on the CONTENT_SAVED autosave stream."
|
|
354
|
+
@synonyms "done, finished editing, commit, snapshot"
|
|
355
|
+
@tier "domain"
|
|
356
|
+
TITLE_COMMITTED -> external {
|
|
357
|
+
title : string
|
|
358
|
+
id : string
|
|
359
|
+
}
|
|
360
|
+
@description "The viewer renamed the record in place by editing the document title; carries the committed title and the row id for a partial persist."
|
|
361
|
+
@synonyms "rename, retitle, change title, edit title"
|
|
362
|
+
@tier "domain"
|
|
363
|
+
EDIT_CONTENT
|
|
364
|
+
@description "The viewer clicked the document body (or its Edit button) to start an editing session."
|
|
365
|
+
@synonyms "edit, start editing, click to edit"
|
|
366
|
+
@tier "presentation"
|
|
367
|
+
DONE_EDITING
|
|
368
|
+
@description "The viewer pressed Done to end the document editing session."
|
|
369
|
+
@synonyms "done, finish, stop editing"
|
|
370
|
+
@tier "presentation"
|
|
152
371
|
}
|
|
153
372
|
|
|
154
373
|
listens {
|
|
155
374
|
INIT {
|
|
156
375
|
id : string
|
|
376
|
+
slug : string
|
|
377
|
+
draft : string
|
|
157
378
|
}
|
|
158
|
-
@description "Opens the detail surface for
|
|
379
|
+
@description "Opens the detail surface for one record: by id, or by slug for address-routed records (the internal re-fetch after an update still carries id). `draft` is set only by a document-first create's navigate, and opens the document look's body straight in the editor."
|
|
159
380
|
@synonyms "open record, view record, show details, drill in"
|
|
160
381
|
@tier "domain"
|
|
382
|
+
CONTENT_CHANGE {
|
|
383
|
+
value : string
|
|
384
|
+
}
|
|
385
|
+
@description "The rich text surface reported an edit (already debounced to typing pauses by the component)."
|
|
386
|
+
@synonyms "typing, content changed, body edited"
|
|
387
|
+
@tier "domain"
|
|
388
|
+
EDIT_CONTENT
|
|
389
|
+
@description "Start the document editing session."
|
|
390
|
+
@synonyms "edit, start editing, click to edit"
|
|
391
|
+
@tier "presentation"
|
|
392
|
+
DONE_EDITING
|
|
393
|
+
@description "End the document editing session; work is already autosaved when it fires."
|
|
394
|
+
@synonyms "done, finish, stop editing"
|
|
395
|
+
@tier "presentation"
|
|
396
|
+
TITLE_COMMITTED {
|
|
397
|
+
title : string
|
|
398
|
+
id : string
|
|
399
|
+
}
|
|
400
|
+
@description "The document title was renamed in place."
|
|
401
|
+
@synonyms "rename, retitle, change title"
|
|
402
|
+
@tier "domain"
|
|
161
403
|
OPEN_VIEW {
|
|
162
404
|
id : string!
|
|
163
405
|
row : RecordItem
|
|
@@ -242,8 +484,8 @@ orbital RecordItemOrbital {
|
|
|
242
484
|
@tier "presentation"
|
|
243
485
|
subtitleField : string = description
|
|
244
486
|
@label "Which field is the subtitle?"
|
|
245
|
-
@description "Name of the field shown under the title (e.g. company, category).
|
|
246
|
-
@synonyms "subtitle field, secondary field, subheading"
|
|
487
|
+
@description "Name of the field shown under the title (e.g. company, category). Set to \"\" for no subtitle at all — the right choice for the document look, whose metadata lives in the properties strip, not a second line."
|
|
488
|
+
@synonyms "subtitle field, secondary field, subheading, no subtitle"
|
|
247
489
|
@tier "presentation"
|
|
248
490
|
include : [string] = []
|
|
249
491
|
@label "Which relation fields to hydrate?"
|
|
@@ -255,10 +497,31 @@ orbital RecordItemOrbital {
|
|
|
255
497
|
@description "init = a routed detail page; the record loads as soon as the page mounts (the default). event = an overlay that stays blank until a row-click OPEN_VIEW hands it an id, which is what a list-embedded slide-over needs — mounting an init-loading surface on a list page would fetch a record it has no id for."
|
|
256
498
|
@synonyms "open on click, slide over, row click, blank until opened, list embedded"
|
|
257
499
|
@tier "policy"
|
|
258
|
-
detailLook : "panel" | "slide-over" | "page" = panel
|
|
500
|
+
detailLook : "panel" | "slide-over" | "page" | "document" = panel
|
|
259
501
|
@label "Detail look"
|
|
260
|
-
@description "Visual treatment for the record surface. panel = an inline detail-panel in the page flow (bodyContent); slide-over = the same panel as a right-hand drawer over the list behind it (slideOverBodyContent); page = a full-width record page with roomier spacing (pageBodyContent). Selecting a
|
|
261
|
-
@synonyms "detail style, drawer, slide over, full page"
|
|
502
|
+
@description "Visual treatment for the record surface. panel = an inline detail-panel in the page flow (bodyContent); slide-over = the same panel as a right-hand drawer over the list behind it (slideOverBodyContent); page = a full-width record page with roomier spacing (pageBodyContent); document = a wiki-page/note/article surface — big editable title over a rich-text body, no labelled field rows, click-to-edit with autosave (documentBodyContent / documentEditingContent). Selecting a look swaps which body tree renders after the record loads; any body may still be overridden directly at the call site."
|
|
503
|
+
@synonyms "detail style, drawer, slide over, full page, document, wiki page, note, article"
|
|
504
|
+
@tier "presentation"
|
|
505
|
+
contentField : string = content
|
|
506
|
+
@label "Which field holds the document body?"
|
|
507
|
+
@description "Name of the field on the loaded record that carries the rich-text HTML body the document look renders and edits (e.g. content, body, articleText)."
|
|
508
|
+
@synonyms "body field, content field, document field"
|
|
509
|
+
@tier "presentation"
|
|
510
|
+
editLabel : string = "Edit"
|
|
511
|
+
@label "Edit button label"
|
|
512
|
+
@description "Label of the quiet button above the document body that starts an editing session (the body itself is also clickable)."
|
|
513
|
+
@tier "presentation"
|
|
514
|
+
doneLabel : string = "Done"
|
|
515
|
+
@label "Done button label"
|
|
516
|
+
@description "Label of the button that ends the document editing session; work is already autosaved when it is pressed."
|
|
517
|
+
@tier "presentation"
|
|
518
|
+
autosaveHint : string = "Saves as you type"
|
|
519
|
+
@label "Autosave hint"
|
|
520
|
+
@description "Muted caption next to the Done button telling the author their work persists on its own."
|
|
521
|
+
@tier "presentation"
|
|
522
|
+
placeholder : string = "Start writing…"
|
|
523
|
+
@label "Placeholder text"
|
|
524
|
+
@description "Placeholder shown in the document body when it is empty."
|
|
262
525
|
@tier "presentation"
|
|
263
526
|
bodyContent : render-ui = {
|
|
264
527
|
children: [{
|
|
@@ -322,8 +585,147 @@ orbital RecordItemOrbital {
|
|
|
322
585
|
@label "Page look body"
|
|
323
586
|
@description "Render-ui SExpr rendered after the record loads when detailLook = page: a full record page, width-capped so field rows do not stretch past a readable measure (Almadar_UI_Beauty.md §3)."
|
|
324
587
|
@tier "internal"
|
|
588
|
+
documentBodyContent : render-ui = {
|
|
589
|
+
actions: @config.actions
|
|
590
|
+
editEvent: EDIT_CONTENT
|
|
591
|
+
editLabel: @config.editLabel
|
|
592
|
+
entity: @entity.loadedRow
|
|
593
|
+
maxInlineActions: @config.maxInlineActions
|
|
594
|
+
placeholder: @config.placeholder
|
|
595
|
+
subtitle: @entity.description
|
|
596
|
+
title: @entity.name
|
|
597
|
+
titleCommitEvent: TITLE_COMMITTED
|
|
598
|
+
type: document-panel
|
|
599
|
+
value: @entity.content
|
|
600
|
+
className: "w-full"
|
|
601
|
+
}
|
|
602
|
+
@label "Document look body (reading)"
|
|
603
|
+
@description "Render-ui SExpr for detailLook = document while reading: the pure writing surface — editable title with the rich-text body DIRECTLY beneath it, Edit and the actions menu tucked into the header corner. Metadata never sits between title and content; compose the sibling RecordItemDetails trait beside this one for the settings rail. Binds only @entity fields, never ?data, because it is also painted by arms whose payload is not the record."
|
|
604
|
+
@tier "internal"
|
|
605
|
+
documentEditingContent : render-ui = {
|
|
606
|
+
actions: @config.actions
|
|
607
|
+
autosaveHint: @config.autosaveHint
|
|
608
|
+
contentChangeEvent: CONTENT_CHANGE
|
|
609
|
+
doneEvent: DONE_EDITING
|
|
610
|
+
doneLabel: @config.doneLabel
|
|
611
|
+
editing: true
|
|
612
|
+
entity: @entity.loadedRow
|
|
613
|
+
maxInlineActions: @config.maxInlineActions
|
|
614
|
+
placeholder: @config.placeholder
|
|
615
|
+
subtitle: @entity.description
|
|
616
|
+
title: @entity.name
|
|
617
|
+
titleCommitEvent: TITLE_COMMITTED
|
|
618
|
+
type: document-panel
|
|
619
|
+
value: @entity.content
|
|
620
|
+
className: "w-full"
|
|
621
|
+
}
|
|
622
|
+
@label "Document look body (editing)"
|
|
623
|
+
@description "Render-ui SExpr for detailLook = document during an editing session: the same surface with the body live and the header corner showing the autosave hint + Done. Binds only @entity fields, never ?data."
|
|
624
|
+
@tier "internal"
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
;; The document's info panel — an on-demand DRAWER, never page furniture:
|
|
629
|
+
;; the composing organism routes the doc's loaded event into SHOW_DETAILS
|
|
630
|
+
;; (which only stashes the row) and a "Details" action on the document's
|
|
631
|
+
;; menu into OPEN_DETAILS, which slides the panel in from the right over
|
|
632
|
+
;; the content. The drawer's X / backdrop dismiss emits CLOSE back to this
|
|
633
|
+
;; trait. A sibling of RecordItemDetail: the organism just MOUNTS this
|
|
634
|
+
;; trait and wires the two listens — no layout wiring.
|
|
635
|
+
;; EDITING metadata is not done here: it rides the document's "⋯" menu as
|
|
636
|
+
;; normal actions (the settings modal, favorite/status workflows). The
|
|
637
|
+
;; META_CHANGE → META_SAVED commit circuit stays available for a call-site
|
|
638
|
+
;; detailsContent override that opts back into in-place editing.
|
|
639
|
+
trait RecordItemDetails -> @rebindable RecordItem [interaction, instance] {
|
|
640
|
+
initial: idle
|
|
641
|
+
|
|
642
|
+
state idle {
|
|
643
|
+
INIT -> idle
|
|
644
|
+
;; stash only — the drawer opens on demand, never on load
|
|
645
|
+
SHOW_DETAILS -> idle
|
|
646
|
+
(set @entity.loadedRow (object/merge ?data {}))
|
|
647
|
+
OPEN_DETAILS -> showing
|
|
648
|
+
(render-ui drawer @config.detailsContent)
|
|
649
|
+
;; validator self-loop; the real consumer is the host's persistor.
|
|
650
|
+
META_SAVED -> idle
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
state showing {
|
|
654
|
+
;; a refetch echo while the drawer is open repaints it in place
|
|
655
|
+
SHOW_DETAILS -> showing
|
|
656
|
+
(set @entity.loadedRow (object/merge ?data {}))
|
|
657
|
+
(render-ui drawer @config.detailsContent)
|
|
658
|
+
OPEN_DETAILS -> showing
|
|
659
|
+
(render-ui drawer @config.detailsContent)
|
|
660
|
+
;; the drawer X / backdrop dismiss
|
|
661
|
+
CLOSE -> idle
|
|
662
|
+
(render-ui drawer null)
|
|
663
|
+
;; navigation must never strand an open drawer over the next page
|
|
664
|
+
INIT -> idle
|
|
665
|
+
(render-ui drawer null)
|
|
666
|
+
META_CHANGE -> showing
|
|
667
|
+
(emit META_SAVED { id: ?id, patch: ?patch })
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
emits {
|
|
671
|
+
META_SAVED -> external {
|
|
672
|
+
id : string!
|
|
673
|
+
patch : RecordItem
|
|
674
|
+
}
|
|
675
|
+
@description "A property in the settings rail committed in place; patch carries the row id plus the one changed field, ready to hand straight to a persist update. Quiet contract: the host persists without refetching or repainting the open document."
|
|
676
|
+
@synonyms "property saved, metadata changed, field committed, update property"
|
|
677
|
+
@tier "domain"
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
listens {
|
|
681
|
+
SHOW_DETAILS {
|
|
682
|
+
data : RecordItem
|
|
683
|
+
}
|
|
684
|
+
@description "The document finished loading; stash the row the details drawer will show. Hosts route the doc trait's RecordItemLoaded (or SlugLoaded first element) here."
|
|
685
|
+
@synonyms "record loaded, show metadata, populate details"
|
|
686
|
+
@tier "domain"
|
|
687
|
+
OPEN_DETAILS {
|
|
688
|
+
id : string
|
|
689
|
+
}
|
|
690
|
+
@description "Open the details drawer. Hosts route the document's Details action (e.g. Doc.VIEW_INFO) here."
|
|
691
|
+
@synonyms "show info, open details, document info, page info"
|
|
692
|
+
@tier "presentation"
|
|
693
|
+
CLOSE {
|
|
694
|
+
id : string
|
|
695
|
+
}
|
|
696
|
+
@description "Close the details drawer; the drawer chrome's X / backdrop dismiss emits this."
|
|
697
|
+
@synonyms "dismiss, close drawer, hide details"
|
|
698
|
+
@tier "presentation"
|
|
699
|
+
META_CHANGE {
|
|
700
|
+
id : string
|
|
701
|
+
patch : RecordItem
|
|
702
|
+
}
|
|
703
|
+
@description "A property in the rail committed (switch toggled, select changed, text blurred)."
|
|
704
|
+
@synonyms "property changed, metadata edited, field committed"
|
|
705
|
+
@tier "domain"
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
config {
|
|
709
|
+
metaFields : [MetaFieldSpec] = []
|
|
710
|
+
@label "Which metadata properties appear in the details drawer?"
|
|
711
|
+
@description "The record's metadata shown as read-only INFO in the details drawer (Updated, address, status, …). Editing metadata is a normal action under the document's menu, not an inline affordance here."
|
|
712
|
+
@synonyms "properties, metadata fields, document info, page info, front matter"
|
|
713
|
+
@tier "presentation"
|
|
714
|
+
detailsTitle : string = Details
|
|
715
|
+
@label "Details drawer heading"
|
|
716
|
+
@description "Heading of the settings card (e.g. Details, Page info, Document settings)."
|
|
717
|
+
@tier "presentation"
|
|
718
|
+
detailsContent : render-ui = {
|
|
719
|
+
entity: @entity.loadedRow
|
|
720
|
+
fields: @config.metaFields
|
|
721
|
+
title: @config.detailsTitle
|
|
722
|
+
type: document-details
|
|
723
|
+
}
|
|
724
|
+
@label "Details drawer content tree"
|
|
725
|
+
@description "Render-ui SExpr painted into the drawer slot on OPEN_DETAILS; binds the row stashed by SHOW_DETAILS. Info-only by default — no metaCommitEvent, so every row displays rather than edits (editing rides the document's actions menu). Override at the call site to extend the panel or to opt into in-place commits via metaCommitEvent: META_CHANGE."
|
|
726
|
+
@tier "internal"
|
|
325
727
|
}
|
|
326
728
|
}
|
|
327
729
|
|
|
328
|
-
page "/recorditems" as RecordItemDetailPage -> RecordItemDetail
|
|
730
|
+
page "/recorditems" as RecordItemDetailPage -> RecordItemDetail, RecordItemDetails
|
|
329
731
|
}
|
|
@@ -76,7 +76,7 @@ orbital RelatedItemOrbital {
|
|
|
76
76
|
gap: sm
|
|
77
77
|
type: stack
|
|
78
78
|
}, { type: divider }, {
|
|
79
|
-
entity: ?data
|
|
79
|
+
entity: (array/map ?data (fn row { id: @row.id, name: (object/get @row @config.labelField) }))
|
|
80
80
|
fields: []
|
|
81
81
|
look: @config.tableLook
|
|
82
82
|
renderItem: (fn item {
|
|
@@ -218,6 +218,10 @@ orbital RelatedItemOrbital {
|
|
|
218
218
|
@label "Section title"
|
|
219
219
|
@description "Heading shown above the related list."
|
|
220
220
|
@tier "presentation"
|
|
221
|
+
labelField : string = name
|
|
222
|
+
@label "Which field labels each related row?"
|
|
223
|
+
@description "Name of the field on the (rebound) related row rendered as the row label and carried as SELECT_RELATED's name — set it to `title` (etc.) whenever the bound entity labels rows differently. The rows are mapped through this at render time, because a trait-level `fields {}` rename is declaration-only on the runtime path and every populated row used to render a blank label."
|
|
224
|
+
@tier "presentation"
|
|
221
225
|
relationField : string = ""
|
|
222
226
|
@label "Relation field"
|
|
223
227
|
@description "RelatedItem field compared against SHOW_RELATED's key to scope the fetch to one parent document; empty renders the full unfiltered collection (default)."
|
|
@@ -16,7 +16,7 @@ orbital RevisionOrbital {
|
|
|
16
16
|
versionNumber : number! @description "A sequential identifier representing the revision's order." @synonyms "sequence, order, iteration"
|
|
17
17
|
authorId : string @description "Unique identifier for the user who made the change." @synonyms "user identifier, creator, contributor"
|
|
18
18
|
authorName : string @description "The name of the user who made the change." @synonyms "user, contributor, owner"
|
|
19
|
-
content : string @description "The
|
|
19
|
+
content : string @description "The snapshotted body of the revision — plain text or the rich-text HTML a document-look record carries. Rollback hands the whole row back to the host, which restores content onto the source document." @synonyms "text, body, data, value"
|
|
20
20
|
summary : string @description "A brief explanation of the revision's changes." @synonyms "description, note, comment, explanation"
|
|
21
21
|
searchTerm : string @description "The current search query the user submitted." @synonyms "query, term, q" = ""
|
|
22
22
|
createdAt : string
|