Repository navigation
控制台保存筛选条件会 422:ViewFilterRuleSchema 拒绝 filter-builder 盖的 id,而 wire 成员的 .strip() 救不到嵌套块 #5114
Description
Activity
- added a commit that references this issue
on Aug 4, 2026 - addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2and removed
on Aug 4, 2026 xuyushun441-sys commented
on Aug 4, 2026 CollaboratorAuthorMore actions🔒 认领:PM 循环热修派发(p1——控制台过滤器保存今天在 main 上 422)
会话:session_01Ehu85kbvMcrNTUJjwxvLJ9
分支:claude/issue-5114-filter-rule-hotfix
Worktree:objectstack-issue-5114
域:domain:spec
文件面:packages/spec/src/ui/view.zod.ts(仅ViewFilterRuleSchema站点)+ pin 测试 + 台账 ui 行注记修法定调(照批 18 对
ListView.sort的既定处理,不重议):回退该站点为留开 + 三处落锚(JSDoc + pin + 台账),⛔ 不声明id(React list key 不上授权面);正解(递归 wire 变体)由 #5074 承接,落地后重关。验收:控制台过滤器保存 PUT 的真实 body(含crypto.randomUUID()的id,批 18 已给三路径探针)parse 通过 + dogfood 浏览器端到端确认(批 18 未跑真应用,本单必须跑)。⚠️ 与在飞批 18 PR #5070 同文件——等 #5070 落地后基于新 main 开工,派发词已写明。
Generated by Claude Code
xuyushun441-sys commented
on Aug 4, 2026 CollaboratorAuthorMore actions热修已开 draft PR #5154 —— 附一条 dogfood 挖出来的、必须先说的更正
本 issue 的处方是对的、必要的,但它不足以让「控制台保存筛选条件」通。 真浏览器端到端跑出来的结论:控制台把整个
FilterGroup对象存进filter,而 spec 声明的是ViewFilterRule[]—— 那是外层类型错,挡在id前面。已另案 #5159(p1,未指派,产出端 objectui)。端到端怎么跑的(本 issue 正文点名要补的那一步)
pnpm objectui:build出固定 pin(f5bc4c78)的真/_consolebundle → showcase 起在自有端口(--ui --seed-admin,自有 DB)→ Playwright 真实登录 →/_console/apps/showcase_app/showcase_task→ 点工具栏Filter→Add filter。抓到:PUT /api/v1/meta/view/showcase_task.default -> 422 {"code":"INVALID_METADATA","issues":[{"path":"","message":"Invalid input","code":"invalid_union"}]} body.filter = {"id":"root","logic":"and", "conditions":[{"id":"712135fb-…","field":"title","operator":"equals","value":""}]}注意
conditions[0]正是本 issue 说的那个形状 ——{id: <uuid>, field, operator, value}。两个缺陷叠在同一份 body 上。把这份真 body 三变体回放到两个真实运行的 server
变体 修前( origin/main)修后(#5154) ① 原样( FilterGroup对象)422 422 ← #5159,本 PR 修不到 ② 拆掉分组 → rule[],保留 UIid422 ACCEPTED ← 本 issue ③ 拆掉分组 → rule[],去掉idACCEPTED ACCEPTED(对照) ②③ 对照把变量锁死在
id上:本 PR 的效果就是把 ② 从 422 翻成通过,一分不多一分不少。外层不修,控制台仍然 422。给排期的三点
- fix(spec): 重开 ViewFilterRuleSchema —— 控制台盖的 UI 行 id 让保存筛选条件 422(#5114 热修) #5154 该合 —— 它是必要的一半,且 ② 那一格今天在
main上是真红的(任何发rule[] + id的产出端都吃 422,含已存库带id的行)。 - 控制台「筛选」把整个 FilterGroup 对象存进 view 的
filter(spec 声明的是ViewFilterRule[])—— 真浏览器实测 422,#5114 热修修不到 #5159 得跟着排,否则「控制台保存筛选条件」这条用户可见路径不会因为 fix(spec): 重开 ViewFilterRuleSchema —— 控制台盖的 UI 行 id 让保存筛选条件 422(#5114 热修) #5154 合并而恢复。按契约优先应在产出端折平(Studio 侧widgets.tsx:1727已有同款转换),⛔ 不要放宽ListViewSchema.filter。 - 控制台「筛选」把整个 FilterGroup 对象存进 view 的
filter(spec 声明的是ViewFilterRule[])—— 真浏览器实测 422,#5114 热修修不到 #5159 里附带记了一条:FilterGroup.logic: 'or'今天没有落盘表示,折平时会被静默降级成and—— 修 控制台「筛选」把整个 FilterGroup 对象存进 view 的filter(spec 声明的是ViewFilterRule[])—— 真浏览器实测 422,#5114 热修修不到 #5159 时需要一并判定。
⚠️ 另外,三条路径里第 3 条(扁平 overlay)的 422 消息是invalid_union/"Invalid input"(#5014 的压平)—— 报错里读不出id、也读不出filter。这既是本 bug 能在main上活着没人发现的原因,也是 #5159 一样没人发现的原因。
Generated by Claude Code
- fix(spec): 重开 ViewFilterRuleSchema —— 控制台盖的 UI 行 id 让保存筛选条件 422(#5114 热修) #5154 该合 —— 它是必要的一半,且 ② 那一格今天在
- added a commit that references this issue
on Aug 4, 2026 - added 4 commits that reference this issue
on Aug 4, 2026
发现于 #4001 批 18,现存于
main,不是本批引入的。批 18 在自己的ListView.sort上撞到同一机制并及时回退,顺手把这条已落地的实测出来。事实(实测,非阅读推断)
ViewFilterRuleSchema在批 18 之前的某一批已收紧为strictObject。objectui 的筛选构建器每行都盖一个id:objectui/packages/components/src/custom/filter-builder.tsx:228——id: crypto.randomUUID()在
origin/main上直接 parse:第三行是关键:那是扁平化 personalization overlay 的形状,也就是控制台 PUT 真正发出的 body。它命中
ViewMetadataSchema的扁平成员——那个成员是刻意.strip()的——然而依旧被拒。机制(这条比这个 bug 本身更值钱)
.strip()不递归,和.strict()一样不递归。ViewMetadataSchema用扁平成员上的.strip()来放行 Studio 的往返辅助键,view.zod.ts里那段块注释也是这么写的。但.strip()只重新打开顶层。任何在ListViewSchema内部被收紧的嵌套块,仍然经由那个成员被解析——所以嵌套块里一个控制台盖的键,无论成员姿态如何都会 422。这意味着这个战役在
view.zod.ts(以及任何有 wire overlay 成员的形状)上收紧嵌套块时,.strip()的救援是够不到的,而块注释读起来像是够得到。批 18 已就地把那段注释改成实测描述,并在view-strictness-batch18.test.ts里钉住了这条递归性质。影响
控制台改一次筛选条件并保存 →
saveMetaItem的safeParse失败 → 422。凡是经由 filter-builder 落盘的 view personalization 都受影响。randomUUID站点;端到端复现请按.claude/skills/dogfood-verification走一遍再动手。处方(倾向,不代劳裁定)
与 #5074 同族,别就地声明
id了事:它是 React 列表键,不是协议。声明它等于把 UI 造物放进可授权面,并且会教 AI 作者去生成一个 UUID —— 正是本战役要防的那类「声明即鼓励」。方向应当与 #5074 一致:授权形状收紧、wire 形状单独开放,且开放必须能递归到嵌套块(这正是当前
.strip()做不到的那一半)。可选做法之一是让 personalization 写路径在校验前剥掉这类 UI 造物(与stripReadDecorations同款范式,只是方向相反——那边剥的是读路径装饰键)。复核用最小脚本