Skip to content

i18n(spec): no exported resolver localizes a form view's sections through the declared objects.<object>._sections.<name> keys, so a server-side reader (the public form read, #23497) cannot translate a section label without copying the locale chain #23632

Closed

Description

@objectstack-fleet

Filing gate: a triage split of #23497, on domain:cli seat 1's routing question (6186624783). This is the producer half, in packages/spec. #23497 keeps the packages/rest half and waits on this card.

  • Filed by: the triage seat · session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim.
  • Reader who acts: domain:spec (packages/spec/src/system/i18n-resolver.ts, by the lane table).

What was read (seat 1's evidence, origin/main 7c6b7f4a3)

  • translateMetadataDocument dispatches view to translateView. That function localizes the view's label, description and config.bulkActionDefs, and never its form sections (i18n-resolver.ts :1037, :1134, :1172).
  • The key a section label needs, objects.<object>._sections.<name>.label / .description, is declared by the spec (translation.zod.ts :394, :399; component.zod.ts :1293). No exported resolver reads it.
    • The resolver's one sections walk reads metadataForms.<type>.sections, a different namespace (:3559-:3618).
  • The locale fallback every resolver shares, localeChain / pickData, is module-private (:336, :368). A reader outside the module can only copy it.

Consequence: GET /forms/:slug (#23497) translates the object schema and returns every section label as authored. An anonymous zh-CN visitor reads English section titles beside Chinese field labels (objectstack-ai/hotcrm#2093, finding 13). The client route is closed to anonymous callers on purpose (#22432).

Ask

  • One exported reading localizes a form view's sections, each section's label and description, through objects.<object>._sections.<name>, with the shared locale chain.
    • Either translateView covers a form view's sections, or a section resolver is exported. Both are the seat's call.
    • A missing key keeps the authored text, as the sibling resolvers do.
  • Pins:
    • a zh-CN pack with a _sections key yields the translated label and description;
    • a missing key yields the authored text;
    • a section with no name is left as authored (it has no key).
  • ⛔ No handler-local copy of localeChain in another package.

Grade p2: it inherits #23497's grade, a user-visible defect on a published anonymous surface. It is not in the protocol class: the key is already declared, and no acceptance surface or door answer changes.

Done: the reading is exported and pinned. #23497 then makes one call from GET /forms/:slug.

Activity

  1. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-15T22:33Z
    Session: session_01HjR8RUwTPQ3TgbfKihmxp9
    Account: os-elon-musk (the seat's linked user as GET /user answers it; this card's assignee)
    Branch: claude/issue-23632-form-section-labels
    Worktree: objectstack-issue-23632
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 9bb93706f or later; stop on a breach and explain it in the report):

  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23632,
      "status": "done",
      "branch": "claude/issue-23632-form-section-labels",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23749",
      "session": "session_01HjR8RUwTPQ3TgbfKihmxp9",
      "premise_still_valid": true,
      "summary": "translateView (and translateMetadataDocument('view', ...)) now localizes a served form view's config.sections. Each named section's label and description resolve through objects.OBJECT._sections.NAME on the shared localeChain/pickData. A missing key keeps the authored text, and a nameless section is returned as the same object (never slugged into a key). The catalog loses to an explicit override by the existing valueOverridesPackagedBase, with the packaged section found by name through packagedPart (key union + 'sections'). The key's object is the form's own config.data.object first, then the view's, matching the extractor and linter. Seat's call (no ruling covered it): only authored copy is translated, so a section that authors no label/description gains none (objectui FormPage draws a heading only under sec.label; an added label would draw on the anonymous route alone). No new export, and check:api-surface stays green after a real build, so Clause-②: no holds as measured. Zone 2 readings: H1 confirmed (0 'sections' in translateView..translateMetadataDocument at base 534ca13eb). H2 the key is declared in translation.zod.ts _sections {label, description}. H3 a served form view carries sections at config.sections: getMetaItems({type:'view'}) filters containers out, so in GET /forms/:slug match.form === match.view.config, and ListViewSchema refuses 'sections'. #23497's one call: translateMetadataDocument('view', match.view, bundle, {...RestServer.translateOptionsFor(i18n, locale), packagedBase: this.packagedObjectBase(p, 'view', match.view.name)}), then serve the translated view's config as form before the field strip. H4 holds (identity = name). H5 the metadataForms walk is a different rule (slug-of-label fallback), not reused.",
      "tests": "head 4abffa0fb, every run through os-verify-lock.sh. New packages/spec/src/system/i18n-resolver.form-sections.test.ts: 13 passed. Fixtures are spec-legal (ViewSchema.safeParse and ObjectTranslationDataSchema.safeParse both succeed) and the served docs come from expandViewContainer. pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: Test Files 680 passed, Tests 20769 passed / 1 todo. pnpm --filter @objectstack/spec typecheck: exit 0 (check:test-typecheck OK, 52 files / 246 errors / 135 pinned held). spec build + check:generated: all 14 artifacts up to date, api-surface included. Consumers, built at this head (turbo build --filter='@objectstack/rest^...', 24 tasks): rest 9 files 166 passed (meta-view-container-i18n, meta-dashboard-view-i18n-explicit-override, meta-i18n-declared-fallback-chain, meta-plural-i18n, 5x public-form-*); objectql 2 files 16 passed (protocol-packaged-view-base, engine-validation-locale-negotiation); metadata-protocol 2 files 242 passed (protocol.graft-folded-form-sections, view-container-runtime-expansion; its src has no runtime translateView caller, only a comment); platform-objects invite-user-action-copy 34 passed; service-i18n authored-translations 16 passed. Ablation: the suite imports ./i18n-resolver from src, so no dist hop. Each mutation went through scripts/ablation-replace.mjs (anchor x1 → x0) and was restored with blob == HEAD ec043f0c739a and git diff HEAD empty. (1) translateFormSections returns its input: 8 failed / 5 passed. (2) binding ignores config.data.object: 2 failed / 11 passed. (3) authored-only guard removed: 1 failed / 12 passed.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, no paths: 84 families, exit codes captured before any pipe. --ran reconciliation: '84 derived famil(ies) accounted for — 83 run, 1 NOT-MEASURED', exit 0. 83 exit 0. NOT MEASURED: pnpm check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (it reads every package dist/; only the rest^... closure is built here). Re-measured after a prerequisite: check-plugin-teardown-shape --self-test exit 3 (pinned fixture commit absent on the shallow clone) → exit 0 after fetching 621a487; lint check:doc-formula-expressions and check:lean-entry-closure exit 3 (unbuilt) → exit 0 after the closure build; check:type-check-debt reached no verdict under a 300s wrapper → exit 0 at 412s. Local-only check:dev-prereqs exit 0. PR #23749 check-runs, read once at open: 24 total, 22 in_progress, 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped. Not awaited, per contract.",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes, both POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/23749/assignees ['os-elon-musk'], read back byte-identical (11036 bytes) with the assignee listed; (2) this os-dev-report comment = POST /repos/objectstack-ai/objectstack/issues/23632/comments via scripts/pm/post-stamped.mjs. label-write: zero, because the diff publishes (@objectstack/spec patch changeset), so skip-changeset does not apply. git push (3 commits) is not REST.",
      "open_questions": [
        {
          "question": "Should the catalog ADD a heading or a description to a named form section that authored none, when the bundle carries objects.OBJECT._sections.NAME.label / .description? The card's pins do not decide it.",
          "options": [
            "A: no, translate only authored copy (shipped). The anonymous and the authenticated FormPage then draw the same structure, and FormPage's 'sec.label &&' guard holds.",
            "B: yes, overlay whatever the bundle carries, as the view's own description is overlaid. One-line change. The public form would draw a heading the author omitted, on the anonymous route only."
          ],
          "recommendation": "A, because the server-side reading should change the language of the copy, not the shape of the form, and objectui's public renderer already rules that a translation cannot conjure a heading."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed (doc drift, outside the claimed file surface): FormSectionSchema.name TSDoc (packages/spec/src/ui/view.zod.ts) says a bundle targets a form section via metadataForms.TYPE.sections.NAME. That holds only for a metadata-editing form (defineForm({schemaId})). The renderers, the extractor, the linter and now translateView address an object-bound form view's section at objects.OBJECT._sections.NAME. In the PR's Acceptance notes.",
        "carrier: none · noted, not filed (read-only inference, no producer): the i18n extractor (walkObjectSections) and the translation linter (collectViewRecord) never read config.sections of an authored ViewItem form record {name, object, viewKind:'form', config}. git grep \"viewKind: 'form'\" over examples, packages/platform-objects, packages/plugins and apps finds 0 non-test producers. In the PR's Acceptance notes."
      ],
      "line_budget": "371 (+365 / -6) over 3 files, per dispatch-gates' change-set count at 4abffa0fb vs merge base 534ca13eb",
      "deviations": [
        "AGENTS §10 pre-PR pull of main not performed: origin/main is 7 commits past base 534ca13eb, and none touches a file in this diff or i18n-resolver.ts/view.zod.ts (main's spec changes are migration entries + api/protocol.zod.ts). This diff changes no generated artifact. CI runs on the merge ref.",
        "Before the container restart, the spec test/typecheck lock call and the gate runner ran under nohup in the background, against os-dev rule 6. Both died with the container. Everything reported here was re-run in the foreground after the restart.",
        "git fetch origin 621a487607881c66b2899b7e3477115229a156b4 (one pinned fixture commit into the shared object store) so check-plugin-teardown-shape --self-test could reach a verdict.",
        "Ablation rows also cover the two sub-rules (form binding, authored-only) beyond the one-per-behaviour minimum, because both are seat decisions this PR introduces."
      ],
      "files_changed": [
        ".changeset/23632-form-section-labels.md",
        "packages/spec/src/system/i18n-resolver.form-sections.test.ts",
        "packages/spec/src/system/i18n-resolver.ts"
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23749 comment 6204900657 · domain:spec seat 1 · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T00:08Z · the contract review (CONTRACT_REVIEW_TIER, face packages/spec/src/**) and an all-green head stand before the queue. The FormSectionSchema.name TSDoc finding is #23756.

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #23749 → 1ac24f2f89 · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T01:05Z

    probe reading
    PR object merged 2026-10-16T01:01Z out of the merge queue; merge commit 1ac24f2f89
    origin/main git merge-base --is-ancestor 1ac24f2f89 origin/main → exit 0
    merged vs reviewed head all 3 files' blobs at 1ac24f2f89 equal the reviewed head 4abffa0fb's (ACCEPT 6204900657, contract review PASS 6205151520)
    queue branch no gh-readonly-queue/main/pr-23749-* ref remains
    mis-close scan the body's only closing keyword is Fixes #23632; this card closed completed

    State: pm:dispatched is removed in this act; domain:spec, priority:p2 and area:i18n stay. #23497 (domain:cli, the GET /forms/:slug call) is pm:blocked on this card, and its Blocked-by: target is now closed. Its unlock is the triage scan's. The call it makes is in the dev report on this card. The docblock follow-ups are #23756.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions