Skip to content

[finding] 已保存视图的 ViewFilterRule[] 被原样当 FilterNode 送进 $filter,未在客户端折成 AST #3431

Description

@yinlianghui

发现于 #3419(把 ObjectDataPage「Save as view」的 URL 下钻三元组折成 spec rule)。不在该 PR 的 fence 内,也没有在真后端上验证过,故按 observation 记录,不带 pm:queue。

观察

已保存视图的 filter 落盘形状是 spec 的 ViewFilterRule[]({ field, operator, value })—— objectstack#5159 的修法就是让 persistViewFilter 写 folded.rules。读回来时:

  • ObjectView.tsx(约 1347 行)const base = viewDef.filter ?? listSchema.filter,再 [...baseArr, ...urlFilters] —— 这里会把 rule 对象和 URL 三元组混在同一个数组里;
  • 传给 ListView 的 schema.filter,ListView.tsx:1089 交给 buildEffectiveFilter;
  • buildEffectiveFilter → mergeFilterNodes → toFilterNode(packages/core/src/utils/filter-converter.ts:199),而 toFilterNode 对数组是原样返回:
if (Array.isArray(source)) return source.length > 0 ? (source as FilterNode) : undefined;

也就是说 [{ field: 'stage', operator: 'equals', value: 'open' }] 会被当成一个 FilterNode 直接进 $filter。客户端这一路没有任何地方把 rule 对象折成 [field, op, value](ListView.mapOperator 只在 convertFilterGroupToAST 里被调用,处理的是 FilterBuilder 的 group,不覆盖 schema.filter)。

值得注意的是 mergeFilterNodes 自己的注释就警告过这个形状:

spreading a ViewFilterRule[] puts bare rule OBJECTS where the AST expects nodes, and the server neither understands nor rejects that cleanly —— isFilterAST says no (a 400 since objectstack#4121), while parseFilterAST reads the rule as a Mongo condition and filters on columns literally named field / operator / value.

注释是针对 spread 写的,但 toFilterNode 的原样返回把同一个数组整体交出去,落到 $filter 上是同一类载荷。

为什么标成 observation 而不是缺陷

两种可能我没能区分,需要一个跑起来的后端才能定:

  1. 服务端本来就接受 ViewFilterRule[] —— 那这条只是注释与实现的语气不一致,顺手补一句说明即可;
  2. 服务端不接受 —— 那么今天每一条带 filter 的已保存视图筛选都是坏的(400 或静默按字面列名过滤)。若真如此,chore(plugin-editor): 移除被跟踪的 Vite temp-config 冻结产物 #5159 落地后应该早就被发现,所以我更倾向 1;但「早该被发现」不是证据。

复现方向(给接手的人)

起一套栈(AGENTS.md「每个 agent 独立测试栈」),在某对象上存一条带 filter 的列表视图,打开它,看网络面板里 $filter 的实际载荷与返回行数:是 [{"field":...}] 且结果正确,还是 400 / 未筛选。

相关

Activity

  1. yinlianghui commented on Aug 6, 2026

    @yinlianghui
    CollaboratorAuthor

    发现分诊轮(晋级):摘 finding 换 bug+pm:queue(objectui 分片 PM,会话 session_01GTRjn8xBqp75dk7kFupVRt)。前提复核 live:ObjectView.tsx:1348-1350 仍混拼 viewDef.filter 与 urlFilters、filter-converter.ts:201 数组原样透传,且 objectstack 侧 parseFilterAST 对「对象元素数组」返回 undefined——saved view 的过滤条件今天大概率被静默丢弃。本条是全池最重的一单;派发时第一步做跨栈实证(可用 live-e2e lane),再按 producer 侧折叠(#3427 先例)修。


    Generated by Claude Code

  2. self-assigned this
    on Aug 6, 2026
  3. yinlianghui commented on Aug 6, 2026

    @yinlianghui
    CollaboratorAuthor

    认领:objectui 分片 PM 并行批第 46 单
    会话:session_01GTRjn8xBqp75dk7kFupVRt
    分支:claude/issue-3431-savedview-filter-fold-read
    Worktree:objectui-issue-3431
    文件面:packages/app-shell/src/views/ObjectView.tsx(读回混拼处)± packages/core/src/utils/filter-converter.ts(若裁定折叠落 core)+ 两包测试 + changeset。⛔ 不动 packages/spec 与 objectstack 仓(服务端行为只验证不修改;若实证指向服务端缺陷,按跨分片转移立单)。

    派发首步(决定 issue 命运):跨栈实证二选一——起独立测试栈按复现方向抓 $filter 实际载荷与结果,或用 live-e2e lane 的真后端写一条带 filter 的保存视图用例。实证结论决定走「修读回折叠」(1 假 2 真)还是「补注释关单」(1 真)。修法方向沿 #3427 先例:producer/读回侧折叠成 AST,复用既有 canonical 出口,禁消费端猜测式宽容。


    Generated by Claude Code

  4. yinlianghui commented on Aug 6, 2026

    @yinlianghui
    CollaboratorAuthor

    验收:ACCEPT → PR #3471(并行批第 46 单,会话 session_01GTRjn8xBqp75dk7kFupVRt),已跑检查全绿(shards 收尾中),转 ready + auto-merge。

    复核要点:

    1. 实证是决定性的、双通道的:真后端直探(rule 数组 → 400 INVALID_FILTER;折叠后 → 200 出行,showcase 自带的 in_progress 视图就是现成复现体)+ 仓内源码回放全读路径;静态侧用 spec 自己的 isFilterAST/parseFilterAST 钉住,无网络也能守。possibility 2 为真:今天每条带 filter 的保存视图都是硬 400 且连计数条都不渲染——本单是分诊轮判级正确性的最好证明。
    2. 缝位裁定优于派发词:折叠落 core 的 toFilterNode 而非 ObjectView——四条理由中「早一跳折叠 = 把 AST 写进 spec 声明的 ViewFilterRule[] 槽,#0.1 反向违例」是决定性的;且该缝覆盖第二个生产者(plugin-view 自己的 432 行),派发词的缝会漏修一半。docblock 早已声明该形状为源形状 Implement visual designer for Object UI schemas #1——这是「文档说了、实现没做」的教科书案例。
    3. 零第二张表:实测 19 个 view 操作符全部已是合法 AST 成员,折叠纯结构;未知拼写原样透传保留响亮 400。两个边缘裁定(无 value 出二元节点防 stringify 空洞变 null;空 field 不折叠保 400 优于静默空列表)全部有实测依据。
    4. fence 偏差(plugin-view 的「认证缺陷」fixture 整体替换)属消费半径重判,合规;三类 fixture 三种处置各有判词。
    5. 衍生处置:UserFilters 的私有 specOperatorToAst 把 not_in/nin 译成 not in(带空格),服务端 400;且它是第二张手工算子表 #3470(not_in→'not in' 二次手表映射,实测 400)已分诊 bug+pm:queue;live spec 未入 allowlist 系遵守 lane 自己的防 flake 政策,晋级 chore 已另立单跟踪。

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions