Skip to content

Commit 36ac4b4

Browse files
docs(forms): restate the redirect status note as what the pinned Console does (#22651)
Fixes #22468 Clause-②: no ## What changed `content/docs/ui/forms.mdx`, section "What a `redirect` `url` may be", two lines: 1. **The dated note is gone.** "Current renderer status (2026-08-11)" said the Console redirects verbatim and does not substitute or escape `{{record.…}}` tokens, and told authors to write a plain path. It is replaced by one undated sentence saying what the Console does: > The Console applies the three rules at submit time: it resolves each `{{record.…}}` token from the record the submit just wrote, URL-escapes the value, and navigates to the resolved path inside the console shell after `delayMs`; a `url` the schema refuses is never followed — the submit is still confirmed and the refusal is shown on screen. 2. **The `redirect` row of the "Renderer behavior" table on the same page.** It said `window.location.assign(url)`, which is the same out-of-date claim in another form: a full-page load of the raw `url`. The Console moved away from that in the same objectui change. The row now reads: "Navigate in-app to `url`, its `{{record.…}}` tokens substituted and URL-escaped, after `delayMs` (defaults to 0) — see below." The section's three numbered rules and the "The default is mode-aware" subsection are unchanged. ## Evidence: what the pinned Console does The pin is `.objectui-sha` = `20c6d351ad74d2b14a93becdc134d51b91b2d2e6`. It is the same on this branch's base `cc305a3cf` and on `origin/main` `0ec426897`. I read it in objectui with `git show 20c6d351ad74d2b14a93becdc134d51b91b2d2e6:PATH`: - `apps/console/src/components/submitRedirect.ts`, `resolveSubmitRedirect(url, record)`: - Rule 1: it calls `checkSubmitRedirectUrl`, which parses a minimal `FormViewSchema` from `@objectstack/spec/ui`. If the schema refuses the url, the function returns the spec's refusal message and does not navigate. - Rule 2: it replaces each `{{record.field_name}}` token with `encodeURIComponent` of the field's scalar value. If a brace is still left after that, the redirect is refused. - Rule 3: the result is the resolved path. - `apps/console/src/components/FormPage.tsx`, submit handler, `case 'redirect'`: - It calls `resolveSubmitRedirect(behavior.url, …)`. The token scope is the submitted payload, then the server's echoed record, then the written id. - On a refusal it calls `toast.error(refusal)`, keeps the refused state on screen and calls `setSubmitted(true)`. - On success it keeps the resolved path as pending. An effect then runs `setTimeout(() => navigate(path), behavior.delayMs ?? 0)`, which is a React Router navigation under the console basename, not `window.location.assign`. ## Fixing the table row here as well The four conditions for a bounded in-place fix all hold: 1. It is the same defect class as the card: a statement about how the Console handles the redirect arm that is false at the pin. 2. The edit is mechanical, and its new wording is set by the measured code above. 3. No other claim holds the file. The claim's serial-constraints line read 13 open PRs and none touches it. 4. It adds no new verification surface: it is the same file and the same docs gate family. The file surface is unchanged: `content/docs/ui/forms.mdx` only. ## Grep: no other page restates the old status The pattern is the old note's own phrases plus the old table spelling: ``` PAT='Current renderer status|does not yet substitute|reach the browser literally|write a plain path|objectui#4190|objectui/issues/4190|redirects (\*\*)?verbatim|window\.location\.assign' git grep -n -i -E "$PAT" REF -- content/docs skills ``` | REF | exit | hits | |---|---|---| | `00c98860b` (this PR's head) | 1 | none | | `cc305a3cf` (base, positive control) | 0 | `content/docs/ui/forms.mdx:358` (the table row) and `content/docs/ui/forms.mdx:385` (the note) | The scope is the whole `content/docs` tree, including the generated `references/`, plus the published `skills/`. Nothing under `content/docs/references/**` or `content/docs/releases/**` matches, so neither was edited. `references/ui/view.mdx:500` restates rule 3 only, which is the contract and not a status. The same pattern over the rest of the repo at head finds no hand-written docs page. It finds these, none of them a page this card covers: - `packages/spec/CHANGELOG.md:42202,42248-42249`: a released, release-owned entry that was accurate when it shipped. Not edited. - `packages/console/CHANGELOG.md:2251`: describes the objectui change correctly. - `packages/spec/src/ui/view.zod.ts:4058-4072` and `packages/spec/src/ui/view-submit-redirect-url.test.ts:9`: code comments. See Acceptance notes. - `packages/client/src/index.ts:4752`: an unrelated auth redirect. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) at head `00c98860b`: 40 commands, the same 40 the dispatch named. - All 40 ran at `00c98860b` and exited 0. - `pnpm --filter @objectstack/spec run check:skill-examples` first exited 3 with PREREQUISITE NOT MET because `packages/client-react/dist` had no `.d.ts`. That is not a measurement. I built `@objectstack/client-react...` and `@objectstack/client...`, re-ran it, and it exited 0: "262 prose examples type-check across 3 surface(s)". - Before the spec and lint gates I built the `@objectstack/lint...` closure, which includes spec, under `scripts/pm/os-verify-lock.sh` (`VERDICT command-exit 0`). - `--ran` reconciliation with the exit codes recorded: "40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN". - Not run locally, as CI owns them: the whole-tree families, the Build Docs job and the type-check lanes that the derivation lists outside its 40. Docs only, nothing published: `skip-changeset`. ## Acceptance notes These are not filed. They are comment drift, outside the three filing classes, and nobody else is set to edit these files. - `packages/spec/src/ui/view.zod.ts:4058-4072`: the JSDoc above the `submitBehavior.url` refine still says objectui's `FormPage` "performs a verbatim redirect" and that the resolved value "reaches `window.location.assign`". At the pin, the Console navigates through the router. This is a code comment, not `.describe()`, so it reaches no generated page. - `packages/spec/src/ui/view-submit-redirect-url.test.ts:9`: the test header says the same "redirects verbatim". Same nature. --- _Generated by [Claude Code](https://claude.ai/code/session_01Q7Fy4uVkvBWgj9CLihdATJ)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent d817f18 commit 36ac4b4

1 file changed

Lines changed: 2 additions & 2 deletions

File tree

‎content/docs/ui/forms.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ formViews: {
355355
| `kind` | Renderer behavior |
356356
|---|---|
357357
| `thank-you` | Replace the form with a confirmation panel (`title`, `message`). |
358-
| `redirect` | `window.location.assign(url)` after `delayMs` (defaults to 0). |
358+
| `redirect` | Navigate in-app to `url`, its `{{record.…}}` tokens substituted and URL-escaped, after `delayMs` (defaults to 0) — see below. |
359359
| `continue` | Re-read prefill values and reset state — user can submit another response without reload. |
360360
| `next-record` | Stub for queue contexts; falls back to thank-you when no queue is wired. |
361361

@@ -382,7 +382,7 @@ submitBehavior: { kind: 'redirect', url: '/u/{{os.user.id}}' } // not
382382

383383
Widening this — an allowlist of absolute origins, say — waits for measured demand; the ruling took the narrowest shape deliberately.
384384

385-
> **Current renderer status (2026-08-11).** The console shipping at the pinned `.objectui-sha` redirects **verbatim** on this value: it does not yet substitute `{{record.…}}` tokens or escape them. The three rules above are the contract, and the consumer half is tracked in [objectui#4190](https://github.com/objectstack-ai/objectui/issues/4190). Until it lands, a token would reach the browser literally — so write a plain path unless you are following that issue.
385+
The Console applies the three rules at submit time: it resolves each `{{record.…}}` token from the record the submit just wrote, URL-escapes the value, and navigates to the resolved path inside the console shell after `delayMs`; a `url` the schema refuses is never followed — the submit is still confirmed and the refusal is shown on screen.
386386

387387
### The default is mode-aware
388388

0 commit comments

Comments
 (0)