feat(spec): action param 的 options[] 声明逐选项 visibleWhen (#5016) - #6235
Conversation
批 14 把 ActionParamSchema.options[] 关成 { label, value } 并把能力问题
留给 #5016。逐键量过消费面后,只开 visibleWhen 一个键 —— 它是唯一一个
在 action param 这条路上真有读者的:内联参数的 options 逐字下沉
(resolveActionParam 内联分支 → ActionParamDialog → paramToField),四个
选项控件都经 useCascadingOptions → resolveCascadingOptions 按它过滤,
且 evalFieldPredicate 接受 ExpressionInputSchema 产出的 { dialect, source }
信封。此前挡在作者和这个能工作的门控之间的,只有 spec 这道门。
color / default 继续拒绝:前者只被"已存值"的展示渲染器读(网格单元格 /
详情徽章),对话框只拿列表建输入控件;后者是层级写错,参数的默认值走
高一层的 defaultValue。icon / disabled 也未升级进 SelectOptionSchema
(#5016 的 C 选项)—— 重测确认 objectui 无任何读者,四个控件里的
disabled 全是字段级 props.disabled。四个键各自保留指路的 guidance。
visibleWhen 的 guidance 条目必须移除(声明键到不了 unrecognized_keys
这条路,留着就是 alias-integrity 判定的死条目),并把 SelectOptionSchema
的两个拼法 visible / showWhen 作为 alias 接过来。
新测试全部走真实的门(getMetadataTypeSchema('action') 与
ObjectSchema.actions[]),并断言键在 parse 输出里"活着到达",而不只是
parse 成功 —— 只断言 success 在批 14 之前那个静默剥离的世界里同样会绿。
Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…5016) expression-conformance 的 ratchet 会重新扫描 packages/spec/src 下每个 ExpressionInputSchema 声明,新增的 ui/action.zod.ts:visibleWhen 没有归类, CI 的 Dogfood Regression Gate (3/3) 因此报 "UNCLASSIFIED surface"。本仓 scoped 的 spec / runtime 测试看不到这一条 —— 它只在 dogfood 分片里跑。 单开一行而不是并进 cel-ui:cel-ui 那批是 SchemaRenderer 藏元素,这一条是 在字段控件内部收窄一个选项列表,evaluator 不同。tier 取 fail-soft-log, 与 cel-field-rule 一致 —— evalFieldPredicate 的 fallback 是 true,谓词坏掉 时选项保持可选,而不是无声删掉一个作者没打算拿走的选择。 Co-Authored-By: Claude <noreply@anthropic.com>
|
第二个提交( 首轮 CI 的
修法是给
复推后 24 个 check 全部完成: Generated by Claude Code |
Fixes #5016
执行 2026-08-06 维护者裁 B(2026-08-07 窗口改判进 v17)。前置的逐键 liveness 审计改变了落点:裁决把审计设为前置条件,审计跑完的结论是「不是整套复用
SelectOptionSchema,而是只开visibleWhen一个键」。下面第 1 节是审计证据,第 2 节是与硬约束有关的一处前提证伪,两处都请复核。1. 逐键 liveness 审计(objectui 只读,未改一行 objectui 代码)
问的不是「这个键在 objectui 存不存在」,而是「一个 action param 的选项列表能不能走到它的读者」。对话框只拿选项列表建一个输入控件,提交完就把列表丢掉 —— 它没有「已存值展示」这个 surface。
visibleWhenSelectOptionSchemaSelectField/MultiSelectField/RadioField/CheckboxesField四个控件均调useCascadingOptions→core/src/evaluator/optionRules.ts:101o?.visibleWhen;FieldRulePredicate = string | { dialect?, source }(fieldRules.ts:33)正好收ExpressionInputSchema的信封colorSelectOptionSchemafields/src/index.tsx:1118,1133的SelectCellRenderer(网格/详情徽章)、metadata-viewer.tsx:152的状态机视图。四个输入控件无一读它 ——MultiSelectField.tsx:9在本地Option上声明了color?却从不渲染defaultSelectOptionSchemametadata-viewer.tsx:153options.find((o) =o.default),读的是对象字段的选项。对话框参数的默认值走参数自己的defaultValue,高一层iconicon命中只有WidgetRegistry.ts:173的manifest.icon与schema-builder.ts:348,均无关disableddisabled都是字段级props.disabled(SelectField.tsx:140、MultiSelectField.tsx:125、RadioField.tsx:92、CheckboxesField.tsx:126),没有逐选项的所以
icon/disabled按裁决「证据不足则不升」→ 未升级 C;default按派单「实测无消费方则不进 extend 面」→ 未纳入。color的审计结论与派单的预设不同:派单据 issue 正文认为它活("packages/fields 的option?.color"),但那个读者是对象字段已存值的展示渲染器,action param 的选项列表到不了它。按同一条「不声明惰性键」的理由一并排除 —— 这是本 PR 唯一超出派单明文授权的判断,请复核(见文末 open question)。实现上没有用
.extend()裁决写的是
SelectOptionSchema.extend()。实测这条路会丢掉整套错误面:strictObject的 error map 闭包在基座的{ surface, history, aliases, guidance }上,knownKeys也从基座的shape读(shared/strict-object.ts),而.extend()产生的 clone 不带新的 declaration(该文件 docblock 自述「a marker on the instance does not survive the clone.extend()make」)。照搬会把批 14 那套icon/disabled/default的指路文案、以及optionValue/optionLabel/displayName三条 alias 全部替换成SelectOptionSchema的表,surface 名也会变成 "this select option"。因此保留本 surface 自己的strictObject,只把visibleWhen的定义(同一个ExpressionInputSchema)接进来 —— 一套词汇的目标达成,错误面不倒退。顺带两处必须同步:
visibleWhen的guidance条目 ——guidance只从unrecognized_keys这条路被查,声明键永远到不了,留着就是shared/alias-integrity.test.ts判定的死条目。visible/showWhen两条 alias —— 目标键现在这个 shape 接受了,符合 finding 12「never suggest a key the schema cannot accept」。2. 前提证伪:硬约束的「同一个 PR」在这条路上不成立
裁决的硬约束是「必须与
resolveActionParams的normaliseOptions停止重建条目同一个 PR落地,否则会造出『声明了、门过了、渲染器收不到』的键」。实测两件事:(a)
normaliseOptions不在本仓。 它只存在于 objectuipackages/app-shell/src/utils/resolveActionParams.ts:228;本仓packages/spec/src/ui/action.zod.ts的两处命中是注释引用。一个 PR 跨不了两个仓库。(b) 更重要的是,本次开放的这条路根本不经过它。
resolveActionParam里normaliseOptions只作用在param.options ?? normaliseOptions(field.options, …)的右半 —— 即从字段继承的列表。作者显式写在 param 上的options走内联分支,options: param.options逐字下沉,随后ActionParamDialog.tsx:207逐条{ ...o, label: pickLocalized(...) }(spread,保留额外键)、paramToField.tsoptions: param.options(原样)。所以 spec 一放开,作者写的visibleWhen今天就到得了渲染器,零 objectui 改动。硬约束想防的危害在本次落点上不会发生。
normaliseOptions的丢弃是真的,但它丢的是字段自己早已声明的逐选项词汇(今天 main 上每一个 field-backed select 参数都在丢),既早于本次改动也不受其影响 —— 那是一条独立的 objectui 缺陷,已另行立单。本 PR 的拒绝文案因此仍然刻意不开「把参数改成 field-backed 去继承」这张药方。3. 验证
visibleWhen从 shape 上摘掉后 —— (i) 三条端到端用例转红,报unrecognized_keys: ["visibleWhen"](红,因为 shape 是 strict,是「响亮拒绝」而非「静默剥离」);(ii)shared/alias-integrity.test.ts的 "every alias target is a key the schema really accepts" 同时转红,2 条 —— 新接的两条 alias 指向了 shape 不接受的键(finding 12 通道)。恢复后全绿。getMetadataTypeSchema('action')(MetadataManager.validate/GET /api/v1/meta/ Studio 表单用的那道门)与ObjectSchema.actions[],且断言键在 parse 输出里活着到达({ dialect: 'cel', source: … }),不是只断言success—— 只断言 success 在批 14 之前那个静默剥离的世界里同样会绿。pnpm --filter @objectstack/spec test:330 files / 8426 tests 全绿(含新增 7 条)。pnpm --filter @objectstack/runtime test:105 files / 1506 tests 全绿;typecheck绿(先--filter '@objectstack/runtime^...' build起依赖)。check:generated10/10 绿(content/docs/references/ui/action.mdx由gen:docs重生成,未手改;authorable-surface/ json-schema 零变化 —— 该产物记的是顶层 authorable 属性,不含逐选项键)。check:liveness/check:empty-state/check:skill-examples/check:variant-docs/check:exported-any/check:dual-source-exports。pnpm lint绿;check:nul-bytes(5921 tracked,另做grep -naP自扫)/check:doc-authoring/check:adr-anchors/check:spec-parsed-alias/check:docs-audit-scope/check:release-notes全绿。4. 边界
⛔ 未动 objectui 任何代码(只读审计)。⛔
bulk-action.zod.ts的.passthrough()原样保留 —— #4909 的两条理由在本条路上都不成立,正文已辨析。⛔ 未碰content/docs/releases/。changeset 定@objectstack/spec: major(随 v17 列车),FROM→TO 写明了行为激活面:16.x 里作者写的visibleWhen被静默剥掉、选项永远可选,17.0.0 起键保留并生效、选项集会变窄。5. 与在飞单的关系
ActionParam因required: z.boolean().optional().default(false)早已落在「形状差」桶里,本次新增的visibleWhen(ExpressionInputSchema是转换 union)不改变它的分桶;check:spec-parsed-alias在本改动上绿(1384 / 719 / 665,与基线同)。给 ADR-0122 第二期:major 窗口翻转裸名为解析态语义 + XInput 去留 #6083 的知会点:若本 PR 先落,ActionParam的options[].visibleWhen在z.input侧是string | Expression、z.infer侧是Expression,翻转时按这条读;若 ADR-0122 第二期:major 窗口翻转裸名为解析态语义 + XInput 去留 #6083 先落,本分支 merge 吸收其翻转语义后需复核action.test.ts里result.data的类型引用(断言用的是运行时值,不依赖裸名,预期零改)。enforceActionParams/valueSchemaFor这套真名。composeStacks的i18n仍是 last-wins —— #5005 裁决否掉的那个形状,只剩这一个键还在用 #5051(composeStacks的i18nlast-wins,packages/spec/src/stack.zod.ts)零文件交集。Open question(需维护者一句话确认)
color是否要一并开放?裁决说的是「复用SelectOptionSchema」,审计测出它在这条路上无读者,我按「不声明惰性键」排除了。方向不对称:现在补开是加性的、一行的;先开了再收窄是破坏性的。所以本 PR 取可回退的那一侧,等一句确认。Generated by Claude Code