fix(plugin-detail): 相关列表 Add 选择器兑现 add.picker.filter,作者限定的候选范围真的生效 (#3831) - #3908
Conversation
…3831) `record:related_list.add.picker.filter` 被 spec 声明为「Restrict which records the picker offers」,渲染器从未读过它:`RelatedList` 挂 `RecordPickerDialog` 时传 `objectName` / `title` / `displayField` / `columns` / `cellRenderer` / `fieldsMeta` / `multiple` / `onSelect` / `onSelectRecords`,没有任何 filter。作者 写下「只允许指派 active 的岗位」「只允许挂未过期的许可」,拿到的是 `picker.object` 的全部记录;选中后直接建链接行或改父,`os validate` / `os build` 全绿,运行时零诊断。 现在它按原样传给 `baseFilter` —— 不是 `lookupFilters`,后者会把条件渲染成用户可编辑 的筛选栏行,等于把作者的硬性限制降级成建议。 ## 为什么改到了 packages/fields `baseFilter` 声明为 `Record<string, any>`、以对象展开合并,这个形状服务依赖型 lookup 链(#2215)恰好正确,却根本装不下 spec 的 `ViewFilterRule[]`:TS 接受数组塞进该槽位 (数组满足 `any` 的字符串索引),展开把它压成 `{"0": rule, "1": rule}`,查询于是去过滤 名为 `0` / `1` 的列 —— 类型全绿、查询错误、无任何诊断。绕开它只剩「在 objectui 里再写 一份 spec-operator 词汇表」一条路,而这份词汇表已有两份(spec 的 `AST_OPERATOR_MAP`、 data-objectstack 的 `FILTER_OPERATOR_ALIASES`),#3948 就是两份的代价。 所以槽位按结构判别(`Array.isArray`)接受两种形状,判别子是精确的而非启发式的 —— AST 节点必是数组、规则必是普通对象,与 `toFilterNode` 同一谓词: - 记录形式保持键覆盖语义**逐字节不变**:级联父值必须**替换**同字段上过期的 `lookupFilters` 条目,而不是与之求交(`account = 'stale' AND account = 'a1'` 会返回 零行)。`LookupField.dependsOn.test.tsx` 一行未动且保持绿。 - 规则数组经 `mergeFilterNodes`(仓内唯一 filter 下沉口,与 plugin-list 的 `buildEffectiveFilter`、plugin-view 的 ObjectView 共用)下沉,19 个 operator 全部 无损,包括记录形式没有 `$op` 可用的 `before` / `after` / `is_empty` / `is_not_empty`。不新增第二份词汇表。 槽位类型同时收紧为 `unknown`,`useRecordQuery` 的 filter 类型与空判随之数组感知 (`Object.keys` 对数组返回下标,旧的记录专用判断对 AST 节点只是碰巧成立)。 ## 顺带 `RelatedListProps.add.picker.filter` 从 `any` 收紧为 spec 的 `ViewFilterRule[]`; `record:related_list.add` 的 input description 删掉 #3808 按 #3165 先例写入的 KNOWN GAP 句(gap 兑现之后才删),`recordRelatedListInputs.spec-parity.test.ts` 的 那枚钉子随之翻转 —— 现在它反过来在「gap 警告被放回」或「接线被回滚却仍宣称限制生效」 时报红。 反向验证跑了两向,方向先判后跑:摘掉 RelatedList 的直传 → 数组路钉红且点名 `$filter` undefined,无 filter 用例保持绿;把合并回退成旧的对象展开 → 两个文件共 5 枚钉红,且报的是 `{ '0': { field: 'is_active', … } }` 这一**损坏**形态而非缺失, 记录路钉与 #2215 六枚钉全绿。 Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
|
✅ 验收(PM,session 实物核验:头 六条件核对:① RelatedList 一行直传 + 转 ready 并挂 auto-merge。越界 #3909( Generated by Claude Code |
Fixes #3831
record:related_list.add.picker.filter被 spec 声明为「Restrict which records the picker offers」,渲染器从未读过它。RelatedList挂RecordPickerDialog时传objectName/title/displayField/columns/cellRenderer/fieldsMeta/multiple/onSelect/onSelectRecords,没有任何 filter。作者写下「只允许指派 active 的岗位」「只允许挂未过期的许可」,拿到的是picker.object的全部记录;选中后handleAddRecords直接建链接行或改父,os validate/os build全绿,运行时零诊断 —— 作者只能靠肉眼发现对话框里多出了不该出现的记录。现在它按原样传给
baseFilter,不是lookupFilters:后者会把条件渲染成用户可编辑的筛选栏行,等于把作者的硬性限制降级成建议。更正 issue 正文的一处误述
正文写「
picker.filter在 pin 版是宽类型」。实读@objectstack/spec@17.0.0-rc.5(src/ui/component.zod.ts:308):是严格的
ViewFilterRule[]——{field, operator, value?},operator 为 19 个 canonical 值的 enum,与 list 级filter、ListViewSchema.filter、ViewTab.filter同族。spec 侧无需任何收敛,但形状差距因此比正文预期的大,见下。为什么改动落到了 packages/fields
派发时的围栏是「只在 RelatedList 侧接线」。复核后发现在该围栏内无法无损接线,已带证据上报并由维护者裁决放宽(#3831 评论)。原因:
baseFilter声明为Record< string, any >、以对象展开合并。这个形状服务依赖型 lookup 链(#2215)恰好正确,却装不下 spec 的规则数组 —— TS 接受数组塞进该槽位(数组满足any的字符串索引,tsc --strict实测 exit 0),展开再把它压成{"0": rule, "1": rule},查询于是去过滤名为0/1的列:类型全绿、查询错误、无任何诊断。绕开它只剩一条路:在 objectui 里再写一份 spec-operator 词汇表。而这份词汇表已存在两份(spec 的
AST_OPERATOR_MAP、data-objectstack 的FILTER_OPERATOR_ALIASES),filter-converter.ts的文档块以「One lowering, one place」明确反对第三份,并引 #3948 说明两份的代价。实测记录方言确实装不下全部 operator:落地形状
baseFilter按结构判别(Array.isArray)接受两种形状。判别子是精确而非启发式的 —— AST 节点必是数组、规则必是普通对象,与toFilterNode同一谓词:lookupFilters条目,而不是与之求交。这一点承重 —— 朴素改成合取会问account = 'stale' AND account = 'a1',返回零行。LookupField.dependsOn.test.tsx一行未动且保持绿。mergeFilterNodes(仓内唯一 filter 下沉口,与 plugin-list 的buildEffectiveFilter、plugin-view 的 ObjectView 共用)下沉,19 个 operator 全部无损,包括记录形式没有$op可用的before/after/is_empty/is_not_empty。两者同时在场时,记录侧仍先合成再作为一个节点下沉,键覆盖优先级在合取中存活。槽位类型同时从
Record< string, any >收紧为unknown;useRecordQuery的 filter 类型与空判随之数组感知(Object.keys对数组返回下标,旧的记录专用判断对 AST 节点只是碰巧成立)。RelatedListProps.add.picker.filter从any收紧为 spec 的ViewFilterRule[]—— 宽类型正是错误形状的藏身处。KNOWN GAP 句与它的钉子
record:related_list.add的 input description 删掉 #3808 按 #3165 先例写入的 KNOWN GAP 句 —— 在 gap 兑现之后,不是之前。recordRelatedListInputs.spec-parity.test.ts原本钉着那句话(「fails the moment someone deletes the warning without doing it」),现已翻转方向:现在它在「gap 警告被放回」或「接线被回滚却仍宣称限制生效」时报红。反向验证(方向先判后跑)
两向都跑了,预判先写:
摘掉 RelatedList 的直传 —— 预判数组路钉红、无 filter 用例绿、fields 侧钉全绿。实测吻合,3 枚红全部点名
expected undefined to be defined。一处预判偏保守需如实说明:我原判「不降级成筛选栏行」那枚会以「什么都没产出」的空原因保持绿(#5046 式假绿),实测它也红了 —— 它的首行waitFor是对接线的真实前置条件,筛选栏断言只在限制真的生效后才执行。比预判更严,不是更松。把合并回退成旧的对象展开(保留直传)—— 预判两个文件的数组路钉红,且报损坏而非缺失;记录路钉与 #2215 全绿。实测吻合,5 枚红全部报:
LookupField.dependsOn.test.tsx六枚钉全绿 —— 这是判别式设计的承重预判。另有一枚「空规则数组不发$filter」在回退下仍绿(展开[]得{},空判成立),即旧代码下它是因错误的原因而绿;如实记录,不当作覆盖。验证
pnpm exec vitest run packages/plugin-detail/ packages/fields/→ 127 files / 1481 tests passed(含新增 10 枚钉;Cascading lookup (dependsOn) broken in forms: stays gated after parent is chosen; table picker bypasses the dependent filter #2215、fix(plugin-detail): 相关列表的 Add 门改问 add.picker.object,漏写 picker 不再把整块换成红卡 (#3838) #3894 的 addPickerGuard、addPickerLabelField 均绿)AssignedUsersSection/AccessExplainPanel(app-shell 用RecordPickerDialog)+apps/consoleregistry-inputs-spec-parity → 51 passedpnpm exec turbo run type-check --concurrency=2→ 78 successful, 78 totalpnpm check:control-bytes→ OK(3785 tracked text files)未做
QueryParams.$filter仍声明为Record< string, any >,而 plugin-list / plugin-view 的下沉口早已往里送 AST 数组 —— 类型漂移先于本单存在,收敛它会波及packages/types的公共面,故在此仅于单个赋值点就地转换并注明,另立观察单。#3898(baseFilterColumns命名)按分诊未顺手改。Generated by Claude Code