Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion content/docs/protocol/objectui/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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).

<Callout type="warn">
**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.
</Callout>

### 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.
Expand Down
2 changes: 1 addition & 1 deletion content/docs/ui/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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=…` |

<Callout type="warn">
Prefer `confirmText`/`params` over the two action properties that are **not
Expand Down
19 changes: 16 additions & 3 deletions content/docs/ui/forms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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_<field>=<value>` 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.

Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions content/docs/ui/public-data-collection.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```
Expand Down
Loading