Skip to content

Commit c8417f4

Browse files
os-helpclaude
andcommitted
docs(ui): state the type: 'form' action contract — in-shell, submit lands on the record (#7245)
`type: 'form'` is the platform's first-class way to open an object's form, but nothing in the hand-written docs said what it promises the author. The docs described the route and the four `submitBehavior` kinds, and flatly labelled `thank-you` "(default)" — which is today's mode-blind renderer fallback, not the contract. State the ruled contract where authors read about form actions: - Action Protocol / Form Actions gains a "What `type: 'form'` promises" subsection — renders in-shell (the route stays, so deep-linking survives), internal submit defaults to a redirect to the created record, and an explicit `submitBehavior` always wins — plus a public-vs-internal table. - Forms guide §8 replaces the bare "(default)" marker with a mode-aware defaults table: `thank-you` for the public `/console/f/:slug` path, redirect-to-created-record for the internal path. - Forms guide §9 and the two action tables (`ui/actions`, protocol `Action Types`) restate the in-shell/lands-on-record clause and link the contract. - The public-collection guide says the explicit `thank-you` there is copy, not behavior, and points at the split. Where the shipped renderer does not match the contract yet, the pages say so inline rather than documenting the defect: a dated "current renderer status" note on both pages pointing at the ObjectUI half tracked from the #7245 thread. Part of #7245. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QnGHxdcvU63spYLamVnpkX
1 parent 1788e19 commit c8417f4

4 files changed

Lines changed: 47 additions & 5 deletions

File tree

content/docs/protocol/objectui/actions.mdx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The `type` field selects how an action is dispatched. The complete enum is **`sc
4949
| `flow` | Invoke a Screen/automation Flow by name. | Yes |
5050
| `modal` | Open a modal/page by name. | Yes |
5151
| `api` | Call an API endpoint (`method` defaults to `POST`). | Yes |
52-
| `form` | Open a FormView by name, routed to `/console/forms/:name`. | Yes |
52+
| `form` | Open a FormView by name, routed to `/console/forms/:name` **inside the console shell**; the submit lands on the created record. | Yes |
5353

5454
`target` is the canonical binding for every non-`script` type, and since protocol 17 it is the **only** one. The deprecated `execute` alias was removed (#3855): authoring it is rejected with an error naming the replacement, not silently stripped. Run `os migrate meta --from 16` to rewrite existing sources automatically — the removal also ships in the machine-readable change manifest (`spec-changes.json`), which composes across however many majors you are jumping.
5555

@@ -159,6 +159,33 @@ type: form
159159
target: customer_quick_edit
160160
```
161161

162+
#### What `type: 'form'` promises
163+
164+
`type: 'form'` is the platform's first-class way to open an object's form — the shape that replaced the `type: 'modal'` object fallback — so its contract is stated here rather than left to each renderer to decide (ruled 2026-08-10 on [#7245](https://github.com/objectstack-ai/objectstack/issues/7245)):
165+
166+
1. **It renders in-shell.** The internal form route nests **inside** the console shell: sidebar, app navigation and breadcrumb stay. A form action never drops the operator onto an unchromed standalone page with the browser Back button as the only way home. The route survives too — `/console/forms/:name` stays deep-linkable and bookmarkable, which is what a route buys you over a transient dialog.
167+
2. **A successful submit lands on the created record.** On the internal path the default post-submit behavior is a **redirect to the record that was just created**, not a confirmation panel. The `thank-you` panel is the default for the **public** `/console/f/:slug` path only, where there is no record the anonymous submitter is allowed to see.
168+
3. **An explicit `submitBehavior` on the FormView always wins**, in either mode. The defaults above are what you get for declaring nothing; they are not a ceiling.
169+
170+
| | Public path (`/console/f/:slug`) | Form action / internal path (`/console/forms/:name`) |
171+
| --- | --- | --- |
172+
| Audience | anonymous visitors | authed operators — where `type: 'form'` sends them |
173+
| Chrome | standalone page, no console chrome | nested inside the console shell |
174+
| Default when `submitBehavior` is omitted | `thank-you` confirmation panel | redirect to the created record |
175+
176+
The four `submitBehavior` kinds and their options are documented on the [Forms guide](/docs/ui/forms).
177+
178+
<Callout type="warn">
179+
**Current renderer status (2026-08-10).** The console shipping at the pinned
180+
`.objectui-sha` does **not** implement this contract yet: it renders
181+
`/console/forms/:name` as a top-level page outside the shell, and it falls back
182+
to the `thank-you` panel in both modes. The statement above is the contract —
183+
the renderer half is tracked in ObjectUI and linked from the
184+
[#7245 thread](https://github.com/objectstack-ai/objectstack/issues/7245). Until
185+
it lands, an internal FormView that must land on its new record has to declare
186+
`submitBehavior` explicitly.
187+
</Callout>
188+
162189
### Modal Actions
163190

164191
Open a modal/page by name. `target` is resolved as a **page** first, then as an object (which opens that object's create/edit form); when it names neither, the action falls through to its server-side handler.

content/docs/ui/actions.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The types you'll actually use:
2121
| `url` | Navigate / open a link | `target` is the URL (`${ctx.record.id}` interpolation supported) |
2222
| `modal` | Open a modal page — client-side only, no server dispatch | `target` names the modal page (to collect input *and* run logic, use `script` + `params`) |
2323
| `api` | Call an HTTP endpoint directly | `target` is the endpoint; `method` / `bodyShape` / `bodyExtra` shape the request |
24-
| `form` | Open a form view, prefilled with the current record | `target` names the FormView; routed to `/forms/:target?recordId=…` |
24+
| `form` | Open a form view, prefilled with the current record — in-shell, and the submit lands on the created record ([contract](/docs/protocol/objectui/actions#what-type-form-promises)) | `target` names the FormView; routed to `/forms/:target?recordId=…` |
2525

2626
<Callout type="warn">
2727
Prefer `confirmText`/`params` over the two action properties that are **not

content/docs/ui/forms.mdx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Both modes:
1717
- Use the same `FormView` Zod schema (`@objectstack/spec/ui`)
1818
- Render through the same `FormPage` renderer shipped by the ObjectUI console (a separate package/repo)
1919
- Honor `?prefill_<field>=<value>` URL params
20-
- Honor `submitBehavior` (thank-you / redirect / continue / next-record)
20+
- Honor `submitBehavior` (thank-you / redirect / continue / next-record) — with **mode-aware defaults** when it is omitted (see [§8](#8-submitbehavior--what-happens-after-submit))
2121

2222
A **public form** is the Salesforce *Web-to-Lead* style embeddable form — declare a `FormView` with `sharing.allowAnonymous: true`, give it a `publicLink`, and the framework wires the anonymous REST endpoints automatically.
2323

@@ -341,14 +341,27 @@ formViews: {
341341

342342
| `kind` | Renderer behavior |
343343
|---|---|
344-
| `thank-you` *(default)* | Replace the form with a confirmation panel (`title`, `message`). |
344+
| `thank-you` | Replace the form with a confirmation panel (`title`, `message`). |
345345
| `redirect` | `window.location.assign(url)` after `delayMs` (defaults to 0). |
346346
| `continue` | Re-read prefill values and reset state — user can submit another response without reload. |
347347
| `next-record` | Stub for queue contexts; falls back to thank-you when no queue is wired. |
348348

349+
### The default is mode-aware
350+
351+
`submitBehavior` is optional, and **what you get for omitting it depends on the mode** — a public collection form and an authed create have different right answers (ruled 2026-08-10 on [#7245](https://github.com/objectstack-ai/objectstack/issues/7245)):
352+
353+
| Mode | Default when `submitBehavior` is omitted | Why |
354+
|---|---|---|
355+
| **Public** (`/console/f/:slug`) | `{ kind: 'thank-you' }` — the confirmation panel | The anonymous submitter may not read the record back, so a receipt is all there is to show. |
356+
| **Internal** (`/console/forms/:name`) | Redirect to the **created record** | An operator who just created a record belongs on that record, not on a "submission received" receipt. |
357+
358+
An explicit `submitBehavior` overrides the default in either mode, so nothing here removes an option — it only changes what an author gets for declaring nothing.
359+
360+
> **Current renderer status (2026-08-10).** The console shipping at the pinned `.objectui-sha` still applies `thank-you` as the default in **both** modes. The table above is the contract; the renderer half is tracked in ObjectUI and linked from the [#7245 thread](https://github.com/objectstack-ai/objectstack/issues/7245). Until it lands, declare `submitBehavior` explicitly on an internal FormView that must land somewhere specific.
361+
349362
## 9. `type: 'form'` action — declarative form launchers
350363

351-
App actions can declare `type: 'form'` to open a FormView without resorting to free-form URLs. The `target` is the FormView name; the runtime navigates to `/console/forms/:name`.
364+
App actions can declare `type: 'form'` to open a FormView without resorting to free-form URLs. The `target` is the FormView name; the runtime navigates to `/console/forms/:name` **inside the console shell** — the operator keeps the sidebar, navigation and breadcrumb, and a successful submit lands on the created record per the mode-aware default above. The full contract is stated on the [Action Protocol page](/docs/protocol/objectui/actions#what-type-form-promises).
352365

353366
{/* os:check */}
354367
```ts

content/docs/ui/public-data-collection.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ formViews: {
3434
}
3535
```
3636

37+
The `submitBehavior` above sets the **copy**, not the behavior: `thank-you` is already the default on the public path, because an anonymous submitter may not read the record back. The internal path defaults the other way — a `type: 'form'` action lands the operator on the record it just created — so do not carry a public form's confirmation panel over to an internal one by habit. See [`submitBehavior` is mode-aware](/docs/ui/forms#the-default-is-mode-aware).
38+
3739
That `sharing` block wires the anonymous endpoints automatically:
3840

3941
```

0 commit comments

Comments
 (0)