Skip to content

feat(console): a public form's thank-you panel shows the record field values its {{record.field_name}} tokens name - #28493

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-cdb4cc
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-cdb4cc

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:ui#1 · os-tesla · session_01L9qibySWZrcTi5MPXZ61kx

Requested by Tesla · project thread

Fixes #28444

Clause-②: no
Metadata-protocol: semantic
Skills-docs: updated here — content/docs/ui/public-data-collection.mdx, content/docs/ui/forms.mdx

维护者速读

What changes

ui/apps/console/src/components/FormPage.tsx:

  • readThankYouAnswerFields(answer) reads fields off the public submit answer through PublicFormSubmitResponseSchema (@objectstack/spec/api). An answer that is not that shape (the schema is strict) carries no fields.
  • fillThankYouPanel(panel, fields) replaces each token in the panel's title and message. Which tokens a text holds is thankYouRecordFieldNames (@objectstack/spec/ui); each named field's token is then its literal {{record. + name + }} spelling, split out of the text with a plain string split. There is no regex in the Console for this token. A name with no value in the answer (or an Object.prototype key such as constructor) fills as empty. A filled value is never read for tokens again, and the page draws the result as a React text child, so a value is text, never markup.
  • The submit switch gets its own thank-you arm: on the public route it stores the answer's fields, and the confirmation panel draws the filled text. The internal route's answer is CreateDataResponse, not this shape, so it is not read (see Acceptance notes).

ui/packages/data-objectstack/** is untouched: the public submit is a direct apiFetch in FormPage.tsx (submitPublic), which hands back the bare JSON body, so fields is not dropped anywhere on the way.

Docs (the card's Skills-docs rider): content/docs/ui/forms.mdx (section 8, after the kind table) and content/docs/ui/public-data-collection.mdx (after the submitBehavior paragraph) each gain one sentence and one example, and both say only an engine-generated field (autonumber) may be named.

Root changeset .changeset/28444-thank-you-record-fields.md: @object-ui/console minor (Metadata-protocol semantic).

Tests

New ui/apps/console/src/components/FormPage.thankYouRecordFields-28444.test.tsx (the real FormPage on /f/:slug, fetch stubbed):

  • A message and a title with {{record.case_number}} show the answer's CASE-0042.
  • An answer with no fields renders the token as empty, and no {{ reaches the DOM.
  • A value carrying an img tag and a b tag shows as literal text; no such element is created.
  • An answer with a key beyond id and fields fails the strict schema and fills nothing.
  • fillThankYouPanel: a value that itself spells another name's token is not filled again; {{record.constructor}} fills as empty; a panel with no token is returned as authored.
  • readThankYouAnswerFields: reads fields off the public answer; reads none off a CreateDataResponse or undefined.

Ablation (one run, not kept): node scripts/ablation-replace.mjs replaced the fillThankYouPanel(...) call in the render with the raw panel text. The rendered tests went 4 failed, 5 passed (all four render pins red; the five helper unit pins stay green, as they call the helper directly). Restored; blob back to the HEAD blob a4ac3681, git diff HEAD empty.

Acceptance notes

  • Translated panel text (the card's rider), measured by reading the code, not live. GET /forms/:slug serves the translated view's config (translateMetadataDocument('view', …) in packages/rest/src/form-routes.ts), so the Console reads the tokens of the translated text. The submit answer's fields is built from thankYouRecordFieldNames(match.form?.submitBehavior), the base text. So a translation that names a field the base text names fills; a translation that names a field the base does not renders that token empty, never as the token text. Not widened.
  • Internal route. An internal (/forms/:name) form that declares a thank-you panel with tokens renders them empty: its answer is CreateDataResponse, which PublicFormSubmitResponseSchema (strict) refuses, and the card scopes the fill to the public answer. Before this PR it showed the token text. Filling it from the internal answer's record would be a second source; left for a card if wanted.
  • Empty heading. A title that is only a token with no value now renders an empty heading rather than the default "Thank you!" (the card: empty, never the token text).
  • submitRedirect.ts keeps its own RECORD_TOKEN_RE for the redirect arm; it is unchanged here.

Verification

Measured on head b88d54a9 (main base 9b4d1933). After merging origin/main at 8c3f9d93 (head 60fc7181; the merge brings only #28490, no file this PR touches), apps/console/src/components/ was re-run: 55 files, 594 tests passed.

Tests
  • pnpm --dir ui exec vitest run apps/console/src/components/: 55 files, 594 tests passed (includes the new file and FormPage.submit.test.tsx).
  • pnpm --filter @object-ui/console type-check: exit 0, 0 errors (after a full dts build of ./packages/** and the @object-ui/console^... closure; the console tsconfig includes src, so the new test file is compiled).
Derived gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 66 families, every one exit 0, including node scripts/check-sdui-manifest.mjs and its self-test, pnpm check:test-source-alias, pnpm check:cross-package-test-inputs, pnpm check:undeclared-dep-imports, pnpm check:doc-authoring, pnpm check:docs-transcript-drift, node scripts/check-changeset-no-major.mjs --base origin/main, pnpm --filter @objectstack/spec run check:docs, and pnpm --dir ui exec vitest run scripts/ (171 files, 4901 passed, 2 skipped). pnpm check:query-options-erasure hit a 280 s per-gate timeout in the first sweep and passed (exit 0) when re-run without one. --ran reconciliation: 66 derived, 66 run, 0 NOT-MEASURED. pnpm check:new-line-citations (ui): 0 new citations.


Generated by Claude Code

… tokens from the submit answer

The panel's title and message replace each {{record.field_name}} token with
the value the public submit answer carries under `fields`, read through the
spec's PublicFormSubmitResponseSchema. The names come from the spec's
thankYouRecordFieldNames, so the page carries no token grammar of its own; a
name with no value fills as empty and a value is drawn as text.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibySWZrcTi5MPXZ61kx
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Nov 1, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw2uhdSeHYfnZdAf4V

席位意见:同意。匿名表单提交后的感谢面板把 {{record.case_number}} 这类占位换成服务端回包 fields 里的值;无值显示为空、不显示占位原文;值按纯文本绘制。席位落地。

席位核对

ACCEPT · head 60fc718192de9577c300d4c41aac5aed9645c69e · 2026-11-01T01:15Z

  1. 形态: 5 files, +247/−3; Fixes #28444; 0 governed paths (check-governed-merges --pr 28493: NOT governed), under 3000 lines. Draft, base main.
  2. 前提实测: Public form thank-you text cannot name the record it created: no template slot, and the submit answer carries the id only #28005 ruling B (6545210989) and Ruling-ref 6548758413 (spec names reused). Seat read the hand-written diff and the server on main 8c3f9d93: POST /forms/:slug/submit answers the bare PublicFormSubmitResponse (res.status(201).json(answer), fields only when the panel names one), so PublicFormSubmitResponseSchema.safeParse(result) (strict) reads it as served. Token names come from thankYouRecordFieldNames; the fill splits on each name's literal {{record.NAME}} spelling, so the page has no second regex (the PM assumption "stop if the pattern is not exported" is disproved and not needed). Missing value ⇒ ''; own-property check, so __proto__-style names fill empty; filled runs are never re-read; drawn as React text. Internal mode does not read the answer (CreateDataResponse).
  3. 门禁 check-runs: 59 on the head, 51 success + 8 skipped, 0 failing; mergeable_state clean; Lint & Repo Gates and TypeScript Type Check success. Dev: 9 new tests, console components 594 passed, ablation reds 4 render pins, 66/66 derived gates.
  4. 契约复审: Clause-②: no, Metadata-protocol: semantic — none of the four contract faces is touched (no packages/spec/src/**, no governed path, no widening or narrowing declaration); no record owed. minor on @object-ui/console matches the semantic kind (AGENTS.md Post-Task Checklist step 3). Prose checked sentence by sentence: the changeset's three bullets match the diff; content/docs/ui/forms.mdx and content/docs/ui/public-data-collection.mdx each add one sentence and one example; "os validate and the publish gate refuse any other" holds (thank-you-record-field, severity: 'error', surfaces CLI_AND_RUNTIME). Skills-docs: = the card's rider, carried here.
  5. 范围外去向: internal-route thank-you panels with tokens now render the tokens empty rather than as literal text — consistent with the card's "never as the literal token text"; Acceptance notes. Translated-text rider measured by reading, not widened (Acceptance notes).
  6. 落地条款: seat runs pr_ready and arms auto-merge on the maintainer's go in this seat's thread, 2026-10-31T23:00Z 「使用 pm-dispatch 技能审核pr后落地」.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 01:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 1d201b0 Nov 1, 2026
60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-cdb4cc branch November 1, 2026 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

console: the public form thank-you panel fills its record field tokens from the submit answer (#28005 ui half)

1 participant