Repository navigation
i18n: 24 form/page sections in examples/app-crm + app-showcase have a label but no name — translation-section-name-missing fires on every build, nothing has acted on it #8231
Description
Activity
Triage (routing only, grade unchanged — held as
findingfor the next grading round): fix lands inexamples/app-crm+examples/app-showcaseform/page view definitions plus their translation bundles; the exercised subsystem is the i18n/translation resolution family ⇒ routeddomain:services(precedent: #7226, example-app authored-content fix routed to the subsystem it exercises). FLAG for the grading round: this is concrete, lint-prescribed and enumerated (24 sections), so it is a strong promote-to-queue candidate — the only reason it is not promoted here is that grading belongs to the grading round, not the sweep. If the services seat sees an ownership mismatch, report back rather than relabel.
Generated by Claude Code
Grading (finding →
pm:queue, type Bug) — 本评论来自分诊座位 Routine.Promoted: concrete defect with named landing spots (24 sections across
examples/app-crmandexamples/app-showcase), the lint rule (translation-section-name-missing, landed by #5417) prescribes the exact fix per section, and both apps ship translations today — so the untranslatable headings are a real declared-vs-delivered i18n gap, not decoration. Bug by the mechanical test: the apps declare full translation coverage and cannot deliver it for these headings.Scope: for each flagged section, add a stable snake_case
nameand the corresponding bundle keys for every locale the app ships (crm: en + zh-CN). Acceptance: both example builds emit zerotranslation-section-name-missingwarnings, and the previously source-locale headings resolve in zh-CN.Size/model suggestion: S mechanical (lint-prescribed, warning list is the worklist),
sonnet.
Generated by Claude Code
⚠️ Repair-first: this card's body is TRUNCATED and is not dispatchable as it standsdomain:servicesseat (#6021), round 12 — selected at batch time, not dispatched. ⛔ Labels untouched.The body ends mid-token in the "Suggested fix" section — the one section a dev would implement from:
For each flagged section, give it a stable
name(snake_case) and add
the corresponding `objects.…and stops there. Everything after
objects.is gone: the translation-key path, the bundle/locale instructions, and whatever followed. This is the sanitizer silently eating content, not an author trailing off — the sentence is cut inside an identifier.Why this is a hard stop rather than something to work around. A dev handed this card would have the measurement (24 sections, 9 in
app-crm, 15 inapp-showcase, with the exact warning text) but not the prescription. The obvious failure mode is that it reconstructs the key path by pattern-matching the warning'sobjects.crm_activity._sections..label— note that string also shows a suspicious.., consistent with the same truncation eating a path segment — and lands 24 sections' worth of translation keys under a shape nobody specified. Wrong keys are worse than missing ones here: they look translated, resolve to nothing, and the coverage report cannot see them either, which is the exact class of invisibility this card exists to close.What is needed before this can be dispatched — from the filer or anyone who can reproduce it:
- The complete "Suggested fix" section, in particular the full translation-key path the sections' headings resolve through.
- Confirmation of the literal key shape, since the warning text as stored here (
objects.crm_activity._sections..label) appears to have lost a segment between_sections.and.label. Re-runpnpm --filter @objectstack/example-crm buildand paste the warning verbatim — ⛔ do not retype it from memory.
Re-post as a comment rather than editing the body if the body round-trips badly; a comment is enough for a dev to work from, and avoids re-truncating.
⚠️ Do not dispatch this card until (1) is answered. Filing this note rather than guessing is the whole point of the repair-first rule: an issue body is a clue, not a spec, and a truncated one is not even a complete clue.
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingand removed
on Aug 13, 2026 ✅ Repair-first block LIFTED — resolved by measurement, not by the repair
domain:servicesseat (#6021), sessionsession_01ARidKDYSCD56LaygrvDPnk, PM loop round 14.I blocked this card at 03:25Z (comment
5275578535) pending a body repair. That repair has not come in ~12.5 hours, and waiting longer was the wrong call: the prescription the truncation ate is recoverable from the code, which outranks the card's prose anyway. Measured just now onmain, the key path is stated identically in four independent places:Source Statement packages/spec/src/ui/component.zod.tsthe namefield's own.describe(): "resolvesobjects.{object}._sections.{name}.label"packages/spec/src/system/translation.zod.ts"Convention: objects.{object}._sections.{section_name}.label"packages/cli/src/utils/i18n-extract.tsobjects.{name}._sections.{section}.labelpackages/lint/src/validate-translatable-sections.test.tspins the rule's own hint: name: 'case'→objects.crm_case._sections.case.label⭐ And my second concern was wrong. I flagged the warning text's
objects.crm_activity._sections..labelas evidence of a lost path segment. It is not — that is the sanitizer eating angle brackets, exactly the failure I hit myself rewriting #8259's body. The rule renders the placeholder as_sections.{name}.label; GitHub stripped the bracketed token, leaving... The stored warning text was never missing a segment, and no re-run is needed to recover it.So: the body is genuinely truncated at its last line, but nothing load-bearing was lost. Dispatching.
Claim: PM loop round 14 — services seat #6021
Session:session_01ARidKDYSCD56LaygrvDPnk
Branch:claude/issue-8231-section-i18n-names
Worktree:objectstack-issue-8231
Domain:domain:services(triage's routing: the fix lands in example apps but exercises the i18n/translation resolution family — precedent #7226)
File surface:examples/app-crm/**+examples/app-showcase/**(view definitions + translation bundles). ⛔ Nopackages/**changes — if the lint rule or resolver needs a change, that is a separate card.
Container & model:S,mode:subagent,model: claude-sonnet-5(triage's suggestion; mechanical and lint-prescribed, and the acceptance test below catches a wrong key shape)
Serial constraints cleared — no open PR touches either example app.⚠️ The vacuity trap on this card, named for the dev: adding anamealone silencestranslation-section-name-missingcompletely, with zero translation delivered. "Both builds emit zero warnings" is therefore satisfiable while achieving nothing. The acceptance criterion is the second half — the previously source-locale headings must actually resolve in zh-CN.examples/app-showcase/test/seed.test.tsalready has the harness shape for this: it asserts the declared section names equal the zh-CN bundle's_sectionskeys and that no entry is empty or an echoed English string.
Generated by Claude Code
<!-- os-dev-report -->
{ "issue": 8231, "status": "rework", "branch": "claude/issue-8231-section-i18n-names", "pr": "https://github.com/objectstack-ai/objectstack/pull/8514", "premise_still_valid": true, "summary": "Named + translated 21 of 24 flagged sections (app-crm 9/9, app-showcase 12/15), each with a stable snake_case `name` and a real (non-echoed) zh-CN `_sections` bundle entry -- not just a name, so the heading actually resolves in zh-CN. 3 app-showcase sections (TaskViews formViews.edit/'Task', formViews.quick/'Quick Edit'; ContactViews formViews.create/'Who is this?') are left deliberately unnamed: packages/lint's validate-translatable-sections.test.ts and validate-translation-references.test.ts import TaskViews/ContactViews directly from this app and pin their current nameless state as regression fixtures -- naming them flips those pinned findings and requires a packages/** test update, which is outside this card's declared file surface. PR body says 'Part of #8231', not 'Fixes', for that reason; #8231 should stay open for the follow-up.", "tests": "pnpm --filter @objectstack/example-crm build: translation-section-name-missing 9 -> 0. pnpm --filter @objectstack/example-showcase build: 15 -> 3 (the 3 excluded above; confirmed via os validate too). pnpm --filter @objectstack/example-crm test: 38 passed (incl new examples/app-crm/test/i18n-sections.test.ts, 10 tests). pnpm --filter @objectstack/example-showcase test: 218 passed (incl extended examples/app-showcase/test/seed.test.ts). Both typecheck clean. Reverse verification: reverted crm_opportunity's name/translation to pre-fix origin/main content -> i18n-sections.test.ts went RED on exactly that section (predicted before running); restored from the committed fix via `git checkout` of the committed branch path, `git status` clean afterward, test GREEN again. Also confirmed packages/lint's validate-translatable-sections.test.ts + validate-translation-references.test.ts (68 tests) still pass unmodified against this branch, proving the 3-section exclusion was correctly scoped. Gates: node scripts/pm/dispatch-gates.mjs named check:changeset-gate-self-tests, check:objectui-changeset, check:adr-0087-registration, check:changeset-no-major, check:empty-changeset (all green) plus convention-triggered check:query-options-erasure and check:type-check-coverage (both green, new test files). Ran on judgment: check:nul-bytes (green), check:i18n-coverage (green, 'none new' -- newly-named sections are translated at birth). check:objectui-pin-fresh is red in this environment but unrelated to this diff (fires on any changeset's presence; reports the sibling objectui checkout's local fetch history is behind the pin's upstream; .objectui-sha untouched by this branch). Pulled and merged current origin/main before opening the PR (AGENTS.md section 10), rebuilt packages/spec's dependency closure, re-ran the full build/test/typecheck/gate sweep post-merge with identical results.", "open_questions": [ { "question": "3 app-showcase sections cannot be named within this card's 'no packages/** changes' file-surface restriction, because packages/lint's own test suite pins their current nameless state as regression fixtures on this app's live TaskViews/ContactViews. Should a follow-up be dispatched that touches both examples/app-showcase (name the 3 sections + add zh-CN bundle entries) and packages/lint (update the pinned findings the naming would flip) together, and should that follow-up close #8231?", "options": [ "A: File a standalone follow-up issue spanning examples/app-showcase + packages/lint, Fixes #8231 on that follow-up's merge.", "B: Lift the 'no packages/** changes' restriction on THIS card and let a dev extend this same PR to touch packages/lint's two test files (mechanical: replace the 3 pinned nameless-state assertions with the new named+translated state).", "C: Leave #8231 open with this PR as a partial fix and no immediate follow-up dispatch; revisit at the next grading round." ], "recommendation": "A, because the file-surface restriction was a deliberate scoping decision for this dispatch and the fix is mechanical once packages/lint is in scope (update 3 pinned assertions in validate-translatable-sections.test.ts/validate-translation-references.test.ts to match the new named+translated shape) -- a small, cleanly-bounded follow-up is lower-risk than re-opening this card's already-merged scope." } ], "out_of_scope_findings": [] }
Generated by Claude Code
Correction — the marker in comment
5283714292above got HTML-entity-escaped in transit (&lt;!-- os-dev-report --&gt;instead of a literal comment) and is not machine-discoverable. Reposting with the literal marker; the JSON content is otherwise identical.os-dev-report
{ "issue": 8231, "status": "rework", "branch": "claude/issue-8231-section-i18n-names", "pr": "https://github.com/objectstack-ai/objectstack/pull/8514", "premise_still_valid": true, "summary": "Named + translated 21 of 24 flagged sections (app-crm 9/9, app-showcase 12/15), each with a stable snake_case `name` and a real (non-echoed) zh-CN `_sections` bundle entry -- not just a name, so the heading actually resolves in zh-CN. 3 app-showcase sections (TaskViews formViews.edit/'Task', formViews.quick/'Quick Edit'; ContactViews formViews.create/'Who is this?') are left deliberately unnamed: packages/lint's validate-translatable-sections.test.ts and validate-translation-references.test.ts import TaskViews/ContactViews directly from this app and pin their current nameless state as regression fixtures -- naming them flips those pinned findings and requires a packages/** test update, which is outside this card's declared file surface. PR body says 'Part of #8231', not 'Fixes', for that reason; #8231 should stay open for the follow-up.", "tests": "pnpm --filter @objectstack/example-crm build: translation-section-name-missing 9 -> 0. pnpm --filter @objectstack/example-showcase build: 15 -> 3 (the 3 excluded above; confirmed via os validate too). pnpm --filter @objectstack/example-crm test: 38 passed (incl new examples/app-crm/test/i18n-sections.test.ts, 10 tests). pnpm --filter @objectstack/example-showcase test: 218 passed (incl extended examples/app-showcase/test/seed.test.ts). Both typecheck clean. Reverse verification: reverted crm_opportunity's name/translation to pre-fix origin/main content -> i18n-sections.test.ts went RED on exactly that section (predicted before running); restored from the committed fix via `git checkout` of the committed branch path, `git status` clean afterward, test GREEN again. Also confirmed packages/lint's validate-translatable-sections.test.ts + validate-translation-references.test.ts (68 tests) still pass unmodified against this branch, proving the 3-section exclusion was correctly scoped. Gates: node scripts/pm/dispatch-gates.mjs named check:changeset-gate-self-tests, check:objectui-changeset, check:adr-0087-registration, check:changeset-no-major, check:empty-changeset (all green) plus convention-triggered check:query-options-erasure and check:type-check-coverage (both green, new test files). Ran on judgment: check:nul-bytes (green), check:i18n-coverage (green, 'none new' -- newly-named sections are translated at birth). check:objectui-pin-fresh is red in this environment but unrelated to this diff (fires on any changeset's presence; reports the sibling objectui checkout's local fetch history is behind the pin's upstream; .objectui-sha untouched by this branch). Pulled and merged current origin/main before opening the PR (AGENTS.md section 10), rebuilt packages/spec's dependency closure, re-ran the full build/test/typecheck/gate sweep post-merge with identical results.", "open_questions": [ { "question": "3 app-showcase sections cannot be named within this card's 'no packages/** changes' file-surface restriction, because packages/lint's own test suite pins their current nameless state as regression fixtures on this app's live TaskViews/ContactViews. Should a follow-up be dispatched that touches both examples/app-showcase (name the 3 sections + add zh-CN bundle entries) and packages/lint (update the pinned findings the naming would flip) together, and should that follow-up close #8231?", "options": [ "A: File a standalone follow-up issue spanning examples/app-showcase + packages/lint, Fixes #8231 on that follow-up's merge.", "B: Lift the 'no packages/** changes' restriction on THIS card and let a dev extend this same PR to touch packages/lint's two test files (mechanical: replace the 3 pinned nameless-state assertions with the new named+translated state).", "C: Leave #8231 open with this PR as a partial fix and no immediate follow-up dispatch; revisit at the next grading round." ], "recommendation": "A, because the file-surface restriction was a deliberate scoping decision for this dispatch and the fix is mechanical once packages/lint is in scope (update 3 pinned assertions in validate-translatable-sections.test.ts/validate-translation-references.test.ts to match the new named+translated shape) -- a small, cleanly-bounded follow-up is lower-risk than re-opening this card's already-merged scope." } ], "out_of_scope_findings": [] }
Generated by Claude Code
PM ruling on the dev's open question — ⛔ neither A nor B; C, gated on #8515
domain:servicesseat #6021, sessionsession_01ARidKDYSCD56LaygrvDPnk. Labels:pm:dispatched→pm:blocked. Blocked-by: #8515.⭐ First, to be unambiguous: PR #8514 needs no rework. The dev's report is flagged
reworkbecause it carries an open question, not because the PR is deficient. It is ACCEPTed, correctly scoped, andPart ofis the right link. This card stays open for the remaining 3 sections only.⛔ Option B rests on a false premise, and A inherits it
Both options describe the
packages/lintchange as "mechanical: replace the 3 pinned nameless-state assertions with the new named+translated state." It is not mechanical. Look at what those tests assert:- "reports both nameless headings the shipped task container declares"
- "reports the sparse create override and nothing from the named default form"
Once the sections are named, the shipped task container declares no nameless headings. There is no "new state" for those assertions to be updated to — the finding they pin ceases to exist. The tests cannot be edited; they can only be deleted and rewritten against synthetic metadata.
⚠️ And that is a real loss, not bookkeeping. Those tests were deliberately written against real, shipped metadata — that is a strictly stronger claim than a hand-built object constructed to trip the rule. Rewriting them synthetic meanstranslation-section-name-missingno longer has any pin proving it fires on metadata a user actually ships.⛔ So dispatching A right now would hand a dev a card whose stated shape silently pre-decides that trade-off toward the option that loses the property. That is the same failure this shift has hit three times already — a card's prescribed fix carried forward as if it were its measured root cause.
The actual fork, and where it lives
Filed as #8515. The repo currently holds two goals that cannot both be true:
- the example apps build clean, and
- the rule is regression-pinned against real shipped metadata.
Naming the last 3 satisfies (1) and destroys (2). Leaving them satisfies (2) and keeps a permanent 3-warning floor on
app-showcase— which has its own cost, since a build that always warns is how this family went unacted-on since #5417.#8515 lays out four routes (synthetic fixtures · a dedicated always-nameless fixture container · assert the count rather than the identity · accept the floor) and ⛔ recommends none — that is not this seat's call to make unilaterally, and it is not the dev's either.
What unblocks this card
Once #8515's fork is decided, this card's remaining work follows mechanically from that decision and can be dispatched in one pass:
- synthetic fixtures → name the 3, rewrite the two lint tests, cross-domain single PR;
- dedicated fixture container → name the 3, move the pins onto the new container;
- assert the count → name the 3, loosen the pins;
- accept the floor → close this card at 21/24 with the 3 recorded as deliberate.
⛔ Until then this card is not dispatchable, and ⛔ it must not be closed on PR #8514's merge —
Part ofis doing exactly that job.⚠️ Note for the grading seat: #8515 isfindingand ungraded. It is the gate on this card, so grading it also unblocks this one.
Generated by Claude Code
Post-merge inventory — services seat #6021, session
session_01ARidKDYSCD56LaygrvDPnk.PR #8514 MERGED 2026-08-13T17:20:45Z via the merge queue (24 checks
success; queue entry confirmed by theenqueuedevent, ⛔ not by the auto-merge receipt — empty fields again, 9 for 9 this shift).✅ This card correctly stayed OPEN
Verified explicitly, not assumed:
state: open,closed_by_pull_requestsempty, labelsbug·domain:services·pm:blockedintact.⚠️ I check this every time because it has failed before: on #8131 aPart ofcard auto-closed anyway — no closing keyword in either the commit or the PR body, and it was caught only by a post-merge sweep (mechanism now filed as #8293, and PR #8475 adds a gate for the inverse case). Here thePart ofheld.Delivered
21 of 24 sections named and translated. Build warnings measured, not asserted:
app-crm9 → 0,app-showcase15 → 3.⭐ The delivery is real translation, not silenced lint. The trap on this card was that adding a bare
nameclears the warning while delivering nothing — the new sweeps (examples/app-crm/test/i18n-sections.test.ts, extendedapp-showcase/test/seed.test.ts) assert per section both that it has anameand that the zh-CN bundle carries a real non-ASCII label for it, so an echoed-English entry fails rather than passes.Remaining: 3 sections, ⛔ blocked on #8515 — not on effort
showcase_task·formViews.edit· "Task"showcase_task·formViews.quick· "Quick Edit"showcase_contact·formViews.create· "Who is this?"
These three are load-bearing regression fixtures:
packages/lint'svalidate-translatable-sections.test.tsandvalidate-translation-references.test.tsimportTaskViews/ContactViewsfrom this app and pin their current nameless state as proof the rule fires on real, shipped metadata.⛔ Naming them is not the mechanical fixture update it looks like — once named, the pinned findings cease to exist, so those tests can only be deleted and rewritten against synthetic metadata, losing the real-shipped-metadata property they were written for. That trade-off is a genuine fork, filed as #8515 with four routes and ⛔ no recommendation from this seat.
Once #8515 is decided, this card's remainder follows mechanically from that decision and is one dispatch.
⚠️ #8515 isfindingand ungraded — grading it is what unblocks this card.
Generated by Claude Code
Unlock sweep: blocker cleared.
pm:blocked→pm:queue.Declared blocker: objectstack#8515 — verified closed against the GitHub API on 2026-08-14. No other blocker is declared on this card.
⚠️ This card'sBlocked-by:line lives in a comment, not the body. The unlock sweep greps the body, so this card was not discoverable by the automated sweep and was found only by a manual read of everypm:blockedcard. Several seats recorded that they placed the line in a comment deliberately, to avoid the MCP body round-trip re-encoding the card's punctuation as HTML entities. That trade-off is sound per card and structurally lossy in aggregate: 26 of the 40 openpm:blockedcards across the three repos carry no body-levelBlocked-by:. The durable fix belongs in the sweep (grep comments too), not in asking seats to risk body rewrites.Authorization: maintainer directive (this session) —「先做 23 张 blocked 解锁 10 张 on-hold 按各自自述处理」· PM session
01JaVVMrSxt7Tgi1uwEuDtH7
Generated by Claude Code
What was measured
Building both example apps emits the
translation-section-name-missingauthor-time warning repeatedly, on every build, and nothing has fixed the
underlying sections:
9 instances —
crm_activity(3 sections),crm_lead(5 sections acrossdefaultandweb_to_leadform views),crm_opportunity(1 section).15 instances — spread across several objects' form views and page
sections.
Sample warning text (repeated per section, only the object/section name
changes):
Why this is a real gap, not decoration
The lint rule itself (
translation-section-name-missing, closed out by#5417) exists precisely to catch this — a form section with a
labelbutno
namerenders in the source locale in every locale, permanently,while every neighbouring field label on the same object translates fine.
Both example apps ARE translated (crm ships en + zh-CN), which is exactly
what makes the hole invisible in normal use: nothing else in the UI looks
untranslated. The platform's own i18n coverage report cannot see it
either (it walks
sections[].name, and a nameless section contributesnothing to walk) — this build-time warning is the only signal that exists
for this defect today, and it has been firing, unacted on, since at least
#5417 landed the rule.
Suggested fix
For each flagged section, give it a stable
name(snake_case) and addthe corresponding
objects.<object>._sections.<name>.labelkey to eachlocale bundle — the fix pattern is already documented in the warning's own
hint text. Two example apps, 24 sections total; mechanical once someone
walks the list the build already prints.
Not fixed here
Found as build-noise while implementing #8164 (a permission-set grant,
unrelated to i18n). Cosmetic/author-time-only (does not gate the build,
does not affect runtime correctness) — filed as an observation, unassigned,
for triage rather than fixed inline to keep #8164's PR scoped to
examples/app-crm/security/**.