Repository navigation
feat(spec): a form view's thank-you panel is translated through _views.VIEW.submitBehavior - #28195
Conversation
…EW.submitBehavior Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxqJVde7kDCjJSHEUo
…ger; add changeset Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxqJVde7kDCjJSHEUo
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxqJVde7kDCjJSHEUo
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibxqJVde7kDCjJSHEUo
📓 Docs Drift CheckThis PR changes 3 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 177 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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
|
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: At |
|
domain:spec#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: 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 ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Should-fix (none blocks the contract):
Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D 维护者速读
席位核对 · ACCEPT ·
|
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 也认这个键。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_viewstranslation entry declared no key for it andtranslateViewnever readconfig.submitBehavior, so a public form read in another language confirmed the submit in the authoring language under a translated heading and translated field labels.translation.zod.ts). The_views.VIEWentry gainssubmitBehavior: { 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 owntitle(one key cannot stand for two source strings, the same reasonemptyState.titleis nested). Its inner aliases mirror the thank-you member's own (heading,text,body,description).urlandkindcarryguidanceprescriptions, since neither is display copy andkindis what an author copying the authored block would bring along.thankYouon the view node namessubmitBehavioras the rename.i18n-resolver.ts).translateViewcalls a newtranslateFormSubmitBehaviorbesidetranslateFormTitle, readingobjects.OBJECT._views.VIEW_KEY.submitBehavior.{title,message}on the same locale chain and the same view key. Rules, each the form title's: only akind: 'thank-you'panel is touched; only an authored non-empty string is translated (a key cannot invent amessagethe 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 onevalueOverridesPackagedBaseagainst the packaged view'sconfig.submitBehavior(a packaged view with no panel makes every served string authored). Theconfigreference is kept when nothing changes.packages/cli/src/utils/i18n-extract.ts). A newpushFormViewCopyemits a served form's_viewstails,titleandsubmitBehavior.title/.message, and both form sites (container forms throughpushContainerFormTitles, and aviewKind: 'form'item) call it. Soos i18n extractscaffolds 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 recordstitle.packages/lint/src/validate-translation-references.ts).FORM_ONLY_VIEW_TAILS=title,submitBehavior. AsubmitBehaviorkey over a list view is refused like atitlekey is, and the default form's key is legal for those two tails only.examples/app-showcase/src/system/translations/index.ts). The showcase authors two thank-you panels (showcase_inquiryformViews.contact, the public/forms/contact-usweb-to-lead form, andshowcase_contactformViews.create). The extractor now counts their four authored strings for zh-CN, so zh-CN gains them andcheck:i18n-coveragestays at its baseline (none new, below). This file was added to the claim's surface by the seat's amendment.content/docs/references/system/translation.mdx(gen:docs) anddocs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md(gen:strictness-ledger, one more strict object)..changeset/28004-form-thank-you-translation.md:@objectstack/spec,@objectstack/cli,@objectstack/lint, allminor, with both declaration lines.The public door really runs the translator.
packages/rest/src/form-routes.ts:353callstranslateMetadataDocument('view', match.view, bundle, { ...this.translateOptionsFor(i18n, locale), packagedBase: … })insideGET /api/v1/forms/SLUG, and:357servesview.configas the response'sformwhen the matched form is the item'sconfig; the response carriesVary: Accept-Language(:403). The console'sFormPagereadsloaded.form.submitBehavior(ui/apps/console/src/components/FormPage.tsx:2577) and draws the authoredtitle/message(:2957–:2958), so the translated panel reaches an anonymous visitor with no ui change. The internal/_console/forms/NAMEpath reads the view through/meta/view, whichmeta-item-read-gate.ts:3062already sends throughtranslateView.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'stitle, the exact two-strings-one-node confusion thetitlekey was built to avoid. The nested key mirrorssubmitBehavior.title/.messageas authored.Tests
Every run below is on head
af93f347unless noted.packages/spec/src/system/i18n-resolver.form-thank-you.test.ts(new, 11): parsed fixtures (container throughViewSchema, bundle node throughObjectTranslationDataSchema); 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 amessagegains none; aredirectbehavior is untouched and keeps itsconfigreference; 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 (urlrefused asunrecognized_keysat the panel path,kindrefused 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 owntitleonly, so the newsubmitBehavior.titleslot 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 (on1fb17014;af93f347added 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 werepublished-subpath-console.pinandpublished-subpath-hook-body.pin, which refuse to run against an unbuiltpackages/cli("packages/cli is not built"). Afterpnpm --filter @objectstack/cli buildboth pass (with the two form coverage files: 4 files, 39 tests).typecheckfor@objectstack/spec,@objectstack/lint,@objectstack/cli: all Done (exit 0).pnpm --filter @objectstack/spec check:generated --fix: regeneratedgen:docsandgen: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.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'sLint & Repo Gatesruns it; this diff touches no package entry or build config.1fb17014toaf93f347; the commit in between changed one test file and the changeset's wording only. Re-run onaf93f347afterpnpm --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-testfirst refused on the shallow clone (its positive control commit621a4876was absent); after fetching that commit: 48 cases pass, exit 0.pnpm check:i18n-coveragefirst refused (example closure unbuilt); after building the closure it names:OK (14 config(s), 527 baselined untranslated string(s), none new).Acceptance notes
domain:uicard the order reserves may turn out to be a verification-only item; the seat decides.object-formcomponent.ObjectFormPropsSchema.submitBehavior(packages/spec/src/ui/component.zod.ts) is the same thank-you block authored on a page component (the showcase'snew-project-wizardpage 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/objectstack-i18n/SKILL.mdlists the_views.{view_name}tails; teachingsubmitBehavior.title/.messagethere is owed (line above) and stays off this PR to keep it off governed paths.Generated by Claude Code