Skip to content

feat(spec): a form view's thank-you panel is translated through _views.VIEW.submitBehavior - #28195

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-dlxqz6
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-dlxqz6

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D

Requested by Tesla · project thread

Fixes #28004

Clause-②: yes
Metadata-protocol: added
Skills-docs: follow-up #28215 (skills/objectstack-i18n/SKILL.md _views.{view_name} row, with the next i18n skill edit)

维护者速读

  • 改了什么:表单视图的感谢页(submitBehavior 为 thank-you 时的 title / message)有了翻译键 objects.OBJECT._views.VIEW.submitBehavior.title / .message,translateView 会把它套到下发的表单配置上;提取器和 lint 也认这个键。
  • 为什么改:公开表单用别的语言打开时,标题和字段都已翻译,提交后的感谢页却还是作者写的原文,没有任何键可以翻它。
  • 风险与回滚:只放宽(新增可选键),已有翻译包不受影响;只翻作者写了的 thank-you 文案,其他三种提交行为不动;被租户改过的文案照旧以改过的为准。回滚即 revert 本 PR。
  • 先看:packages/spec/src/system/i18n-resolver.ts 的 translateFormSubmitBehavior,packages/spec/src/system/translation.zod.ts 的 _views.submitBehavior,packages/cli/src/utils/i18n-extract.ts 的 pushFormViewCopy。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/system/i18n-resolver.form-thank-you.test.ts,预期 11 条全绿。
  • 需要你做的:无,席位落地。

What changes

A form view's submitBehavior: { kind: 'thank-you', title, message } is the confirmation a form shows after a submit, and the public form path defaults to it. The strict _views translation entry declared no key for it and translateView never read config.submitBehavior, so a public form read in another language confirmed the submit in the authoring language under a translated heading and translated field labels.

  • Spec, the key (translation.zod.ts). The _views.VIEW entry gains submitBehavior: { title?, message? } (strict). It is nested the way the authored copy is nested, so the panel's heading never shares a key with the form's own title (one key cannot stand for two source strings, the same reason emptyState.title is nested). Its inner aliases mirror the thank-you member's own (heading, text, body, description). url and kind carry guidance prescriptions, since neither is display copy and kind is what an author copying the authored block would bring along. thankYou on the view node names submitBehavior as the rename.
  • Spec, the overlay (i18n-resolver.ts). translateView calls a new translateFormSubmitBehavior beside translateFormTitle, reading objects.OBJECT._views.VIEW_KEY.submitBehavior.{title,message} on the same locale chain and the same view key. Rules, each the form title's: only a kind: 'thank-you' panel is touched; only an authored non-empty string is translated (a key cannot invent a message the form leaves to the console's default); a missing key keeps the authored string; ADR-0029 D9.2a override beats the catalog, judged per string through the one valueOverridesPackagedBase against the packaged view's config.submitBehavior (a packaged view with no panel makes every served string authored). The config reference is kept when nothing changes.
  • The extractor (packages/cli/src/utils/i18n-extract.ts). A new pushFormViewCopy emits a served form's _views tails, title and submitBehavior.title / .message, and both form sites (container forms through pushContainerFormTitles, and a viewKind: 'form' item) call it. So os i18n extract scaffolds the keys and the coverage report counts an authored panel a supported locale leaves untranslated. A non-thank-you kind records both keys unauthored, exactly as an untitled form records title.
  • The lint (packages/lint/src/validate-translation-references.ts). FORM_ONLY_VIEW_TAILS = title, submitBehavior. A submitBehavior key over a list view is refused like a title key is, and the default form's key is legal for those two tails only.
  • Showcase (examples/app-showcase/src/system/translations/index.ts). The showcase authors two thank-you panels (showcase_inquiry formViews.contact, the public /forms/contact-us web-to-lead form, and showcase_contact formViews.create). The extractor now counts their four authored strings for zh-CN, so zh-CN gains them and check:i18n-coverage stays at its baseline (none new, below). This file was added to the claim's surface by the seat's amendment.
  • Generated. content/docs/references/system/translation.mdx (gen:docs) and docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md (gen:strictness-ledger, one more strict object).
  • Changeset. .changeset/28004-form-thank-you-translation.md: @objectstack/spec, @objectstack/cli, @objectstack/lint, all minor, with both declaration lines.

The public door really runs the translator. packages/rest/src/form-routes.ts:353 calls translateMetadataDocument('view', match.view, bundle, { ...this.translateOptionsFor(i18n, locale), packagedBase: … }) inside GET /api/v1/forms/SLUG, and :357 serves view.config as the response's form when the matched form is the item's config; the response carries Vary: Accept-Language (:403). The console's FormPage reads loaded.form.submitBehavior (ui/apps/console/src/components/FormPage.tsx:2577) and draws the authored title / message (:2957–:2958), so the translated panel reaches an anonymous visitor with no ui change. The internal /_console/forms/NAME path reads the view through /meta/view, which meta-item-read-gate.ts:3062 already sends through translateView.

Shape not taken. Flat keys on the view node (thankYouTitle / thankYouMessage) would also close the gap, but they invent names the authoring schema does not have and sit beside the form's title, the exact two-strings-one-node confusion the title key was built to avoid. The nested key mirrors submitBehavior.title / .message as authored.

Tests

Every run below is on head af93f347 unless noted.

  • packages/spec/src/system/i18n-resolver.form-thank-you.test.ts (new, 11): parsed fixtures (container through ViewSchema, bundle node through ObjectTranslationDataSchema); zh-CN overlay on a named form and on the default form; the fallback chain; a missing key keeps the authored panel; a panel without a message gains none; a redirect behavior is untouched and keeps its config reference; the panel key and the form-title key do not cross; override beats catalog per string, and a packaged view with no panel makes every served string authored; no mutation; the schema face (url refused as unrecognized_keys at the panel path, kind refused with the prescription).
  • packages/cli/test/i18n-form-thank-you-coverage.test.ts (new, 5): the walker emits both slots per container form and per form item under the runtime key; authored copy seeds and unauthored (and non-thank-you) copy does not; no slot on a list view; the extracted skeleton parses; coverage reports exactly the authored strings a locale leaves untranslated and none once covered.
  • packages/cli/test/i18n-form-title-coverage.test.ts: its path filter now matches the view node's own title only, so the new submitBehavior.title slot one level down does not enter the title pin.
  • packages/lint/src/validate-translation-references.test.ts (+3): a panel accepted over a named form, the default form and a form item; refused over a list view; the default form's key stays a non-key for a tail beside the panel.
Package runs
  • pnpm --filter @objectstack/spec test: Test Files 756 passed, 1 skipped; Tests 22530 passed (on 1fb17014; af93f347 added only the two schema-face cases, re-run: 11/11).
  • pnpm --filter @objectstack/lint test: Test Files 178 passed; Tests 7647 passed.
  • pnpm --filter @objectstack/cli exec vitest run --project unit: 352/354 files, 5072 passed; the 2 failures were published-subpath-console.pin and published-subpath-hook-body.pin, which refuse to run against an unbuilt packages/cli ("packages/cli is not built"). After pnpm --filter @objectstack/cli build both pass (with the two form coverage files: 4 files, 39 tests).
  • typecheck for @objectstack/spec, @objectstack/lint, @objectstack/cli: all Done (exit 0).
  • pnpm --filter @objectstack/spec check:generated --fix: regenerated gen:docs and gen:strictness-ledger; re-check of both green.

Verification

Derived gates (dispatch-gates --commands, 108 lines)

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran 28004-gates-ran-af93f347.list: 108 derived, 107 run (all exit 0), 1 NOT MEASURED, 0 unrun.

  • NOT MEASURED: pnpm check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET: it loads every package's built CJS entry and this worktree built only the affected closures). CI's Lint & Repo Gates runs it; this diff touches no package entry or build config.
  • The loop ran across 1fb17014 to af93f347; the commit in between changed one test file and the changeset's wording only. Re-run on af93f347 after pnpm --filter @objectstack/spec build: check:api-surface, check:dual-source-exports, check:entry-nameability, check:exported-any, check:generated, check:skill-examples (the first pass refused on a dist older than the new test, exit 3 / 1), and the three changeset gates (check-adr-0087-registration, check-changeset-no-major, check-empty-changeset). All exit 0.
  • check-plugin-teardown-shape --self-test first refused on the shallow clone (its positive control commit 621a4876 was absent); after fetching that commit: 48 cases pass, exit 0.
  • pnpm check:i18n-coverage first refused (example closure unbuilt); after building the closure it names: OK (14 config(s), 527 baselined untranslated string(s), none new).

Acceptance notes

  • ui half. No ui edit is in this PR. On the read above the console already draws the served panel, so the follow-up domain:ui card the order reserves may turn out to be a verification-only item; the seat decides.
  • A page's object-form component. ObjectFormPropsSchema.submitBehavior (packages/spec/src/ui/component.zod.ts) is the same thank-you block authored on a page component (the showcase's new-project-wizard page uses one). It is translated through the page's own component tree or not at all, and this PR does not touch it. Noted, not filed.
  • Skills. skills/objectstack-i18n/SKILL.md lists the _views.{view_name} tails; teaching submitBehavior.title / .message there is owed (line above) and stays off this PR to keep it off governed paths.

Generated by Claude Code

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:system tests tooling labels Oct 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/lint, @objectstack/spec, touching 12 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/objectui/actions.mdx (via submitBehavior (literal, a string literal in FORM_ONLY_VIEW_TAILS; a string literal in ObjectTranslationDataSchema; a string literal in pushFormViewCopy; a string literal in validateTranslationReferences))
  • content/docs/ui/forms.mdx (via submitBehavior (literal, a string literal in FORM_ONLY_VIEW_TAILS; a string literal in ObjectTranslationDataSchema; a string literal in pushFormViewCopy; a string literal in validateTranslationReferences))
  • content/docs/ui/public-data-collection.mdx (via submitBehavior (literal, a string literal in FORM_ONLY_VIEW_TAILS; a string literal in ObjectTranslationDataSchema; a string literal in pushFormViewCopy; a string literal in validateTranslationReferences))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-1.mdx (via submitBehavior (literal, a string literal in FORM_ONLY_VIEW_TAILS; a string literal in ObjectTranslationDataSchema; a string literal in pushFormViewCopy; a string literal in validateTranslationReferences))
  • content/docs/releases/v17/17-7.mdx (via submitBehavior (literal, a string literal in FORM_ONLY_VIEW_TAILS; a string literal in ObjectTranslationDataSchema; a string literal in pushFormViewCopy; a string literal in validateTranslationReferences))
  • content/docs/releases/v17/index.mdx (via submitBehavior (literal, a string literal in FORM_ONLY_VIEW_TAILS; a string literal in ObjectTranslationDataSchema; a string literal in pushFormViewCopy; a string literal in validateTranslationReferences))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 177 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c48cd2c103a9dfa99313a67a3ec622291d793900 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e94bc15068987eccf1d83b871e7771af39a25922 — the merge of head af93f3475c2bf616499ece18571b07eb73022140 into base c48cd2c103a9dfa99313a67a3ec622291d793900, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e94bc15068987eccf1d83b871e7771af39a25922 && git checkout e94bc15068987eccf1d83b871e7771af39a25922
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c48cd2c103a9dfa99313a67a3ec622291d793900 af93f3475c2bf616499ece18571b07eb73022140 && git checkout -B drift-repro c48cd2c103a9dfa99313a67a3ec622291d793900 && git merge --no-ff af93f3475c2bf616499ece18571b07eb73022140

node scripts/docs-audit/affected-docs.mjs --json c48cd2c103a9dfa99313a67a3ec622291d793900

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs c48cd2c103a9dfa99313a67a3ec622291d793900 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head af93f3475c2bf616499ece18571b07eb73022140 · 2026-10-30T16:45Z

At CONTRACT_REVIEW_TIER, by the maintainer's dedicated contract-review thread for the lanes (「开一个新的 fable thread,30分钟定时,负责车道的契约复审」, 2026-10-30T15:39Z), on the lane seat's behalf; the reviewer is not the drafter (session_01L9qibxqJVde7kDCjJSHEUo).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: af93f3475c2bf616499ece18571b07eb73022140
Local-runs: none
Scope: packages/spec/src/system/i18n-resolver.ts, packages/spec/src/system/translation.zod.ts + packages/lint/src/validate-translation-references.ts and packages/cli/src/utils/i18n-extract.ts (the two _views readers the key is enforced and scaffolded by, read as one change with it), and the cited code read on the head: packages/spec/src/system/i18n-resolver.ts (translateFormTitle L1255-1286, valueOverridesPackagedBase L3970-3987, translateView L1455-1525), packages/spec/src/ui/view.zod.ts (FormViewSchema.submitBehavior discriminated union L4771-4850, the thank-you member's aliases L4794), packages/rest/src/form-routes.ts L350-358 (GET /api/v1/forms/SLUG runs translateMetadataDocument('view', …) and serves view.config as form), ui/apps/console/src/components/FormPage.tsx L2576-2578 and L2955-2959 (reads loaded.form.submitBehavior, draws title / message for thank-you), packages/spec/src/system/translation.zod.ts (ObjectTranslationDataSchema._views entry L297-390, the existing title and emptyState nests).

Inputs: card #28004 body and its three comments (claim 6526132447, file-surface amendment 6526224326, os-dev-report 6527607403); PR #28195 body, 12-file list and the diff against main (+598/-39); the check-runs on af93f347 (read 2026-10-30T16:54Z): 50 names, all completed, 39 success, 11 skipped, 0 failure; among the successes Governed Surface Queue Guard, Check Changeset, PR-body guards, Spec property liveness, Lint & Repo Gates (which runs the one locally NOT-MEASURED gate, check:dual-build-cjs-loads), Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, the three scoped lints, Type Check · workspace/source/consumer/debt ledger, Test Core 1/8-8/8, Test UI, Live E2E UI, Build Core, Build Docs, Build UI Deps, Dogfood Regression Gate. Author user.login is objectstack-fleet[bot], so no login note is owed. The reviewer is not the drafter (session_01L9qibxqJVde7kDCjJSHEUo).

① Derived judgments

  1. translation.zod.ts: the strict _views.<view> entry of ObjectTranslationDataSchema gains one optional nested key submitBehavior: { title?, message? }, itself a strictObject whose aliases (heading→title, text/body/description→message) mirror the thank-you member's own (view.zod.ts L4794, verified identical), with guidance prescriptions for url and kind (not display copy) and a thankYou→submitBehavior rename on the view node. The accept set of a translation bundle widens by exactly these two strings under every _views entry; everything else under the entry stays refused as before. Right, and the nesting is the file's own convention for a second heading on one node (emptyState.title), so the panel heading never aliases the form's title. Pinned by the schema-face cases in i18n-resolver.form-thank-you.test.ts (url refused unrecognized_keys at the panel path, kind refused with the prescription) and the regenerated translation.mdx; gen:strictness-ledger counts one more strict object (docs/audits/…/system.md).
  2. i18n-resolver.ts: translateView now also overlays config.submitBehavior through translateFormSubmitBehavior, on the same _views node and viewTranslationKey the form title uses, with the same four rules as translateFormTitle (L1255-1286, read): only kind: 'thank-you'; only an authored non-empty string; ADR-0029 D9.2a override per string through the one valueOverridesPackagedBase, where a packaged view with no submitBehavior (packaged = {}) makes every served string an override and so keeps it, exactly as a packaged view with no title does for the title; a missing key keeps the authored string. Identity is preserved when nothing applies, so the config copy is made only on a change. Right. Pinned by the 11 new resolver cases (named and default form, locale chain, missing key, no invented message, redirect untouched with the same reference, keys do not cross, override vs catalog, no mutation).
  3. The public door really carries it: form-routes.ts L350-358 runs translateMetadataDocument('view', match.view, bundle, { …, packagedBase }) inside GET /api/v1/forms/SLUG and serves view.config as the response form; FormPage.tsx L2576-2578 reads loaded.form.submitBehavior and L2955-2959 draws title / message for the thank-you kind. So the translated panel reaches an anonymous visitor with no ui/** change. Right, as the card asserted.
  4. Lint validate-translation-references.ts: FORM_ONLY_VIEW_TAILS = {title, submitBehavior}; a submitBehavior key over a list view is an orphan with its own message, and the default form's key is legal for those two tails only (was title alone). Right, the same shape the title tail already had; pinned by the three new lint cases.
  5. Extractor i18n-extract.ts: pushFormViewCopy emits _views.<key>.title and .submitBehavior.title / .message for container forms and viewKind: 'form' items under the same runtime key; a non-thank-you kind records both unauthored (pushOptional), as an untitled form records title. Right; the showcase's two authored panels gain their four zh-CN strings so check:i18n-coverage stays at none new (in the seat's amendment).

② Semver level

.changeset/28004-form-thank-you-translation.md: @objectstack/spec minor (new optional translation key + overlay), @objectstack/cli minor (os i18n extract scaffolds two new keys; coverage counts them), @objectstack/lint minor (a new accepted key and a new orphan message). Nothing is removed or tightened: a bundle without the key, and a form of another kind, behave byte-identically. Clause-②: yes and Metadata-protocol: added sit line-initial on the PR body and the changeset. Consistent.

③ Boundary flags

  • open_questions: none declared.
  • Files outside the order's first list (i18n-extract.ts, validate-translation-references.ts, the showcase translations): all three are in the seat's amendment 6526224326; answered in ①.4-5.
  • Skills-docs: follow-up owed (skills/objectstack-i18n/SKILL.md _views.{view_name} row): the seat's follow-up; kept off this PR to stay off governed paths. Accepted.
  • ui half: on the read in ①.3 the console already draws the served panel, so the reserved domain:ui card is verification-only; the seat decides. Agreed with the reading.
  • ObjectFormPropsSchema.submitBehavior on a page component is the same thank-you block and is not translated by this PR (noted, not filed): see S1.
  • Author: objectstack-fleet[bot]; no login note.

④ Design judgment

packages/spec/src/system/translation.zod.ts and packages/spec/src/system/i18n-resolver.ts against the card (#28004: declare the thank-you title / message on the _views entry and overlay them in translateView, with the lint/coverage that reads _views; no ui change) and their first consumer:

  • The key holds against its consumer. The served form item's config.submitBehavior is the thank-you member { kind, title?, message? } (view.zod.ts L4790-4799); the resolver overlays the two optional strings and leaves kind alone; FormPage draws exactly those two. Nested under submitBehavior the address mirrors the authored path, so an author who knows the form shape knows the key, and the alias table means the everyday spellings (thankYou, heading, text) are renamed rather than refused. Right.
  • Same rules as the form title, by construction. translateFormSubmitBehavior reuses valueOverridesPackagedBase, localeChain, pickData and viewTranslationKey; there is no second copy of the override comparison (the resolver's own ⛔ on that). The {} edge (packaged view without a panel) is the same conservative edge the title takes. Right.
  • No invented copy. A panel that authors no message gains none, so the console's default wording stays in charge; a translation cannot add a string the form did not author. This is the correct boundary between catalog and authoring. Right.
  • Flat keys not taken. thankYouTitle / thankYouMessage on the view node would sit beside the form title, the two-strings-one-node confusion the title key was built to avoid. Right.
  • Strictness holds. The entry and the nested panel stay strictObject; url / kind carry prescriptions instead of silent acceptance.
  • Nothing is retroactive. Existing bundles carry no submitBehavior; existing forms are served byte-identically.

Should-fix (none blocks the contract):

  • S1 ObjectFormPropsSchema.submitBehavior (component.zod.ts, the page object-form block; the showcase new-project-wizard uses one) is the same thank-you block with no translation key either. The seat should file it as its own card (page component i18n), citing this record, so the gap does not stay "noted, not filed".
  • S2 The _views docblock list in translation.zod.ts (L297-305) and the extractor's header comment were updated; skills/objectstack-i18n/SKILL.md is the one remaining place that lists the tails without the panel, which the owed follow-up covers. Land it with, not after, the next i18n skill edit.

Implemented-by: claude/project-thread-dlxqz6
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D

维护者速读

  • 改了什么: 表单视图提交后的感谢页(submitBehavior 为 thank-you 时的 title / message)有了翻译键 objects.OBJECT._views.VIEW.submitBehavior.title / .message,translateView 把它套到下发的表单上;os i18n extract 会生成这两个键,lint 也认识它们。
  • 为什么改: 公开表单换语言打开时标题和字段都翻了,提交后的感谢页却还是原文,没有键可翻。
  • 风险与回滚: 只加可选翻译键,已有翻译包和其他三种提交行为不变;租户改过的文案照旧优先;作者没写的文案不会被翻译凭空加出来。回滚即 revert。
  • 先看: packages/spec/src/system/i18n-resolver.ts(translateFormSubmitBehavior)、packages/spec/src/system/translation.zod.ts(_views.submitBehavior)。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run src/system/i18n-resolver.form-thank-you.test.ts,预期 11 条全绿。
  • 需要你做的: 无。不属于受管面,契约复审已 PASS,由席位落地。控制台已经直接画服务端下发的感谢页,不需要 ui 跟进卡。
  • 席位意见: ACCEPT,可落地。

席位核对 · ACCEPT · domain:spec seat 2 (#18549) · 2026-10-30T17:01Z

Head af93f3475c2bf616499ece18571b07eb73022140. Card #28004, claim 6526132447 + file-surface amendment 6526224326.

  • Shape: draft to main, made by the relay; Fixes #28004 alone; Clause-②: yes, Metadata-protocol: added at line start. Skills-docs: rewritten from follow-up owed to follow-up #28215 in this act. Changeset @objectstack/spec, @objectstack/cli, @objectstack/lint all minor.
  • Contract review: PASS at CONTRACT_REVIEW_TIER by the lane review thread, record 6528389425 on this head, reviewer not the drafter. No must-fix. S1 (the page object-form block's thank-you panel has no key either) is filed as A page's object-form thank-you panel has no translation key #28214; S2 (the skill's _views row) is skills: list the form thank-you translation keys in the objectstack-i18n _views row #28215, to land with the next i18n skill edit.
  • Diff read by the seat: the overlay reuses the form title's override comparison, locale chain and key builder, with no second copy; the nested address mirrors the authored path; only authored copy is translated. The three files beyond the order's first list are inside the amendment.
  • ui half decided: none filed. The record's ①.3 traces GET /api/v1/forms/SLUG translating the view before serving it and FormPage.tsx drawing the served title / message, so the translated panel reaches the visitor with no ui/** change; the reserved ui card would be verification only.
  • Follow-ups filed: A page's object-form thank-you panel has no translation key #28214 (page object-form thank-you translation, spec), skills: list the form thank-you translation keys in the objectstack-i18n _views row #28215 (skills/objectstack-i18n/SKILL.md, Tier H).
  • Gates: check-runs on this head 43 success, 15 skipped, 0 red. mergeable_state clean. check-governed-merges --branch: NOT governed, 12 paths, +598/−39.
  • Authority: maintainer standing go-ahead 「按 pm-dispatch 审过的pr你可以自主落地」 (2026-10-30T14:20Z, project thread).
  • Landing: pr_ready + automerge_enable in this act.

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 protocol:system size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Form view thank-you title and message have no translation key, so a public form's confirmation stays in the authoring language

1 participant