Skip to content

fix(cli): _views 翻译键改用运行时视图身份命名(#5164 第 1 棒 / cli 段) - #6124

Merged
qq9340100 merged 1 commit into
mainfrom
claude/issue-5164-views-key-default-cli-leg
Aug 7, 2026
Merged

fix(cli): _views 翻译键改用运行时视图身份命名(#5164 第 1 棒 / cli 段)#6124
qq9340100 merged 1 commit into
mainfrom
claude/issue-5164-views-key-default-cli-leg

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Part of #5164

本 PR 只交付维护者 2026-08-06 裁决(方案 A)的第 1 棒 / cli 段。第 2 棒 packages/lint 收窄见 #6038,第 3 棒 objectui viewSuffixesobjectstack-ai/objectui#3502 —— 所以首行刻意写 Part of 而不是 Fixes,合并后 issue 由 PM 保持打开,等三棒跑完再关。

前提复核(实测,先于实现)

在 origin/main f7f54a960 上重跑了 issue 正文那段「组装器 vs 提取器」的测量,分歧确认复现:

容器 { list: { label: 'All Tasks', type: 'grid', data: {…object: 'task'}, columns: ['title'] } }
组装器 expandViewContainerWithDiagnostics('task', 容器)
  -> ["task.default"]                      // 裸键 default
提取器 collectExpectedEntries({ views: [容器] })
  -> ["objects.task._views.list.label", …] // 裸键 list

一个只声明了默认 list、没有 listViews 的应用,因此拿到一份键为 list 的骨架、一个键为 default 的运行时视图 —— 无论作者按哪一侧编写都命中不了,界面上永远是英文原文。

顺带按派发单要求审了同文件的 FORM 面:该文件根本不为 form 视图产 _views(注释写明 form 视图在 viewLabel 解析约定里没有对应物),formViews 只喂 _sections;文件里也不存在 ?? 'form' 一类的同族兜底站点。所以 form 面无需镜像,这是设计,不是遗漏。

实现:提取器改为「向组装器查询」,而不是字面改成 ?? 'default'

派发单预设的字面改法(view.list.name ?? 'list'?? 'default')实测在 app-crm 形状上仍然错,这是本 PR 唯一偏离派发词的地方,记录如下:

examples/app-crm 三个容器的默认 list 与各自的 listViews.all 结构签名完全相同,组装器按签名把两者折叠,list 家族的真实注册键只有 all;而它们的 formViews.default 已经占住了 对象名.default。字面改法会把 全部商机 写进一个表单视图的键上 —— 把今天可见的孤儿键换成一个看不见的错标,而且它会通过 lint(因为 formViews.default 确实注册了 default),比现状更糟。

所以提取器现在调用组装器本身取这个键:

function defaultListViewKey(object: string, container: any): string | undefined {
  if (!container?.list || typeof container.list !== 'object') return undefined;
  const item = expandViewContainer(object, container).find((i) => i.viewKind === 'list' && i.isDefault);
  
}

expandViewContainer 是运行时视图身份的唯一生产者,而本 issue 的病根正是「第二次自行推导会漂移」。问身份本身,而不是再推导一遍,同时白拿组装器仅有的规则:

  1. name 的默认列表键为 default(不再是 list),带 name 的沿用作者的 name;
  2. 与某个 listViews 条目结构相同的默认列表被折叠进该条目,没有自己的键 —— 提取器不再为它多写一个谁也读不到的骨架条目;
  3. 附带:被碰撞改名的默认列表(listViews 已占 default 时组装器给 default_2)骨架跟着改名,因为改名后的才是注册表键。

packages/spec 零字节改动(裁决否决 B 案,组装器命名不变),只是复用它已导出的函数。

键相等实测(改后)

showcase 五个对象,组装器 list 家族键集合与提取器 _views 键集合完全相等(两侧独有项均为空):

object 组装器(list 家族) 提取器(_views) 只在组装器 只在提取器
showcase_task …, default default, … (空) (空)
showcase_project by_status,budget_chart,default default,by_status,budget_chart (空) (空)
showcase_inquiry triage,default default,triage (空) (空)
showcase_business_unit org_chart,default default,org_chart (空) (空)
showcase_contact default default (空) (空)

改前每个对象都是「只在组装器: default / 只在提取器: list」。

app-crm(折叠形状)改后提取器输出 ["all","pipeline"] / ["all","calendar"],与组装器 list 家族一致;对象名.default 在那里是表单视图,提取器按约定不覆盖。

译文随迁

  • examples/app-showcase:5 个 _views.list 块改名为 default(showcase_project / showcase_task / showcase_contact / showcase_inquiry / showcase_business_unit)。
  • examples/app-crm:3 个 _views.list删除而不是改名 —— 它们是折叠形状,改名后指向的是表单视图(见上),而同块里的 all 已经带着一模一样的译文串。issue 正文说它「是看着正确的死重量」,实测确认,并且删除后 lint 少 3 条。
  • examples/app-todo:zh-CN / ja-JP 两个包各 1 块改名为 default。派发单没点名 app-todo,但它与 showcase 同属「无名默认 list」形状(实测组装器给 todo_task.default),不随迁会让覆盖棘轮变红 —— 属同一次迁移的必然范围。

覆盖棘轮基线:实测不需要调整

派发单预期「基线 adjust DOWN」。实测下来这条预设不成立,照实记录而不硬凑:

$ node scripts/check-i18n-coverage.mjs
check-i18n-coverage: OK (12 config(s), 660 baselined untranslated string(s), none new).

$ node scripts/check-i18n-coverage.mjs --update && git diff scripts/i18n-coverage-baseline.json
(无输出 —— 基线零变化)

原因:裁 A 下需求侧仍然要求一个键(只是改名),译文侧同批改名,覆盖数不变;app-crm 删掉的那三条本来就是「已译且被要求」,改后变成「不再被要求」,缺失数同样不变。08-05 那条评论预期的下调,对应的是「walker 侧彻底不再产该键」的那一支,不是裁 A 这一支。基线文件因此不在本 PR 的 diff 里。

验收锚点:os lint 全量对照

showcase(node packages/cli/bin/run.js lint examples/app-showcase/objectstack.config.ts --json),改前 / 改后总量完全一致(total=523 errors=0 warnings=501),那 5 条「一条规则要求该键、另一条判它孤儿」的矛盾对是原样迁移,没有新增:

before: translations[0]["zh-CN"].objects.showcase_{project,task,contact,inquiry,business_unit}._views.list
after : translations[0]["zh-CN"].objects.showcase_{project,task,contact,inquiry,business_unit}._views.default

这 5 条正是第 2 棒 #6038 要让 collectViewRecord 注册的键 —— 派发单已言明「lint 侧收窄归第 2 棒,现有双拼写容忍预期保留」。

app-crm 则是净减少:total 104 → 101(warnings 102 → 99),三条 _views.listtranslation-target-unknown 消失,无新增。

反向验证(方向事前预测:红;实测:红)

?? 'list' 放回同一位置后跑新增的 pin 测试:8 例中 6 例红,断言文本正是本 PR 要钉的等式:

AssertionError: expected [ 'list' ] to deeply equal [ 'default' ]
AssertionError: expected undefined to be 'All Tasks'
AssertionError: expected [ 'list', 'all' ] to deeply equal [ 'all' ]
AssertionError: expected [ 'list', 'open' ] to deeply equal [ 'default', 'open' ]
AssertionError: expected [ 'default', 'list' ] to deeply equal [ 'default', 'default_2' ]
Tests  6 failed | 2 passed (8)

另外 2 例保持绿是正确的:它们是「默认 list 带显式 name」和「根本没有默认 list」两种形状,新旧代码在这两种形状上本来就同解。

测试

新增 packages/cli/test/i18n-extract-view-key-identity.test.ts(8 例)。它用组装器本身当 oracle,而不是硬编码 'default' 这个拼写 —— 钉的是不变式(两侧同键),不是某一侧当下的意见;将来若身份规则再变,测试跟着变,不会变成一条只会重复自己的死断言。

pnpm --filter @objectstack/cli test               →  Test Files  88 passed (88) / Tests  880 passed (880)
pnpm --filter @objectstack/example-crm test       →  Test Files   1 passed (1)  / Tests   20 passed (20)
pnpm --filter @objectstack/example-showcase test  →  Test Files  13 passed (13) / Tests  144 passed (144)
typecheck(cli + showcase + crm + todo)           →  全部 Done
pnpm lint / check:i18n / check:i18n-coverage / check:nul-bytes / check:engine-double-contract
  / check:error-code-casing / check:route-envelope / check:adr-anchors / check:doc-authoring
  / check:published-files                        →  全绿

不在本次范围

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wbxm29qPKnLf44AbSxizqW


Generated by Claude Code

The i18n extractor derived a container's default-list translation key as
`view.list.name ?? 'list'`, while the composer that assigns the runtime view
identity (`expandViewContainer`) names the same view `<object>.default`. An app
declaring only a default `list` therefore got a bundle skeleton keyed `list`, a
registry entry keyed `default`, and an English label on screen forever — no
authoring side could ever make it resolve.

Maintainer ruling 2026-08-06: canonical = the runtime identity's bare key. This
is leg 1 of 3 (cli); `packages/lint` narrowing is #6038, objectui `viewSuffixes`
is objectui#3502.

The extractor now ASKS the composer for the key instead of re-deriving it, so no
third dialect can drift back in. That also inherits the composer's signature
dedupe: a default list that merely restates a `listViews` entry is collapsed into
it and gets no key of its own — the `examples/app-crm` shape, whose `_views.list`
entry was dead weight that looked correct.

Migrated in lockstep: app-showcase (5 blocks) and app-todo (2 bundles) renamed to
`default`; app-crm (3 collapsed blocks) removed. `check:i18n-coverage` measured
green with the baseline unchanged — both ends moved together, so no count did.

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

vercel Bot commented Aug 7, 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 7, 2026 2:44am

Request Review

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v17.mdx (via @objectstack/cli)

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.

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 7, 2026
@qq9340100
qq9340100 marked this pull request as ready for review August 7, 2026 02:54
@qq9340100
qq9340100 added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit b3c1f3c Aug 7, 2026
24 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5164-views-key-default-cli-leg branch August 7, 2026 03:07
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants