复现(实测于 origin/main @ 2f881a90,objectui#8071 的成员 pin 工作中发现)
object-grid 的 bulkActions 与 bulkActionDefs 是同一个 affordance 的两套词汇:
bulkActions 成员 = 裸动作名字符串,resolveBulkActions 拿它去 objectDef.actions 解析并提升成 def;
bulkActionDefs 成员 = 完整 BulkActionDef 对象,原样使用。
把其中一个的词汇写进另一个,两边都没有任何东西会拒绝:注册声明两个都是 type: 'array' 且无 of,
spec 的 ComponentPropsMap['object-grid'] 两行都是 z.array(z.unknown()),JSON 视图上 tsc 也看不见。
方向一(bulkActions: [{ name: 'approve' }])静默丢弃 —— resolveBulkActions 的
if (typeof name !== 'string' || name === '') continue; 跳过它,无任何诊断。这是可以接受的失败形态。
方向二(bulkActionDefs: ['approve'])不是静默,是崩溃:
TypeError: Cannot read properties of undefined (reading 'replace')
❯ formatActionLabel packages/plugin-grid/src/components/RowActionMenu.tsx:43:17
❯ BulkActionButton packages/plugin-grid/src/components/BulkActionBar.tsx:64:21
Array.isArray(schema.bulkActionDefs) 为真 ⇒ 字符串原样进入 rawAuthored ⇒
BulkActionBar 用它渲染 BulkActionButton,def.name 是 undefined,
def.label ?? formatActionLabel(def.name) 在 render 中抛出。作者第一次勾选多行时整条选择条就没了
(React 还会先抱怨一次 Each child in a list should have a unique "key" prop,因为 key={def.name} 也是 undefined)。
已有的失败探针
objectui#8071 的成员 pin 把这条行为按当前形态钉住了,以免它被误读成方向一的静默:
packages/plugin-grid/src/__tests__/bulkActionMembers-8071.test.tsx 第 5 行断言
renderAndSelectAll({ bulkActionDefs: ['approve'] }) 必须 reject 并匹配上面的 TypeError。
⚠️ 那一条是记录现状,不是契约:本卡的修复落地时它会转红,必须在同一笔里改写成修复后的行为。
为什么值得单开一卡
BulkActionBar 渲染的是作者可写的元数据,不是内部数据结构。一个 spec 与注册面都接受的成员形状
不应该让渲染抛错 —— 至少要退化成"跳过该成员"(与方向一对齐),更好的是留一条具名诊断。
两个方向的处置不一致本身就是缺陷:同一类作者错误,一边静默一边崩。
Refs: objectui#8071(发现处)· objectui#8068(成员 pin 判据)· objectui#3002 / objectui#3139(两套词汇的来源)
复现(实测于
origin/main@2f881a90,objectui#8071 的成员 pin 工作中发现)object-grid的bulkActions与bulkActionDefs是同一个 affordance 的两套词汇:bulkActions成员 = 裸动作名字符串,resolveBulkActions拿它去objectDef.actions解析并提升成 def;bulkActionDefs成员 = 完整BulkActionDef对象,原样使用。把其中一个的词汇写进另一个,两边都没有任何东西会拒绝:注册声明两个都是
type: 'array'且无of,spec 的
ComponentPropsMap['object-grid']两行都是z.array(z.unknown()),JSON 视图上tsc也看不见。方向一(
bulkActions: [{ name: 'approve' }])静默丢弃 ——resolveBulkActions的if (typeof name !== 'string' || name === '') continue;跳过它,无任何诊断。这是可以接受的失败形态。方向二(
bulkActionDefs: ['approve'])不是静默,是崩溃:Array.isArray(schema.bulkActionDefs)为真 ⇒ 字符串原样进入rawAuthored⇒BulkActionBar用它渲染BulkActionButton,def.name是undefined,def.label ?? formatActionLabel(def.name)在 render 中抛出。作者第一次勾选多行时整条选择条就没了(React 还会先抱怨一次
Each child in a list should have a unique "key" prop,因为key={def.name}也是undefined)。已有的失败探针
objectui#8071 的成员 pin 把这条行为按当前形态钉住了,以免它被误读成方向一的静默:
packages/plugin-grid/src/__tests__/bulkActionMembers-8071.test.tsx第 5 行断言renderAndSelectAll({ bulkActionDefs: ['approve'] })必须 reject 并匹配上面的TypeError。为什么值得单开一卡
BulkActionBar渲染的是作者可写的元数据,不是内部数据结构。一个 spec 与注册面都接受的成员形状不应该让渲染抛错 —— 至少要退化成"跳过该成员"(与方向一对齐),更好的是留一条具名诊断。
两个方向的处置不一致本身就是缺陷:同一类作者错误,一边静默一边崩。
Refs: objectui#8071(发现处)· objectui#8068(成员 pin 判据)· objectui#3002 / objectui#3139(两套词汇的来源)