Repository navigation
fix(spec): translateView localizes a form view's sections through objects.OBJECT._sections.NAME - #23749
Conversation
…ects.OBJECT._sections.NAME Claude-Session: https://claude.ai/code/session_01HjR8RUwTPQ3TgbfKihmxp9 Co-authored-by: Claude <noreply@anthropic.com>
…ECT._sections.NAME Claude-Session: https://claude.ai/code/session_01HjR8RUwTPQ3TgbfKihmxp9 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjR8RUwTPQ3TgbfKihmxp9 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 5 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 — 142 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 d8d8141d05539494628fa1242284edad437c37a7 && git checkout d8d8141d05539494628fa1242284edad437c37a7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a869ea6362e2f44daeedc557b848493fd33f9020 4abffa0fb2204c52dad84c4f78f158267c0d3d6f && git checkout -B drift-repro a869ea6362e2f44daeedc557b848493fd33f9020 && git merge --no-ff 4abffa0fb2204c52dad84c4f78f158267c0d3d6f
node scripts/docs-audit/affected-docs.mjs --json a869ea6362e2f44daeedc557b848493fd33f9020
|
席位核对 · ACCEPT · PR #23749 @
|
Contract reviewRendered: 2026-10-16T00:24Z · check-runs on the head read at 2026-10-16T00:23Z Served-tier: Inputs: card #23632 (body and every comment), PR #23749 (body, file list, the three-dot diff ① Derived judgmentsPublic surface — unchanged, as claimed. Locale chain and override rule — one copy, not a second. Key convention — the declared key, read where the extractor and the linter put it. Reach — as the docblock claims. The rest layer's three call sites ( One inaccurate sentence in the hunk, no behaviour behind it. Config identity — ② Semver level
③ Boundary flagsThe dev's open question — whether the catalog should add a heading or a description to a named section that authored none. Answered here; no escalation is needed: the shipped rule, translate authored copy only, holds. The server-side overlay changes the language of copy, not the shape of the form. Dev deviations, all accepted. §10 pre-PR pull not performed: Out-of-scope findings. Check-runs on the head at 2026-10-16T00:23Z: 40 runs, 39 completed (33 success, 6 skipped, 0 failure), 1 in progress — Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #23632
Clause-②: no
What this does
translateView(andtranslateMetadataDocument('view', …), which dispatches to it) now localizes a served form view'ssections. Each named section'slabelanddescriptionresolve throughobjects.OBJECT._sections.NAME.label/.description, on the samelocaleChain/pickDataevery other resolver inpackages/spec/src/system/i18n-resolver.tsuses. No new export: the card's first option ("translateViewcovers a form view'ssections").namehas no key and is returned as the same object. Its label is never slugged into a key: the slug-of-label fallback in themetadataForms.TYPE.sectionswalk belongs to a different namespace and a different rule, and the translation linter refuses that spelling for_sections.packagedBase.config.sectionsby thenamethe bundle addresses it by, through the existingpackagedPartfinder (its key union gains'sections'). The comparison is the existingvalueOverridesPackagedBase, with no second copy. A section the base does not carry was authored after the fact and keeps its strings. With no base supplied, the catalog applies.config.data.object) first, then the view's. That is the order the i18n extractor (walkObjectSections) and the translation linter (collectViewRecord) already use for form sections, so the key read here is the key they scaffold and accept.configis rebuilt only when a def or a section actually moved. A view with no section translation keeps itsconfigreference, as before.Measured readings (dispatch Zone 2)
All against
origin/main534ca13eb(the branch base).translateViewnever readsections: confirmed. FromtranslateViewtotranslateMetadataDocument, the base holds 0 occurrences ofsections. The resolver's only othersectionswalk isresolveMetadataFormLabels(metadataForms.TYPE.sections). It is a different namespace with a slug-of-label fallback, so it is not the same rule and was not reused.translation.zod.tsdeclares_sections(label,description) onObjectTranslationDataSchema, andcomponent.zod.tsdeclares sectionnameas the_sectionsanchor (record:detailsandobject-formsections). The form view's ownFormSectionSchema.name(ui/view.zod.ts) says only "Stable section identifier for i18n lookup". Its TSDoc points atmetadataForms.TYPE.sections.NAME, which see the acceptance notes below.config.sections.getMetaItems({ type: 'view' })filters every aggregated container out (metadata-protocolprotocol.ts, "Never surface the aggregated … container"). So every elementGET /forms/:slugscans is a ViewItem, andanonymousFormIntakeCandidates'sconfigarm (viewKind === 'form') is the one that matches there:match.form === match.view.config.ListViewSchemarefuses asectionskey (unrecognized_keys, measured against the built package), soconfig.sectionsexists only on the form arm, and noviewKindgate is needed.GET /forms/:sluglocalizes the object schema but returns the form's section labels untranslated, so an anonymous zh-CN visitor reads English section titles (the producer behind objectui#12333) #23497 makes:translateMetadataDocument('view', match.view, bundle, { ...RestServer.translateOptionsFor(i18n, locale), packagedBase: this.packagedObjectBase(p, 'view', match.view.name) }). The route then serves the translated view'sconfigasform, before its existing field strip. That is the same pair of arguments the object half of the same handler already passes.name, the key the bundle addresses. A nameless section is never translated, so it needs no identity.metadataFormswalk: a different rule, see 1.git grep -l "translateMetadataDocument\|translateView" -- packages ':!packages/spec'): the runtime callers are inpackages/rest/src(meta-item-read-gate.ts,rest-server.ts).metadata-protocol/src/protocol.tsnamestranslateViewin a comment only. The rest are tests (objectql,platform-objects,plugin-approvals,service-i18n,cli). Readings are under Tests.Decision: the catalog never adds a heading or a description
A section that authors no
labelstays without one, and the same holds fordescription, even when the bundle carries_sections.NAME.label. The card's pins do not decide this case, so here is the reason. The public form renderer (objectuiFormPage) draws a heading only undersec.label &&, and its comment rules that "a translation cannot conjure a heading the author did not write". A label added here would draw a heading on the anonymous route only. The authenticatedFormPagepath would draw none for the same form. Pinned, and ablated below. The alternative is to overlay whatever the bundle carries, the way the view's owndescriptionis overlaid. That would be a one-line change if ruled otherwise.Tests
packages/spec/src/system/i18n-resolver.form-sections.test.ts, 13 cases. The fixtures are spec-legal: the container passesViewSchema.safeParseand each bundle object node passesObjectTranslationDataSchema.safeParse. The served documents come fromexpandViewContainer. Cases: the card's three pins, the only-authored-copy rule, an inline locale-map heading, the form's own binding, the locale chain (BCP-47 ladder, declared fallback chain, default locale),configreference identity and no mutation, and four override-rule cases (unedited, edited per string, not in base, no base).4abffa0fb, each command run throughscripts/pm/os-verify-lock.sh:pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 680 files, 20769 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0.check:test-typecheckOK, and the new test file adds no signature totest-typecheck-debt.json.pnpm --filter @objectstack/spec check:generated(after a realpnpm --filter @objectstack/spec build): all 14 artifacts up to date,check:api-surfaceincluded. So no export, parameter or type widens, andClause-②: noholds as measured.dist(turbo run build --filter='@objectstack/rest^...'). The checks below ran per package, by file:@objectstack/rest, 9 files, 166 passed:meta-view-container-i18n,meta-dashboard-view-i18n-explicit-override,meta-i18n-declared-fallback-chain,meta-plural-i18n, and the fivepublic-form-*suites.@objectstack/objectql, 2 files, 16 passed:protocol-packaged-view-base,engine-validation-locale-negotiation.@objectstack/metadata-protocol, 2 files, 242 passed:protocol.graft-folded-form-sections,view-container-runtime-expansion. Its source has no runtime caller oftranslateView, only a comment.@objectstack/platform-objectsinvite-user-action-copy, 34 passed.@objectstack/service-i18nauthored-translations, 16 passed.Gates
The local subset is
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon this diff: 84 families. Each exit code was captured before any pipe, and the record was reconciled with--ran:84 derived famil(ies) accounted for — 83 run, 1 NOT-MEASURED. 83 exit 0. The one NOT MEASURED ispnpm check:dual-build-cjs-loads, exit 3, PREREQUISITE NOT MET: it reads every package'sdist/, and this worktree built only the closure of@objectstack/rest. CI runs it. Two families first answered exit 3 and gave their verdict, exit 0, once their prerequisites were met:check-plugin-teardown-shape --self-testafter fetching its pinned fixture commit, andcheck:doc-formula-expressions/check:lean-entry-closureonce their packages were built. The local-onlycheck:dev-prereqsgives exit 0. Every family CI runs is read from this PR's check runs, not re-run here.Not done before opening:
origin/mainis 7 commits past this branch's base. None of them touches a file in this diff ori18n-resolver.ts/view.zod.ts: the spec files they change are migration entries andapi/protocol.zod.ts. This diff changes no generated artifact either. The PR's CI runs on the merge ref.Ablation (one per rule, each restored and proven by blob hash == HEAD and an empty
git diff HEAD)The suite imports
./i18n-resolverrelatively (source, notdist/), so no rebuild sits between mutation and run. Each mutation went throughscripts/ablation-replace.mjs, whose anchor must hit exactly once.translateFormSectionsreturns its input (return changed ? next : sections;→return sections;)config.data.objectnever adds a heading or a description) / 12 passedAcceptance notes (observations, not filed)
FormSectionSchema.nameTSDoc names the wrong namespace for an object-bound form view. It says a bundle targets the section viametadataForms.TYPE.sections.NAME. That holds for a metadata-editing form (defineForm({ schemaId })). The renderers, the extractor, the linter and now this resolver all address an object-bound form view's section underobjects.OBJECT._sections.NAME. The.describe()string names no namespace. Doc drift outside this card's file surface. Carrier: none.config.sections.walkObjectSectionsandcollectViewRecordread a view record's top-levelsections,formandformViews, neverconfig.sectionsof an authored{ name, object, viewKind: 'form', config }record. No source producer of such a form record exists in this tree (git grep "viewKind: 'form'"overexamples,packages/platform-objects,packages/plugins,apps: zero hits outside tests), so nothing reaches it. Read-only inference, not filed. Carrier: none./meta/viewread now serves section copy translated. This is the intended reach. An editor that read a view with a locale and saved that body back would persist the translation. The viewlabeland the bulk-action defs already have that exposure on the same read, so this widens it to one more key and does not change its kind.GET /forms/:slugstill servesformuntranslated until rest(public forms):GET /forms/:sluglocalizes the object schema but returns the form's section labels untranslated, so an anonymous zh-CN visitor reads English section titles (the producer behind objectui#12333) #23497 makes the call above. rest(public forms):GET /forms/:sluglocalizes the object schema but returns the form's section labels untranslated, so an anonymous zh-CN visitor reads English section titles (the producer behind objectui#12333) #23497 is not addressed here.Generated by Claude Code