fix(app-shell): 把 Save as view 的 URL 下钻条件折成 spec ViewFilterRule (#3419) - #3427
Merged
Merged
Conversation
…terRules (#3419) ObjectDataPage renders from a runtime filter AST (FilterTriple, i.e. [field, '=', value]), but "Save as view" persists a ViewItem whose ListViewSchema.filter declares z.array(ViewFilterRuleSchema) -- { field, operator, value } over the canonical operator words. The two were the same key in two vocabularies: handleSaveAsView wrote the triples through verbatim, so a view saved with drill conditions active was off-spec the moment it was written (ViewItemSchema rejects it on config.filter.0, "expected object, received array"). Contract-first (AGENTS.md #0.1): the fold is at the PRODUCER, no consumer is taught to accept triples. Operators go through the spec's own normalizeFilterOperator -- the same exit viewFilterFold uses for the FilterBuilder half of this problem -- via a symbol-to-alias bridge derived by inverting URL_FILTER_OPS, so canonicalisation still happens in exactly one place. field/value are carried verbatim. A triple whose operator has no canonical spelling is dropped from the persisted view with a debug-level note rather than written off-spec (declared = enforced; parseUrlFilterTriples emits no such operator today, so this is defence in depth against the URL contract growing one). Saving with no drill conditions is byte-identical to before. The spec assembly moved into an exported buildSaveAsViewSpec so the whole producer step -- not just the fold helper -- is pinned against the real record gate; a test on the fold alone would stay green if the call site went back to raw triples. 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
|
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 #3419
前提复核(先于实现)
issue 的前提在
origin/main(已含 #3420 的 viewEnvelope spec pin)上仍然成立,只是行号漂了:handleSaveAsView现在在ObjectDataPage.tsx:250附近,那一行...(urlFilters.length ? { filter: urlFilters } : {})原样未动。用 lockfile 解析到的@objectstack/spec@17.0.0-rc.2复现,拿到的就是 issue 里那条:顺带核实了一个 issue 没写、但决定实现形状的事实:
normalizeFilterOperator并不认识符号操作符。它只折 spec 自己的词形别名(eq/gte/notEquals…),symbols 会原样返回:所以「直接把三元组的 operator 丢给
normalizeFilterOperator」是不够的,见下面的桥接表。改法(producer 侧折叠,契约优先)
这个页面渲染用的是运行时 filter AST(三元组,
ListView直接当$filter发出去);持久化的是 ViewItem,ListViewSchema.filter声明的是z.array(ViewFilterRuleSchema)。同一个filter键、两套词汇表 —— 原样写出去的那份从落盘那一刻起就过不了 record gate。按 AGENTS.md #0.1,折叠放在 producer,没有给任何消费侧加三元组容忍:
TRIPLE_OP_TO_SPEC_ALIAS)。这不是第二张规范映射表:range 那半边是把URL_FILTER_OPS反转出来的(它的后缀gte/lte/gt/lt本身就是 spec 的别名 key),所以 URL 契约以后新增 range 操作符会自动被桥接;只有=是手写的,因为等值形式没有[op]后缀可反转。normalizeFilterOperator一处 —— 与viewFilterFold.foldFilterGroupToSpecRules(FilterBuilder 那一半,objectstack#5159)同一个出口。=折成equals,>=折成greater_than_or_equal。field/value原样带过,''也保留(spec 接受,重写等于偷改用户存的东西)。parseUrlFilterTriples今天只会吐上面那五个操作符,这条路径是防 URL 契约以后长出 spec 没有对应词的操作符的纵深防御。filter键,和以前一样。为什么多出一个
buildSaveAsViewSpecspec 组装从回调里提出来成了一个导出函数。理由是可验证性:只测折叠 helper 的话,call site 退回
filter: urlFilters时测试依然全绿 —— 那正是要钉住的回归。现在测试打的是「producer 那一整步 + 真 record gate」。剩下唯一没被单测覆盖的一环是回调调用它的那一行,这个边界在下面的反向验证里如实标了。运行时那条路径(
schemamemo 里的filter: urlFilters)故意没动:那是喂给ListView的 AST,三元组正是它要的形状,改了会真的改坏筛选。测试
新增
packages/app-shell/src/views/ObjectDataPage.saveAsViewFilterFold.test.ts(10 例):等值下钻、日期桶下钻(同字段两条 range)、URL 契约能吐的每个 operator(从URL_FILTER_OPS派生,新增的漏不掉)、field/value 原样带过、无规范拼写时丢弃 + 只保留其余、全丢时不写filter键,以及「无下钻条件时 spec 与对话框 payload 完全一致」。issue 里那条 zod 报错也作为可执行证据钉住了(它断言的是 spec 的判定,改前改后都绿)。反向验证(方向在跑之前就定好):把
buildSaveAsViewSpec里的折叠退回原始urlFilters,预测「折叠类用例红、spec 判定那条和无下钻那三条绿」。实测完全一致 —— 6 红 4 绿:全量结果:
pnpm exec vitest run packages/app-shell→ 281 files / 2444 passed, 1 skippedpnpm --filter @object-ui/app-shell type-check→ 通过(先跑了--filter '@object-ui/app-shell^...' build)pnpm exec eslint(改动文件)→ 0 error;新增的react-refresh/only-export-componentswarn 是组件文件导出 helper 的既有形态(ObjectView.tsx同样有一条),规则本身就是warnnode scripts/check-control-bytes.mjs→ OK;另对改动文件做了grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'自查,无命中check-changeset-fixed/check-changeset-no-major→ 通过(patch,@object-ui/app-shell)消费半径扫过一遍:全仓只有
ObjectDataPage.tsx和新增测试引用这条保存路径,其余命中都是 i18n 文案,没有别的包的 fixture 拼了三元组 view filter。Generated by Claude Code