Skip to content

fix(lint): 声明了 label 却没有 name 的表单段落 —— 永远无法被翻译的标题,现在会告警 (#5417) - #5455

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-5417-lint-nameless-section
Aug 5, 2026
Merged

fix(lint): 声明了 label 却没有 name 的表单段落 —— 永远无法被翻译的标题,现在会告警 (#5417)#5455
baozhoutao merged 1 commit into
mainfrom
claude/issue-5417-lint-nameless-section

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5417

前提核验(先证后写)

按操作规程,先在 origin/main(5a45b9bf1)上用最小 fixture 实测,确认 issue 的前提今天仍然成立 —— 两侧都对无名段落零告警:

两个核验脚本是临时文件,已删除,未进入提交。前提成立,继续实现。

做了什么

新增规则 validateTranslatableSections(规则 id translation-section-name-missing),加入 reference-integrity 套件,因此 os validate / os lint / os compile 三个命令同时生效。

判据即 PM 裁定的口径:声明了 label(或 detail 段落的 title)而没有 name 的段落,在其所属对象有任何翻译时告警

为什么这是第三条腿:_sections 以段落 name 为键,渲染器也只按 name 查找(sectionLabel(objectName, section.name, authored),无 name 时回落到作者写的 label)。于是无名段落 —— 既不产生任何「孤儿键」让 validateTranslationReferences 报,也不产生任何「期望键」让覆盖率走查要 —— 是构造上不可翻译,而我们所有门禁对它结构性失明。

锚集对齐

规则覆盖的 section 面与两处已落地实现严格一致(#5416 的 walker section 双源、#5422 的校验器锚集):

射程外且有理由:fieldGroups 派生段落的键就是 fieldGroups[].key,天然有 name;sections 写成按名索引的 map 时,map 的键就是 name。

为什么是生产端诊断,而不是消费端容忍

诱人的「修法」是在渲染器把 label 转成 slug 去捞第二把键。那正是 Prime Directive #12 禁止的宽松回落:它会把 _sections.[label 的 slug] 固化成与已声明契约并存的第二套事实契约,而且作者一改标题文案键就漂移。objectui#3373 已经把渲染器的 name 守卫钉为正确。所以缺陷在生产端,规则就放在生产端。提示里给出的 snake_case 名字只是建议作者写下来的名字,不是任何东西会去解析的键 —— 这句话写进了 hint 本身,免得 AI 作者反过来假设 label 就是键。

严重级别与 opt-in

  • warning,与同族规则同档(ADR-0072 D1):不崩、不死,只是一个标题留在源语言。方案 2(收紧 packages/specFormSection.name 为必填)未搭车,按 PM 裁定属破坏性授权变更,留待维护者表态;本 PR 不动 packages/spec、不动 packages/cli
  • 单语项目零告警:只有当段落所属对象在某个 stack.translations bundle 里有节点时才告警,与 computeI18nCoverage 的 opt-in 不变量一致。

仓内真实锚(实测,如实报告)

PM 点名的 showcase formViews.create 无名段落确实命中。实跑 pnpm --filter @objectstack/example-showcase validate:

✓ Validation passed (1109ms)
⚠ object "showcase_task" · formViews.edit · section "Task": ...
⚠ object "showcase_project" · page "showcase_project_detail" · record:details · section "Overview": ...

全配置共 14 条(6 条来自 form view,8 条来自 record:details 页面),退出码 0 —— 只有 error 会 gate,warning 走 advisory 通道,CI 不会因此变红。

一处如实更正:ContactViews(那个 { label: 'Who is this?' })从 views barrel 导出,但并未登记进 showcase config 的 views: 数组,所以它不在这 14 条里。测试里仍按 #5415 同款方式直接组合它(它在一个文件里同时给出「默认 form 四个段落都有 name」与「稀疏 create 覆写没有 name」的对照),另外补了一条用确实登记在 config 里TaskViews 的用例,免得「真实元数据」的说法被夸大。

测试

27 条新用例,含:HotCRM 形状逐条告警断言、opt-in 门四态、8 个锚点各自双向断言(无名 → 本规则报 1 条;有名 + 已翻译 → 本规则静默且 validateTranslationReferences 也接受,这就是「覆盖面必须一致」的共导断言,任一侧漂移就恰好红一半)、射程外四态、真实 showcase 两组。

反向验证(先判方向后跑):预判为 。删掉 collectViewSites 里的默认 form 锚点分支 → 4 条用例转红,全部是 expected [] to have a length of 3 but got +0 / length of 1 but got +0,即该分支承重、断言不是靠「什么都没产生」而绿。已还原。

消费半径扫查

规则跑在哪里,就扫哪里的 fixture(#5046 的教训:改在 packages/lint,坏的 fixture 在 packages/cli 的命令对等测试里)。packages/cliauthoring-rule-command-parityformView 用的是 sections: [] 且无 translations;i18n-section-coverage 走的是 fieldGroups 面。9 个消费半径内的 cli 测试文件全绿。


Generated by Claude Code

…e heading no key can address (#5417)

`_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 (#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 #5416's walker and #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 #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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 12:45pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)
  • content/docs/releases/v17.mdx (via @objectstack/lint)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lint: a form section with a label but no name can never be translated and nothing warns — 70/70 HotCRM form sections are in that state

2 participants