#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 成功,出来的是: ```json {"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,未指派。
Activity
feat(spec)!: tighten unknown keys on nine ui/ shapes, reclassify two …
os-zhuang commentedon Aug 6, 2026
分诊(spec 车道欠账代扫,维护者 2026-08-05 指令):挂
needs-user-decision。action param 的options[]是否复用SelectOptionSchema逐选项词汇(A 维持最小 / B 复用且必须与normaliseOptions同 PR / C 再补icon/disabled收敛四份拼法)——契约词汇统一问题,填单人倾向 B 并给出了「必须同 PR 否则 declared≠enforced」的硬约束,成立。target:v18在身不急;裁决 B/C 时须先做color/default逐键 liveness 审计再拆单(跨 framework+objectui 两仓)。不构成认领。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
os-zhuang commentedon Aug 6, 2026
维护者裁决(2026-08-06,经本会话转达):取 B(
options[]复用SelectOptionSchema),硬约束照正文:必须与resolveActionParams的normaliseOptions停止重建条目同一个 PR 落地——单独放开 spec 会造出「声明了、门过了、渲染器收不到」的键。前置:先做color/default的逐键 liveness 审计;C(把icon/disabled收进 spec)仅当审计发现 objectui 真读它们才升级,否则不做。bulk-action的.passthrough()特例维持(#4909 理由不适用于本单,正文已辨析)。执行窗口 = 协议 18(跨 framework+objectui),转pm:on-hold:重启条件 = v18 列车开启时由 spec 座位回捡并按跨仓拆分派发。会话:session_01N3uGFF8teXbpgtbEJ1aYXuGenerated by Claude Code
fix(spec): withdraw the `icon` input from the object.form options rep…
test(spec): the first ui/ file group's test titles state each cited d…