fix(lint): translation-target-unknown 读取视图容器的默认 form.sections (#5415) - #5422
Merged
Merged
Conversation
…ult `form.sections` (#5415) `collectViewRecord` derived an object's legal `_sections` names from `fieldGroups[].key`, the named sections on `listViews.*` / `formViews.*`, a page's named `record:details` sections, and the view record's own `sections`. The view CONTAINER's default `form` — what `defineView({ list, form })` declares and `ObjectForm` renders when no named form view is asked for — was in none of those, so `view.form.sections[].name` contributed nothing: a bundle correctly translating a heading that DOES render was reported as keyed to a section "nothing declares", with a hint advising the author to delete it. The default form now feeds the same section collector as `formViews.*`, bound by `bindingOf(view.form) ?? listBinding` — the resolution the CLI i18n walker performs on `view.form.data.object` — so the rule that demands a key and the rule that accepts one agree on which object a heading belongs to. Every anchor is now a call into one collector instead of its own copy of the loop. Tests import the real showcase contact metadata (`Contact` +`ContactViews`): its object declares `field.group` and no `fieldGroups[]`, so the default form is its ONLY section anchor, which is exactly what a hand-reduced fixture could not have pinned. Over-widening controls kept: an unnamed section is still untranslatable, an undeclared key is still reported, and the default form is bound by its own `data`, not by the list beside it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
…t-container-form-sections
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
This was referenced Aug 5, 2026
baozhoutao
marked this pull request as ready for review
August 5, 2026 11:08
baozhoutao
enabled auto-merge
August 5, 2026 11:08
This was referenced Aug 5, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 6, 2026
…ck-ai#5420) (objectstack-ai#5438) `src/ui/views/contact.view.ts` declares the whole "create form != edit form" reference implementation — a default grouped `form` with four named sections, a sparse `formViews.create`, and the list's `addRecord: { mode: 'form', formView: 'create' }` binding — and `src/ui/views/index.ts` exports it. `objectstack.config.ts` named the other four containers on line 23 and line 196 and never named this one. There is no directory scan behind `views:`; the CLI reads exactly that array. So the metadata compiled, type-checked and linted clean while reaching nothing: `nav_contacts` rendered a derived default form instead of the authored one, `addRecord.formView` never bound, and no static pass (`os validate` / `os lint` / `os i18n extract` / the coverage ratchet) could see it either — which is also why objectstack-ai#5405's new `i18n/missing-section` gate found 10 headings in the showcase and zero of them under `showcase_contact`. `content/docs/ui/create-vs-edit-form.mdx` cites this file as the live reference implementation the whole time. Registration makes exactly five zh-CN coverage keys newly reachable, measured on the real config rather than predicted: os lint examples/app-showcase/objectstack.config.ts 483 warnings (before) -> 488 (registered, untranslated) -> 484 (translated) + i18n/missing-view objects.showcase_contact._views.list.label + i18n/missing-section objects.showcase_contact._sections.{contact,work,status,notes}.label All five are translated in this commit, following PR objectstack-ai#5416's rework: zh-CN takes its words from the vocabulary this bundle already uses, and `en` gains nothing (the default locale is satisfied by the inline labels; echoing the source string only fakes coverage). The ratchet baseline `scripts/i18n-coverage-baseline.json` and `i18n.supportedLocales` are untouched — `check-i18n-coverage: OK (12 config(s), 660 baselined untranslated string(s), none new)`, showcase back to exactly 451. Two guards in `test/seed.test.ts`: * every container the barrel exports reaches `stack.views`, matched by target object (`defineStack` parses the config, so a registered container is a structural copy and never `===` the export). Reverting the config hunk fails it naming `ContactViews`. * the four section names and their zh-CN `_sections` keys are asserted as set EQUALITY, because the two i18n gates read that set in opposite directions — `i18n/missing-section` fails on a declared section with no entry, `translation-target-unknown` on an entry no section declares. With objectstack-ai#5422 on main this is the first time the relationship is testable on the real config: the four correctly-translated sections are NOT reported as orphans. The remaining `translation-target-unknown` on `_views.list` is objectstack-ai#5164's open contradiction between the three `_views` producers, not new here — the showcase already carried four identical instances (project / task / inquiry / business_unit) before this change. Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 6, 2026
…e heading no key can address (objectstack-ai#5417) (objectstack-ai#5455) `_sections` is keyed by the section's `name`, and every renderer resolves a section heading that way (`sectionLabel(objectName, section.name, authored)`), falling back to the authored label when there is no name. So a section authored with a `label` and no `name` is untranslatable by construction — and both existing gates are structurally blind to it: there is no orphan key for `validateTranslationReferences` to report, and the coverage walker (objectstack-ai#5405) emits one expected key per `sections[].name`, so a nameless section demands nothing and the report reads 100%. Measured: 70/70 HotCRM form-view sections are in that state at full declared coverage; `os validate` over examples/app-showcase reports 14 (6 form views, 8 record:details pages) and still exits 0. `validateTranslatableSections` (`translation-section-name-missing`) joins the reference-integrity suite, so it runs on validate/lint/compile at once, and reads exactly the anchors objectstack-ai#5416's walker and objectstack-ai#5422's fact set already agree on — a container's `sections`, its DEFAULT `form.sections`, every `listViews.*`/`formViews.*` sub-container, the same three on object-embedded views, and `record:details` sections anywhere in a page's component tree via the shared `walkPageComponents`. fieldGroups-derived sections are out of range by construction (their key IS the name), as is a name-keyed `sections` map. Warning, and opt-in: a section warns only when the object it renders under carries some translation of its own, so a monolingual stack stays silent exactly as `computeI18nCoverage` does. The fix is a diagnostic at the producer — slugifying the label into a lookup key would fossilize a second de-facto contract next to the declared one (Prime Directive objectstack-ai#12, and the renderer guard objectui#3373 pinned as correct). Per the PM ruling on the issue, requiring `name` on `FormSection` outright (the schema route) is a breaking authoring change left to the maintainer; packages/spec is untouched. Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5415
前提复核(改之前先证伪)
按正文在
origin/main(切 worktree 时9fad07f66,issue 钉的是b4872a868,两者之间未触packages/lint)重跑了 showcase 复现:直接 import 真实的examples/app-showcase/src/ui/views/contact.view.ts,把正确翻译showcase_contact._sections.contact的 bundle 喂给validateTranslationReferences,输出与 issue 正文逐字一致:前提成立。
改了什么
collectViewRecord原本从四个锚点收集对象的合法_sections名:fieldGroups[].key、listViews.*/formViews.*的具名段落、页面record:details的具名段落、以及视图记录自身的sections。容器的默认form(defineView({ list, form, formViews })里那个、没有指定具名表单视图时ObjectForm渲染的那个)哪一条都不是 —— 循环写死['listViews', 'formViews'],而它既不在其中、也不是记录自身的sections,于是view.form.sections[].name对事实集零贡献。现在默认
form走与formViews.*完全相同的段落收集路径,绑定取bindingOf(view.form) ?? listBinding—— 即先form.data.object、再记录级对象、最后旁边那个 list 的绑定 —— 与 CLI i18n walker 的viewObjectName对view.form.data.object的解析对齐。这正是 #5405 要建立的集合关系(覆盖走查器要求的 key ⊆ 引用校验器接受的 key)在 lint 侧的那一半。顺带把四处段落收集收敛成一个
addSections收集器:锚点清单从此是「一串调用点」,而不是四份会各自漂移的循环副本。没有任何收紧:无名段落依旧不可翻译(它没有可查的稳定 key),任何锚点都没声明的
_sectionskey 依旧上报 —— 只是 hint 现在会列出真实存在的锚点,而不再断言「declares no named section at all」。未触
_views族(#5164 领地),未触packages/lint/src/index.ts(PR #5416 在队列中触该文件)。测试
packages/lint/src/validate-translation-references.test.ts新增 4 例:form的具名段落可解析(合成最小形状)。form按自己的data绑定,而不是旁边的 list —— 一个 stack 里同时断言两个方向。只断言「crm_lead仍被上报」是不可证伪的:在修复前它同样通过,因为默认 form 什么都没贡献(空集合让断言"通过"的经典陷阱);是crm_contact那一半让这一对有了方向。import { Contact }+import { ContactViews })。选真实 import 而不是手工缩写:缺陷本身就是「锚点清单漏了一项」,手写 fixture 只能钉住作者记得的锚点。而这个面恰好是暴露它的形状 —— 对象只声明field.group、没有fieldGroups[],所以默认 form 是它唯一的段落锚点。一例钉四个具名段落全部被接受;一例是防过度放宽的反例:contract(contact的错拼)与who_is_this(formViews.create那个无名段落的 label)两个 key 仍各上报一条,且 hint 改口列出Declared sections: contact, notes, status, work。按车道口径,两条真实元数据 import 的用例显式
}, 60_000)。反向验证(方向先预判,再跑)
预判:删掉新加的这一条 limb,4 条新用例应全红(这是纯放宽,不存在 #5018 那种反转,也不存在 #5046 那种「计数从 1 变 0 反而多出诊断」)。实测一致:
恢复 limb 后:
消费半径已按规则的调用点(而非改动包)扫过:
translation-target-unknown/validateTranslationReferences仅由packages/lint内的reference-integrity-suite+index.ts消费,全仓_sections的 fixture 只出现在本规则的测试与packages/spec的 translation schema 测试里 —— 没有 #5046 那种跨包 fixture 需要同步。关于 #5416
切 worktree 时 PR #5416(#5405)尚未合入
main,故按分诊口径跳过「走查器要求 ⊆ 校验器接受」的集合断言,不为它引入跨分支依赖;本修复不依赖 #5416。#5416 落地后可在其上补该断言。越界发现(已另开 issue,未在本 PR 修)
ContactViewsis exported but never registered inobjectstack.config.ts— the docs' canonical "create form ≠ edit form" example does not run #5420 —ContactViews在examples/app-showcase/objectstack.config.ts的views里从未注册(barrel 导出了、config 第 23/196 行漏了),而 app 导航有nav_contacts、文档content/docs/ui/create-vs-edit-form.mdx又把该文件列为活参考实现。本 PR 的 fixture 直接 import 该模块,不依赖 showcase 注册。runtime-lazy-deps.test.ts"gating a flow in-process" runs on the default 5s timeout and flakes under load — its cold-load sibling already carriesCOLD_LOAD_TIMEOUT_MS#5421 —packages/lint/src/runtime-lazy-deps.test.ts的 in-process 用例用默认 5s 超时(同文件的冷加载兄弟用例已带COLD_LOAD_TIMEOUT_MS),负载下 4 跑 2 挂,与本改动无关。🤖 Generated with Claude Code
https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
Generated by Claude Code