Skip to content

Commit ab0bc89

Browse files
docs(spec): onSuccess.navigate and a url target say an in-app path is an app-qualified console route (#25458) (#26435)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBErsDvMNeedf4Bt2ucCK9Yt)_ Fixes #25458 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么:** 只改说明文字。`onSuccess.navigate` 与 `type: 'url'` 动作的 `target` 现在写明:应用内路径是控制台路由,记录页是 `/apps/APP/OBJECT/record/ID`,APP 是应用的路由段(包 id,应用名也能解析);规范里的示例与协议文档的示例同步改正。 - **为什么改:** 原文只说「以 `/` 开头的应用内路径」,没说要带应用段,作者照写 `/crm_opportunity/ID` 就走偏了;hotcrm#2247 裁决 C 第 2(b) 项要求把这一点写清楚,且不新增写法。 - **风险与回滚:** 零运行时风险,schema 接受与拒绝的取值完全不变,只有 describe 文本、生成的参考文档和一页手写文档变了;回滚即 revert 本 PR。 - **先看:** `packages/spec/src/ui/action.zod.ts`(onSuccess 文档块新增的 console route 一段,以及 `navigate` 的 describe)、`content/docs/protocol/objectui/actions.mdx`(URL Actions 一节)、`.changeset/25458-navigate-console-route.md`。 - **两分钟验证:** 在本分支跑 `grep -n "console route" packages/spec/src/ui/action.zod.ts`,预期 4 处命中(target 的文档块与 describe、onSuccess 的文档块与 navigate 的 describe)。 - **需要你做的:** 无,席位落地。 ## What changes Wording only, on the route ruling C on objectstack-ai/hotcrm#2247 ordered as item 2(b). No schema accepts or refuses anything it did not before; no new authorable form (no `${ctx.app}` member, no object-plus-record target). - `packages/spec/src/ui/action.zod.ts`, `onSuccess` docblock: a new paragraph says that an in-app path is a console route, written without the console's mount prefix (`/_console` under the framework CLI). The record page is `/apps/APP/OBJECT/record/ID`, and the object's list is `/apps/APP/OBJECT`. `APP` is the app's route segment (ADR-0048): its package id, e.g. `app.objectstack.hotcrm`. The app's `name` resolves too, and it is the only segment that reaches an app other than the first in a package that ships several. A `./` path resolves against the current route. The contract declares no app-relative form, so write the app segment. - Same docblock: the example `navigate: '/apps/crm/tasks/${result.id}'` becomes `'/apps/crm/tasks/record/${result.id}'`. The old one worked only through the console's shorthand redirect, which fires only when the last segment looks like a record id. - `onSuccess.navigate` `.describe()`: the same facts in one sentence after the scheme set. - `target` docblock and `.describe()` (the `type: 'url'` scheme set): an in-app path is a console route, and a record is `/apps/APP/OBJECT/record/ID`. It points to `onSuccess.navigate` for what `APP` is, so the explanation lives in one place. - `content/docs/protocol/objectui/actions.mdx`, URL Actions: the same sentence. The example `target: /_console/dashboards/sales_overview` becomes `/apps/crm/dashboard/sales_overview`. The old one carried the mount prefix the router adds itself, and the console has no `dashboards` route at the root; the dashboard route is `dashboard/:dashboardName` under `/apps/:appName/*`. - Regenerated with `gen:schema && gen:docs`: `content/docs/references/ui/action.mdx`, `ui/component.mdx`, `data/object.mdx` and `kernel/metadata-plugin.mdx` (the last three embed `ActionSchema`'s `target`). - `.changeset/25458-navigate-console-route.md`: `'@objectstack/spec': patch`. The console's routes, quoted from `main` at `9a0108cd`: - `ui/apps/console/src/App.tsx:332`, the app mount: `Route path="/apps/:appName/*" element={AppContent}`. - `ui/packages/app-shell/src/console/AppContent.tsx:1443`, the record route inside it: `Route path=":objectName/record/:recordId"`. At `:1478` is the shorthand `":objectName/:maybeRecordId"`, which redirects only when `looksLikeRecordId` holds (at least 6 URL-safe characters, not a reserved second segment). - `ui/packages/app-shell/src/utils/appRoute.ts:46`, `appRouteSegment`: the package id `_packageId`, falling back to `name`. `matchAppBySegment` matches the package id first and then the name. ## Tests Head `a7e78c3d`. - `pnpm --filter @objectstack/spec build`: lock VERDICT command-exit 0. - `pnpm --filter @objectstack/spec test`: Test Files 727 passed (727), Tests 22009 passed, 1 todo (22010). Lock VERDICT command-exit 0. - Targeted: `src/ui/action.test.ts`, `src/ui/action-on-success.test.ts` and `src/ui/action-url-target-scheme-set.test.ts` pass, 322 tests. - `pnpm --filter @objectstack/spec check:generated`: exit 0, all 14 generated artifacts up to date. - No ablation: the diff changes describe text and docs, not a behaviour a test pins. ## Acceptance notes - **The card's premise is partly stale.** objectui#13379, the console half, landed when objectui moved into `ui/`. `ui/packages/app-shell/src/utils/hostInConsolePath.ts` is wired into `useConsoleActionRuntime.tsx` and `RecordDetailView.tsx`. On `main` today, a rooted path whose first segment names no console route, such as `/crm_opportunity/${result.id}`, is hosted under the app the user is in. It no longer redirects Home. The wording does not promise that behaviour: the triage line and the dispatch exclude an app-relative form. The docblock names it as the console's accommodation and not a form the schema promises. The description and its example now match what the console does for the form they teach. - The same scheme-set sentence ("an in-app path starting with / or ./") also appears outside this card's file surface and is left unchanged: `packages/spec/src/ui/dashboard.zod.ts` (`actionUrl`, ~:143/:151), `packages/spec/src/ui/view.zod.ts` (the empty-state link url, ~:1971) and `packages/spec/src/ui/component.zod.ts` (the action row's `target` and `onSuccess`, ~:4066, :4085, :4166). This is noted here and not filed as a card; carrier: none. - `skills/objectstack-ui/rules/actions.md:71` already writes the app-qualified record route (`/apps/crm/opportunity/record/...`). It agrees with the new text and is untouched (governed surface). - Both docblocks still cite the console's classifier as `navigation-target.ts`. The file today is `ui/packages/core/src/actions/navigationTarget.ts`. This is a naming drift outside the wording asked for here; noted, not touched. ## Verification <details><summary>dispatch-gates (head a7e78c3)</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 100 derived commands over 7 changed paths, 56 changed lines. The first pass gave 93 exit 0 and 7 exit 3 (PREREQUISITE NOT MET). I then built the closures `@objectstack/lint...`, `@objectstack/objectql...` and `@objectstack/client-react...` (turbo, 37 of 37 tasks; lock VERDICT command-exit 0) and ran `git fetch --unshallow origin main`. After that, six of the seven re-ran to exit 0: `check-plugin-teardown-shape.mjs --self-test`, `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples`, `check:docs-transcript-drift` and `check:lean-entry-closure`. NOT MEASURED: `pnpm check:dual-build-cjs-loads`, exit 3. It needs every workspace package's `dist` (a full `pnpm build`), so I left it to CI. `--ran` reconciliation: `✓ dispatch-gates --ran: 100 derived famil(ies) accounted for — 99 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).` </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01WGufzi6gpRSxJRE6UzJn2x)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 07586f5 commit ab0bc89

7 files changed

Lines changed: 44 additions & 12 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): `onSuccess.navigate` and a `type: 'url'` action's `target` say that an in-app path is an app-qualified console route (`/apps/APP/OBJECT/record/ID`) (#25458)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Wording only; no schema accepts or refuses anything new. The describe text of `ActionSchema.onSuccess.navigate` and of `ActionSchema.target` said "an in-app path starting with / or ./" and never said which path. They now say that an in-app path is a console route, written without the console's mount prefix, and that a record is `/apps/APP/OBJECT/record/ID`. `APP` is the app's route segment (ADR-0048): its package id, and its `name` also resolves. No app-relative form is declared and no `${ctx.*}` member names the current app, so write the app segment. The docblock example `/apps/crm/tasks/${result.id}` becomes `/apps/crm/tasks/record/${result.id}`, the route it was only redirected to.

‎content/docs/protocol/objectui/actions.mdx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,13 @@ body:
8888

8989
Navigate to an internal route or external URL. `target` supports `${param.X}` and `${ctx.X}` interpolation (renderers `encodeURIComponent` interpolated values before substituting them into query positions).
9090

91+
An internal route is a console route, written without the console's mount prefix (`/_console` under the framework CLI; the router adds it). Object, record and dashboard routes carry the app segment: a record is `/apps/APP/OBJECT/record/ID`, where `APP` is the app's route segment, its package id (its `name` also resolves). No app-relative form is declared, so write the app segment. The same holds for an action's `onSuccess.navigate`.
92+
9193
```yaml
9294
name: view_dashboard
9395
label: View Dashboard
9496
type: url
95-
target: /_console/dashboards/sales_overview
97+
target: /apps/crm/dashboard/sales_overview
9698
9799
name: open_in_maps
98100
label: Open in Maps

‎content/docs/references/data/object.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ const result = ApiMethod.parse(data);
489489
| **locations** | `Enum<'list_toolbar' \| 'list_item' \| 'record_header' \| 'record_more' \| …>[]` | optional | Locations where this action is visible |
490490
| **component** | `Enum<'action:button' \| 'action:icon' \| 'action:menu' \| 'action:group'>` | optional | Visual component override |
491491
| **type** | `Enum<'script' \| 'url' \| 'modal' \| 'flow' \| 'api' \| 'form'>` | optional (default: `"script"`) | Action functionality type — the dispatch route. The declarative single-record field write is not a type: it is `operation: 'update'` + `patch` on the default `script` route. |
492-
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ — every other scheme and a protocol-relative //host are refused. |
492+
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (a console route; a record is /apps/APP/OBJECT/record/ID, APP being the app's route segment — see onSuccess.navigate) — every other scheme and a protocol-relative //host are refused. |
493493
| **openIn** | `Enum<'self' \| 'new-tab'>` | optional | For type:'url' — where to open `target`. 'new-tab' opens a new browser tab; 'self' navigates in place. When omitted, external/absolute URLs open in a new tab and relative URLs navigate in place. Static execution option — keep it OUT of `params` (which is user-input-collection only). |
494494
| **body** | `{ language: 'expression'; source: string } \| { language: 'js'; source: string; capabilities?: Enum<'api.read' \| 'api.write' \| 'api.transaction' \| 'crypto.uuid' \| 'log'>[]; timeoutMs?: integer; … }` | optional | Action body — expression (L1) or sandboxed JS (L2). Only used when type is `script`. |
495495
| **operation** | `Enum<'update'>` | optional | The declarative single-record field write, mirroring a list view's `bulkActionDefs`: `'update'` applies `patch` (merged under the collected `params`) to the current record on the data plane AS THE CALLER — never system-elevated — so the caller's permissions, the object's hooks and its validations fire as for a user edit. `type` stays at its default `'script'` (the platform action route the write is performed on); `target`/`body`/`method`/`bodyExtra` are refused beside it. `'delete'` and `'custom'` have no row-level form. |

‎content/docs/references/kernel/metadata-plugin.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ const result = MetadataBulkResultSchema.parse(data);
322322
| **locations** | `Enum<'list_toolbar' \| 'list_item' \| 'record_header' \| 'record_more' \| …>[]` | optional | Locations where this action is visible |
323323
| **component** | `Enum<'action:button' \| 'action:icon' \| 'action:menu' \| 'action:group'>` | optional | Visual component override |
324324
| **type** | `Enum<'script' \| 'url' \| 'modal' \| 'flow' \| 'api' \| 'form'>` | optional (default: `"script"`) | Action functionality type — the dispatch route. The declarative single-record field write is not a type: it is `operation: 'update'` + `patch` on the default `script` route. |
325-
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ — every other scheme and a protocol-relative //host are refused. |
325+
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (a console route; a record is /apps/APP/OBJECT/record/ID, APP being the app's route segment — see onSuccess.navigate) — every other scheme and a protocol-relative //host are refused. |
326326
| **openIn** | `Enum<'self' \| 'new-tab'>` | optional | For type:'url' — where to open `target`. 'new-tab' opens a new browser tab; 'self' navigates in place. When omitted, external/absolute URLs open in a new tab and relative URLs navigate in place. Static execution option — keep it OUT of `params` (which is user-input-collection only). |
327327
| **body** | `{ language: 'expression'; source: string } \| { language: 'js'; source: string; capabilities?: Enum<'api.read' \| 'api.write' \| 'api.transaction' \| 'crypto.uuid' \| 'log'>[]; timeoutMs?: integer; … }` | optional | Action body — expression (L1) or sandboxed JS (L2). Only used when type is `script`. |
328328
| **operation** | `Enum<'update'>` | optional | The declarative single-record field write, mirroring a list view's `bulkActionDefs`: `'update'` applies `patch` (merged under the collected `params`) to the current record on the data plane AS THE CALLER — never system-elevated — so the caller's permissions, the object's hooks and its validations fire as for a user edit. `type` stays at its default `'script'` (the platform action route the write is performed on); `target`/`body`/`method`/`bodyExtra` are refused beside it. `'delete'` and `'custom'` have no row-level form. |

‎content/docs/references/ui/action.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const result = ActionSchema.parse(data);
3636
| **locations** | `Enum<'list_toolbar' \| 'list_item' \| 'record_header' \| 'record_more' \| 'record_related' \| 'record_section'>[]` | optional | Locations where this action is visible |
3737
| **component** | `Enum<'action:button' \| 'action:icon' \| 'action:menu' \| 'action:group'>` | optional | Visual component override |
3838
| **type** | `Enum<'script' \| 'url' \| 'modal' \| 'flow' \| 'api' \| 'form'>` | optional (default: `"script"`) | Action functionality type — the dispatch route. The declarative single-record field write is not a type: it is `operation: 'update'` + `patch` on the default `script` route. |
39-
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ — every other scheme and a protocol-relative //host are refused. |
39+
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (a console route; a record is /apps/APP/OBJECT/record/ID, APP being the app's route segment — see onSuccess.navigate) — every other scheme and a protocol-relative //host are refused. |
4040
| **openIn** | `Enum<'self' \| 'new-tab'>` | optional | For type:'url' — where to open `target`. 'new-tab' opens a new browser tab; 'self' navigates in place. When omitted, external/absolute URLs open in a new tab and relative URLs navigate in place. Static execution option — keep it OUT of `params` (which is user-input-collection only). |
4141
| **body** | `{ language: 'expression'; source: string } \| { language: 'js'; source: string; capabilities?: Enum<'api.read' \| 'api.write' \| 'api.transaction' \| 'crypto.uuid' \| 'log'>[]; timeoutMs?: integer; … }` | optional | Action body — expression (L1) or sandboxed JS (L2). Only used when type is `script`. |
4242
| **operation** | `Enum<'update'>` | optional | The declarative single-record field write, mirroring a list view's `bulkActionDefs`: `'update'` applies `patch` (merged under the collected `params`) to the current record on the data plane AS THE CALLER — never system-elevated — so the caller's permissions, the object's hooks and its validations fire as for a user edit. `type` stays at its default `'script'` (the platform action route the write is performed on); `target`/`body`/`method`/`bodyExtra` are refused beside it. `'delete'` and `'custom'` have no row-level form. |
@@ -178,7 +178,7 @@ L2 sandboxed JS body — runs inside an isolated VM with declared capabilities
178178

179179
| Property | Type | Required | Description |
180180
| :--- | :--- | :--- | :--- |
181-
| **navigate** | `string` | ✅ | Route/URL template navigated to after the action succeeds. Interpolates $`{param.*}` (params-dialog values), $`{ctx.*}` (origin/apiBase/user/org/recordId/selection) and $`{result.*}` (the action's server response payload — NEW with this key, e.g. $`{result.id}`; on a type:'flow' action the completed run's answer, whose `output` carries the flow's isOutput variables, e.g. $`{result.output.renewal.id}`). An http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (an SPA route hop); a template that opens with $&#123; is judged after interpolation. Every other scheme and a protocol-relative //host are refused. Renderers MUST encodeURIComponent values in query positions. |
181+
| **navigate** | `string` | ✅ | Route/URL template navigated to after the action succeeds. Interpolates $`{param.*}` (params-dialog values), $`{ctx.*}` (origin/apiBase/user/org/recordId/selection) and $`{result.*}` (the action's server response payload — NEW with this key, e.g. $`{result.id}`; on a type:'flow' action the completed run's answer, whose `output` carries the flow's isOutput variables, e.g. $`{result.output.renewal.id}`). An http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (an SPA route hop); a template that opens with $&#123; is judged after interpolation. Every other scheme and a protocol-relative //host are refused. An in-app path is a console route, written without the console's mount prefix; a record is /apps/APP/OBJECT/record/ID, where APP is the app's route segment (its package id; its name also resolves). No app-relative form is declared and no $`{ctx.*}` member names the current app, so write the app segment. Renderers MUST encodeURIComponent values in query positions. |
182182
| **openIn** | `Enum<'self' \| 'newTab'>` | optional (default: `"self"`) | Where to perform the post-success navigation: 'self' (default — in-place SPA navigation, immune to popup blocking) or 'newTab'. Closed enum — no general navigation DSL. |
183183

184184

@@ -340,7 +340,7 @@ L2 sandboxed JS body — runs inside an isolated VM with declared capabilities
340340
| **type** | `Enum<'script' \| 'url' \| 'modal' \| 'flow' \| 'api' \| 'form'>` | optional (default: `"script"`) | Action functionality type — the dispatch route. The declarative single-record field write is not a type: it is `operation: 'update'` + `patch` on the default `script` route. |
341341
| **name** | `string` | optional | Machine name (lowercase snake_case) |
342342
| **label** | `string \| Record<string, string>` | optional | Display label |
343-
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ — every other scheme and a protocol-relative //host are refused. |
343+
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (a console route; a record is /apps/APP/OBJECT/record/ID, APP being the app's route segment — see onSuccess.navigate) — every other scheme and a protocol-relative //host are refused. |
344344
| **openIn** | `Enum<'self' \| 'new-tab'>` | optional | For type:'url' — where to open `target`. 'new-tab' opens a new browser tab; 'self' navigates in place. When omitted, external/absolute URLs open in a new tab and relative URLs navigate in place. Static execution option — keep it OUT of `params` (which is user-input-collection only). |
345345
| **method** | `Enum<'POST' \| 'PATCH' \| 'PUT' \| 'DELETE'>` | optional | HTTP method for type:"api" actions. Defaults to POST. |
346346
| **params** | `{ name?: string; field?: string; objectOverride?: string; label?: string \| Record<string, string>; … }[]` | optional | Input parameters required from user — an ActionParam[] DEFINITION array, never a payload map (a static request body goes in `bodyExtra`). |

‎content/docs/references/ui/component.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const result = ActionButtonPropsSchema.parse(data);
6161

6262
| Property | Type | Required | Description |
6363
| :--- | :--- | :--- | :--- |
64-
| **navigate** | `string` | ✅ | Route/URL template navigated to after the action succeeds. Interpolates $`{param.*}` (params-dialog values), $`{ctx.*}` (origin/apiBase/user/org/recordId/selection) and $`{result.*}` (the action's server response payload — NEW with this key, e.g. $`{result.id}`; on a type:'flow' action the completed run's answer, whose `output` carries the flow's isOutput variables, e.g. $`{result.output.renewal.id}`). An http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (an SPA route hop); a template that opens with $&#123; is judged after interpolation. Every other scheme and a protocol-relative //host are refused. Renderers MUST encodeURIComponent values in query positions. |
64+
| **navigate** | `string` | ✅ | Route/URL template navigated to after the action succeeds. Interpolates $`{param.*}` (params-dialog values), $`{ctx.*}` (origin/apiBase/user/org/recordId/selection) and $`{result.*}` (the action's server response payload — NEW with this key, e.g. $`{result.id}`; on a type:'flow' action the completed run's answer, whose `output` carries the flow's isOutput variables, e.g. $`{result.output.renewal.id}`). An http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (an SPA route hop); a template that opens with $&#123; is judged after interpolation. Every other scheme and a protocol-relative //host are refused. An in-app path is a console route, written without the console's mount prefix; a record is /apps/APP/OBJECT/record/ID, where APP is the app's route segment (its package id; its name also resolves). No app-relative form is declared and no $`{ctx.*}` member names the current app, so write the app segment. Renderers MUST encodeURIComponent values in query positions. |
6565
| **openIn** | `Enum<'self' \| 'newTab'>` | optional (default: `"self"`) | Where to perform the post-success navigation: 'self' (default — in-place SPA navigation, immune to popup blocking) or 'newTab'. Closed enum — no general navigation DSL. |
6666

6767

@@ -153,7 +153,7 @@ const result = ActionButtonPropsSchema.parse(data);
153153

154154
| Property | Type | Required | Description |
155155
| :--- | :--- | :--- | :--- |
156-
| **navigate** | `string` | ✅ | Route/URL template navigated to after the action succeeds. Interpolates $`{param.*}` (params-dialog values), $`{ctx.*}` (origin/apiBase/user/org/recordId/selection) and $`{result.*}` (the action's server response payload — NEW with this key, e.g. $`{result.id}`; on a type:'flow' action the completed run's answer, whose `output` carries the flow's isOutput variables, e.g. $`{result.output.renewal.id}`). An http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (an SPA route hop); a template that opens with $&#123; is judged after interpolation. Every other scheme and a protocol-relative //host are refused. Renderers MUST encodeURIComponent values in query positions. |
156+
| **navigate** | `string` | ✅ | Route/URL template navigated to after the action succeeds. Interpolates $`{param.*}` (params-dialog values), $`{ctx.*}` (origin/apiBase/user/org/recordId/selection) and $`{result.*}` (the action's server response payload — NEW with this key, e.g. $`{result.id}`; on a type:'flow' action the completed run's answer, whose `output` carries the flow's isOutput variables, e.g. $`{result.output.renewal.id}`). An http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (an SPA route hop); a template that opens with $&#123; is judged after interpolation. Every other scheme and a protocol-relative //host are refused. An in-app path is a console route, written without the console's mount prefix; a record is /apps/APP/OBJECT/record/ID, where APP is the app's route segment (its package id; its name also resolves). No app-relative form is declared and no $`{ctx.*}` member names the current app, so write the app segment. Renderers MUST encodeURIComponent values in query positions. |
157157
| **openIn** | `Enum<'self' \| 'newTab'>` | optional (default: `"self"`) | Where to perform the post-success navigation: 'self' (default — in-place SPA navigation, immune to popup blocking) or 'newTab'. Closed enum — no general navigation DSL. |
158158

159159

@@ -228,7 +228,7 @@ const result = ActionButtonPropsSchema.parse(data);
228228
| **type** | `Enum<'script' \| 'url' \| 'modal' \| 'flow' \| 'api' \| 'form'>` | optional (default: `"script"`) | Action functionality type — the dispatch route. The declarative single-record field write is not a type: it is `operation: 'update'` + `patch` on the default `script` route. |
229229
| **name** | `string` | optional | Machine name (lowercase snake_case) |
230230
| **label** | `string \| Record<string, string>` | optional | Display label |
231-
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ — every other scheme and a protocol-relative //host are refused. |
231+
| **target** | `string` | optional | URL, Script Name, Flow ID, or API Endpoint. Supports $`{param.X}` and $`{ctx.X}` interpolation. For type:'url': an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ (a console route; a record is /apps/APP/OBJECT/record/ID, APP being the app's route segment — see onSuccess.navigate) — every other scheme and a protocol-relative //host are refused. |
232232
| **openIn** | `Enum<'self' \| 'new-tab'>` | optional | For type:'url' — where to open `target`. 'new-tab' opens a new browser tab; 'self' navigates in place. When omitted, external/absolute URLs open in a new tab and relative URLs navigate in place. Static execution option — keep it OUT of `params` (which is user-input-collection only). |
233233
| **method** | `Enum<'POST' \| 'PATCH' \| 'PUT' \| 'DELETE'>` | optional | HTTP method for type:"api" actions. Defaults to POST. |
234234
| **params** | `{ name?: string; field?: string; objectOverride?: string; label?: string \| Record<string, string>; … }[]` | optional | Input parameters required from user — an ActionParam[] DEFINITION array, never a payload map (a static request body goes in `bodyExtra`). |

0 commit comments

Comments
 (0)