Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

记录页头的动作谓词走遗留 JS 求值器而非 CEL:.size() / .contains() / in / today() 在详情页一律抛错并静默隐藏按钮 #3521

Closed

Description

@baozhoutao

记录页头(page:header)的动作 visible / disabled 不走规范 CEL 引擎,而是走 objectui 的遗留 JS 求值器(packages/components/src/renderers/layout/containers.tsx 里 new ExpressionEvaluator(evalCtx) + evaluator.evaluateExpression(src))。结果是同一条谓词在列表里对、在详情页头静默错,而且是 fail-closed —— 按钮直接消失,页面上没有任何痕迹。

实测(showcase showcase_field_zoo / Specimen — Full,真实 runtime)

同一批动作同时声明 list_item 与 record_header。行内 kebab(走 evalRowPredicate → @objectstack/formula)全部正确求值;页头把其中 15 条判为抛错并隐藏。控制台原话:

[page:header] action "showcase_zoo_t_multiselect" hidden: its predicate threw —
  Failed to evaluate expression ""red" in record.f_multiselect":
  Unexpected token "i" at position 6
[page:header] action "showcase_zoo_t_date" hidden: its predicate threw —
  Failed to evaluate expression "record.f_date != null && record.f_date < today()":
  "today" is not a function

失效的是每一个 CEL 独有的构造:

构造 例子 页头结果
方法调用 record.f_tags.size() > 0、record.f_textarea.contains("x")、record.f_email.matches("…") 抛错 → 隐藏
in 运算符 "red" in record.f_multiselect 解析失败 → 隐藏
stdlib 函数 record.f_date < today() today is not a function → 隐藏

只用 == != < && \|\| ! 的谓词两边一致,所以问题一直没被发现 —— 直到有人在谓词里用了一次 .contains()。

为什么这不只是「少几个函数」

  1. 同一条元数据,两种方言。 行 kebab / 批量栏 / 条件格式自 Unify list-view conditional features (conditional formatting / row-action visibility) onto the CEL engine #1584 / ADR-0058 起统一走 evalRowPredicate(bare string 即 CEL,只有带遗留标记的字符串才回退 JS 并打 deprecation 警告)。页头没有跟上,它把每一条谓词都当 JS。
  2. 与服务端也不一致。 服务端校验/共享/流程一律 CEL。作者按服务端方言写的谓词,在页头上是 fail-closed 隐藏。
  3. 失败无声。 只有一条 warn-once 的 console 输出;产品上的表现就是「这个按钮在列表里有、点进详情页就没了」。

方案

把页头的两处求值(visible/hidden 用的 evalExpr,以及 disabled 用的 dEvaluator)改为走 evalRowPredicate(@object-ui/core) —— 与行 kebab 同一个入口、同一套绑定(record.* + 裸字段 + data.* + 宿主 scope)、同一套 fail-closed + warn-once 语义,遗留方言字符串仍由 isLegacyDialectSource 兜住回退,所以现存的 ${…} / === 写法不回归。

关系字段的绑定刚在 #3501 统一过(toPredicateRecord 已接进这两处),本单是同一处剩下的那一半:绑定对了,方言还没对。

验收

一条 visible: 'record.tags.size() > 0'(或 in / today())的动作,在列表行菜单、批量栏、记录页头三处对同一记录给出相同显隐结论;页头不再对 CEL 独有构造抛错。showcase 里 showcase_zoo_dialect_split 与 showcase_zoo_t_* 系列是现成的实测夹具(framework examples/app-showcase/src/ui/actions/predicate-matrix.action.ts)。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: pm:queue. pm-state only (whole-repo seat, no domain:*).

    Why queued: a named landing site (packages/components/src/renderers/layout/containers.tsx — the new ExpressionEvaluator(evalCtx) / evaluator.evaluateExpression(src) pair used by visible/hidden, plus the disabled evaluator), a stated fix (route both through evalRowPredicate from @object-ui/core, same entry as the row kebab), a real-runtime repro with console transcripts, and ready-made fixtures. Nothing to ask.

    The acceptance criterion is the valuable half — keep it: the same predicate on the same record must give the same show/hide verdict in the row menu, the bulk bar and the record header. That is what makes this a convergence fix rather than "add a few missing functions"; a patch that teaches the legacy evaluator .size() / in / today() would satisfy the symptom list and leave the two dialects in place.

    Serialization / adjacency (three-repo dedup done, no duplicate):

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

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

    @yinlianghui
    Contributor

    🔒 认领(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt)

    即将派发 os-dev 实施。


    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