@almadar/std 16.191.0 → 16.193.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/infra/atoms/std-approval-gate.lolo +184 -18
- package/behaviors/lolo/infra/atoms/std-status-lifecycle.lolo +16 -3
- package/behaviors/lolo/ui/core/atoms/std-app-layout.lolo +6 -0
- package/behaviors/lolo/ui/core/atoms/std-billable-hour.lolo +1 -0
- package/behaviors/lolo/ui/core/atoms/std-browse.lolo +3 -0
- package/behaviors/lolo/ui/core/atoms/std-image-upload-multi.lolo +37 -4
- package/behaviors/lolo/ui/core/atoms/std-record-detail.lolo +37 -0
- package/behaviors/lolo/ui/core/atoms/std-tag-strip.lolo +314 -0
- package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +195 -13
- package/behaviors/lolo/ui/core/atoms/std-tree.lolo +104 -12
- package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +3 -1
- package/behaviors/lolo/ui/core/molecules/ui-calendar-grid.lolo +25 -1
- package/behaviors/lolo/ui/core/molecules/ui-data-grid.lolo +37 -1
- package/behaviors/lolo/ui/core/molecules/ui-data-list.lolo +46 -1
- package/behaviors/lolo/ui/core/molecules/ui-document-details.lolo +30 -2
- package/behaviors/lolo/ui/core/molecules/ui-document-panel.lolo +14 -0
- package/behaviors/lolo/ui/core/molecules/ui-file-tree.lolo +25 -0
- package/behaviors/lolo/ui/core/molecules/ui-positioned-canvas.lolo +19 -1
- package/behaviors/lolo/ui/core/molecules/ui-reply-tree.lolo +21 -1
- package/behaviors/lolo/ui/core/molecules/ui-sortable-list.lolo +15 -1
- package/behaviors/lolo/ui/core/molecules/ui-table-view.lolo +36 -1
- package/behaviors/lolo/ui/core/organisms/ui-detail-panel.lolo +41 -1
- package/behaviors/lolo/ui/core/organisms/ui-entity-cards.lolo +34 -1
- package/behaviors/lolo/ui/core/organisms/ui-entity-list.lolo +30 -1
- package/behaviors/lolo/ui/core/organisms/ui-entity-table.lolo +36 -1
- package/behaviors/lolo/ui/core/organisms/ui-form-section.lolo +39 -1
- package/behaviors/lolo/ui/core/organisms/ui-form.lolo +39 -1
- package/behaviors/lolo/ui/core/organisms/ui-master-detail.lolo +24 -1
- package/behaviors/lolo/ui/core/organisms/ui-media-gallery.lolo +30 -1
- package/behaviors/lolo/ui/core/organisms/ui-stat-card.lolo +37 -1
- package/behaviors/lolo/ui/core/organisms/ui-timeline.lolo +18 -1
- package/behaviors/lolo/ui/core/templates/ui-counter-template.lolo +21 -1
- package/behaviors/lolo/ui/core/templates/ui-dashboard-layout.lolo +5 -0
- package/behaviors/lolo/ui/core/templates/ui-generic-app-template.lolo +16 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-book-chapter-view.lolo +12 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-book-table-of-contents.lolo +13 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-book-viewer.lolo +23 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-case-study-organism.lolo +23 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-feature-grid-organism.lolo +24 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-hero-organism.lolo +22 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-pricing-organism.lolo +23 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-showcase-organism.lolo +24 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-stats-organism.lolo +22 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-step-flow-organism.lolo +25 -1
- package/behaviors/lolo/ui/marketing/organisms/ui-team-organism.lolo +23 -1
- package/behaviors/lolo/ui/marketing/templates/ui-about-page-template.lolo +11 -1
- package/behaviors/lolo/ui/marketing/templates/ui-feature-detail-page-template.lolo +11 -1
- package/behaviors/lolo/ui/marketing/templates/ui-landing-page-template.lolo +13 -1
- package/behaviors/lolo/ui/marketing/templates/ui-pricing-page-template.lolo +11 -1
- package/behaviors/registry/infra/atoms/std-approval-gate.orb +717 -45
- package/behaviors/registry/infra/atoms/std-status-lifecycle.orb +85 -10
- package/behaviors/registry/ui/core/atoms/std-app-layout.orb +8 -0
- package/behaviors/registry/ui/core/atoms/std-billable-hour.orb +5 -2
- package/behaviors/registry/ui/core/atoms/std-browse.orb +12 -0
- package/behaviors/registry/ui/core/atoms/std-image-upload-multi.orb +129 -7
- package/behaviors/registry/ui/core/atoms/std-record-detail.orb +316 -0
- package/behaviors/registry/ui/core/atoms/std-tag-strip.orb +1360 -0
- package/behaviors/registry/ui/core/atoms/std-tag-taxonomy.orb +564 -10
- package/behaviors/registry/ui/core/atoms/std-tree.orb +427 -66
- package/behaviors/registry/ui/core/atoms/std-version-history.orb +5 -1
- package/behaviors/registry/ui/core/molecules/ui-calendar-grid.orb +43 -0
- package/behaviors/registry/ui/core/molecules/ui-data-grid.orb +55 -0
- package/behaviors/registry/ui/core/molecules/ui-data-list.orb +64 -0
- package/behaviors/registry/ui/core/molecules/ui-document-details.orb +106 -1
- package/behaviors/registry/ui/core/molecules/ui-document-panel.orb +18 -0
- package/behaviors/registry/ui/core/molecules/ui-file-tree.orb +41 -0
- package/behaviors/registry/ui/core/molecules/ui-positioned-canvas.orb +37 -0
- package/behaviors/registry/ui/core/molecules/ui-reply-tree.orb +39 -0
- package/behaviors/registry/ui/core/molecules/ui-sortable-list.orb +45 -0
- package/behaviors/registry/ui/core/molecules/ui-table-view.orb +54 -0
- package/behaviors/registry/ui/core/organisms/ui-detail-panel.orb +59 -0
- package/behaviors/registry/ui/core/organisms/ui-entity-cards.orb +52 -0
- package/behaviors/registry/ui/core/organisms/ui-entity-list.orb +48 -0
- package/behaviors/registry/ui/core/organisms/ui-entity-table.orb +54 -0
- package/behaviors/registry/ui/core/organisms/ui-form-section.orb +57 -0
- package/behaviors/registry/ui/core/organisms/ui-form.orb +57 -0
- package/behaviors/registry/ui/core/organisms/ui-master-detail.orb +42 -0
- package/behaviors/registry/ui/core/organisms/ui-media-gallery.orb +48 -0
- package/behaviors/registry/ui/core/organisms/ui-stat-card.orb +55 -0
- package/behaviors/registry/ui/core/organisms/ui-timeline.orb +36 -0
- package/behaviors/registry/ui/core/templates/ui-counter-template.orb +39 -0
- package/behaviors/registry/ui/core/templates/ui-dashboard-layout.orb +7 -0
- package/behaviors/registry/ui/core/templates/ui-generic-app-template.orb +34 -0
- package/behaviors/registry/ui/marketing/organisms/ui-book-chapter-view.orb +30 -0
- package/behaviors/registry/ui/marketing/organisms/ui-book-table-of-contents.orb +31 -0
- package/behaviors/registry/ui/marketing/organisms/ui-book-viewer.orb +41 -0
- package/behaviors/registry/ui/marketing/organisms/ui-case-study-organism.orb +41 -0
- package/behaviors/registry/ui/marketing/organisms/ui-feature-grid-organism.orb +42 -0
- package/behaviors/registry/ui/marketing/organisms/ui-hero-organism.orb +40 -0
- package/behaviors/registry/ui/marketing/organisms/ui-pricing-organism.orb +41 -0
- package/behaviors/registry/ui/marketing/organisms/ui-showcase-organism.orb +42 -0
- package/behaviors/registry/ui/marketing/organisms/ui-stats-organism.orb +40 -0
- package/behaviors/registry/ui/marketing/organisms/ui-step-flow-organism.orb +43 -0
- package/behaviors/registry/ui/marketing/organisms/ui-team-organism.orb +41 -0
- package/behaviors/registry/ui/marketing/templates/ui-about-page-template.orb +29 -0
- package/behaviors/registry/ui/marketing/templates/ui-feature-detail-page-template.orb +29 -0
- package/behaviors/registry/ui/marketing/templates/ui-landing-page-template.orb +31 -0
- package/behaviors/registry/ui/marketing/templates/ui-pricing-page-template.orb +29 -0
- package/dist/behaviors/exports-reader.js +4884 -1967
- package/dist/behaviors/functions/index.d.ts +293 -13
- package/dist/behaviors/functions/index.js +4880 -1968
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +4885 -1968
- package/dist/behaviors/query.js +4884 -1967
- package/dist/behaviors-embeddings.hash.json +3 -3
- package/dist/behaviors-embeddings.json +21 -13
- package/dist/behaviors-registry.json +685 -60
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4885 -1968
- 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 +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
app std-approval-gate "1.0
|
|
2
|
-
"std-approval-gate — generic approval gate / approver workflow. Use when a user voices that an action must be reviewed, approved, signed off, or authorized by a privileged role before it proceeds (admin approval, supervisor sign-off, manager approval, gatekeeper review, editor approval, senior editor approval, reviewer approval, publishing approval, draft approval, content review, submit drafts then editors review and approve or reject, needs approval before going live, requires sign-off before publish). Persists ApprovalRequest rows + surfaces
|
|
1
|
+
app std-approval-gate "1.1.0"
|
|
2
|
+
"std-approval-gate — generic approval gate / approver workflow. Use when a user voices that an action must be reviewed, approved, signed off, or authorized by a privileged role before it proceeds (admin approval, supervisor sign-off, manager approval, gatekeeper review, editor approval, senior editor approval, reviewer approval, publishing approval, draft approval, content review, submit drafts then editors review and approve or reject, needs approval before going live, requires sign-off before publish). Persists ApprovalRequest rows + surfaces the PENDING ones in a review queue showing what is being approved and who asked, with Approve / Deny actions — Deny captures a written reason. approverRoles is ENFORCED by the review transitions themselves: an APPROVE/DENY from a viewer whose role is not listed does not fire. On approval emits ApprovalGranted; on denial emits ApprovalDenied (with the reason). Hosts that replay the gated action MUST route a failed replay back via APPROVAL_REPLAY_FAILED so the request reopens instead of being silently consumed. Compose onto any orbital whose action you want to gate: the target orbital emits an external event, an ApprovalRequest row is created (stamp subjectLabel/subjectId/requestedBy so reviewers see what they are judging), and downstream effects wait on ApprovalGranted. Differs from std-mod-queue, which is content-moderation-specific."
|
|
3
3
|
orbital ApprovalGateOrbital {
|
|
4
4
|
uses Spinner from "std/behaviors/ui-spinner"
|
|
5
5
|
uses Typography from "std/behaviors/ui-typography"
|
|
@@ -18,17 +18,34 @@ orbital ApprovalGateOrbital {
|
|
|
18
18
|
|
|
19
19
|
type ApprovalGranted = Event { requestId : string, payload : object } "Re-emitted on approval; carries the held payload so the downstream listener can replay the gated action without a lookup."
|
|
20
20
|
|
|
21
|
-
type
|
|
21
|
+
type DenyCommit = { id : string!, notes : string }
|
|
22
22
|
|
|
23
|
+
type ApprovalDenied = Event { requestId : string, reason : string } "Emitted on denial; carries the reviewer's written reason. Downstream listeners log, notify, or return the subject to its prior state."
|
|
24
|
+
|
|
25
|
+
;; The queue is a shared review surface — every signed-in role may need to
|
|
26
|
+
;; SEE what is pending about their own submissions; WHO may decide is the
|
|
27
|
+
;; role guard on the APPROVE/DENY transitions (the FSM enforces it on the
|
|
28
|
+
;; affordance and on any bus-delivered event alike). Entity-layer access
|
|
29
|
+
;; predicates CANNOT live here: a generic atom declares no [identity]
|
|
30
|
+
;; entity, and ORB_S_ACCESS_POLICY_REQUIRES_IDENTITY (correctly) rejects a
|
|
31
|
+
;; @user-reading directive without a roster — while a host cannot add
|
|
32
|
+
;; predicates to a foreign atom's entity. Ledgered
|
|
33
|
+
;; (R-APPROVAL-REQUEST-ENTITY-LAYER-UNGATED); the guard layer above is the
|
|
34
|
+
;; enforcement both paths carry today.
|
|
23
35
|
entity ApprovalRequest [persistent: approval_requests] {
|
|
36
|
+
@read none "Shared review surface: submitters watch their requests, approvers work the queue; decision authority is enforced at the transitions, not by hiding rows."
|
|
24
37
|
id : string!
|
|
25
38
|
gatedEvent : string @description "The event that triggered the approval request." @synonyms "trigger, event, source" = ""
|
|
39
|
+
subjectLabel : string @description "Human title of the thing being approved (the article's headline, the page's title) — what the reviewer sees in the queue." @synonyms "title, subject, what is being approved" = ""
|
|
40
|
+
subjectId : string @description "Id of the record being approved, for a queue row's View click-through to the subject's own surface." @synonyms "record id, target id, article id, page id" = ""
|
|
41
|
+
requestedBy : string @description "User id of the submitter." @synonyms "submitter, author, requester id" = ""
|
|
42
|
+
requestedByName : string @description "Display name of the submitter shown in the queue." @synonyms "submitter name, requester, who asked" = ""
|
|
26
43
|
status : "pending" | "approved" | "denied" | "escalated" @description "Current state of the approval process." @synonyms "approval status, state, condition" = pending
|
|
27
44
|
reviewer : string @description "The user or system responsible for reviewing the request." @synonyms "assignee, owner, approver, responsible party" = ""
|
|
28
45
|
requestedAt : number @description "Timestamp indicating when the approval request was initiated." @synonyms "creation time, request date, submission time" = 0
|
|
29
46
|
decidedAt : number @description "Timestamp indicating when a decision was made on the request." @synonyms "decision time, resolved at, completed at" = 0
|
|
30
47
|
escalatedAt : number @description "Timestamp indicating when the request was escalated." @synonyms "escalation time, escalation timestamp, escalated time" = 0
|
|
31
|
-
notes : string @description "
|
|
48
|
+
notes : string @description "The reviewer's written reason, captured by the deny step." @synonyms "comments, remarks, explanation, reason, details" = ""
|
|
32
49
|
payload : object @description "The held data of the gated action, replayed by the downstream listener when the request is granted." @synonyms "request data, held payload, pending data, gated data" = {}
|
|
33
50
|
}
|
|
34
51
|
|
|
@@ -63,6 +80,21 @@ orbital ApprovalGateOrbital {
|
|
|
63
80
|
}
|
|
64
81
|
}
|
|
65
82
|
|
|
83
|
+
trait DenyReasonTitle = Typography.traits.TypographyRender -> ApprovalRequest {
|
|
84
|
+
config {
|
|
85
|
+
content: "Deny request"
|
|
86
|
+
variant: h3
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
trait DenyReasonHint = Typography.traits.TypographyRender -> ApprovalRequest {
|
|
91
|
+
config {
|
|
92
|
+
color: muted
|
|
93
|
+
content: "Tell the submitter why — the reason is recorded on the request."
|
|
94
|
+
variant: caption
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
66
98
|
trait CloseButton = Button.traits.ButtonRender -> ApprovalRequest {
|
|
67
99
|
config {
|
|
68
100
|
action: CLOSE
|
|
@@ -86,15 +118,30 @@ orbital ApprovalGateOrbital {
|
|
|
86
118
|
trait ApprovalGateReview -> @rebindable ApprovalRequest [interaction, collection, approval] {
|
|
87
119
|
initial: idle
|
|
88
120
|
|
|
121
|
+
;; Every queue fetch is scoped to PENDING rows: a decided row must leave
|
|
122
|
+
;; the queue, or its still-live Approve action re-emits ApprovalGranted
|
|
123
|
+
;; with the ORIGINAL payload and replays a stale edit over current state.
|
|
89
124
|
state idle {
|
|
90
125
|
INIT -> idle
|
|
91
126
|
(fetch ApprovalRequest {
|
|
127
|
+
filter: ["=", (object/get @entity status), "pending"]
|
|
92
128
|
emit: { failure: ApprovalRequestLoadFailed, success: ApprovalRequestLoaded }
|
|
93
129
|
})
|
|
94
130
|
ApprovalRequestLoaded -> idle
|
|
95
131
|
ApprovalRequestLoadFailed -> idle
|
|
132
|
+
;; A replay failure can also arrive while the queue is closed.
|
|
133
|
+
APPROVAL_REPLAY_FAILED -> idle
|
|
134
|
+
(set @entity.id ?requestId)
|
|
135
|
+
(set @entity.status pending)
|
|
136
|
+
(set @entity.reviewer "")
|
|
137
|
+
(set @entity.decidedAt 0)
|
|
138
|
+
(persist update ApprovalRequest @entity {
|
|
139
|
+
emit: { failure: ApprovalRequestReviewFailed, success: ApprovalRequestReviewed }
|
|
140
|
+
})
|
|
141
|
+
(notify "Approval could not be applied — the request was reopened." error)
|
|
96
142
|
OPEN -> loading when @config.enabled
|
|
97
143
|
(fetch ApprovalRequest {
|
|
144
|
+
filter: ["=", (object/get @entity status), "pending"]
|
|
98
145
|
emit: { failure: ApprovalRequestLoadFailed, success: ApprovalRequestLoaded }
|
|
99
146
|
})
|
|
100
147
|
(render-ui @config.reviewSlot {
|
|
@@ -127,20 +174,29 @@ orbital ApprovalGateOrbital {
|
|
|
127
174
|
cols: 1
|
|
128
175
|
entity: ?data
|
|
129
176
|
fields: [{
|
|
177
|
+
label: What
|
|
178
|
+
name: subjectLabel
|
|
179
|
+
variant: h4
|
|
180
|
+
}, {
|
|
130
181
|
label: Action
|
|
131
182
|
name: gatedEvent
|
|
132
183
|
variant: badge
|
|
184
|
+
}, {
|
|
185
|
+
label: "Requested by"
|
|
186
|
+
name: requestedByName
|
|
187
|
+
variant: caption
|
|
133
188
|
}, {
|
|
134
189
|
label: Requested
|
|
135
190
|
name: requestedAt
|
|
136
191
|
variant: caption
|
|
137
|
-
}, {
|
|
138
|
-
label: Status
|
|
139
|
-
name: status
|
|
140
|
-
variant: badge
|
|
141
192
|
}]
|
|
142
193
|
gap: sm
|
|
143
194
|
itemActions: [{
|
|
195
|
+
event: OPEN_SUBJECT
|
|
196
|
+
icon: eye
|
|
197
|
+
label: View
|
|
198
|
+
variant: ghost
|
|
199
|
+
}, {
|
|
144
200
|
event: APPROVE
|
|
145
201
|
icon: check
|
|
146
202
|
label: Approve
|
|
@@ -168,27 +224,92 @@ orbital ApprovalGateOrbital {
|
|
|
168
224
|
}
|
|
169
225
|
|
|
170
226
|
state reviewing {
|
|
171
|
-
|
|
227
|
+
;; The role guard IS the enforcement (both execution paths honor
|
|
228
|
+
;; transition guards, for the button and for a bus-delivered event
|
|
229
|
+
;; alike): empty approverRoles = ungated, otherwise the viewer's role
|
|
230
|
+
;; must be listed or the decision does not fire.
|
|
231
|
+
APPROVE -> reviewing when (or ["=", (array/len @config.approverRoles), 0] (array/includes @config.approverRoles @user.role))
|
|
172
232
|
(set @entity.id ?id)
|
|
173
233
|
(set @entity.status approved)
|
|
174
234
|
(set @entity.reviewer @user.id)
|
|
235
|
+
(set @entity.decidedAt (time/now))
|
|
175
236
|
(persist update ApprovalRequest @entity {
|
|
176
237
|
emit: { failure: ApprovalRequestReviewFailed, success: ApprovalRequestReviewed }
|
|
177
238
|
})
|
|
178
239
|
(emit ApprovalGranted { payload: ?row.payload, requestId: ?id })
|
|
179
|
-
|
|
240
|
+
;; Deny is a two-step: capture the written reason first, commit second.
|
|
241
|
+
DENY -> denying when (or ["=", (array/len @config.approverRoles), 0] (array/includes @config.approverRoles @user.role))
|
|
180
242
|
(set @entity.id ?id)
|
|
243
|
+
(set @entity.subjectLabel (str/default (object/get ?row subjectLabel) ""))
|
|
244
|
+
(render-ui @config.reviewSlot {
|
|
245
|
+
children: [{
|
|
246
|
+
align: center
|
|
247
|
+
children: [@trait.DenyReasonTitle, @trait.CloseButton]
|
|
248
|
+
direction: horizontal
|
|
249
|
+
gap: sm
|
|
250
|
+
justify: between
|
|
251
|
+
type: stack
|
|
252
|
+
}, @trait.DenyReasonHint, @trait.ApprovalDivider, {
|
|
253
|
+
cancelEvent: CANCEL_DENY
|
|
254
|
+
entity: { id: @entity.id }
|
|
255
|
+
fields: (notes)
|
|
256
|
+
mode: edit
|
|
257
|
+
submitEvent: CONFIRM_DENY
|
|
258
|
+
type: form-section
|
|
259
|
+
}]
|
|
260
|
+
direction: vertical
|
|
261
|
+
gap: md
|
|
262
|
+
type: stack
|
|
263
|
+
})
|
|
264
|
+
OPEN_SUBJECT -> reviewing
|
|
265
|
+
ApprovalRequestReviewed -> loading
|
|
266
|
+
(fetch ApprovalRequest {
|
|
267
|
+
filter: ["=", (object/get @entity status), "pending"]
|
|
268
|
+
emit: { failure: ApprovalRequestLoadFailed, success: ApprovalRequestLoaded }
|
|
269
|
+
})
|
|
270
|
+
ApprovalRequestReviewFailed -> reviewing
|
|
271
|
+
(render-ui @config.reviewSlot {
|
|
272
|
+
children: [@trait.ErrorAlert]
|
|
273
|
+
type: stack
|
|
274
|
+
})
|
|
275
|
+
;; A host reports the gated action could not be applied after approval
|
|
276
|
+
;; (an entity-access denial, a failed write): reopen the request so it
|
|
277
|
+
;; is never silently consumed.
|
|
278
|
+
APPROVAL_REPLAY_FAILED -> reviewing
|
|
279
|
+
(set @entity.id ?requestId)
|
|
280
|
+
(set @entity.status pending)
|
|
281
|
+
(set @entity.reviewer "")
|
|
282
|
+
(set @entity.decidedAt 0)
|
|
283
|
+
(persist update ApprovalRequest @entity {
|
|
284
|
+
emit: { failure: ApprovalRequestReviewFailed, success: ApprovalRequestReviewed }
|
|
285
|
+
})
|
|
286
|
+
(notify "Approval could not be applied — the request was reopened." error)
|
|
287
|
+
CLOSE -> idle
|
|
288
|
+
(render-ui @config.reviewSlot null)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
state denying {
|
|
292
|
+
CONFIRM_DENY -> reviewing
|
|
293
|
+
(set @entity.id (object/get ?data id))
|
|
181
294
|
(set @entity.status denied)
|
|
182
295
|
(set @entity.reviewer @user.id)
|
|
296
|
+
(set @entity.decidedAt (time/now))
|
|
297
|
+
(set @entity.notes (str/default (object/get ?data notes) ""))
|
|
183
298
|
(persist update ApprovalRequest @entity {
|
|
184
299
|
emit: { failure: ApprovalRequestReviewFailed, success: ApprovalRequestReviewed }
|
|
185
300
|
})
|
|
186
|
-
(emit ApprovalDenied { requestId: ?id })
|
|
301
|
+
(emit ApprovalDenied { requestId: (object/get ?data id), reason: (str/default (object/get ?data notes) "") })
|
|
302
|
+
CANCEL_DENY -> loading
|
|
303
|
+
(fetch ApprovalRequest {
|
|
304
|
+
filter: ["=", (object/get @entity status), "pending"]
|
|
305
|
+
emit: { failure: ApprovalRequestLoadFailed, success: ApprovalRequestLoaded }
|
|
306
|
+
})
|
|
187
307
|
ApprovalRequestReviewed -> loading
|
|
188
308
|
(fetch ApprovalRequest {
|
|
309
|
+
filter: ["=", (object/get @entity status), "pending"]
|
|
189
310
|
emit: { failure: ApprovalRequestLoadFailed, success: ApprovalRequestLoaded }
|
|
190
311
|
})
|
|
191
|
-
ApprovalRequestReviewFailed ->
|
|
312
|
+
ApprovalRequestReviewFailed -> denying
|
|
192
313
|
(render-ui @config.reviewSlot {
|
|
193
314
|
children: [@trait.ErrorAlert]
|
|
194
315
|
type: stack
|
|
@@ -200,6 +321,7 @@ orbital ApprovalGateOrbital {
|
|
|
200
321
|
state error {
|
|
201
322
|
OPEN -> loading
|
|
202
323
|
(fetch ApprovalRequest {
|
|
324
|
+
filter: ["=", (object/get @entity status), "pending"]
|
|
203
325
|
emit: { failure: ApprovalRequestLoadFailed, success: ApprovalRequestLoaded }
|
|
204
326
|
})
|
|
205
327
|
(render-ui @config.reviewSlot {
|
|
@@ -222,9 +344,27 @@ orbital ApprovalGateOrbital {
|
|
|
222
344
|
id : string!
|
|
223
345
|
row : ApprovalRequest
|
|
224
346
|
}
|
|
225
|
-
@description "
|
|
347
|
+
@description "Opens the deny step for a request; the decision commits with the written reason."
|
|
226
348
|
@synonyms "reject, decline"
|
|
227
349
|
@tier "domain"
|
|
350
|
+
OPEN_SUBJECT -> external {
|
|
351
|
+
id : string!
|
|
352
|
+
subjectId : string
|
|
353
|
+
subjectLabel : string
|
|
354
|
+
}
|
|
355
|
+
@description "A reviewer clicked View on a queue row. Hosts route subjectId to the subject record's own surface so the reviewer can read what they are approving. Scalars only — the row stays in the queue."
|
|
356
|
+
@synonyms "view subject, open record, inspect request, see the article"
|
|
357
|
+
@tier "domain"
|
|
358
|
+
CONFIRM_DENY {
|
|
359
|
+
data : DenyCommit
|
|
360
|
+
}
|
|
361
|
+
@description "The deny form committed; data carries the request id + the written reason."
|
|
362
|
+
@synonyms "submit denial, confirm rejection"
|
|
363
|
+
@tier "internal"
|
|
364
|
+
CANCEL_DENY
|
|
365
|
+
@description "The deny form was dismissed without committing; the queue repaints."
|
|
366
|
+
@synonyms "cancel denial, back to queue"
|
|
367
|
+
@tier "internal"
|
|
228
368
|
CLOSE
|
|
229
369
|
@description "Dismisses the review queue overlay."
|
|
230
370
|
@synonyms "dismiss, hide, cancel, done"
|
|
@@ -233,13 +373,14 @@ orbital ApprovalGateOrbital {
|
|
|
233
373
|
requestId : string
|
|
234
374
|
payload : object
|
|
235
375
|
}
|
|
236
|
-
@description "Signals that an approval process has been successfully completed; carries the held payload for the downstream listener to replay."
|
|
376
|
+
@description "Signals that an approval process has been successfully completed; carries the held payload for the downstream listener to replay. Contract: if the replay fails (access denial, write failure), the host MUST emit APPROVAL_REPLAY_FAILED back so the request reopens."
|
|
237
377
|
@synonyms "approved, authorized, confirmed, validated"
|
|
238
378
|
@tier "domain"
|
|
239
379
|
ApprovalDenied -> external {
|
|
240
380
|
requestId : string
|
|
381
|
+
reason : string
|
|
241
382
|
}
|
|
242
|
-
@description "Indicates an approval request has been denied."
|
|
383
|
+
@description "Indicates an approval request has been denied, with the reviewer's written reason."
|
|
243
384
|
@synonyms "rejected, declined"
|
|
244
385
|
@tier "domain"
|
|
245
386
|
ApprovalRequestLoaded {
|
|
@@ -291,9 +432,34 @@ orbital ApprovalGateOrbital {
|
|
|
291
432
|
id : string!
|
|
292
433
|
row : ApprovalRequest
|
|
293
434
|
}
|
|
294
|
-
@description "
|
|
435
|
+
@description "Opens the deny step for a request."
|
|
295
436
|
@synonyms "reject, decline, deny"
|
|
296
437
|
@tier "domain"
|
|
438
|
+
OPEN_SUBJECT {
|
|
439
|
+
id : string!
|
|
440
|
+
subjectId : string
|
|
441
|
+
subjectLabel : string
|
|
442
|
+
}
|
|
443
|
+
@description "A reviewer asked to view the subject of a queue row; the host routes the navigation."
|
|
444
|
+
@synonyms "view subject, open record, inspect"
|
|
445
|
+
@tier "domain"
|
|
446
|
+
CONFIRM_DENY {
|
|
447
|
+
data : DenyCommit
|
|
448
|
+
}
|
|
449
|
+
@description "The deny form committed with the written reason."
|
|
450
|
+
@synonyms "submit denial, confirm rejection"
|
|
451
|
+
@tier "internal"
|
|
452
|
+
CANCEL_DENY
|
|
453
|
+
@description "The deny form was dismissed; return to the queue."
|
|
454
|
+
@synonyms "cancel denial, back"
|
|
455
|
+
@tier "internal"
|
|
456
|
+
APPROVAL_REPLAY_FAILED {
|
|
457
|
+
requestId : string!
|
|
458
|
+
error : string
|
|
459
|
+
}
|
|
460
|
+
@description "The host could not apply the granted action (access denial, failed write). The request reopens as pending so a decision is never silently lost."
|
|
461
|
+
@synonyms "replay failed, apply failed, approval rollback"
|
|
462
|
+
@tier "domain"
|
|
297
463
|
ApprovalRequestLoaded {
|
|
298
464
|
data : [ApprovalRequest]
|
|
299
465
|
}
|
|
@@ -330,7 +496,7 @@ orbital ApprovalGateOrbital {
|
|
|
330
496
|
@tier "policy"
|
|
331
497
|
approverRoles : [string] = []
|
|
332
498
|
@label "Who can approve requests?"
|
|
333
|
-
@description "Roles eligible to
|
|
499
|
+
@description "Roles eligible to decide. ENFORCED by the APPROVE/DENY transition guards: a viewer whose @user.role is not listed cannot fire a decision, from the button or from the bus. Empty = every signed-in viewer may decide (ungated)."
|
|
334
500
|
@synonyms "approver, reviewer, authorized roles, sign-off roles, who approves"
|
|
335
501
|
@tier "policy"
|
|
336
502
|
threshold : number = 0
|
|
@@ -370,4 +536,4 @@ orbital ApprovalGateOrbital {
|
|
|
370
536
|
}
|
|
371
537
|
|
|
372
538
|
page "/approvals/queue" as ApprovalGateReviewPage -> ApprovalGateReview
|
|
373
|
-
}
|
|
539
|
+
}
|
|
@@ -20,9 +20,14 @@ orbital StatusLifecycleOrbital {
|
|
|
20
20
|
state idle {
|
|
21
21
|
ChangeStatusRequested -> idle when (and @config.enabled (array/some @config.transitions (fn t (and ["=", (object/get @t from), ?from] ["=", (object/get @t to), ?to]))))
|
|
22
22
|
(persist update @config.targetEntity (object/set { id: ?id } @config.statusField ?to) {
|
|
23
|
-
emit: { failure: StatusChangeFailed, success:
|
|
23
|
+
emit: { failure: StatusChangeFailed, success: StatusPersisted }
|
|
24
24
|
})
|
|
25
|
-
|
|
25
|
+
;; StatusChanged is re-emitted ONLY off the persist's success (guarded
|
|
26
|
+
;; on ?id against the optimistic/authoritative double-fire) — the old
|
|
27
|
+
;; synchronous emit fired even when the write failed, so a denied
|
|
28
|
+
;; mutation still looked like a completed transition downstream.
|
|
29
|
+
StatusPersisted -> idle when ["!=", (str/default ?id ""), ""]
|
|
30
|
+
(emit StatusChanged { entityId: ?id, status: (str/default (object/get ?row @config.statusField) "") })
|
|
26
31
|
StatusChangeFailed -> idle
|
|
27
32
|
}
|
|
28
33
|
|
|
@@ -31,7 +36,11 @@ orbital StatusLifecycleOrbital {
|
|
|
31
36
|
entityId : string
|
|
32
37
|
status : string
|
|
33
38
|
}
|
|
34
|
-
|
|
39
|
+
StatusPersisted {
|
|
40
|
+
id : string
|
|
41
|
+
row : StatusRecord
|
|
42
|
+
}
|
|
43
|
+
StatusChangeFailed -> external {
|
|
35
44
|
error : string
|
|
36
45
|
code : string
|
|
37
46
|
}
|
|
@@ -43,6 +52,10 @@ orbital StatusLifecycleOrbital {
|
|
|
43
52
|
from : string
|
|
44
53
|
to : string
|
|
45
54
|
}
|
|
55
|
+
StatusPersisted {
|
|
56
|
+
id : string
|
|
57
|
+
row : StatusRecord
|
|
58
|
+
}
|
|
46
59
|
StatusChangeFailed {
|
|
47
60
|
error : string
|
|
48
61
|
code : string
|
|
@@ -41,6 +41,7 @@ orbital AppLayoutOrbital {
|
|
|
41
41
|
notificationClickEvent: @config.notificationClickEvent
|
|
42
42
|
notifications: @config.notifications
|
|
43
43
|
searchEvent: @config.searchEvent
|
|
44
|
+
sidebarContent: @config.sidebarTrait
|
|
44
45
|
topBarActions: @config.topBarActions
|
|
45
46
|
type: @config.layoutPattern
|
|
46
47
|
user: (if @config.viewerName { avatar: @config.viewerAvatar, email: @config.viewerEmail, name: @config.viewerName } null)
|
|
@@ -90,6 +91,11 @@ orbital AppLayoutOrbital {
|
|
|
90
91
|
@label "Content trait"
|
|
91
92
|
@description "Trait rendered inside the app layout content area"
|
|
92
93
|
@tier "presentation"
|
|
94
|
+
sidebarTrait : trait
|
|
95
|
+
@label "Sidebar trait"
|
|
96
|
+
@description "Optional trait rendered in the sidebar between the nav links and the footer — the Notion/Confluence-style always-visible page tree; scrolls independently. Unset renders plain nav only."
|
|
97
|
+
@synonyms "sidebar tree, nav tree, page tree in sidebar, hierarchy rail"
|
|
98
|
+
@tier "presentation"
|
|
93
99
|
appName : string = App
|
|
94
100
|
@label "What should this app be called?"
|
|
95
101
|
@description "Name shown in the top-left of the chrome header."
|
|
@@ -38,6 +38,7 @@ orbital BrowseItemOrbital {
|
|
|
38
38
|
itemActions: @config.itemActions
|
|
39
39
|
maxInlineActions: @config.maxInlineActions
|
|
40
40
|
pageSize: @config.displayPageSize
|
|
41
|
+
selfFetch: false
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
listens {
|
|
@@ -55,6 +56,7 @@ orbital BrowseItemOrbital {
|
|
|
55
56
|
maxInlineActions: @config.maxInlineActions
|
|
56
57
|
pageSize: @config.displayPageSize
|
|
57
58
|
selectable: @config.selectable
|
|
59
|
+
selfFetch: false
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
listens {
|
|
@@ -69,6 +71,7 @@ orbital BrowseItemOrbital {
|
|
|
69
71
|
itemClickEvent: @config.itemClickEvent
|
|
70
72
|
maxInlineActions: @config.maxInlineActions
|
|
71
73
|
variant: compact
|
|
74
|
+
selfFetch: false
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
listens {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
app std-image-upload-multi "1.0.0"
|
|
2
2
|
"std-image-upload-multi — multi-image upload with drop zone, gallery preview, and per-batch progress."
|
|
3
3
|
orbital UploadedImageOrbital {
|
|
4
|
+
type UploadBatchFile = { name : string!, size : number, type : string, content : string }
|
|
5
|
+
|
|
6
|
+
type StoredImageItem = { id : string!, key : string, url : string, name : string, sizeBytes : number, mimeType : string }
|
|
7
|
+
|
|
4
8
|
type UploadedImageLoaded = Event { data : [UploadedImage] } "Fired when the uploaded-image collection finishes loading"
|
|
5
9
|
|
|
6
10
|
type UploadedImageLoadFailed = Event { error : string, code : string } "Fired when the uploaded-image collection fails to load"
|
|
@@ -96,8 +100,13 @@ orbital UploadedImageOrbital {
|
|
|
96
100
|
type: stack
|
|
97
101
|
})
|
|
98
102
|
UPLOAD -> uploading
|
|
99
|
-
(
|
|
100
|
-
|
|
103
|
+
(call-service storage uploadMany {
|
|
104
|
+
acl: public
|
|
105
|
+
bucket: @config.bucket
|
|
106
|
+
files: ?files
|
|
107
|
+
maxSize: @config.maxBytesPerImage
|
|
108
|
+
} {
|
|
109
|
+
emit: { failure: UploadedImageUploadFailed, success: UploadedImageStored }
|
|
101
110
|
})
|
|
102
111
|
(render-ui main {
|
|
103
112
|
align: center
|
|
@@ -133,6 +142,12 @@ orbital UploadedImageOrbital {
|
|
|
133
142
|
}
|
|
134
143
|
|
|
135
144
|
state uploading {
|
|
145
|
+
;; The storage service owns the bytes; rows persist only the returned
|
|
146
|
+
;; addresses — ?items is shaped for this bulk create (url/name/sizeBytes/mimeType).
|
|
147
|
+
UploadedImageStored -> uploading
|
|
148
|
+
(persist create UploadedImage ?items {
|
|
149
|
+
emit: { failure: UploadedImageUploadFailed, success: UploadedImageCreated }
|
|
150
|
+
})
|
|
136
151
|
UploadedImageCreated -> loading
|
|
137
152
|
(fetch UploadedImage {
|
|
138
153
|
emit: { failure: UploadedImageLoadFailed, success: UploadedImageLoaded }
|
|
@@ -192,7 +207,7 @@ orbital UploadedImageOrbital {
|
|
|
192
207
|
|
|
193
208
|
emits {
|
|
194
209
|
UPLOAD {
|
|
195
|
-
files :
|
|
210
|
+
files : [UploadBatchFile]!
|
|
196
211
|
}
|
|
197
212
|
@description "Signals that an image upload process has begun."
|
|
198
213
|
@synonyms "start, initiate, begin"
|
|
@@ -216,6 +231,13 @@ orbital UploadedImageOrbital {
|
|
|
216
231
|
@description "A new image has been successfully uploaded."
|
|
217
232
|
@synonyms "added, new, received"
|
|
218
233
|
@tier "domain"
|
|
234
|
+
UploadedImageStored {
|
|
235
|
+
items : [StoredImageItem]
|
|
236
|
+
count : number
|
|
237
|
+
}
|
|
238
|
+
@description "The storage service accepted the whole batch; items carry each file's stored address and metadata, ready to persist."
|
|
239
|
+
@synonyms "uploaded, stored, saved to storage"
|
|
240
|
+
@tier "internal"
|
|
219
241
|
UploadedImageUploadFailed {
|
|
220
242
|
error : string
|
|
221
243
|
code : string
|
|
@@ -240,7 +262,7 @@ orbital UploadedImageOrbital {
|
|
|
240
262
|
|
|
241
263
|
listens {
|
|
242
264
|
UPLOAD {
|
|
243
|
-
files :
|
|
265
|
+
files : [UploadBatchFile]!
|
|
244
266
|
}
|
|
245
267
|
@description "Indicates a file upload has begun."
|
|
246
268
|
@synonyms "start, begin, initiate"
|
|
@@ -270,6 +292,13 @@ orbital UploadedImageOrbital {
|
|
|
270
292
|
@description "A new image has been successfully uploaded."
|
|
271
293
|
@synonyms "added, received, new image"
|
|
272
294
|
@tier "domain"
|
|
295
|
+
UploadedImageStored {
|
|
296
|
+
items : [StoredImageItem]
|
|
297
|
+
count : number
|
|
298
|
+
}
|
|
299
|
+
@description "The storage service accepted the batch; the items are persisted as uploaded-image rows."
|
|
300
|
+
@synonyms "uploaded, stored, saved to storage"
|
|
301
|
+
@tier "internal"
|
|
273
302
|
UploadedImageUploadFailed {
|
|
274
303
|
error : string
|
|
275
304
|
code : string
|
|
@@ -309,6 +338,10 @@ orbital UploadedImageOrbital {
|
|
|
309
338
|
@label "MIME filter"
|
|
310
339
|
@description "Browser accept filter for the file picker"
|
|
311
340
|
@tier "domain"
|
|
341
|
+
bucket : string = "images"
|
|
342
|
+
@label "Storage bucket"
|
|
343
|
+
@description "Object-storage bucket the batch uploads into"
|
|
344
|
+
@tier "domain"
|
|
312
345
|
}
|
|
313
346
|
}
|
|
314
347
|
|
|
@@ -20,6 +20,8 @@ orbital RecordItemOrbital {
|
|
|
20
20
|
status : "active" | "inactive" | "pending" @description "The record's current state." @synonyms "state, condition, stage" = active
|
|
21
21
|
slug : string @description "URL-friendly identifier, for records opened by address instead of id." @synonyms "permalink, url key, address" = ""
|
|
22
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
|
+
coverUrl : string @intrinsic @description "The document look's cover/hero image URL, stamped from the loaded row's coverField." = ""
|
|
24
|
+
iconName : string @intrinsic @description "The document look's icon name, stamped from the loaded row's iconField." = ""
|
|
23
25
|
loadedRow : object @intrinsic @description "The full row exactly as last fetched (relations hydrated when config.include asks). The details drawer binds it, and hosts that need it (e.g. a settings modal) stash it from RecordItemLoaded — the document panel itself takes only scalar props and emits scalar payloads ({ id, title })." = {}
|
|
24
26
|
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" = ""
|
|
25
27
|
createdAt : string
|
|
@@ -128,6 +130,8 @@ orbital RecordItemOrbital {
|
|
|
128
130
|
(set @entity.name (object/get ?data @config.titleField))
|
|
129
131
|
(set @entity.description (str/default (object/get ?data @config.subtitleField) ""))
|
|
130
132
|
(set @entity.content (object/get ?data @config.contentField))
|
|
133
|
+
(set @entity.coverUrl (str/default (object/get ?data @config.coverField) ""))
|
|
134
|
+
(set @entity.iconName (str/default (object/get ?data @config.iconField) ""))
|
|
131
135
|
(render-ui main @config.documentEditingContent)
|
|
132
136
|
RecordItemLoaded -> viewing when ["!=", (str/default (object/get @entity openDraft) ""), "1"]
|
|
133
137
|
;; (object/merge … {}) is a type adapter only: type_compat lacks "declared object accepts a named row type" (ledgered)
|
|
@@ -136,6 +140,8 @@ orbital RecordItemOrbital {
|
|
|
136
140
|
(set @entity.name (object/get ?data @config.titleField))
|
|
137
141
|
(set @entity.description (str/default (object/get ?data @config.subtitleField) ""))
|
|
138
142
|
(set @entity.content (object/get ?data @config.contentField))
|
|
143
|
+
(set @entity.coverUrl (str/default (object/get ?data @config.coverField) ""))
|
|
144
|
+
(set @entity.iconName (str/default (object/get ?data @config.iconField) ""))
|
|
139
145
|
(if ["=", @config.detailLook, document]
|
|
140
146
|
(render-ui main @config.documentBodyContent)
|
|
141
147
|
(if ["=", @config.detailLook, slide-over]
|
|
@@ -149,6 +155,8 @@ orbital RecordItemOrbital {
|
|
|
149
155
|
(set @entity.name (object/get (array/first ?data) @config.titleField))
|
|
150
156
|
(set @entity.description (str/default (object/get (array/first ?data) @config.subtitleField) ""))
|
|
151
157
|
(set @entity.content (object/get (array/first ?data) @config.contentField))
|
|
158
|
+
(set @entity.coverUrl (str/default (object/get (array/first ?data) @config.coverField) ""))
|
|
159
|
+
(set @entity.iconName (str/default (object/get (array/first ?data) @config.iconField) ""))
|
|
152
160
|
(render-ui main @config.documentEditingContent)
|
|
153
161
|
RecordItemSlugLoaded -> viewing when (and [">", (array/len ?data), 0] ["!=", (str/default (object/get @entity openDraft) ""), "1"])
|
|
154
162
|
(set @entity.loadedRow (array/first ?data))
|
|
@@ -156,6 +164,8 @@ orbital RecordItemOrbital {
|
|
|
156
164
|
(set @entity.name (object/get (array/first ?data) @config.titleField))
|
|
157
165
|
(set @entity.description (str/default (object/get (array/first ?data) @config.subtitleField) ""))
|
|
158
166
|
(set @entity.content (object/get (array/first ?data) @config.contentField))
|
|
167
|
+
(set @entity.coverUrl (str/default (object/get (array/first ?data) @config.coverField) ""))
|
|
168
|
+
(set @entity.iconName (str/default (object/get (array/first ?data) @config.iconField) ""))
|
|
159
169
|
(if ["=", @config.detailLook, document]
|
|
160
170
|
(render-ui main @config.documentBodyContent)
|
|
161
171
|
(if ["=", @config.detailLook, slide-over]
|
|
@@ -245,6 +255,8 @@ orbital RecordItemOrbital {
|
|
|
245
255
|
(set @entity.name (object/get ?data @config.titleField))
|
|
246
256
|
(set @entity.description (str/default (object/get ?data @config.subtitleField) ""))
|
|
247
257
|
(set @entity.content (object/get ?data @config.contentField))
|
|
258
|
+
(set @entity.coverUrl (str/default (object/get ?data @config.coverField) ""))
|
|
259
|
+
(set @entity.iconName (str/default (object/get ?data @config.iconField) ""))
|
|
248
260
|
(if ["=", @config.detailLook, document]
|
|
249
261
|
(render-ui main @config.documentBodyContent)
|
|
250
262
|
(if ["=", @config.detailLook, slide-over]
|
|
@@ -258,6 +270,8 @@ orbital RecordItemOrbital {
|
|
|
258
270
|
(set @entity.name (object/get (array/first ?data) @config.titleField))
|
|
259
271
|
(set @entity.description (str/default (object/get (array/first ?data) @config.subtitleField) ""))
|
|
260
272
|
(set @entity.content (object/get (array/first ?data) @config.contentField))
|
|
273
|
+
(set @entity.coverUrl (str/default (object/get (array/first ?data) @config.coverField) ""))
|
|
274
|
+
(set @entity.iconName (str/default (object/get (array/first ?data) @config.iconField) ""))
|
|
261
275
|
(if ["=", @config.detailLook, document]
|
|
262
276
|
(render-ui main @config.documentBodyContent)
|
|
263
277
|
(if ["=", @config.detailLook, slide-over]
|
|
@@ -507,6 +521,16 @@ orbital RecordItemOrbital {
|
|
|
507
521
|
@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
522
|
@synonyms "body field, content field, document field"
|
|
509
523
|
@tier "presentation"
|
|
524
|
+
coverField : string = ""
|
|
525
|
+
@label "Which field is the cover image?"
|
|
526
|
+
@description "Name of the image-typed field on the loaded record painted as the document look's full-bleed hero banner (e.g. coverImage, heroImage). Empty = no banner, the default."
|
|
527
|
+
@synonyms "cover field, hero image field, banner field"
|
|
528
|
+
@tier "presentation"
|
|
529
|
+
iconField : string = ""
|
|
530
|
+
@label "Which field is the document icon?"
|
|
531
|
+
@description "Name of the field on the loaded record whose icon name renders beside the document title (e.g. icon). Empty = no icon, the default."
|
|
532
|
+
@synonyms "icon field, glyph field"
|
|
533
|
+
@tier "presentation"
|
|
510
534
|
editLabel : string = "Edit"
|
|
511
535
|
@label "Edit button label"
|
|
512
536
|
@description "Label of the quiet button above the document body that starts an editing session (the body itself is also clickable)."
|
|
@@ -587,6 +611,8 @@ orbital RecordItemOrbital {
|
|
|
587
611
|
@tier "internal"
|
|
588
612
|
documentBodyContent : render-ui = {
|
|
589
613
|
actions: @config.actions
|
|
614
|
+
coverImage: @entity.coverUrl
|
|
615
|
+
icon: @entity.iconName
|
|
590
616
|
editEvent: EDIT_CONTENT
|
|
591
617
|
editLabel: @config.editLabel
|
|
592
618
|
id: @entity.id
|
|
@@ -604,6 +630,8 @@ orbital RecordItemOrbital {
|
|
|
604
630
|
@tier "internal"
|
|
605
631
|
documentEditingContent : render-ui = {
|
|
606
632
|
actions: @config.actions
|
|
633
|
+
coverImage: @entity.coverUrl
|
|
634
|
+
icon: @entity.iconName
|
|
607
635
|
autosaveHint: @config.autosaveHint
|
|
608
636
|
contentChangeEvent: CONTENT_CHANGE
|
|
609
637
|
doneEvent: DONE_EDITING
|
|
@@ -675,6 +703,14 @@ orbital RecordItemOrbital {
|
|
|
675
703
|
@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
704
|
@synonyms "property saved, metadata changed, field committed, update property"
|
|
677
705
|
@tier "domain"
|
|
706
|
+
RELATION_OPEN -> external {
|
|
707
|
+
field : string!
|
|
708
|
+
id : string!
|
|
709
|
+
name : string
|
|
710
|
+
}
|
|
711
|
+
@description "A relation chip in the details drawer was clicked (a tag, the author, the category); id is the linked row's id. Hosts route it to the linked record's own surface — a navigate, a scoped overlay, a filtered list."
|
|
712
|
+
@synonyms "open relation, linked record clicked, tag clicked, chip clicked"
|
|
713
|
+
@tier "domain"
|
|
678
714
|
}
|
|
679
715
|
|
|
680
716
|
listens {
|
|
@@ -718,6 +754,7 @@ orbital RecordItemOrbital {
|
|
|
718
754
|
detailsContent : render-ui = {
|
|
719
755
|
entity: @entity.loadedRow
|
|
720
756
|
fields: @config.metaFields
|
|
757
|
+
relationEvent: RELATION_OPEN
|
|
721
758
|
title: @config.detailsTitle
|
|
722
759
|
type: document-details
|
|
723
760
|
}
|