From c8417f4e13b71469cfb3aee79bd6c27f8d381c23 Mon Sep 17 00:00:00 2001 From: os-help Date: Mon, 10 Aug 2026 10:38:56 +0000 Subject: [PATCH] =?UTF-8?q?docs(ui):=20state=20the=20`type:=20'form'`=20ac?= =?UTF-8?q?tion=20contract=20=E2=80=94=20in-shell,=20submit=20lands=20on?= =?UTF-8?q?=20the=20record=20(#7245)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 Claude-Session: https://claude.ai/code/session_01QnGHxdcvU63spYLamVnpkX --- content/docs/protocol/objectui/actions.mdx | 29 +++++++++++++++++++++- content/docs/ui/actions.mdx | 2 +- content/docs/ui/forms.mdx | 19 +++++++++++--- content/docs/ui/public-data-collection.mdx | 2 ++ 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/content/docs/protocol/objectui/actions.mdx b/content/docs/protocol/objectui/actions.mdx index 7d3fc06d38..53b2afc6ec 100644 --- a/content/docs/protocol/objectui/actions.mdx +++ b/content/docs/protocol/objectui/actions.mdx @@ -49,7 +49,7 @@ The `type` field selects how an action is dispatched. The complete enum is **`sc | `flow` | Invoke a Screen/automation Flow by name. | Yes | | `modal` | Open a modal/page by name. | Yes | | `api` | Call an API endpoint (`method` defaults to `POST`). | Yes | -| `form` | Open a FormView by name, routed to `/console/forms/:name`. | Yes | +| `form` | Open a FormView by name, routed to `/console/forms/:name` **inside the console shell**; the submit lands on the created record. | Yes | `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. @@ -159,6 +159,33 @@ type: form target: customer_quick_edit ``` +#### What `type: 'form'` promises + +`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)): + +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. +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. +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. + +| | Public path (`/console/f/:slug`) | Form action / internal path (`/console/forms/:name`) | +| --- | --- | --- | +| Audience | anonymous visitors | authed operators — where `type: 'form'` sends them | +| Chrome | standalone page, no console chrome | nested inside the console shell | +| Default when `submitBehavior` is omitted | `thank-you` confirmation panel | redirect to the created record | + +The four `submitBehavior` kinds and their options are documented on the [Forms guide](/docs/ui/forms). + + +**Current renderer status (2026-08-10).** The console shipping at the pinned +`.objectui-sha` does **not** implement this contract yet: it renders +`/console/forms/:name` as a top-level page outside the shell, and it falls back +to the `thank-you` panel in both modes. The statement 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, an internal FormView that must land on its new record has to declare +`submitBehavior` explicitly. + + ### Modal Actions 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. diff --git a/content/docs/ui/actions.mdx b/content/docs/ui/actions.mdx index 700cbb18cb..c7850372e0 100644 --- a/content/docs/ui/actions.mdx +++ b/content/docs/ui/actions.mdx @@ -21,7 +21,7 @@ The types you'll actually use: | `url` | Navigate / open a link | `target` is the URL (`${ctx.record.id}` interpolation supported) | | `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`) | | `api` | Call an HTTP endpoint directly | `target` is the endpoint; `method` / `bodyShape` / `bodyExtra` shape the request | -| `form` | Open a form view, prefilled with the current record | `target` names the FormView; routed to `/forms/:target?recordId=…` | +| `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=…` | Prefer `confirmText`/`params` over the two action properties that are **not diff --git a/content/docs/ui/forms.mdx b/content/docs/ui/forms.mdx index b6f529c2d9..36a1e9c997 100644 --- a/content/docs/ui/forms.mdx +++ b/content/docs/ui/forms.mdx @@ -17,7 +17,7 @@ Both modes: - Use the same `FormView` Zod schema (`@objectstack/spec/ui`) - Render through the same `FormPage` renderer shipped by the ObjectUI console (a separate package/repo) - Honor `?prefill_=` URL params -- Honor `submitBehavior` (thank-you / redirect / continue / next-record) +- Honor `submitBehavior` (thank-you / redirect / continue / next-record) — with **mode-aware defaults** when it is omitted (see [§8](#8-submitbehavior--what-happens-after-submit)) 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. @@ -341,14 +341,27 @@ formViews: { | `kind` | Renderer behavior | |---|---| -| `thank-you` *(default)* | Replace the form with a confirmation panel (`title`, `message`). | +| `thank-you` | Replace the form with a confirmation panel (`title`, `message`). | | `redirect` | `window.location.assign(url)` after `delayMs` (defaults to 0). | | `continue` | Re-read prefill values and reset state — user can submit another response without reload. | | `next-record` | Stub for queue contexts; falls back to thank-you when no queue is wired. | +### The default is mode-aware + +`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)): + +| Mode | Default when `submitBehavior` is omitted | Why | +|---|---|---| +| **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. | +| **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. | + +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. + +> **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. + ## 9. `type: 'form'` action — declarative form launchers -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`. +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). {/* os:check */} ```ts diff --git a/content/docs/ui/public-data-collection.mdx b/content/docs/ui/public-data-collection.mdx index 8b6a170748..14fddf5e3a 100644 --- a/content/docs/ui/public-data-collection.mdx +++ b/content/docs/ui/public-data-collection.mdx @@ -34,6 +34,8 @@ formViews: { } ``` +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). + That `sharing` block wires the anonymous endpoints automatically: ```