Skip to content

Commit 476af72

Browse files
os-zhuangclaude
andauthored
docs: action disabled/errorMessage/undoable + predicate scope + object-form screen wizard (#2007)
Fills hand-written doc + authoring-skill gaps for capabilities that shipped but were undocumented: - actions.mdx: add `errorMessage`/`undoable` to the field table and feedback list; add an "Evaluation context" subsection (predicates are bare CEL against the record; `record.<field>` resolves identically on every surface; narrower scope than record-alert's os.*); warn against `${…}`/`{…}`-wrapped predicates. - flow.mdx: document the `screen` node's object-form mode (`objectName`/`mode`/ `recordId`/`defaults`/`idVariable`) and the atomic parent+child save / id rebind that powers multi-step object-form wizards. - skills/objectstack-ui: document `disabled` (CEL, greys vs `visible` hides), `errorMessage`, `undoable`, and the bare-CEL `record.*` predicate convention, with a worked Reassign example. Docs/skill only; no shipped-package changes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b8471d5 commit 476af72

4 files changed

Lines changed: 102 additions & 5 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
---
3+
4+
docs: document action `disabled`/`errorMessage`/`undoable`, the action
5+
predicate evaluation context (bare CEL against `record`, consistent across
6+
surfaces), and the `screen` node's `object-form` wizard mode
7+
(`objectName`/`mode`/`defaults`/`idVariable` + atomic child persistence).
8+
Also fills the same gaps in the `objectstack-ui` authoring skill. Docs/skill
9+
only — no shipped-package changes.

content/docs/guides/metadata/flow.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,36 @@ rather than evaluating an arbitrary JavaScript string:
206206
}
207207
```
208208

209+
**Screen (object form):**
210+
211+
A `screen` node normally renders a flat `fields` list. Set `config.objectName`
212+
to instead render an object's **entire** create/edit form — including any
213+
master-detail child grids (`inlineEdit`) — as one wizard step. The client
214+
renders the object form and, on save, **persists the record itself** (parent +
215+
inline children atomically); the flow then resumes with the new record's id
216+
bound to `config.idVariable` so a later step can reference it.
217+
218+
```typescript
219+
{
220+
id: 'new_opportunity',
221+
type: 'screen',
222+
label: 'Opportunity Details',
223+
config: {
224+
objectName: 'opportunity', // render this object's full form
225+
mode: 'create', // 'create' (default) | 'edit'
226+
// recordId: '{account_id}', // required for mode: 'edit'
227+
defaults: { // pre-filled values (interpolated)
228+
account: '{account_id}',
229+
stage: 'prospecting',
230+
},
231+
idVariable: 'opportunity_id', // bind the saved record's id for later steps
232+
},
233+
}
234+
```
235+
236+
This is how a single flow walks the user through several full object forms in
237+
sequence (e.g. lead → account → opportunity), each step saving its own record.
238+
209239
## Structured control flow (ADR-0031)
210240

211241
`loop`, `parallel`, and `try_catch` are **structured control-flow constructs**

content/docs/protocol/objectui/actions.mdx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ interface Action {
180180
// UX
181181
confirmText?: string; // Confirmation message before execution
182182
successMessage?: string; // Toast shown after success
183+
errorMessage?: string; // Toast shown on failure (overrides the raw error)
184+
undoable?: boolean; // Offer an Undo affordance after a single-record update succeeds
183185
refreshAfter?: boolean; // Reload the view after execution (default false)
184186
resultDialog?: ResultDialog; // One-shot reveal of API response values
185187
shortcut?: string; // Keyboard shortcut, e.g. "Ctrl+S"
@@ -247,6 +249,23 @@ target: /api/orders/ship
247249
disabled: "record.status != 'paid'"
248250
```
249251

252+
<Callout type="warn">
253+
Predicates are **bare CEL** — `record.status == 'paid'`, not `${record.status == 'paid'}` and not `{record.status} == 'paid'` (in CEL, `{…}` is a map literal). A `${…}`-wrapped or brace-wrapped predicate is not evaluated as a boolean.
254+
</Callout>
255+
256+
#### Evaluation context
257+
258+
Action predicates evaluate against the **record the action is attached to**. Both `record.<field>` and the bare field name resolve to the current record's value:
259+
260+
```yaml
261+
disabled: "record.status == 'converted'" # preferred — works on every surface
262+
disabled: "status == 'converted'" # bare field — also resolves to the record
263+
```
264+
265+
Prefer the `record.<field>` form: it reads unambiguously and resolves identically on **every** surface an action renders (`record_header`, `record_more`, `list_item`, related lists). The bare-field form is supported for brevity but is easy to confuse with a local variable.
266+
267+
This is a narrower scope than [record-alert](/docs/protocol/objectui/record-alert) conditions (which also expose `os.user` / `os.org` / `os.env`) — action predicates see the record only.
268+
250269
## Confirmation & Feedback
251270

252271
Actions express confirmation and feedback through dedicated string fields — there is no nested confirm-dialog object with `requireTyping`/`confirmLabel`.
@@ -265,10 +284,25 @@ refreshAfter: true
265284
```
266285

267286
- `confirmText` — message shown in a confirm dialog before the action runs.
268-
- `successMessage` — toast shown after a successful run.
287+
- `successMessage` — toast shown after a successful run. When omitted the UI shows a generic "Action completed" toast, so set this for any action whose outcome isn't self-evident.
288+
- `errorMessage` — toast shown when the action fails; overrides the raw server error with author-controlled copy.
289+
- `undoable` — for a single-record update action, offer an **Undo** affordance in the success toast (and via `Ctrl+Z`). The runtime captures the record's prior values before the write and restores them on undo. Only meaningful for reversible single-record mutations.
269290
- `refreshAfter` — reload the current view after success.
270291
- `mode` — a semantic hint (`create` / `edit` / `delete` / `custom`) the UI uses to pick confirm copy and default variants.
271292

293+
```yaml
294+
# A reversible owner reassignment: custom success/error copy + Undo
295+
name: reassign_lead
296+
label: Reassign Lead
297+
type: api
298+
target: lead
299+
params:
300+
- { field: assigned_to, required: true }
301+
undoable: true
302+
successMessage: Lead reassigned.
303+
errorMessage: Couldn't reassign this lead — try again.
304+
```
305+
272306
### Result Dialog (one-shot reveals)
273307

274308
For API actions that return a value the user must copy **now** (a freshly minted secret, TOTP URI, or backup codes), `resultDialog` renders the response in an acknowledge-only dialog instead of a toast:

skills/objectstack-ui/SKILL.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,11 +1088,35 @@ Register them under `defineStack({ actions: [...] })`.
10881088
| `list_toolbar` | Bulk action on selected rows (`input.selectedIds`) |
10891089
| `global` | Global action launcher (utility bar) |
10901090

1091-
### Visibility & Confirmation
1091+
### Visibility, Disable & Feedback
10921092

1093-
Use `visible` (CEL predicate, prefer the `P\`...\`` tagged template) to
1094-
gate the action against the current record. Set `confirmText` for any
1095-
destructive or irreversible operation.
1093+
- `visible` — CEL predicate (prefer the `P\`...\`` tagged template); when false the action is **hidden**.
1094+
- `disabled``boolean` **or** a CEL predicate; when true the action **shows but greys out**. Use this (not `visible`) when the action should stay discoverable but locked in the current state.
1095+
- `confirmText` — set for any destructive or irreversible operation.
1096+
- `successMessage` / `errorMessage` — author-controlled toast copy on success / failure. Always set `successMessage` for non-obvious outcomes; without it the UI shows a generic "Action completed" toast.
1097+
- `undoable: true` — on a single-record update, offers an **Undo** in the success toast (and `Ctrl+Z`); the runtime snapshots prior values and restores them.
1098+
1099+
Predicates are **bare CEL**`record.status == "converted"`, evaluated against
1100+
the current record. `record.<field>` resolves identically on every surface
1101+
(`record_header`, `list_item`, …); prefer it over the bare-field form. Never
1102+
wrap a predicate in `${…}` or `{…}` braces (see `objectstack-formula`).
1103+
1104+
```typescript
1105+
export const ReassignLeadAction: Action = {
1106+
name: 'reassign_lead',
1107+
label: 'Reassign Lead',
1108+
objectName: 'lead',
1109+
type: 'api',
1110+
target: 'lead',
1111+
locations: ['record_header', 'list_item'],
1112+
// Greys out (stays visible) once the lead is converted:
1113+
disabled: P`record.status == "converted"`,
1114+
params: [{ field: 'assigned_to', required: true }],
1115+
undoable: true, // success toast offers Undo; Ctrl+Z works too
1116+
successMessage: 'Lead reassigned.',
1117+
errorMessage: "Couldn't reassign this lead — try again.",
1118+
};
1119+
```
10961120

10971121
### Examples
10981122

0 commit comments

Comments
 (0)