Skip to content

feat(spec): a page object-form's thank-you panel is translated through pages.PAGE.components.ID.submitBehavior - #28269

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-8iwnj7
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-8iwnj7

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm

Requested by Tesla · project thread

Fixes #28214

Clause-②: yes
Metadata-protocol: added
Skills-docs: follow-up #28215 (skills/objectstack-i18n/SKILL.md: the page row gains components.{component_id}.submitBehavior beside the _views.{view_name} row #28195 already owes, in the same next i18n skill edit)

维护者速读

  • 改了什么:页面里嵌入的 object-form 组件,提交后的感谢页(submitBehavior 为 thank-you 时的 title / message)有了翻译键 pages.PAGE.components.ID.submitBehavior.title / .message,translatePage 会把它套到下发的页面上;提取器会生成这两个键,lint 会拒收挂错组件的键。
  • 为什么改:表单视图自己的感谢页在 feat(spec): a form view's thank-you panel is translated through _views.VIEW.submitBehavior #28195 已经能翻译,但页面里的表单组件引用的是同一个配置却没有键,showcase 的“新建项目(向导)”在任何语言下创建成功后都显示英文的 “Project created”。
  • 风险与回滚:只放宽(新增可选键);规则与表单视图的感谢页共用同一个函数:只翻 thank-you、只翻作者写了的文案、缺键保留原文。回滚即 revert 本 PR。
  • 先看:packages/spec/src/system/i18n-resolver.ts 的 translateFormSubmitBehavior(改为两处共用)和 translatePage 里新增的 object-form 分支,packages/spec/src/system/translation.zod.ts 的 submitBehaviorTranslationSchema,packages/lint/src/validate-translation-references.ts 的页面组件检查。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/system/i18n-resolver.page-form-thank-you.test.ts,预期 12 条全绿。
  • 需要你做的:无,席位落地(等契约复审记录)。

What changes

A page object-form component takes submitBehavior from the form view by reference (ObjectFormPropsSchema.submitBehavior), so a page can author { kind: 'thank-you', title, message } on it. #28195 gave a form view's panel its key and overlay. The page component's panel had neither: the strict pages.PAGE.components.ID entry declared no submitBehavior, and translatePage never read properties.submitBehavior. The showcase's showcase_new_project_wizard page confirmed a create with "Project created" in every locale.

  • Spec, the key (translation.zod.ts). pages.PAGE.components.ID gains submitBehavior: { title?, message? } (strict). The node is built by a new factory, submitBehaviorTranslationSchema(surface), which the form view's _views.VIEW.submitBehavior now uses too, so the two addresses carry one shape: the same alias table (heading, text, body, description) and the same url / kind prescriptions. This follows the actionTranslationSchema precedent in the same file. The components node's alias table gains thankYou: 'submitBehavior', as the _views node has.
  • Spec, the overlay (i18n-resolver.ts). translateFormSubmitBehavior is now the one comparison both carriers call. It takes a lookup(attr) instead of the view's object name and view key. translateView passes the _views lookup it used before. translatePage passes a new lookupPageFormSubmitBehaviorText (locale chain, key by key) for every addressed component whose type is the new exported PAGE_FORM_COMPONENT_TYPE ('object-form'), on the same addressed arbitration as the component copy, so a nested form is reached by the id route too. The rules are therefore the form view's, with no second copy: only a kind: 'thank-you' panel is touched, only an authored non-empty string is translated (a key cannot invent a message), and a missing key keeps the authored string. The component is returned unchanged when nothing applies.
  • The extractor (packages/cli/src/utils/i18n-extract.ts). A new pushThankYouPanel(out, root, behavior, …) emits the two slots and holds the "kind must be thank-you" reading once. pushFormViewCopy and emitPageComponentCopy both call it, so os i18n extract scaffolds pages.PAGE.components.ID.submitBehavior.title / .message for every object-form the page addresses, and the coverage report counts an authored panel a supported locale leaves untranslated. A non-thank-you kind records both keys unauthored, as on the view side.
  • The lint (packages/lint/src/validate-translation-references.ts). A components.ID.submitBehavior key is refused (translation-target-unknown) under a component the page declares with another type, and under an id the page does not declare, with the nearest object-form id offered. A page this stack does not declare is skipped, as for the tab items.
  • Showcase. The wizard's object-form had no id, so it had no address at all. It gets id: 'new_project_form' (examples/app-showcase/src/ui/pages/new-project-wizard.page.ts), and src/system/translations/index.ts gains its zh-CN title, description and panel title / message. The id also makes the form's title and description addressable, which the coverage ratchet counts, so all four strings are translated and check:i18n-coverage stays at its baseline.
  • Generated. packages/spec/api-surface/system.json and export-origins/system.json record the new PAGE_FORM_COMPONENT_TYPE export. check:docs and check:strictness-ledger stayed green with no regeneration (the generated translation reference renders the page node's components member collapsed).
  • Changeset. .changeset/28214-page-form-thank-you-translation.md: @objectstack/spec, @objectstack/cli, @objectstack/lint, all minor, with both declaration lines.

Address: route (a), under the page. The key lives at pages.PAGE.components.ID.submitBehavior, the #6080 route lookupPageComponentCopy already serves for the same component's title and description.

Route not taken: (b), under the bound object (the _sections precedent, objects.OBJECT._sections). The panel is copy the page authors, not the object. Two pages binding the same object can word their confirmation differently (a create wizard and a quick-add card), and an object-scoped key would make them share one translation or need a page-scoped sub-key, which is route (a) spelled longer. _sections is object-scoped because a form section's fields are the object's fields. A thank-you panel has no such tie. Route (b) would also need a second walk to find the bound object (objectName, or a dataSource binding), while (a) reuses the id addressing the extractor, lint and resolver already share through walkAddressedPageComponents. The cost of (a) is that an object-form without an id has no key, the same rule every other page component follows. The showcase form gains an id for that reason.

The page door runs the translator. translatePage is the page entry of the translator table (i18n-resolver.ts, page: translatePage), which /meta/page reads go through (packages/rest/src/meta-item-read-gate.ts, translateMetadataDocument). The console's object-form renderers take submitBehavior from the component's props (ui/packages/plugin-form/src/ObjectForm.tsx, WizardForm.tsx), so the translated panel reaches the reader with no ui change. This is a reading of the code, not a live measure.

Tests

  • packages/spec/src/system/i18n-resolver.page-form-thank-you.test.ts (new, 12): parsed fixtures (the page through PageSchema, both bundle nodes through TranslationDataSchema). A zh-CN overlay through translateMetadataDocument('page', …), with the form's own title translated beside it and a form nested in a page:card reached by id. The fallback chain (ja-JP over [zh-CN]). A missing key keeps the authored panel. Controls: a panel with no message gains none; a redirect behavior is untouched; a key under another component's id does not reach the form, and a page:card with a submitBehavior key gains no panel; the panel key and the component title key do not cross; an object-form without an id has no key; no mutation. The schema face: url refused as unrecognized_keys at the page path, and kind refused with the prescription.
  • packages/cli/test/i18n-page-form-thank-you-coverage.test.ts (new, 4): the walker emits both slots per addressed object-form, nested ones included, and none for an id-less form or a page:card; authored copy seeds, unauthored and non-thank-you copy does not; the extracted skeleton parses; coverage reports exactly the authored strings zh-CN leaves untranslated, and none once covered.
  • packages/lint/src/validate-translation-references.test.ts (+4): a panel over a declared object-form, nested included, reports nothing; refused on a page:card by name; refused under an undeclared id with Did you mean "project_form"?; a page the stack does not declare is not judged.
  • The existing i18n-resolver.form-thank-you.test.ts (11) stays green through the refactor of translateFormSubmitBehavior.
Ablation: removing the page overlay branch turns the overlay cases red

node scripts/ablation-replace.mjs --file packages/spec/src/system/i18n-resolver.ts --anchor 'if (addressed && component.type === PAGE_FORM_COMPONENT_TYPE) {' --replacement 'if (false as boolean) { /* ABLATED-28214 */'. The anchor hit once (1 → 0, blob dfefaad6 → 8ebe517f). The new spec test went to Tests 4 failed | 8 passed (12): the zh-CN overlay, the fallback chain, the no-message control and the title/panel crossing control. The other controls stayed green, as expected, since they assert that nothing is applied. The file was restored to its HEAD blob dfefaad6, git diff HEAD empty. The test imports the resolver from source, so no dist rebuild is involved.

Acceptance notes

  • No packaged-base comparison on pages. translateFormSubmitBehavior applies ADR-0029 D9.2a (an override beats the catalog) when its caller hands it the packaged config. translatePage takes ResolveOptions and no packaged base for any string (its label, description, header copy and component copy all apply the catalog unconditionally), so the page call passes undefined and the panel follows the page's existing rule. The comparison is still the one function, so a later card that hands translatePage its packaged base gets the panel for free. Noted, not filed.
  • The wizard's form section labels (Basics, Health, Budget & Schedule) and their descriptions stay untranslatable on a page form: object-form's properties.sections has no page key, and _sections addresses an object's form sections, not a page component's. Outside this card. Noted, not filed.
  • File surface. examples/app-showcase/src/ui/pages/new-project-wizard.page.ts is outside the claim's surface. Route (a) needs the form to carry an id, and the showcase form had none, so the showcase strings could not land without it (4 added lines, the id and a comment).

Verification

  • Final head 8837fd00 (seat note): one docs-only commit on top of 9e7da93f. Re-deriving gives 108 families, the 88 below plus 20 docs families. The 20 new ones ran on 8837fd00 and all exit 0; --ran: 108 derived, 108 run, 0 NOT-MEASURED. check:doc-authoring exit 0. The readings below are on 9e7da93f and carry, since the commit touches docs only.

All readings are on head 9e7da93f (the four feature commits through 2093ae5e, merged with origin/main at 0027c4a2), after pnpm install --frozen-lockfile and a declarations build of the touched closures, unless noted.

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 88 commands. All 88 were run with stdin from /dev/null and the exit code captured before any pipe: 86 exited 0 on the first pass, and 2 refused with exit 3 (PREREQUISITE NOT MET: check:dual-build-cjs-loads wants every published package built with declarations, check:i18n-coverage wants the example closure it names). After turbo run build over ./packages/** and ./examples/** (77 tasks), both exit 0: check:dual-build-cjs-loads "113 published require entry point(s) across 70 package(s) load", and check:i18n-coverage "OK (14 config(s), 527 baselined untranslated string(s), none new)".
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE: "88 derived famil(ies) accounted for — 88 run, 0 NOT-MEASURED".
  • pnpm --filter @objectstack/spec test (verify lock): VERDICT command-exit 0, Test Files 765 passed, Tests 22638 passed, 1 todo. pnpm --filter @objectstack/spec check:generated: "All 14 generated artifacts are up to date".
  • pnpm --filter @objectstack/lint test: Test Files 181 passed, Tests 7676 passed. pnpm --filter @objectstack/cli exec vitest run --project unit (after pnpm --filter @objectstack/cli build): Test Files 355 passed, Tests 5109 passed. These two ran before the merge of origin/main, whose incoming commits touch one lint test (validate-ai-tool-references.test.ts) and one cli script (scripts/check-app-nav-i18n.mjs), neither the extractor nor the translation-reference check; the derived lint and cli gates ran on the merged head.
  • typecheck for @objectstack/spec, @objectstack/lint, @objectstack/cli: exit 0 (the first lint attempt hit TS7016 on a dependency closure built without declarations; rebuilt with declarations, exit 0).
  • node scripts/check-nul-bytes.mjs: exit 0. node scripts/check-dev-prereqs.mjs: exit 1, on box state only: the ui/ package dists are not built in this worktree. It judges the local checkout, not the diff, and CI does not run its scan.
  • Dogfood: not owed. The title carries no ! and Clause-② carries no (narrowing) arm.
  • CI on this PR: not yet read; the seat reads the check-runs.

Generated by Claude Code

…h components.ID.submitBehavior

translatePage overlays an object-form component's properties.submitBehavior
title / message from pages.PAGE.components.ID.submitBehavior, through
translateFormSubmitBehavior, now generic over its lookup so the form view
and the page component share one comparison.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyMTiYRPdRNbNBCGdF
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:system tests tooling labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/lint, @objectstack/spec, touching 17 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/export-origins/system.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

4 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 appTranslationDataShape; a string literal in pushFormViewCopy; a string literal in pushThankYouPanel))
  • content/docs/ui/forms.mdx (via submitBehavior (literal, a string literal in appTranslationDataShape; a string literal in pushFormViewCopy; a string literal in pushThankYouPanel))
  • content/docs/ui/public-data-collection.mdx (via submitBehavior (literal, a string literal in appTranslationDataShape; a string literal in pushFormViewCopy; a string literal in pushThankYouPanel))
  • content/docs/ui/translations.mdx (via emptyText (literal, a string literal in appTranslationDataShape), submitBehavior (literal, a string literal in appTranslationDataShape; a string literal in pushFormViewCopy; a string literal in pushThankYouPanel))

⛔ 4 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 appTranslationDataShape; a string literal in pushFormViewCopy; a string literal in pushThankYouPanel))
  • content/docs/releases/v17/17-6.mdx (via translatePage (symbol, a top-level function))
  • content/docs/releases/v17/17-7.mdx (via submitBehavior (literal, a string literal in appTranslationDataShape; a string literal in pushFormViewCopy; a string literal in pushThankYouPanel))
  • content/docs/releases/v17/index.mdx (via submitBehavior (literal, a string literal in appTranslationDataShape; a string literal in pushFormViewCopy; a string literal in pushThankYouPanel))

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
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/export-origins/system.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 59 of 211 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 744ad0ad3afbc29fd0bfee9e8a8e3b7ac3958aec → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7568d8d800088a108e66d97181e60ebca3e091cb — the merge of head 8837fd001625eb762b195ac503cac896a3edd6b8 into base 744ad0ad3afbc29fd0bfee9e8a8e3b7ac3958aec, 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 7568d8d800088a108e66d97181e60ebca3e091cb && git checkout 7568d8d800088a108e66d97181e60ebca3e091cb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 744ad0ad3afbc29fd0bfee9e8a8e3b7ac3958aec 8837fd001625eb762b195ac503cac896a3edd6b8 && git checkout -B drift-repro 744ad0ad3afbc29fd0bfee9e8a8e3b7ac3958aec && git merge --no-ff 8837fd001625eb762b195ac503cac896a3edd6b8

node scripts/docs-audit/affected-docs.mjs --json 744ad0ad3afbc29fd0bfee9e8a8e3b7ac3958aec

⚠️ 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 744ad0ad3afbc29fd0bfee9e8a8e3b7ac3958aec → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…ations key table

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyMTiYRPdRNbNBCGdF
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 8837fd001625eb762b195ac503cac896a3edd6b8 · 2026-10-30T21:58Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8837fd001625eb762b195ac503cac896a3edd6b8
Local-runs: none
Scope: packages/spec/src/system/translation.zod.ts (submitBehaviorTranslationSchema factory, the pages.PAGE.components.ID.submitBehavior member, the thankYou alias), packages/spec/src/system/i18n-resolver.ts (translateFormSubmitBehavior taking a lookup, PAGE_FORM_COMPONENT_TYPE, lookupPageFormSubmitBehaviorText, the translatePage branch) + packages/spec/src/system/i18n-resolver.page-form-thank-you.test.ts, packages/spec/api-surface/system.json and export-origins/system.json (the new export), packages/lint/src/validate-translation-references.ts and its test (the components.ID.submitBehavior target check), packages/cli/src/utils/i18n-extract.ts (pushThankYouPanel) and its coverage test, examples/app-showcase/src/ui/pages/new-project-wizard.page.ts (the form id) and src/system/translations/index.ts (zh-CN copy), .changeset/28214-page-form-thank-you-translation.md, content/docs/ui/translations.mdx

Inputs: card #28214 (body, triage 6530114050 and 6531357708, claim 6532865741, claim correction 6534307642 adding the showcase page id to the file surface, os-dev-report 6534288052), the S1 should-fix of record 6528389425 on #28195 that filed it, the PR body, the diff of 9e7da93 against merge base 0027c4a and the one docs-only commit 8837fd0 on top of it (two key-table rows in content/docs/ui/translations.mdx, the form view's panel key and the page form's panel key, nothing else), the head's check-runs (read 2026-10-30T22:34Z: check-runs on 8837fd0: 50 total, 41 success (Auto Label, Build & E2E UI, Build Core, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Spec property liveness, Test Core, Test Core (all 8), Test UI, Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter); 9 skipped path-filtered (18.x upgrade harness (opt-in), Dogfood Regression Gate (matrix shard/3), Dogfood Verify CLI, Lint UI, Packed-tarball smoke (opt-in), Schedule gate · skip when a green push run covers the commit, Temporal Conformance (live PG + MySQL), Test UI (matrix shard/16), UI Published Artifacts); 0 failure, 0 in progress). Author user.login is objectstack-fleet[bot]; the drafter is session_01L9qibyMTiYRPdRNbNBCGdF on claude/project-thread-8iwnj7; the reviewer is not the drafter.

① Derived judgments

  • The key: pages.PAGE.components.ID.submitBehavior: { title?, message? } is a strict node built by the new submitBehaviorTranslationSchema(surface) factory, which the form view's _views.VIEW.submitBehavior now mounts too, so both addresses carry one alias table (heading, text, body, description) and the same url / kind prescriptions; the components node gains thankYou: 'submitBehavior' as the _views node has. The actionTranslationSchema precedent in the same file; no second copy of the shape.
  • The overlay: translateFormSubmitBehavior now takes a lookup(attr) and is the one comparison for both carriers; translateView passes the _views lookup it used before (behaviour unchanged, the viewKey hoisted once), translatePage passes lookupPageFormSubmitBehaviorText for an addressed component of type PAGE_FORM_COMPONENT_TYPE ('object-form'), on the same addressed arbitration as the component's title / description, before the nested early return, so a nested form is reached by the id route. Rules therefore hold at both carriers: only a kind: 'thank-you' panel, only an authored non-empty string, missing key keeps the authored text, identity returned when nothing applies, document not mutated. Matches the card's "same override rules, one comparison" ask.
  • Route (a), under the page, with the alternative (b, under the bound object) named and refused for the reason the card asked to see: the panel is the page's copy, two pages binding one object may word it differently, and the id route is the one extractor, lint and resolver already share; the cost (a form without an id has no key) is the rule every page component follows, and the showcase form gets an id for it.
  • Extractor and lint follow: pushThankYouPanel holds the thank-you reading once for both carriers (a non-thank-you kind records both keys unauthored, as before on the view side); lint refuses a submitBehavior key under a component the page declares with another type or under an undeclared id, offering the nearest object-form id, and skips an undeclared page as the tab-items check does.
  • Tests match the claims: 12 spec cases through parsed fixtures (PageSchema, TranslationDataSchema) and translateMetadataDocument('page', …): translation, fallback chain, missing key, no-message control, redirect control, wrong-id and non-form controls, title-key and panel-key separation, no-id control, non-mutation, and the face's two refusals; lint and extractor tests beside them.

② Semver level

@objectstack/spec, @objectstack/cli, @objectstack/lint: all minor, with Clause-②: yes and Metadata-protocol: added on body and changeset. One optional translation node joins a published schema and one constant is exported: a widening, nothing an author wrote before changes meaning or is refused; added requires at least one moved package at minor, and all three carriers agree (body, claim 6532865741 with its correction, changeset). Skills-docs follow-up #28215 named.

③ Boundary flags

  • The console reads submitBehavior from the component props (ObjectForm.tsx, WizardForm.tsx) and /meta/page goes through translateMetadataDocument, so the translated panel reaches the reader with no ui change. The body says this is a code reading, not a live measure; acceptable for a spec-half card whose renderer is unchanged.
  • File surface: all paths are inside the order's list plus the seat's correction 6534307642 (the showcase page id) and the two content/docs/ui/translations.mdx rows the seat approved in its lane note (commit 8837fd0). The PR body still names 9e7da93; the seat patches it at ACCEPT.
  • Should-fix, not blocking: the generated translation reference renders the page components node collapsed, so the new key is documented in the translations.mdx key table (8837fd0) but not in the generated reference; worth a row there when that generator next expands page nodes.

④ Design judgment

Against the card's scenario (the showcase wizard's "Project created" panel staying English in every locale) the change holds: the id gives the form an address, the zh-CN pack carries the four strings, and the resolver overlays them by the form view's rules. Against the spec on main, the factory removes the only path by which the two panel addresses could drift, and the exported constant makes resolver, extractor and lint agree on which component carries the key, the PAGE_TABS_COMPONENT_TYPE precedent. The route choice is argued on ownership of the copy, which is the right axis.

Implemented-by: claude/project-thread-8iwnj7
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm

维护者速读

  • 改了什么: 页面里 object-form 组件的“提交成功”面板(标题、正文)现在可以翻译了,键位是 pages.页面.components.组件ID.submitBehavior.title / .message。表单视图的同类键 _views.视图.submitBehavior 和它共用一个形状和一套覆盖规则。抽取器会生成这些键,lint 会校验它们,示例应用的新建项目向导也补上了中文。
  • 为什么改: A page's object-form thank-you panel has no translation key #28214。feat(spec): a form view's thank-you panel is translated through _views.VIEW.submitBehavior #28195 只让表单视图的面板可以翻译,嵌在页面里的表单面板在所有语言下仍然显示作者写的原文。
  • 风险与回滚: 只是新增一个可选的翻译节点,已有的翻译包和页面含义都不变。渲染端没有改动。没有 id 的组件不会有键。回滚即 revert。
  • 先看: packages/spec/src/system/i18n-resolver.ts(translateFormSubmitBehavior 改为接收查找函数,translatePage 新增的分支)和 translation.zod.ts 的共用工厂。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run src/system/i18n-resolver.page-form-thank-you.test.ts。
  • 需要你做的: 无。不属于受管面,契约复审 PASS(评论 6534935725),由席位落地。
  • 席位意见: ACCEPT,可落地。

席位核对 · ACCEPT · domain:spec seat 1 (#6017) · 2026-10-30T22:36Z

Head 8837fd001625eb762b195ac503cac896a3edd6b8. Card #28214: claim 6532865741, correction 6534307642, os-dev-report 6534288052. The maintainer's ruling, verbatim: 「按 pm-dispatch 审过的pr你可以自主落地」.

  • Shape: draft to main, made by the relay, with Fixes #28214. These lines are line-initial: Clause-②: yes, Metadata-protocol: added, Skills-docs: follow-up #28215. The changeset is @objectstack/spec, @objectstack/cli and @objectstack/lint, all minor, matching the feat(spec): a form view's thank-you panel is translated through _views.VIEW.submitBehavior #28195 grade.
  • Route: (a), the page's per-component copy, as the order leaned. Route (b) is rejected in the body on ownership of the copy: two pages binding one object can word the panel differently.
    • One comparison: translateFormSubmitBehavior takes a lookup and serves both translateView and translatePage.
    • One node factory serves both addresses.
  • Contract review: record 6534935725 from the lane at CONTRACT_REVIEW_TIER, full ①–④, PASS.
    • Should-fix dropped: the generated translation reference renders the page components node collapsed. The new key is documented in the translations.mdx key table, which is where readers look up keys, so there is no reader gap. A generator change for one collapsed node is not worth a card.
  • File surface: the order's list, plus the showcase page id (correction 6534307642), plus the two translations.mdx key-table rows (seat-approved; the second row documents feat(spec): a form view's thank-you panel is translated through _views.VIEW.submitBehavior #28195's form view key, which was missing).
  • Body: patched by the seat. It now has the signature first line, a docs bullet, and final-head verification on 8837fd00.
  • Gates: at the lane's 22:34Z read, 41 success, 9 skipped, 0 failure. check-governed-merges --pr 28269: NOT governed.
  • Landing: pr_ready + automerge_enable in this act.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 22:38
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 30, 2026 22:38
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 361ea24 Oct 30, 2026
57 of 58 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-8iwnj7 branch October 30, 2026 23:01
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.

A page's object-form thank-you panel has no translation key

1 participant