#4001 批 14 把 ActionParamSchema.options[] 收成了 strictObject({ label, value }) —— 它一直以来的契约,只是从「靠删除来执行」变成了说出口。收紧本身不删任何能用的东西(见下面的实测),但测量过程暴露出一个独立的能力问题,这里存档,不在批 14 里猜。
现状:同一个概念,仓里有三种拼法
| 位置 |
形状 |
posture |
data/field.zod.ts SelectOptionSchema |
label / value / color / default / visibleWhen |
strictObject(已关) |
ui/action.zod.ts ActionParamSchema.options[] |
label / value |
strictObject(批 14 关的) |
ui/bulk-action.zod.ts BulkActionParamSchema.options[] |
label / value + 开放 |
.passthrough()(#4909) |
objectui 那侧还有第四份:SelectOptionMetadata(packages/types/src/field-types.ts)—— label / value / color / icon / disabled / visibleWhen。注意 icon / disabled 在 spec 里任何一处都没有声明,只活在 objectui 自己的接口里。
实测(2026-08-03)
1. action 的门今天就在剥。 一个 action 是注册 metadata type,所以作者写的 param 是经 getMetadataTypeSchema('action') 到达 objectui 的。拿真实 payload 过这道门:
options: [{ label: 'Overload', value: 'overload',
color: 'red', visibleWhen: "record.status == 'open'", icon: 'x', disabled: false }]
parse 成功,出来的是:
{"label":"Overload","value":"overload"}
四个键在任何 renderer 看到它们之前就没了,而且报告成功。所以批 14 的收紧没有拿走任何在工作的东西,只是把静默变成了响亮。
2. 但渲染侧其实读得懂其中两个。 dialog 走 paramToField -> field.options -> getLazyFieldWidget('select') -> SelectField,而 SelectField 通过 useCascadingOptions -> resolveCascadingOptions 解析逐选项 visibleWhen(ADR-0058 / #2284);color 则被 grid/detail 的展示渲染器读(packages/fields/src/index.tsx 的 option?.color)。也就是说:唯一挡在作者和一个能工作的逐选项门控之间的,就是 spec 的这道剥离。
3. 字段回退那条路也不通。 直觉上的处方「把 param 写成 field-backed,继承字段自己的选项」对这两个键是假的 —— resolveActionParams 的 normaliseOptions 把每个条目重建成 { label, value }。两个方向都丢。批 14 的 guidance 因此刻意没有给出这条建议(账本 finding 18:错误里的文案是行为,自信而错的处方比没有更糟)。
要判的事
action param 的选项列表该不该讲 SelectOptionSchema 已经声明、对象表单已经兑现的那套逐选项词汇?
- A. 维持
{ label, value }(批 14 的落点)。契约最小、最诚实。代价:一个已经接好线的门控能力被合约挡着,而且这个不对称本身就是 AI 作者会踩的坑。
- B. 让它复用
SelectOptionSchema(.extend() 覆写 label 为 I18nLabelSchema、value 为 z.string(),.extend() 会继承 strict)。一套词汇,N 个面。代价:normaliseOptions 必须同步停止重建条目,否则声明了却不兑现 —— 又是一次 declared != enforced;且要判 color / default 在这条路上是否真被读,否则是声明惰性键。
- C. B 再加上把
icon / disabled 补进 SelectOptionSchema,让 spec 和 objectui 的选项词汇彻底对齐。代价最大,但这是唯一让「四份拼法」收敛成一份的选项。
个人倾向 B,并且必须和 normaliseOptions 的修改同一个 PR 落地 —— 单独放开 spec 会造出一个声明了、门也过了、渲染器却收不到的键,正好是 ADR-0078 要消灭的那种。但这跨 framework + objectui 两个仓,且要先做一次 color / default 的逐键 liveness 审计,不适合塞进一个收紧批次。
另注:批 14 之所以没跟 bulk-action 走 .passthrough(),是因为 #4909 的两条理由在这里都不成立 —— 那边的 def 是「left as-authored」逐字到达 grid(没有这道 spec 门),且 objectui 的 BulkActionParam 声明了显式的 [key: string]: unknown 兜底;这边的目标类型 SelectOptionMetadata 是封闭接口。目标词汇封闭,正是「声明」胜过「容忍」的场合。
发现于 #4001 批 14,未指派。
#4001 批 14 把
ActionParamSchema.options[]收成了strictObject({ label, value })—— 它一直以来的契约,只是从「靠删除来执行」变成了说出口。收紧本身不删任何能用的东西(见下面的实测),但测量过程暴露出一个独立的能力问题,这里存档,不在批 14 里猜。现状:同一个概念,仓里有三种拼法
data/field.zod.tsSelectOptionSchemalabel/value/color/default/visibleWhenstrictObject(已关)ui/action.zod.tsActionParamSchema.options[]label/valuestrictObject(批 14 关的)ui/bulk-action.zod.tsBulkActionParamSchema.options[]label/value+ 开放.passthrough()(#4909)objectui 那侧还有第四份:
SelectOptionMetadata(packages/types/src/field-types.ts)——label/value/color/icon/disabled/visibleWhen。注意icon/disabled在 spec 里任何一处都没有声明,只活在 objectui 自己的接口里。实测(2026-08-03)
1. action 的门今天就在剥。 一个 action 是注册 metadata type,所以作者写的 param 是经
getMetadataTypeSchema('action')到达 objectui 的。拿真实 payload 过这道门:parse 成功,出来的是:
{"label":"Overload","value":"overload"}四个键在任何 renderer 看到它们之前就没了,而且报告成功。所以批 14 的收紧没有拿走任何在工作的东西,只是把静默变成了响亮。
2. 但渲染侧其实读得懂其中两个。 dialog 走
paramToField->field.options->getLazyFieldWidget('select')->SelectField,而SelectField通过useCascadingOptions->resolveCascadingOptions解析逐选项visibleWhen(ADR-0058 / #2284);color则被 grid/detail 的展示渲染器读(packages/fields/src/index.tsx的option?.color)。也就是说:唯一挡在作者和一个能工作的逐选项门控之间的,就是 spec 的这道剥离。3. 字段回退那条路也不通。 直觉上的处方「把 param 写成 field-backed,继承字段自己的选项」对这两个键是假的 ——
resolveActionParams的normaliseOptions把每个条目重建成{ label, value }。两个方向都丢。批 14 的 guidance 因此刻意没有给出这条建议(账本 finding 18:错误里的文案是行为,自信而错的处方比没有更糟)。要判的事
action param 的选项列表该不该讲
SelectOptionSchema已经声明、对象表单已经兑现的那套逐选项词汇?{ label, value }(批 14 的落点)。契约最小、最诚实。代价:一个已经接好线的门控能力被合约挡着,而且这个不对称本身就是 AI 作者会踩的坑。SelectOptionSchema(.extend()覆写label为I18nLabelSchema、value为z.string(),.extend()会继承 strict)。一套词汇,N 个面。代价:normaliseOptions必须同步停止重建条目,否则声明了却不兑现 —— 又是一次 declared != enforced;且要判color/default在这条路上是否真被读,否则是声明惰性键。icon/disabled补进SelectOptionSchema,让 spec 和 objectui 的选项词汇彻底对齐。代价最大,但这是唯一让「四份拼法」收敛成一份的选项。个人倾向 B,并且必须和
normaliseOptions的修改同一个 PR 落地 —— 单独放开 spec 会造出一个声明了、门也过了、渲染器却收不到的键,正好是 ADR-0078 要消灭的那种。但这跨 framework + objectui 两个仓,且要先做一次color/default的逐键 liveness 审计,不适合塞进一个收紧批次。另注:批 14 之所以没跟
bulk-action走.passthrough(),是因为 #4909 的两条理由在这里都不成立 —— 那边的 def 是「left as-authored」逐字到达 grid(没有这道 spec 门),且 objectui 的BulkActionParam声明了显式的[key: string]: unknown兜底;这边的目标类型SelectOptionMetadata是封闭接口。目标词汇封闭,正是「声明」胜过「容忍」的场合。发现于 #4001 批 14,未指派。