fix(spec): 重开 ViewFilterRuleSchema —— 控制台盖的 UI 行 id 让保存筛选条件 422(#5114 热修) - #5154
Merged
Conversation
…id (#5114) Saving a filter from the console 422'd on `main`. An earlier strictness wave closed `ViewFilterRuleSchema` with `strictObject`, and objectui's filter builder stamps `id: crypto.randomUUID()` on every row it writes (`components/src/custom/filter-builder.tsx:228`, re-stamped on read-back at `plugin-view/src/config/view-config-utils.ts:146`/`:160`). `saveMetaItem` validates the PUT body and then persists the AUTHORED body verbatim, so the `id` is on the wire — and the closed shape rejected it. Measured on all three paths before the change, on `origin/main`: ViewFilterRuleSchema -> unrecognized_keys @ [] `id` ListViewSchema.filter -> unrecognized_keys @ ["filter",0] `id` ViewMetadataSchema (overlay) -> invalid_union @ [] "Invalid input" The third is the body the console actually PUTs, and its message is the #5014 flattening: the key that caused the rejection is not in what the author sees, which is how this sat on `main` unnoticed. The mechanism governs every nested block in this file and is the opposite of what the union's comment implies: `.strip()` does NOT recurse, any more than `.strict()` does. `ViewMetadataSchema` re-opens its flattened members so Studio's round-trip aux keys ride along — but that re-opens the TOP level only, so a nested block closed here is still reached through that member and a console-stamped key inside it 422s regardless of the member's posture. Same finding 批 18 reached one block over on `ListView.sort` (#5070). `id` is deliberately NOT declared. It is a React list key, not protocol: declaring it would put a UI artifact on the authorable surface and tell an AI author to generate a UUID for a filter rule — a `??` fallback wearing a schema. Reopening drops it from `parsed.data` instead, and `saveMetaItem` stores the original body, so the renderer still reads it. The real close is #5074's authoring/wire split applied to this block, whose scope addendum names this site. Verified in both directions: re-close the schema and 7 assertions in the new pin file go red, while its two mechanism CONTROLS (top-level aux key rides, nested `emptyState` still rejects) stay green either way. Ledger gate proven red on both the row (6→5) and the header (76→75). Ledger: ui/ 75 -> 76 strip, authorable 35 -> 36, recomputed from surviving rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ter-rule-hotfix # Conflicts: # docs/audits/2026-07-unknown-key-strictness-ledger.md
…5114) Browser verification against the real vendored console found a second, independent defect stacked on the same request: the list toolbar persists the filter builder's whole `FilterGroup` object into `filter`, where the spec declares `ViewFilterRule[]`. That type mismatch rejects before the `id` is ever reached, so reopening `ViewFilterRuleSchema` does not by itself make "save a filter from the console" work — it is necessary, not sufficient. Replaying the captured console body against two running servers separates them: variant pre-fix post-fix as sent (FilterGroup object) 422 422 <- producer bug unwrapped to rule[] WITH the UI ids 422 ACCEPTED <- this change unwrapped to rule[] without ids ACCEPTED ACCEPTED <- control The changeset headline said the console save no longer 422s. It does. Release notes are the one place that claim would have reached users unqualified, so it now states what actually changed and names the remaining blocker. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5114
p1 热修:
ViewFilterRuleSchema被更早的一批收紧成strictObject,而 objectui 的筛选构建器给每一行盖id: crypto.randomUUID()(React 列表键),saveMetaItem校验后原样存原始 body,所以那个id就在 wire 上 —— 闭合的形状把它变成 422。处理照 #4001 批 18 对
ListView.sort的既定做法(#5070),不重议:回退该站点为留开 + 三处落锚,⛔ 不声明id,正解由 #5074 承接。分支基于 #5070 落地后的origin/main,已 merge 到bcfebb0。改了什么(4 文件)
packages/spec/src/ui/view.zod.tsViewFilterRuleSchema:strictObject(…)→z.object(…),只此一处 + 其 JSDocpackages/spec/src/ui/view-filter-rule-wire-id.test.tsdocs/audits/2026-07-unknown-key-strictness-ledger.mdui/行 + 表头 + 算术散文,从存活行重算.changeset/view-filter-rule-console-id-hotfix.mdpatch⛔ 为什么不顺手声明
id它是 React 列表键,不是协议。声明它 = 把 UI 造物放上可授权面,并教 AI 作者给筛选行生成一个 UUID —— 这是
??兜底换了身 schema 外衣。批 18 Q1 的两轴否决在案、PM 已批。重开后id是被丢弃(不进parsed.data),不是被声明;而saveMetaItem存原始 body,所以渲染器照样读得到它 —— 两条都钉在测试里。机理(比这个站点值钱,已写进 JSDoc)
.strip()不递归,和.strict()一样不递归。ViewMetadataSchema靠扁平成员上的.strip()放行 Studio 往返辅助键,但那只重开顶层;嵌套块仍经由该成员被解析,所以嵌套块里一个控制台盖的键无论成员姿态如何都会 422。这正是本 bug 的成因,也是批 18 在ListView.sort上撞到的同一条。验证
1. 三路径直接 parse —— 先证红,再证绿
修前(分支起点
origin/main):修后:
Invalid input是 #5014 实测的那个压平:报错里连id两个字都没有。这就是这条 bug 能在main上活着没人发现的原因 —— 错误信息读不出病因。2. dogfood:真浏览器 + 真
/_consolebundlepnpm objectui:build出固定 pin(f5bc4c78)的真控制台 → showcase 起在自有端口(--ui --seed-admin,自有 DB)→ Playwright 真实登录 →/_console/apps/showcase_app/showcase_task→ 点工具栏Filter→Add filter。抓到的真实 PUT:conditions[0]正是本 issue 那个形状。把这份真 body 三变体回放到两个真实运行的 server(只改filter):FilterGroup对象)invalid_unioninvalid_union← #5159rule[],保留 UI 的idrule[],去掉id读法:两个缺陷叠在同一份 body 上。外层类型错(#5159,产出端)挡在前面;拆开之后,下一道就是本 PR 修的
id。②③ 的对照把变量锁死在id上 —— 本 PR 的效果就是把 ② 从 422 翻成通过,一分不多一分不少。另有一组不经浏览器、直打
PUT /api/v1/meta/view/:name的对照(扁平 personalization overlay,filter 行带crypto.randomUUID()):#5114 正文里「未跑真实 app 端到端」的缺口到此补上 —— 并且正是这一跑,把 #5159 挖了出来。
3. 仪器纪律 —— 断言两向都证过红
ViewFilterRuleSchemaid被丢弃 + 顶层 aux)check:strictness-ledgerEXIT=1check:strictness-ledgerEXIT=1pin 文件里那两条机理对照(顶层 aux 键随行 / 嵌套已关
emptyState仍拒)刻意跑在不带 filter 的 overlay 上 —— 所以它们在两种姿态下都绿。断言红了、对照没红,那个落差本身就是「.strip()不递归」这条发现。4. 门与套件(合并
origin/main之后重跑)台账合并 —— 第 11 例「两边都对、合并都错」,方向和前十例相反
与 批 17(#5068,把
component.zod.ts29 站点整行改判no gate)如期冲突。按既定纪律:取 main 整段 + 重贴本单编辑 + 逐条断言对方条目存活 + 从存活行重算。component.zod.ts行取 main(批 17 的no gate),view.zod.ts行取本单(批 18 文字的超集 + 控制台保存筛选条件会 422:ViewFilterRuleSchema拒绝 filter-builder 盖的id,而 wire 成员的.strip()救不到嵌套块 #5114)。PageComponent.properties是开放 record,ComponentPropsMap的 29 个站点从不被 parse(#4001 批 17 的 no gate 判定) #5068、批 18 的ListView.sort回退段、widgetno-door 行、i18n5/6 split、ADR-0049 enforce-or-remove: ui/widget.zod.ts 全文件 + ui/i18n.zod.ts 五个形状实测无门(#4001 批 16 改判) #5055/#4001 战役的门测量 BFS 会误报「可达」:zod.describe()共享 def 对象,任意单属性 bridge 把无关形状连起来 #5056、「批 16 is the eighth instance」—— 全在。ui/76 strip of 198;29+6+9+2+7+5+4+4+4+3+1+1+1 = 76,其中 69 属两类 no-parse(38no door+ 31no gate),可授权半边 =view6 +app1 = 7 of 76。前十例都是关站点把这行推低;本单是开一个站点把它推高 —— 我的分支算
36 of 76(批 17 未落地的树),main 算6 of 75(本单未落地的树),合并是7 of 76,两边都没写过。机理已记进散文。⛔
content/docs/releases/零触碰;objectui 零改动(只读);os-regen 四步走完(merge → 取 main 生成物 → 整体重生成 → 断言兄弟条目存活),8 件生成物全绿。顺带发现(⛔ 未在本 PR 修)
filter(spec 声明的是ViewFilterRule[])—— 真浏览器实测 422,#5114 热修修不到 #5159(p1,已立案,未指派)—— 控制台运行时工具栏把整个FilterGroup对象存进filter,spec 声明的是ViewFilterRule[]。产出端(objectui)缺陷,按契约优先应在产出端折平(Studio 侧widgets.tsx:1727已有同款转换),⛔ 不要放宽ListViewSchema.filter。本 PR 合并后控制台保存筛选条件仍会 422,直到 控制台「筛选」把整个 FilterGroup 对象存进 view 的filter(spec 声明的是ViewFilterRule[])—— 真浏览器实测 422,#5114 热修修不到 #5159 落地。filter(spec 声明的是ViewFilterRule[])—— 真浏览器实测 422,#5114 热修修不到 #5159):FilterGroup.logic: 'or'今天没有落盘表示,折平时会被静默降级成and,修 控制台「筛选」把整个 FilterGroup 对象存进 view 的filter(spec 声明的是ViewFilterRule[])—— 真浏览器实测 422,#5114 热修修不到 #5159 时需一并判定。🤖 Generated with Claude Code
https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9