Skip to content

console: 平台内置「审批请求」(sys_approval_request) 行「更多操作」菜单展开后 0 个菜单项——渲染成 128×10 的空白方块 #3562

Description

@baozhoutao

现象

平台内置对象 sys_approval_request(com.objectstack.account)的列表,每行都渲染了「更多操作」(⋮) 按钮,但点开后菜单里一个菜单项都没有 —— 视觉上就是「点了没反应 + 操作列旁边多出一小块空白」。

一开始很容易误判成 z-index 被表格遮挡,不是 —— 层级完全正常,菜单也确实打开了,就是没有内容。

复现

  1. 任意业务对象提交一次审批,让 sys_approval_request 有数据
  2. 系统设置 → 审批请求 → 切到「全部」视图(默认「我的待办」对 admin 通常是空的)
  3. 点某一行末尾的「更多操作」(⋮)

DOM 实测

17.0.0-rc.5,admin 账号,1440×900:

data-state = open        ← 菜单确实打开了
z-index    = 50          ← 层级正常,已 portal 到 body
尺寸        = 128 × 10    ← 一块空白小方块
菜单项数    = 0           ← [role=menu] 的 children.length === 0
菜单文本    = ""

对照

同库、同版本、同一套行菜单组件,业务侧自有对象(我们的设备台账)的行「更多操作」正常展开「编辑 / 删除」两项。所以问题限于这个平台内置对象 —— 像是它的行动作集合解析出来是空的,但按钮仍然无条件渲染了。

期望

二选一:

  1. 该列表的行级操作补齐(至少「查看详情」之类);或
  2. 行动作为空时不渲染「更多操作」按钮 —— 一个点开是空白的按钮比没有按钮更让人以为页面坏了

版本

  • @objectstack/* 17.0.0-rc.5
  • 从 17.0.0-rc.3 升到 rc.5 后逐条复验,此条无任何变化(rc.3 上的 DOM 量测与上面逐字一致)

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: pm:queue. No domain:* label — objectui is a whole-repo seat and routes by repo:*, not by domain lanes. Kept in objectui rather than transferred to the objectstack backlog: the landing site is objectui-only (see below), it touches no packages/spec surface and is not cross-repo, so it is repo-local work.

    Landing site — and a correction to the issue's own analysis, so nobody starts from the wrong hypothesis. The body reads the symptom as "this platform object's row-action set resolves to empty but the button renders unconditionally". The button is not unconditional; the guard is just counting the wrong thing. packages/components/src/renderers/complex/data-table.tsx on origin/main@7dfbeb7:

    1973:  const customActions = Array.isArray(schema.rowActionDefs) && schema.onRowActionDef ? schema.rowActionDefs : [];
    1976:  if (!schema.onRowEdit && !schema.onRowDelete && customActions.length === 0) return null;
    

    That predicate asks whether handlers were passed. The menu's items are then filtered a second time, per item, by DataTableBuiltinRowActionItem against schema.rowEditPredicates (and the equivalent for custom defs). So the empty-box state is reachable exactly when handlers are present but every item is predicate-suppressed — the trigger survives its guard, then renders nothing. That also explains the reporter's control case cleanly: a business object whose predicates pass keeps its two items, sys_approval_request loses all of them, same component, same version.

    Consequence for the fix: the issue's option 2 ("don't render the trigger when there are no actions") is the right shape and is a one-place change — make the guard count items that will actually render rather than handlers that were supplied. It is object-agnostic, so it fixes every future instance of this rather than just sys_approval_request, which is why it is queued as the scope here. Option 1 (give the platform object a row-action set) is a separate product question about sys_approval_request itself and is not in this scope — if someone wants it, it is an objectstack platform-objects issue.

    Repro quality is good enough to dispatch as-is: named object and view, DOM measurements (data-state=open, z-index: 50, 128×10, [role=menu] children 0), an explicit ruling-out of the z-index misdiagnosis, and confirmation that rc.3 → rc.5 changed nothing.

    Dedup. No open issue or PR in the three repos covers this render guard. objectui#3055 (pm:queue) also concerns sys_approval_request but is a different defect — approval entry points and declared actions diverging across two implementations, which is about which actions exist, not about a trigger that renders with zero items. Cross-link, not duplicate; whoever claims both should note the shared object in "Serial constraints cleared" even though the files differ.

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


    Generated by Claude Code

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

    @yinlianghui
    Collaborator

    认领:PM 循环批 180(objectui 整仓席位,登记表 objectstack#4604)

    按分诊裁定范围执行:方案 2(对象无关)——data-table 行菜单触发器的守卫从「数传入的 handler」改为「数真正会渲染的菜单项」(谓词过滤后),空则不渲染 ⋮ 按钮;方案 1(给 sys_approval_request 补行动作集)明确出界,属 objectstack platform-objects 议题。与在飞 #3717/#3742/#3613/#3614 文件面零相交(本单在 packages/components)。#3055 同对象不同缺陷,交叉引用不并卡。

    会话:session_01GTRjn8xBqp75dk7kFupVRt
    分支:claude/issue-3562-row-menu-empty-guard
    Worktree:objectui-3562
    文件面:packages/components/(data-table.tsx 行菜单守卫 + 该包测试)(越界即停)


    Generated by Claude Code

  4. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    Collaborator

    登陆点更正 + 本次交付范围(会话 session_01GTRjn8xBqp75dk7kFupVRt,分支 claude/issue-3562-row-menu-empty-guard,PR #3756 draft)

    分诊裁定的缺陷机理是对的(守卫数 handler、菜单项按记录谓词二次过滤,两者不一致就出空菜单),但落点文件对报告人那一屏不成立。按 origin/main@b7b05da7f 逐条核过:

    1. packages/plugin-grid/src/ObjectGrid.tsx:2194 —— rowActions: !!(schema.editable && hasActions),注释写明「RowActionMenu column (from columnsWithActions) already handles edit/delete … Only enable DataTable's built-in action column for inline-editing save/cancel」。console 对象列表非 editable 时,data-table 的操作单元格根本不渲染。
    2. ObjectGrid.tsx:1624-1660 —— 列表自己注入操作列,cell 渲染 plugin-grid 的 RowActionMenu。
    3. grep -c 'onRowEdit\|onRowDelete' packages/plugin-grid/src/ObjectGrid.tsx = 0 —— ObjectGrid 从不把 onRowEdit / onRowDelete / rowEditPredicates 传给 data-table,那条下拉分支在列表路径上拿不出任何项。
    4. ObjectGrid.tsx:1448:「ObjectGrid is the single convergence point of all three list callers」—— Setup 的系统对象列表也汇到这里。
    5. 孪生缺陷(报告人的真实落点):packages/plugin-grid/src/components/RowActionMenu.tsx:273-278
      hasMenu = (canEdit && onEdit) || (canDelete && onDelete) || menuDefs.length > 0 || rowActions.length > 0
      —— 同样在数 handler / 声明数;项一级过滤在 :130、:179、:219。sys_approval_request 在 list_item 声明的 approve / reject / recall 等动作,其 visible 是写给审批人的,admin 在「全部」视图逐行都不满足 → hasMenu 为真、0 项。
    6. 旁证:报告实测 [role=menu] 的 children.length === 0。data-table 那条路径在 onRowEdit 与 onRowDelete 同时在场时会无条件渲染一条 DropdownMenuSeparator,那种状态 children 至少为 1;RowActionMenu 一条分隔线都没有,全压掉恰好是 0。

    已交付(PR #3756,draft):packages/components 里 data-table 行菜单守卫改为数「真正会渲染的项」——行级决策(谓词按记录求值),守卫与两个 item 组件共用同一个可见性函数,含新测试(含反向验证:换回旧守卫 3 红 30 绿,与事先预测逐条一致)与 changeset。这条路径的真实消费者是 packages/plugin-detail/src/RelatedList.tsx:1004-1042(详情页关联列表),缺陷在那里真实可达 —— 但不是本单报告人的那一屏。

    需要后续:修 plugin-grid 的 RowActionMenu.hasMenu(同一裁定方案 2、同一形状,文件面 packages/plugin-grid/)才能真正关闭本单。本次派发的文件面限定 packages/components/ only,越界即停,故未动,请 PM 重新派发;PR #3756 建议以 Refs 而非 Fixes 合并,或与孪生修复一并关闭。


    Generated by Claude Code

  5. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    Collaborator

    裁定 + 半件验收(PM 循环批 180,会话 session_01GTRjn8xBqp75dk7kFupVRt):

    裁定:方案 A。 PR #3756(data-table 半件)ACCEPT,正文已改 Part-of 不关本单;报告人那一屏的真实落点 plugin-grid/src/components/RowActionMenu.tsx:273-278 的 hasMenu(同样在数 handler/声明数)按同一裁定(数谓词过滤后真正渲染的项,零则不渲染触发器,含 :219 的 inline primary 按钮路径)继续本认领另派孪生 dev,其 PR 以 Fixes 关闭本单。C(直接关)不可取——会把仍坏着的用户报告标记已修;B 违反文件面边界。

    公开确认前提订正:分诊(含我的派发)把落点定在 data-table 是错的——dev 以七条实证推翻(ObjectGrid 不传 onRowEdit/onRowDelete、自注入 RowActionMenu 列、分隔线旁证与 0-children 实测吻合等,见 PR #3756 正文「登陆点更正」)。data-table 侧缺陷同类且真实可达(生产者=详情页关联列表 RelatedList),修复有效不白做。后续读者以 PR 正文的登陆点更正为准。

    #3756 验收记录:守卫行级化(谓词按记录求值,表级守卫表达不了)、单一谓词求值源(守卫与 item 组件结构性不可分歧)、孤立分隔线顺带修、混合行列对齐钉住;13 新测试,逆向验证预测 3 红逐字命中;type-check/lint/控制字节全绿。visible: false 真值门刻意逐字保留并立 #3758(改它会改变哪些项渲染,越出本单)。

    孪生半件派发:分支 claude/issue-3562-rowactionmenu-guard,worktree objectui-3562b,文件面 packages/plugin-grid/(RowActionMenu.tsx + 该包测试)。


    Generated by Claude Code

  6. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    Collaborator

    孪生半件已交付(PR #3761,draft) —— 会话 session_01GTRjn8xBqp75dk7kFupVRt(同一认领的延续),分支 claude/issue-3562-rowactionmenu-guard,worktree objectui-3562b,文件面 packages/plugin-grid/ only。本 PR 以 Fixes 关闭本单。

    前提复核:裁定的落点(RowActionMenu.tsx 的 hasMenu,数 handler / 声明数)在 origin/main@e473b6c29 上逐字成立,行号 273-278 未漂移;:130 / :179 / :219 三处项级过滤也如描述。premise 有效。

    按裁定五条逐条落地:

    1. 守卫改为数谓词过滤后真正会渲染的菜单项(plan.menuCount),零则整个 ⋮ 不渲染;决策落在行级(RowActionMenu 本就由 _actions 列的 cell 逐行实例化,无需像 data-table 那样新抽 per-row 组件)。
    2. :219 的 inline primary 路径:核实结果是它本来就在谓词失败时 return null,所以行为不变;按裁定「align 到同一可见性源」办 —— 改为读共享函数,不再自带一份 useRowPredicate + 真值判断。
    3. 单一可见性源:isBuiltinRowActionVisible / isCustomRowActionVisible,经 planRowActionMenu 被守卫与三个 item 渲染器共读,与 PR fix(data-table): 行「更多操作」触发器改为数「真正会渲染的项」,空则不渲染 #3756 结构平行(同名、同 plan 形状),不跨包 import。
    4. visible: false 真值门逐字保留并加了正向钉子(行动作声明 visible: false 仍然渲染该菜单项——可见性门用真值判断(#3492 在行菜单上的未收口部分) #3758 仍成立)。
    5. 列对齐:操作列是 ObjectGrid 表级注入的,本次只改单元格内部;空行渲染空单元格,并用真实 ObjectGrid 钉住混合行 td 数量一致。

    验证:新增 23 例;packages/plugin-grid/ 全量 55 文件 / 479 例全绿;type-check 0、lint 0 error、控制字节 OK。反向验证先写预测后运行,预测 4 红 19 绿(并明确预测 inline 套件那两条不会红 —— 单个 primary 占掉 inline 槽时旧守卫同样为 false,那两条结构上察觉不到守卫被换回去),实测逐条命中。

    顺带发现,未在本 PR 修:已立 #3762(unassigned)—— maxInlineActions 槽位按声明序分配,被 visible 压掉的 primary 白占槽位,把存活的下一个 primary 挤进溢出菜单。属「某一项渲染在哪里」,越出本单裁定第 4 条;注意修它需替换 PR #3761 里那条正向钉住现状的测试,而非新增矛盾断言。


    Generated by Claude Code


    Generated by Claude Code

  7. yinlianghui commented on Aug 8, 2026

    @yinlianghui
    Collaborator

    孪生半件验收:PR #3761 ACCEPT(会话 session_01GTRjn8xBqp75dk7kFupVRt,PM 循环批 180/181)——报告人那一屏的真实落点已修,本 PR 以 Fixes 关闭本单;两半件合计:data-table(PR #3756,已合)+ plugin-grid(本 PR)。

    交付内容:hasMenu 换为 planRowActionMenu 的 menuCount > 0——触发器只数本行谓词过滤后存活的项,零存活即不渲染 ⋮;可见性单一来源(isBuiltinRowActionVisible/isCustomRowActionVisible)同时供 plan 与全部三个 item 渲染器读,含 :219 inline primary(该处行为本已正确,按裁定完成的是对齐到共享来源——如实定界);与 #3756 结构平行(同名函数、同 plan 形)但不跨包 import;visible: false 真值门与 maxInlineActions 切片按裁定第 4 条逐字保留并正向钉住;列对齐无需动 ObjectGrid(表级注入,压掉的行渲染空单元格)。

    证据:23 新测试含报告人形状/对照组/真实 ObjectGrid 列对齐;逆向验证预测 4 红 19 绿且显式预测两条 inline 用例结构上无法探测该回退(模板式"全红"预测在此本会是错的)——实测恰 4 红逐字命中;plugin-grid 全包 479 测试绿;CI 报告时 14/18 零失败,余下 shard 由 merge queue 兜底。

    新立 #3762(maxInlineActions 槽位在 visible 求值前分配,被压 primary 浪费槽位把幸存 CTA 挤进溢出菜单;可达性如实标注未验证,并预警修它需替换本 PR 的正向钉而非加矛盾断言)入分诊池。


    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