Skip to content

fix(pages): 商机参考栏卡片标题回落到本地化对象标签,并去掉 Open Tasks 的不实过滤声明 (#972) - #983

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-972-reference-rail-entries
Aug 6, 2026
Merged

fix(pages): 商机参考栏卡片标题回落到本地化对象标签,并去掉 Open Tasks 的不实过滤声明 (#972)#983
yinlianghui merged 1 commit into
mainfrom
claude/issue-972-reference-rail-entries

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #972

两半按 measure-first 各自判定,结论一半修、一半转 finding。测量对象是本仓实际装的 @objectstack/console@17.0.0-rc.3 产物与 @objectstack/spec@17.0.0-rc.3 的 Zod 定义,无浏览器实测(本环境不具备条件),下文每条结论都给出产物中的取证位置。

一、title 本地化 —— 修(删字面量,走译文包)

参考栏解析卡片标题的整条链,在 node_modules/@objectstack/console/dist/assets/plugins-views-CJKuyDvq.js 里是:

entry.title || (i18n.objectLabel ? i18n.objectLabel({ name: entry.objectName, label: humanize(entry.objectName) }) : humanize(entry.objectName))

即 issue 记的没错:title 存在就赢,译文包根本不会被查。三个候选修法实测下来:

  • (a) 删掉 title,让它回落到本地化对象标签 —— 可行,已采用。 objectLabelui-components 产物里是 e => i(a(e.name, 'label'), e.label)a 拼出的键就是 objects.*.labeli 逐个命名空间查当前语言的资源包、查不到才用传入的兜底。本仓四个语言包这三个对象的 label 都是齐的(构建产物实测见下表),所以回落落在译文上,不是落在 humanize() 上。取这条链的还有 sectionLabel / fieldLabel —— 与 sales/opportunities 详情布局 :151 / :152opportunity_detail.page.ts 的 aside 区不符:参考栏列的是关联记录不是建议,且没有 Competitors & Notes 面板 #959 已测「record:details 分区标签走 sectionLabel 且确实本地化」是同一个 helper 的同一个 i(),互为佐证。
  • (b) title 写可翻译的内联译文对象 —— 不可行。 同一份产物里 record:alert 的 title 是 gt(n.title, language)(内联译文映射解析器),参考栏则把 entry.title 当作原始 React child 直接渲染。在这里写 { en, 'zh-CN' } 不会被解析,会被交给 React 当对象。
  • (c) 转 finding —— (a) 可行,不需要。

构建产物 dist/objectstack.json 中四个语言包的实测值:

locale crm_quote crm_opportunity_line_item crm_task
en Quote Opportunity Line Item Task
zh-CN 报价单 商机产品明细 任务
ja-JP 見積 商談商品明細 タスク
es-ES Cotización Línea de Oportunidad Tarea

取舍(请维护者过目,回退成本 = 3 行)

英文标题会从字面量的复数变成对象自身的单数 label:Quotes → QuoteProducts → Opportunity Line ItemOpen Tasks → Task。参考栏只读 label、不读 pluralLabel(产物里没有第二条取值路径),所以「带计数徽章的列表卡片配复数标题」这个更好的效果,元数据侧今天拿不到 —— 已单独立 finding,见下。

我判断这笔交换值得做:中文/日文/西语用户看到三张全英文卡片是实打实的本地化缺陷,而英文侧是措辞粗细的问题;并且删掉字面量之后,标题来源收敛到译文包这一处单一事实源,以后新增语言不用再回来改页面。若维护者认为英文措辞更重要,请直接说,我把三行加回去并把本半条转成 finding。

二、Open Tasks 徽章过滤 —— 不修,转 finding

参考栏对每个 entry 只发这一条查询,产物原文:

find(objectName, { $filter: { [relationshipField]: parentId }, $top: limit ?? 3, $count: true })

entry 上除 objectName / relationshipField / limit 外的键一概不读;组件注册的 inputs 只有 hideEmpty 一项。

schema 侧的情况需要说清楚,因为它比「不支持」更危险:@objectstack/specComponentPropsMap 没有 record:reference_rail 这一行,而页面组件的 propertiesz.record(z.string(), z.unknown()).d.ts 里也不存在任何 ReferenceRailEntry 类型。也就是说,在 entry 上补 status neq completed解析通过、typecheck 通过、validate 通过、发布上线,然后什么也不做 —— 徽章照旧计入 Completed,而源码从此声称自己过滤了。这正是「声明了却不生效」的经典陷阱,所以按 PM 裁定记 finding,不硬改。

不过这一半留下的标题不实问题,被上一半顺手解决了:卡片不再叫 Open Tasks,而是叫 Task / 任务 —— 它统计的本来就是「本商机的全部任务」。声明与实况重新对齐,只是靠削掉不实声明,而不是靠补上能力。

三、其他页面的同型问题

全仓 record:reference_rail 只有这一处grep -rn "reference_rail" src/ 命中 opportunity_detail.page.ts:231 一个组件)。account / contact / lead / case 等 detail page 的 aside 里没有参考栏,故无同型问题可核。

四、文档同步(PR #967 刚写实的两行)

#967sales/opportunities 三语的 :152 写实了「参考栏无 filter、徽章计总数」,并按当时的实况把三张卡片称作 Quotes / Products / Open Tasks。本 PR 改变了卡片标题,故同 PR 更新那一行:卡片名改为按对象标签描述(并点明英文下的实际字样)、补一句标题来自译文包、并把「Open Tasks 卡片不按状态过滤」改写为「entry 无法过滤,这也正是它不叫 Open Tasks 的原因」。:151(Competitors & Notes)未改动 —— 那一行描述的是另一件事,本 PR 不触及。#967 写实的「无 filter」这个事实本身仍然成立,只是换了个说法挂靠。

五、守卫与反向验证

新增三道,落在既有的两个家族文件里(metadata-references 管引用解析,i18n-references 管语言包):

  1. every record:reference_rail entry names a real object and a real relationship field —— 此前完全没有守卫覆盖 rail entry:entry 没有 typewalk 不产出它;spec 也不校验。写错一个字段名的后果是卡片查空、又因 hideEmpty 默认开而自动折叠,静默到底。
  2. no record:reference_rail entry declares a literal title —— 在参考栏拿到可翻译标题通道之前,「声明了 title」与「不可翻译」是同一件事,正确数量只能是 0。
  3. no record:reference_rail entry declares a filter the rail cannot apply —— 给后来者的陷阱:照抄 Related 页签谓词的人拿到一条红测试,而不是一个静默失效的键。
  4. every reference-rail object has a translated label in every locale(i18n 家族)—— 这条是让第 2 条安全的另一半。参考栏的兜底不是英文 label,而是 humanize(objectName)Crm Opportunity Line Item),所以缺译文在这个界面上比没有 i18n 还糟;本条把这三个对象的 label 完整性钉死在四个语言包上。

反向验证(方向为事前预测,两条都按预测落地):

  • 预测「把三个字面量 title 加回去 → 第 2 条守卫转红,且恰好 3 条 finding」。实测:× no record:reference_rail entry declares a literal title,报出 crm_quote / crm_opportunity_line_item / crm_task 三条,其余 46 条全绿 —— 方向与条数均符。
  • 第 3 条守卫在干净树上是空过的(没有任何 entry 声明 filter,它靠「什么也没产出」而通过)。这正是需要单独证伪的情形,故预测「在 crm_task entry 上植入 filter: [{ field: 'status', op: 'neq', value: 'completed' }] → 该条转红」。实测:× no record:reference_rail entry declares a filter the rail cannot apply,报出 crm_task 一条。两次临时改动均已还原,工作树最终状态与提交一致。

六、六道门(全部在共享验证锁内串行)

结果
pnpm validate exit=0(5 条 author-time warning 均为既有,与本改动无关)
pnpm typecheck exit=0
pnpm lint exit=0(13 warning / 14 suggestion,既有)
pnpm hygiene exit=0,含控制字节扫描 4 项全绿
pnpm build exit=0,dist/objectstack.json 1921.3 KB
pnpm test -- --maxWorkers=2 exit=0,73 files / 1686 passed, 1 skipped

dist 产物结构已核对:opp_reference_rail 的三个 entry 在产物中为 { objectName, relationshipField, limit }'title' in e'filter' in e 均为 false,四个语言包的三个对象 label 齐备(即上文表格)。

未起任何 dev server,无遗留进程。

范围之外


Generated by Claude Code

参考栏三个 entry 各自写死了英文 title(Quotes / Products / Open Tasks)。
渲染侧把标题解析为 `entry.title || i18n.objectLabel({ name: objectName, … })`
—— 字面量不是「默认值」,它直接胜出,译文包根本不会被查。本仓四个语言包里
`objects.crm_quote.label` / `crm_opportunity_line_item.label` / `crm_task.label`
都是齐的,参考栏却在每个语言下都印英文。删掉这三个字面量,标题交还给译文包。

两点随之而来的后果,已写进文档与 changeset:

- 英文标题从字面量的复数变成对象自身的单数 label(Quote / Opportunity Line
  Item / Task)。参考栏只读 `label`,不读 `pluralLabel`,元数据侧拿不到复数。
- 任务卡片失去 Open 一词 —— 它本来就无权声明。entry 没有 filter 通道,也无法
  补:参考栏只发
  `{ $filter: { [relationshipField]: parentId }, $top: limit }`,entry 上的其余
  键一概不读。真正按 `status neq completed` 过滤的是 Related 页签上的
  `opp_tasks` 关联列表。因为页面组件的 `properties` 是未校验的 `z.record`,
  在 entry 上写 filter 会解析通过、发布上线、然后什么也不做。

新增三道守卫:entry 必须指向真实对象与关系字段(此前无任何守卫覆盖 —— entry
没有 `type`,walk 不产出它,spec 的 ComponentPropsMap 也没有
`record:reference_rail` 这一行);不得声明字面 title;不得声明 filter —— 最后
一道是给后来者的陷阱,照抄 Related 页签谓词的人会拿到一条红测试,而不是一个
静默失效的键。三语文档 sales/opportunities 同步更新。

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

vercel Bot commented Aug 6, 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)
hotcrm Ignored Ignored Aug 6, 2026 2:38pm

Request Review

@github-actions github-actions Bot added ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces labels Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

顺带发现已立 finding:#986 —— record:reference_rail 的 entry 授权面比页面所需更窄,且该形状未被 spec 声明。三条各自独立:

  1. entry 没有 filter 通道,而写了不会报错(spec 的 ComponentPropsMap 没有 record:reference_rail 这一行,properties 是未校验的 z.record)—— 本 PR 第二半转 finding 的正主。
  2. 标题只能取 label、取不到 pluralLabel,所以带计数徽章的列表卡片配到了单数标题 —— 本 PR 取舍的代价。
  3. entry 的 title 是不可翻译的字面量(record:alert 的 title 走内联译文映射解析器,参考栏不走)—— 所以本 PR 只能选「不写」。

按 observation-class 处理:打 finding + upstream:objectstack,不打 pm:queue,留给 PM 分级。本 PR 的守卫只能挡住本仓再写出这三种写法,补不了能力。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 6, 2026 14:42
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 9aac752 Aug 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

商机详情页参考栏的 entries 两处失真:Open Tasks 卡片不按状态过滤;三个 title 硬编码英文并压过本地化对象标签

2 participants