fix(lint): collectViewRecord 按运行时视图身份判定容器默认 list 的 _views 键 (#6038) - #6424
Merged
Merged
Conversation
…ime identity (#5164) (#6038) `collectViewRecord` decided which `_views` name a container's default `list` makes legal by reading `view.list.name` — and registered NOTHING when the author wrote none. The composer that assigns the runtime view identity (`expandViewContainer`, `packages/spec/src/ui/view.zod.ts`) names that very same view `<object>.default`. Leg 1 (#6124) moved the i18n extractor onto the composer's key, which turned a latent disagreement into a live contradiction: in ONE `os lint` run `i18n/missing-view` demanded `objects.<object>._views.default.label` while `translation-target-unknown` called that key an orphan. Measured on main: 8 instances (app-showcase 6, app-todo 2) — no author action made both green. This rule now ASKS the composer for the key too, inheriting its three rules: a nameless default list is `default`; one that structurally restates a `listViews` entry is collapsed into it (so the collapsed-away `list.name` stops being legal — nothing resolves it); a collision-renamed key is judged under the rename, because the rename is the registry key. Measured across all 12 ratcheted configs: 0 new findings, 8 false positives gone, `check:i18n-coverage` baseline unchanged. Leg 2 of 3; objectui `viewSuffixes` is objectui#3502. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
|
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:
|
hotlong
marked this pull request as ready for review
August 7, 2026 17:53
This was referenced Aug 7, 2026
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 #6038
#5164 裁 A(维护者 2026-08-06:
_views翻译键 canonical = 运行时组装器裸键)三段串行的第 2 棒 / lint 段。母单 #5164 已关闭,本单自身用Fixes。1. 四项串行前置的逐项实测(本单第一项硬任务)
派单要求:⛔ 不采信「母单已关闭」这一个事实,动代码前在
origin/main(e15bf7e)上逐项实测。结论:四项全部落地,前置满足,且实测发现 main 上的实际状态与派单担心的方向相反 —— 不是「存量仍是list键、lint 收窄会判红存量」,而是第 1 棒已把两端都迁到default,唯独 lint 不认default,于是 main 上此刻就有 8 处误报。本 PR 是减红,不是增红。view.list.name ?? 'list'→defaultos i18n extract examples/app-showcase/objectstack.config.ts --json --dry-run --filter '_views''default',而是defaultListViewKey()向组装器expandViewContainer查询运行时身份(packages/cli/src/utils/i18n-extract.ts:210)。实测产出_views.default(showcase_task._views.default.label = "All Tasks"等)_views.list译文块随迁examples/*/src全量examples/app-showcase/src/system/translations/index.ts现有 6 个_views块全部键为default(project / task / contact / inquiry / business_unit / field_zoo);examples/**内_views下list:键零残留pnpm check:i18n-coverage(改动前)EXIT=0,OK (12 config(s), 660 baselined untranslated string(s), none new)。两端同批改名,覆盖数不变.changeset/目录.changeset/views-translation-key-runtime-identity.md(@objectstack/cli: minor),含BREAKING(已发布翻译包的键)标注与 FROM → TO 三行对照表 + 一行修法⛔ 未越界补做第 1 棒的任何活:
packages/cli/**、examples/**译文本 PR 一字未动。2.
collectViewRecord现行形状 → 改后形状改前(
packages/lint/src/validate-translation-references.ts:260):作者没写
list.name时什么也不注册。而组装器(packages/spec/src/ui/view.zod.tsexpandViewContainerWithDiagnostics,只读核实、⛔ 未改)给同一个视图的身份是< object >.default。改后:
defaultListViewKey()向组装器查询,而不是第三次自行推导 —— 与第 1 棒packages/cli/src/utils/i18n-extract.ts的同名函数刻意成对(两者都只是组装器的薄读取器,而不是第三、第四份推导)。因此继承组装器仅有的三条规则:name的默认列表键为default(绝不是list);带name的沿用作者的name;{type,label,columns}签名)与某个listViews条目相同的默认列表被折叠进该条目 —— 存活的是那个listViews键,被折叠掉的list.name不再合法(运行时解析不到它);default→default_2)按改名后判定,因为改名后的名字才是注册表键。组装器行为实测(探针,非推断):
顺带清理:该函数注释里「whether it contributes a⚠️ 没有只删句子了事 —— 换成了答案:默认 form 继续只贡献
_viewsname is the neighbouring question #5164 owns」的悬置已删除。_sections,理由是_views.*是列表视图约定(viewLabel/viewDescription解析视图页签),i18n walker 对任何 form 视图都不产_views条目(i18n-extract.ts原话),所以给默认 form 注册一个_views名等于让一个没有消费方的键变合法。行为不变,零变红风险。3. 矛盾对 before / after 实跑取证
否定方(本规则)
改动前后各跑一遍全部 12 个受棘轮覆盖的配置的
os lint --json,逐条 issue 做集合差:汇总口径:
_views键上的translation-target-unknown8 → 0。showcase_field_zoo是第 6 个同族实例;app-todo 另有 2 处(两个语言包),#5164 未记录。要求方(同一次
os lint)否定方的 8 条只是矛盾对的一半。要求方在译文存在时不发声,所以另用一个合成 stack(仅含一个无名默认
list、无译文)在同一个os lint里取证:给同一个 stack 补上
_views.default译文后再跑:i18n findings 归零:要求方不再要,否定方不再否 —— 两条规则对同一个键给出同一答案。矛盾结构性消失,验收判据达成。(探针文件已删除,
git status只剩本 PR 的三个文件。)4. 反向验证(先申报,后执行)
声明 A —— 矛盾消失
申报:修前 showcase 全量
os lint存在 i18n 矛盾对,键为objects.< object >._views.default,两条规则为i18n/missing-view(要求方,packages/cli/src/utils/i18n-extract.ts)与translation-target-unknown(否定方,本文件);修后该矛盾对为 0。实测:成立。showcase 6 对 → 0(另发现 app-todo 2 对,一并归零)。两侧取证见上节。
声明 B —— 变异体
申报(执行前写下):把收窄退回
addView(listBinding, strName(view.list.name))、保留全部新断言,预测转红的是四条肯定式断言(default合法 / 折叠体拒绝list.name/ 改名键default_2合法 / form fixture 的default)加上配对孤儿计数那条;预测结构上不会红的是「具名默认列表沿用作者name」(回归护栏,两侧同绿);并预判「拒绝旧list拼写」那条只会经由提示语断言转红,其 path 断言对本变异体是盲的。实测:
7 failed | 29 passed (36),与申报逐条吻合:断言极性逐条标注:
default对无名默认列表合法toEqual([]))list拼写hint断言=肯定式expected 'Match the key to the view's name (…' to contain 'default'—— 变异体下 Declared views 尾巴是空的。path 断言两侧同绿,这条如实标注list.namedefault_2合法toEqual([]))_views名toEqual([]),fixture 含default)_views.default合法toEqual([]))name空绿自查(本轮已有四位 dev 撞过这个坑):每一条
toEqual([])都在同一个 fixture 上配了一条植入坏键的肯定式断言,证明规则确实跑过、确实会产出 finding,而不是「因为什么都没产出所以绿」。逐条:无名默认列表 fixture 配hot_leads(计数 1);具名列表 fixture 配default(计数 1);折叠 fixture 配all_leads(计数 1);改名 fixture 配default_3(计数 1);默认 form fixture 配form(计数 1);真实 showcase fixture 配list(计数 1)。无一条真空绿。声明 C —— 存量不误伤
申报:修后跑全量
os lint,新增的红必须为 0。实测:成立。12 个配置全跑,
added: 0。⛔ 未为了让门变绿放宽任何规则。fixture 分诊(⚠️ 一处必须的改动,并已证明其必要性)
既有 fixture
leadViews的默认list({type:'grid', name:'all_leads'})与listViews.my_leads({type:'grid'})签名完全相同,组装器会把两者折叠 —— 也就是说all_leads根本不是运行时视图名,fixture 等于在断言「一个谁也解析不到的键是合法的」。按三分法这属于补声明(add declarations),不是 re-spell、也不是整体替换:给默认list加label: 'All Leads'使其成为它自称的那个「独立的默认列表」。必要性已实测(不是「看起来需要」):把这个
label拿掉、保留本 PR 的实现,该 fixture 的两条既有测试双双转红(2 failed | 33 passed)。折叠语义本身另立独立测试单独 pin。消费半径扫查
按规则的消费半径而非编辑包扫查 fixture:
translation-target-unknown的调用方为os lint(经packages/lint导出)。全仓 grep_views.list/_views+list:,除历史 CHANGELOG(⛔ 不改)与本 PR 自身文件外零命中;packages/cli/test/i18n-extract-view-key-identity.test.ts已由第 1 棒 pin 在default侧,与本 PR 同向。5. changeset 级别与依据
.changeset/lint-views-key-runtime-identity.md——@objectstack/lint: patch(真 changeset,⛔ 未打skip-changeset;@objectstack/lint是发布包)。依据:
validateTranslationReferences签名与 rule id 均不变)。同族先例:.changeset/adr-0105-d6-unit-and-subordinates.md(@objectstack/lint: patch,同为既有规则词表/判定修正);severity: 'warning',不改os lint退出码(showcase 496 条 warning 时EXIT=0),也不在check:i18n-coverage棘轮口径内(该棘轮只数i18n/前缀);全仓 12 配置实测新增 0 条,消费方 CI 不会因此变红 —— 不足以升 minor;6. 门禁 EXIT 表(均在
git add之后跑,重活经flock串行 + 堆上限)pnpm --workspace-concurrency=2 --filter @objectstack/lint test62 files / 1527 tests passedpnpm --workspace-concurrency=2 --filter @objectstack/lint typechecktsc --noEmitpnpm lintEXIT=134),提到 8192 MB 后EXIT=0;scopedeslint packages/lint亦 0。仓库规模问题,与本改动无关pnpm check:nul-bytesgrep -naP控制字节类,扫三个改动文件pnpm check:i18npnpm check:i18n-coverage12 config(s), 660 baselined, none newpnpm check:type-check-debt34 ledger entries re-measured, none above its recorded number。本 PR 未新增测试文件(只改既有.test.ts),无 TS2835 风险pnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm check:doc-authoringpnpm check:adr-anchors⛔ 无一条绕过。
7. 不在本 PR 里
packages/cli/**—— 第 1 棒(fix(cli):_views翻译键改用运行时视图身份命名(#5164 第 1 棒 / cli 段) #6124)的面,已落地,一字未动;examples/**译文 —— 同属第 1 棒;实测无遗留需随迁(_views下list:键零残留),故无可报;packages/spec/**—— 裁决否决 B 案,组装器命名不变;本 PR 仅只读核实expandViewContainerWithDiagnostics的命名/折叠/改名三条规则;viewSuffixes去第二候选 —— 第 3 棒 objectui#3502,跨仓;packages/lint/src/validate-form-layout.ts—— lint: 表单类规则只读views[].sections,而真实 app 形状下views[]是视图容器 ——validateFormLayout两条规则在实测 stack 上不可达 #6251 / PR fix(lint): validateFormLayout 走视图容器阶梯,两条规则不再对真实 app 全盘报绿 (#6251) #6382 的面;collectViewRecord的键收集语义,未合并任何遍历;listViews/formViews分支「map key 与内层name两种拼写都收」的收窄 —— 这是另一处过宽,与默认list无关:组装器对具名条目只认 map key(忽略v.name)。收窄它会给存量增红,且不在本单验收判据内(要求方对 form 视图不产任何_views键,故不构成矛盾对)。已作为观察单另记,见下;content/docs/releases/**。8. 顺带发现(已另立观察单,未在本 PR 修)
collectViewRecord的 listViews/formViews 分支收「map key + 内层 name」两种拼写,而组装器只认 map key —— 冲突改名时两者恰好相反 #6422(finding,未自我认领):同一函数的listViews/formViews分支同时接受 map key 与内层name,而组装器对具名条目只认 map key;冲突改名场景下两者会正好相反 —— 组装器把formViews.default改名为default_2,本规则却认default(解析不到)而拒default_2(真键)。目前休眠:lint-view-refs.ts已把视图键冲突判为硬错误,该形状发不出去。Generated by Claude Code