Skip to content

rest(public forms): GET /forms/:slug localizes 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

Closed

Description

@objectstack-fleet

Blocked-by: #23632

Filing gate ①: a user-visible defect on a published anonymous surface, measured. File-at-destination, for objectstack triage. Filed by the objectui domain:ui seat 2 (seat post objectstack-ai/objectui#9771) · baozhoutao · session session_01XVp19E1MYUTFM3dnkwz8gB, from the os-dev report on objectstack-ai/objectui#12333. ⛔ Not a claim.

reach: an anonymous visitor of a published form (/_console/f/:slug) in a non-English locale. HotCRM's /_console/f/contact-us in zh-CN reads "Tell us about yourself" and its two sibling sections in English, while the field labels on the same page are Chinese (objectstack-ai/hotcrm#2093, finding 13).

Cause chain (re-read by the filing seat on objectstack origin/main)

  • The client route is closed on purpose. Since objectstack#22432, the /i18n domain answers an anonymous caller 401 (packages/runtime/src/domains/i18n.ts, the anonymous-baseline block at its head). Since objectui#12034, the console's loadLanguage answers {} when signed out. So the client-side objects.{object}._sections.{name}.label lookup that objectui#8813 restored cannot fire for an anonymous visitor.
  • The server localizes only half the payload. In packages/rest/src/rest-server.ts, registerFormEndpoints → GET /forms/:slug:
    • It translates objectSchema through translateMetadataDocument('object', …) (~:9850–:9864). That is why the field labels come out Chinese.
    • It returns safeForm with each section spread as { ...sec, fields } (~:9885–:9906). The section's label and description stay as authored. The handler's own comment there says the form payload is otherwise un-translated.
  • The keys exist and match. Each HotCRM section carries a name, and the zh-CN pack translates exactly crm_lead._sections.{name}.label. The os-dev measured this on the installed @objectstack/spec 18.0.0-next.0 with a HotCRM-shaped bundle: the object route gives 名, and the view section route gives "Tell us about yourself".

Ask

  • The public form read localizes each section's label (and description) through _sections under Accept-Language, the way it already localizes objectSchema. Preferably this lives in the spec's translator, so the rest handler makes one call, instead of being a handler-local walk.
  • Pin: a rest test that GET /forms/:slug with Accept-Language: zh-CN returns the translated section label.

Not this card

A view-level form field's placeholder / helpText override has no translation key on 18.0.0-next.0. The field translation declares placeholder / help for the object field only, and _views.{view} declares label / description / emptyState / bulkActions. That is the spec-contract question of the objectstack-ai/hotcrm#2094 family, which objectui triage routed to objectstack domain:spec.

Consumer: objectstack-ai/objectui#12333 moves to pm:blocked with Blocked-by: this card. Once a release carrying the fix is installable in objectui, the console needs no change. Only the header of objectui's #8813 pin (FormPage.sectionLabelI18n.test.tsx) needs amending, to say that it covers a visitor who holds a bundle.

Dedupe, run 2026-10-14 with MCP search_issues on objectstack-ai/objectstack, closed included:

  • public form slug section label not translated Accept-Language _sections anonymous form translateView sections → 3 hits.
  • GET /forms/:slug localize form body sections i18n anonymous visitor → 6 hits.

None is this subject. The nearest is #5408 (closed), the signed-in create/edit tab strip.

Dedupe words: public form section label untranslated; forms slug Accept-Language sections; _sections anonymous form; translateView sections.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · bug · priority:p2 · domain:cli · area:i18n · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T14:00Z

    Read fresh. registerFormEndpoints → GET /forms/:slug (packages/rest/src/rest-server.ts) translates objectSchema but spreads each form section as authored. An anonymous zh-CN visitor reads English section titles beside Chinese field labels. The client route is closed to anonymous callers on purpose (#22432).

  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    pm:retriage: the producer half of this card is a packages/spec translator change. A routing and split question for triage

    domain:cli seat 1 (#6024) · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG · 2026-10-15T07:12Z. ⛔ Not a claim. The card stays pm:queue, and its grade and lane remain triage's until triage answers. This seat read it as its next dispatch now that PR #23617 has released rest-server.ts.

    Evidence, read in this act on origin/main 7c6b7f4a3:

    reading where
    GET /forms/:slug localizes objectSchema through translateMetadataDocument('object', …), then returns each form section as { ...sec, fields }, with label / description as authored packages/rest/src/rest-server.ts near :9963-:9977 and :9998-:10019
    translateMetadataDocument dispatches view to translateView, which localizes the view's label, description and config.bulkActionDefs, and never its form sections packages/spec/src/system/i18n-resolver.ts :1037, :1134, :1172
    The key this card needs, objects.<object>._sections.<name>.label / .description, is declared by the spec, but no exported resolver reads it. The resolver's one sections walk is metadataForms.<type>.sections, the admin forms' namespace, a different key declared at translation.zod.ts :394, :399 and component.zod.ts :1293; the walk at i18n-resolver.ts :3559-:3618
    The locale fallback every resolver shares, localeChain and pickData, is module-private i18n-resolver.ts :336, :368

    Why this is routing, not dispatch. The card's own ask prefers the spec's translator ("so the rest handler makes one call, instead of being a handler-local walk"). By the evidence above, that home is the only one that does not copy the locale chain into packages/rest. references/lanes/cli.md says packages/spec always belongs to the spec seat, and a handler-local copy of a spec resolver is the workaround this lane does not ship.

    What is asked, in one answer:

    1. Split the card. The spec half is a section resolver, or translateView translating a form view's sections through objects.<object>._sections, with its pin; it goes to domain:spec. The packages/rest half is GET /forms/:slug making that one call, with the Accept-Language: zh-CN pin the card asks for. It stays here, ordered after the spec half. Or route the whole card if triage reads it otherwise.
    2. Grade each half. The card is p2, the producer behind objectui#12333.

    Until the answer, this seat dispatches nothing from this card.


    Generated by Claude Code

  3. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 15, 2026
  4. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Retriage answered: split · this card keeps the packages/rest half · domain:cli · priority:p2 · pm:blocked on #23632 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-15T07:55Z

    Read fresh: seat 1's routing question 6186624783. Its evidence holds: no exported resolver reads objects.<object>._sections, and the locale chain is module-private. A handler-local walk in packages/rest would copy it.

  5. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 15, 2026
  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock: pm:blocked → pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T01:58Z

    ⛔ Not a claim.

  7. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 28 (#23497: GET /forms/:slug localizes the form's section label / description under Accept-Language through the spec's translateView, so an anonymous visitor reads translated section titles beside the translated field labels) · 2026-10-16T03:40Z
    Session: session_01Q9PZ5Pzqcus4woVvTZwWNU
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23497-public-form-section-i18n
    Worktree: objectstack-issue-23497
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    Scope: batch 1 (serial), by 「当前任务处理完,合并后就改为串行」. #23764 landed at 2026-10-16T03:36Z, so nothing of this seat's is in flight.
    Order, read at this act (execution-duties.md's total order): no maintainer jump-queue card and no contract-face card is open in the lane; no p0, pm:blocking or target: card; no open p1 in pm:queue (#23792 is pm:blocked). Among the p2 cards in pm:queue, bugs come first, then card age: #23497 (2026-10-14), #23737, #23794, then #23050 (not a bug). Triage unlocked this card in 6206314897: #23632 closed when PR #23749 merged as 1ac24f2f89.
    File surface (read on origin/main 9d4fb3c306; stop on a breach and explain it in the report):


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (takeover of #23497: GET /forms/:slug localizes the form's section label / description under Accept-Language through the spec's translateView) · 2026-10-16T06:35Z
    Session: session_018t6a5rvqPRvEDMCVVW9kYT
    Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23497-public-form-section-i18n
    Worktree: objectstack-issue-23497
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648, takeover marker 6209624979)
    Release: claim 6207554529 · session session_01Q9PZ5Pzqcus4woVvTZwWNU · account sunhaolin · 因: the claimant is unreachable (its usage is spent) · 去向: this claim, same branch, same file surface · 出处: the maintainer, in session session_018t6a5rvqPRvEDMCVVW9kYT's chat, verbatim 「前任没有token了,他的任务你要接手继续处理。」
    Resume-from: claude/issue-23497-public-form-section-i18n@357c751a7c9e05fa91da35f39a47b629172de0aa
    Handover record: the released claim's dev pushed three commits by 2026-10-16T03:49Z, the last 357c751a7c (ef0bea4e85 the fix in registerFormEndpoints' GET /forms/:slug handler plus the .changeset/23497-public-form-section-i18n.md, then 980fb8d6e5 and 357c751a7c on the new packages/rest/src/public-form-section-i18n.test.ts). No PR was opened and no os-dev-report was posted, so its verification is UNVERIFIED and is re-run in full; the branch's base is 9d4fb3c306, and origin/main is now 5b6c257b06.
    File surface (unchanged from 6207554529, re-read on origin/main 5b6c257b06; stop on a breach and explain it in the report):


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 23497,
    "status": "done",
    "branch": "claude/issue-23497-public-form-section-i18n",
    "pr": "#23844",
    "session": "session_018t6a5rvqPRvEDMCVVW9kYT (subagent: the dispatching PM session, as the template says; the relay stamped this id on both strokes)",
    "premise_still_valid": true,
    "summary": "Resumed from claude/issue-23497-public-form-section-i18n@357c751a7c. The claim was verified first: the newest Claim 6209637282 names this branch and the Resume-from. origin/main 1ecb08a was merged as dd9822e (a real merge, no rebase or force). The inherited fix ef0bea4 was kept hunk by hunk. In it, GET /forms/:slug makes ONE translateMetadataDocument('view', match.view, bundle, ...) call beside the objectSchema call, with the same locale options and getPackagedViewBase(match.view.name) as packagedBase, then serves the translated config through the unchanged strip. Its comment was corrected after measurement. The inherited changeset and the 8 inherited pins were kept, and the pins were extended to 14. Dispatch assumption 1, measured per slot: the viewKind:'form' item's config is localized. The container form and formViews.KEY slots are unreachable at the route. ObjectStackProtocolImplementation.getMetaItems drops every isAggregatedViewContainer body and serves each container form as the expanded viewKind:'form' item. No door stores a container slot beside a viewKind (scratch probe: ViewMetadataSchema strips form/formViews from viewKind+config bodies, refuses viewKind+form without config; the stack views: channel refuses viewKind bodies). This is pinned over the real protocol and SchemaRegistry with a showcase-shaped container (default form plus formViews.contact). The boundary (a form handed to the route in a container slot is served as matched, as authored, never the item's config) is pinned by two tests. Assumption 2 holds: an environment overlay's edited heading is kept against the real getPackagedViewBase, and ablation M4 reds exactly the two override pins. Assumption 3 holds: ablation M2 (translated config served past the strip) reds 4 strip assertions, so they stay. Draft PR #23844 is open (assignee os-tesla, Clause-② line in the body). CI was in_progress at report time.",
    "tests": "All at final head 8d4b004 unless marked. (1) pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/public-form-section-i18n.test.ts (default reporter, under os-verify-lock): VERDICT command-exit 0; file line '(14 tests) 82ms'; Test Files 1 passed, Tests 14 passed; Duration 13.68s, import 13.46s on a shared box. (2) pnpm --filter @objectstack/rest typecheck: exit 0; check:test-typecheck OK, 0 files / 0 errors in test-typecheck-debt.json; tsc -p tsconfig.test.json --listFiles lists the pin file (871 files). (3) pnpm --filter @objectstack/rest test at b4e56cf: REST_TEST_EXIT=0; Test Files 286 passed (286); Tests 5890 passed, 326 skipped (6216). Between b4e56cf and 8d4b004 only this pin file's engine double changed (findOne removed); rest-server.ts has the same blob 730c2aaa03f2 at both. (4) Closure built first: pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' build, VERDICT command-exit 0, 25 packages. (5) Ablations at b4e56cf (rest-server.ts blob 730c2aaa03f2, identical to final). Each leg went through node scripts/ablation-replace.mjs WRAP mode (anchor x1 to x0, blob changed, so on disk) inside a bash trap that restores with git checkout HEAD -- ABS and checks blob == HEAD plus an empty git diff HEAD, all four legs: yes. No rebuild was needed, because the test imports ./rest-server from src. M1, fix ablated (swap line to 'void view'): 5 failed / 9 passed; red = every zh-CN translated-heading pin (double + real protocol + real override). M2, translated config served past the strip: 4 failed / 10 passed. M3, config-slot guard removed: 2 failed / 12 passed (the two container-slot boundary pins). M4, view packagedBase undefined: 2 failed / 12 passed (both override pins). Final git status --porcelain empty. (6) Engine-member probe (one-off, uncommitted, same trap/restore): the real protocol read find, registry, writeEpoch and getDriverForObject on these paths and never findOne; 14 passed; restored blob == HEAD 964e5672d131.",
    "gates": "Local subset at 8d4b004. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (merge base 1ecb08a, 3 paths) derived 66 commands, the same count as the PM's derivation. All 66 ran, each exit captured before any pipe, all exit 0. Notable: check:engine-double-contract exit 0, check:where-matcher exit 0, check:test-source-alias exit 0, check:cross-package-test-inputs exit 0, check:dual-build-cjs-loads exit 0, check:type-check-debt exit 0 (527s, its own closure build), check:query-options-erasure exit 0 (316s), check:slot-lookup exit 0, check-comment-mask-corpus exit 0. Reconciliation: dispatch-gates --repo objectstack-ai/objectstack --ran FILE (lines 'CMD :: exit N') RAN_EXIT=0, 'Run reconciliation: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN', a DERIVED zero with all 66 exit codes recorded. Pre-final readings, superseded: at b4e56cf, check-plugin-teardown-shape --self-test exited 3 (shallow clone, pinned fixture 621a487 unreachable; NOT MEASURED, fixed by git fetch --depth=1 of that commit, then exit 0); check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, unbuilt packages; NOT MEASURED, exit 0 at final head); check:engine-double-contract exited 1 (RETAINED ledger wanted the new findOne double; resolved by dropping findOne from the double, not by touching the ledger). ESLint: pnpm exec eslint --no-inline-config packages/rest/src/rest-server.ts packages/rest/src/public-form-section-i18n.test.ts ESLINT_EXIT=0. Control-byte self-scan of the changed files: no hits. CI, PR #23844 check-runs on 8d4b004 at report time: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; Lint · repo & tooling gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · CI, release & engine gates, Lint · docs & code guards, Lint · scoped (tooling self-tests, query-options erasure, slot lookup & corpus), Type Check · workspace / source gates / consumer gates / debt ledger, Check Changeset, Governed Surface Queue Guard, Check PR Size, 'The card this PR closes must claim this branch', 'Part-of PR must not also close its card', 'No other open PR may claim the same single-writer path', Flag docs affected by code changes and Check Documentation Links all in_progress or queued. Test Core and Build Core had not appeared yet. Conclusions are owed to CI, not re-run locally.",
    "mcp_calls": "0 (none)",
    "api_writes": "2 fleet-write relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204) executed as objectstack-fleet[bot]: stroke 1, request fw-20261016T081008Z-592de8 (run 38871901340, success): POST /repos/objectstack-ai/objectstack/pulls (draft, PR #23844) and POST /repos//issues/23844/assignees (os-tesla; read back: 10187 bytes sent, 10187 stored, identical). Stroke 2: POST /repos//issues/23497/comments (this os-dev-report). Not REST: git push of the branch 4 times (an up-to-date write-route probe, then dd9822e, b4e56cf, 8d4b004). Plus git fetch --depth=1 origin 621a487, a read that adds objects to the shared .git. No label writes (none named; skip-changeset does not apply).",
    "files_changed": [
    ".changeset/23497-public-form-section-i18n.md (inherited, kept: @objectstack/rest patch)",
    "packages/rest/src/rest-server.ts (registerFormEndpoints' GET /forms/:slug handler only)",
    "packages/rest/src/public-form-section-i18n.test.ts (new pin file: 14 tests)"
    ],
    "deviations": [
    "Posture: before the container restart, the closure build, the rest test+typecheck, the gate battery and an optional showcase build ran nohup in the background, waited on with tail --pid. The restart killed the gate run at gate 10 and the showcase build. That build was mid-way through cleaning packages/spec/dist, so the rest closure was rebuilt in the foreground and every gate was re-run at the final head. After the restart everything ran in the foreground. The one exception: the 49-66 gate chunk crossed the tool's 600 s cap and the harness backgrounded it; it was then waited on synchronously with tail --pid and finished in the same turn.",
    "The optional live check (booting examples/app-showcase and curling /forms/contact-us with zh-CN) was abandoned when its build was killed. It is not owed; the real-protocol pin covers the composed read.",
    "git fetch --depth=1 origin 621a487 added that commit to the shared object store so check-plugin-teardown-shape --self-test could read its pinned fixture.",
    "The rest full suite was not re-run at 8d4b004. Its delta from b4e56cf is the pin file's engine double only, and the pin file plus typecheck were re-run at 8d4b004.",
    "The engine-member probe took three attempts: the first was VERDICT queue-timeout (exit 99, NOT MEASURED, another agent's cli unit run held the lock); the second gave no reading (an afterAll registered inside a test body never ran); the third measured.",
    "The branch is 2 commits behind origin/main 0cfd0c4 (#23828 plugin-auth, #23822 scripts/ci), disjoint from this diff, so it was not re-merged before opening.",
    "Commit trailers use the model-free pair (Claude-Session + Co-authored-by: Claude), per AGENTS.md, over the harness reminder's model-named trailer.",
    "Cleanup: node_modules removed and the worktree removed with git worktree remove (no --force), after the PR opened. The report comment was posted from the shared checkout's scripts/pm."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed (Acceptance notes): a view row stored verbatim before the write door stored parsed bodies (#20051) could still carry a container slot (form / formViews) beside a viewKind. The route serves such a row's matched form as authored, untranslated, never the item's config (pinned). No current door writes that shape (probe + code cited in the PR), so there is no reach for a class a/b/c card."
    ]
    }


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23844 comment 6211141235 · domain:cli seat 2 · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T08:14Z. Landing waits for every check on head 8d4b00444c to be green; the seat then flips it ready and arms auto-merge.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record: PR #23844 → 1eac7ad751 · domain:cli seat 2 · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T08:56Z

    Reading Result
    PR #23844 object merged: true, merge commit 1eac7ad751, merged 2026-10-16T08:55Z through the merge queue (enqueued 2026-10-16T08:37Z)
    origin/main 1eac7ad75 is origin/main itself at this reading; packages/rest/src/rest-server.ts there makes the one translateMetadataDocument('view', match.view, …) call in GET /forms/:slug (1 hit), and .changeset/23497-public-form-section-i18n.md is present
    queue branch no gh-readonly-queue/main/*pr-23844* head remains

    State change: this card closed completed by Fixes #23497 when the PR merged; pm:dispatched removed in this act. bug, priority:p2, area:i18n and domain:cli stay. Issues closed since the merge: this card alone, so the PR body closed nothing else.

    Consumer: objectstack-ai/objectui#12333 waits on this card. Its unlock is a release carrying the @objectstack/rest patch changeset that objectui can install, not this merge; that reading is the objectui seat's and triage's. Then only the header of objectui's FormPage.sectionLabelI18n.test.tsx pin needs amending (the card body).

    This card was taken over from claim 6207554529 by 6209637282 and finished on the same branch.


    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:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions