fix(plugin-list): 删掉 UserFilters 的私有算子表,tab preset 走 spec 单一出口 (#3470) - #3483
Merged
Merged
Conversation
… presets through the spec (#3470) `UserFilters.specOperatorToAst` was the second hand-kept operator map in this package, and it had drifted: it lowered `not_in` — the spec's OWN canonical spelling — and the legacy `nin` to the spaced `'not in'`, which appears in no spec vocabulary. `isFilterAST` refuses it, so a `ViewTab.filter` preset written the canonical way produced an empty list plus 400 the moment its tab was clicked. Measured against a real backend (published @objectstack/*@17.0.0-rc.2 + app-showcase, showcase_task): $filter=[["status","not in",["done"]]] -> 400 {"code":"INVALID_FILTER"} $filter=[["status","not_in",["done"]]] -> 200, 8 rows $filter=[["status","!=","done"]] -> 200, the same 8 rows The table is deleted rather than repaired: all 19 VIEW_FILTER_OPERATORS are already members of VALID_AST_OPERATORS, so the rule -> AST lowering is purely structural and needs no translation. Only the legacy spellings stored metadata still carries need folding, and the spec's own `normalizeFilterOperator` does that — the same single exit the write side (viewFilterFold) and core's saved-view fold (#3431) use, so the directions cannot drift into two dialects. An unknown spelling is passed through verbatim so the server still refuses it loudly. before/after are now passed through instead of being rewritten to `<`/`>`. That was the one judgement call and it was settled by measurement, not assumption: on the same live backend the word and the symbol return identical status and identical record ids, on a `date` field and a `datetime` field, both directions. The other 18 operators were measured the same way and are unchanged in what the server answers; not_in is the only one whose answer changes, 400 -> the rows. Reverse-verified: with the table restored, 41 of the 63 new assertions go red, including the headline `isFilterAST` one. The 22 that stay green are the ones that should — the spec-vocabulary reads, the legacy triplet passthrough, and the unknown-spelling contract pin, whose behaviour the old table's `default:` branch already had. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
…ith no operator (#3470) The deleted table opened with `case undefined: … return '='`, so a tab-preset rule carrying `{field, value}` and NO operator lowered to `['status','=','x']`: accepted by isFilterAST and answered by the server as a real equality predicate. Through the spec's `normalizeFilterOperator` (which returns non-strings untouched) it now lowers to `[field, undefined, value]`, which isFilterAST refuses — the same loud 400 every other off-spec spelling gets. That is the ONLY sub-case in this change whose "before" was a passing 200 rather than a 400, so it is called out rather than folded into the pure-fix claim. It is nonetheless the correct direction: `ViewFilterRuleSchema.operator` is a bare `z.enum` with no default, so an operator-less rule fails `safeParse` with `invalid_value` and cannot be published — inventing `=` for it was a lenient consumer standing in for the contract (AGENTS.md #0.1). Both facts (the spec's refusal and the emitted node's) are asserted, so the claim cannot rot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
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 #3470
先说 before/after 的实测裁决:IDENTICAL —— 这部分是纯修复,不是行为变更
issue 的⚠️ 差异点要求先测量再决定:旧表把
before/after改写成</>,而这两个词本身也是VALID_AST_OPERATORS成员,所以「直接透传」到底是不是纯修复,取决于服务端对它们的日期语义。按裁决用实测回答,没有假设。独立测试栈(#3431 同法):
e2e/live/ci/start-backend.sh,已发布@objectstack/*@17.0.0-rc.2+examples/app-showcase,objectstack dev --seed-admin --fresh -p 4093;对象showcase_task(due_date是date,created_at是datetime)。原始 payload:比对不只看行数,连返回的 record id 集合都逐个比过(探针里
same()在双 200 时比较排序后的 id 数组)。date与datetime两种字段类型、两个方向,状态码与结果集完全一致。所以旧表那次改写是个 no-op,去掉它是纯修复。spec 自己也独立印证了同一结论:canonicalAstOperator('before') === '<'。整表替换的实测:19 个算子里只有
not_in的答案变了(400 → 正确的行)把「canonical 拼法」与「旧
specOperatorToAst的输出」逐一对打:=200 n=2!=200 n=8contains200 n=1notcontains200 n=9startswith200 n=1ends_with200 n=1>200 n=10<200 n=0>=200 n=10<=200 n=0in200 n=4not in400 INVALID_FILTER</>同between200 n=1not_in那 8 行与基线["status","!=","done"]的 8 行是同一批。也就是说:整表替换对 18 个算子是无差别的,唯一变化正是被修的那个缺陷。上表是按 19 个 canonical 算子测的,不覆盖「作者根本没写 operator」这一格。旧表的第一行是
case undefined: … return '=':一条{field, value}没有 operator 的规则,会被凭空补成等值谓词['status','=','x'],isFilterAST 放行、服务端当真答一批行。改后它走
normalizeFilterOperator(非字符串原样返回)→[field, undefined, value]→ isFilterAST 判否 → 与其它 off-spec 拼法一样的响亮 400。这个方向是对的,而且有 spec 背书:
ViewFilterRuleSchema.operator是裸z.enum、没有 default,缺省 operator 的规则safeParse直接invalid_value(已断言),即这类 metadata 根本发布不上去 —— 旧的=补齐是消费侧替契约做主,正是 #0.1 要拆掉的宽容消费者。但它确实是本 PR 里唯一「改前 200、改后 400」的一格,所以单独点名、单独一条测试钉住,不并进上面的 same 表里蒙混过去。改了什么
specOperatorToAst整个删掉,normalizeTabPresets改走 spec 自己的normalizeFilterOperator。删表而不是补表,是因为根本不需要表:19 个
VIEW_FILTER_OPERATORS全部已经是VALID_AST_OPERATORS成员,rule → AST 的下降纯结构性,没有任何东西需要翻译;真正需要折叠的只有存量 metadata 里的旧拼法(gt/eq/nin/notEquals…),而那正是normalizeFilterOperator的职责。这也是写入侧app-shell/views/viewFilterFold.ts和 core 侧 saved-view fold(#3431)用的同一个出口 —— 一个出口,两个方向就不可能各自漂成一种方言。未知拼法原样透传,让
isFilterAST继续判否、服务端继续响亮 400(AGENTS.md #0.1):把bfore悄悄读成before,换来的是一个看着合理的错误结果集,而不是作者能看见的错误。反向验证(方向先预测,再运行)
预测:把私有表放回去,新增断言里钉 canonical 拼法的那些应该转红,而读 spec 词表 / 旧三元组透传 / 未知拼法透传这几条应该保持绿(旧表的
default:分支本来就透传未知拼法)。实测与预测一致:诚实标注:「未知拼法原样透传」那条改前改后都是绿的,它钉的是契约不是变化,测试注释里写明了这一点 —— 不把一条不会翻转的断言粉饰成回归证据。
新断言用 spec 自己的
isFilterAST(就是服务端那道闸)在离线把验收事实钉死,和 #3431 一致,以后不需要真后端也能守住。夹具清扫(按规则的消费半径,不是按包)
全仓搜过
'not in'字面量与 tab preset 夹具。受影响的只有一处:UserFilters.test.tsx里那条「从initialSelections._tab恢复 tab」的用例,它顺手钉了旧映射的['priority','=','urgent']。它测的是 tab 恢复、不是算子翻译,所以按「重新拼写」处置,改成 canonical 的equals并加注说明。phase2-schemas.test.ts用的是已下降的filters: triplet[]旧形状,不走这条路径,未受影响(本 PR 另加了一条断言把「旧形状不许开始被翻译」钉住)。验证
changeset:
patch(@object-ui/plugin-list)。范围外发现(已另立,不在本 PR 修)
objectstack-ai/objectstack#5869 —— 同一轮探针发现:集合算子的比较值是标量时(
["status","not_in","done"],而这是 spec 合法的ViewFilterRule,value未按算子约束形状)服务端答 500 DATABASE_ERROR,而不是带 ADR-0112 信封的 400。与 #5234(静默零行)同轴不同格,已在正文里说明为何先立独立条目、是否并入请对面 PM 裁决。本 PR 不在消费侧加任何形状兜底 —— 那正是 #0.1 要避免的宽容消费者。