Skip to content

fix(plugin-grid,components): 行动作的 visible 门按「已声明」判定,visible: false 不再渲染 (#3758) - #3816

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3758-visible-false-gate
Aug 8, 2026
Merged

fix(plugin-grid,components): 行动作的 visible 门按「已声明」判定,visible: false 不再渲染 (#3758)#3816
yinlianghui merged 1 commit into
mainfrom
claude/issue-3758-visible-false-gate

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3758

修了什么

自定义行动作的可见性用真值判断,于是 visible: false —— 作者能写出的最明确的「永不显示」—— 落进「未声明门」分支,动作对所有行渲染。门判定改为按「是否声明了门」检测(!= null && !== ''),声明本身随后决定 verdict:布尔在求值入口短路成自己的 verdict,不交给 CEL 引擎。

门位:三个变两个(核验读数与派发单不一致,已核实)

派发单与 #3758 正文/分诊(锚 b1204af)列了个门位。按 rule 6 在 origin/main@d9ce38529 上核验时发现:PR #3761(1a33b1aba,「gate the row '...' trigger on renderable items」)此后已把 plugin-grid 的 RowActionMenu.tsx:130(overflow 项)与 :219(inline 按钮)收敛成一处共享函数 isCustomRowActionVisible —— 与 #3756 对 data-table 做的相同。

缺陷与修法不变,门位数由 3 变 2;:130/:219 两个渲染面现在读同一处门,所以改一处即同时覆盖两个面,并且天然不可能只修一半。

门位 改前 改后 覆盖的渲染面
packages/plugin-grid/src/components/RowActionMenu.tsx isCustomRowActionVisible if (!def?.visible) return true; const pred = def?.visible; 然后 if (pred == null || pred === '') return true; overflow 菜单项 + inline primary 按钮 + planRowActionMenu 的计数(即「⋮」触发器是否渲染,#3562 的守卫)
packages/components/src/renderers/complex/data-table.tsx isCustomRowActionVisible if (!action?.visible) return true; const pred = action?.visible; 然后 if (pred == null || pred === '') return true; data-table 行菜单项 + planDataTableRowMenu 的计数

evalRowActionVisibility 未改(布尔短路早已在里面)。其中 pred === '' 一支改后仍不是死代码:isBuiltinRowActionVisible 的门只有 != null,'' 仍从那条路到达 —— 已加注释写明,免得后来的读者当死支删掉。

不变量依据(#3492,已裁不重开)

packages/plugin-grid/src/bulkEligibility.tshasVisibilityGate 注释原文,objectui#3492 落地时写的:

Truthiness cannot answer this: visible: false is a declared gate that excludes everything, and testing def.visible && classified it as ungated — which rendered the button false was written to remove (objectui#3492).

行动作这两处是同一形状里没有一起收口的部分;内建 visibleWhen 的门一直是 != null。本 PR 只是把行动作收口到同一处不变量,不是新决定

行为变化面(刻意窄)

只有 visible 为字面布尔 false(或其它非空 falsy 值)的行动作变化 —— 从渲染变隐藏,即声明所要求的。visible: true 照旧渲染,'' 与未声明照旧不算门,表达式取值的 visible verdict 一律不变。

ActionSchema.visibleExpressionInputSchema(无 boolean 成员),objectstack build 产不出这个形状;手写视图 JSON 与进程内构造 def 可以 —— #3492 正文记录这两条路径确实发生过。故可达性低。

反向验证(方向先判后跑)

预判:6 条 visible: false 断言在未改的门上必须;所有 visible: true / '' / 未声明的对称断言必须绿(它们不是方向钉,而是防止把「声明检测」写成「恒隐藏」的护栏 —— 那样重写能让每一条 visible: false 断言独自通过)。

先在 origin/main 的门上跑新钉子(即「还原门」),实测与预判逐条一致:

❯ RowActionMenu.test.tsx (20 tests | 2 failed)
    × visible:false → the overflow menu item does not render
    × visible:false → the inline primary button does not render
❯ RowActionMenu.emptyGuard.test.tsx (27 tests | 2 failed)
    × a declared `visible: false` excludes the def and leaves no trigger (objectui#3758)
    × renders no trigger when the row's only custom action declares `visible: false`
❯ data-table-row-menu-empty-guard.test.tsx (16 tests | 1 failed)
    × drops a custom action declaring `visible: false`, reaching zero items
❯ data-table-row-action-visible.test.tsx (10 tests | 1 failed)
    × hides an action declaring `visible: false`
 Tests  6 failed | 67 passed (73)

AssertionError: expected [ { name: 'ghost', visible: false } ] to deeply equal []
AssertionError: expected [div role="menuitem" …(5)] to be null

改后同一批:Test Files 10 passed (10) / Tests 125 passed (125)

另有一条测得的前置读数(临时探针,已删):visible: false 在改前三面分别是 行菜单 true(渲染,缺陷)、选择栏 false、记录页头 false —— 即行菜单是唯一读错的那一面,改后三面一致。

测试

  • 三个观测面各一条 visible: false 钉子:plugin-grid overflow 项、inline primary 按钮、data-table 菜单项;
  • 守卫/plan 层三条(planRowActionMenu / planDataTableRowMenu 计数归零)+「⋮」DOM 两条;
  • 每条都配 visible: true(声明门、恒真)与 '' / 未声明(不算门)的对照断言;
  • predicate-surface-parity.test.tsx:新增四条非表达式形状的三面一致用例。该文件头注释此前把这条差异记为「still-open divergence —— the kebab renders a visible: false def」,现已成立并改写。

Fixture 处置:RowActionMenu.emptyGuard.test.tsx 里钉住旧真值行为的那条 fixture(preserves the visible: false truthy gate verbatim)整条替换,不是重拼 —— 它的期望值(['ghost'] / 1)现在是错的 verdict。原注释说明当时为何刻意保留,替换处留注写明 #3758 把它另裁了。

命令与输出

pnpm vitest run packages/plugin-grid packages/components --maxWorkers=2
  → Test Files 152 passed (152) / Tests 1242 passed (1242)

pnpm --filter @object-ui/plugin-grid --filter @object-ui/components type-check
  → packages/components type-check: Done
  → packages/plugin-grid type-check: Done

pnpm vitest run packages/plugin-detail packages/plugin-view --maxWorkers=2   # 消费半径
  → Test Files 66 passed (66) / Tests 575 passed (575)

pnpm exec eslint (7 个改动文件)  → 0 errors(仅既有 warning)
node scripts/check-control-bytes.mjs → OK (3731 tracked text files)
node scripts/check-changeset-no-major.mjs → No changeset declares a major bump

消费半径按规则的调用方枚举,不按被改包:两处门的调用方(两处 plan 函数、四个 item/按钮组件、predicate-surface-parity)全部定向跑过;plugin-detailRelatedList 把子对象 list_item 动作喂给 data-table,故一并跑。全仓扫过 row-action 的布尔 visible fixture,除已处置的两处外无他。

Changeset

.changeset/row-action-declared-visible-gate-3758.md —— @object-ui/plugin-grid + @object-ui/componentspatch(行为修复;按仓规不声明 major)。

范围外发现(未在本 PR 改动)


Generated by Claude Code

…渲染 (#3758)

自定义行动作的可见性**门**用真值判断,于是 `visible: false` —— 作者能写出的
最明确的「永不显示」—— 落进「未声明门」分支,动作对所有行渲染。两处门(收敛后
各一处 `isCustomRowActionVisible`)现按 `!= null && !== ''` 检测「是否声明了门」,
声明本身随后决定verdict:布尔在求值入口短路成自己的verdict,不交给 CEL 引擎。

这不是新决定。objectui#3492 已把同一条不变量钉在批量选择栏上,
`bulkEligibility.ts` 的 `hasVisibilityGate` 注释写明真值判断答不了这个问题
(「`visible: false` is a declared gate that excludes everything」);内建
`visibleWhen` 的门一直是 `!= null`。本次只是把行动作这一族收口到同一处不变量。

两处门位:

- `packages/plugin-grid/src/components/RowActionMenu.tsx` 的
  `isCustomRowActionVisible` —— 同时管 overflow 菜单项、inline primary 按钮和
  `planRowActionMenu` 的计数(即「⋮」触发器是否渲染,#3562 的守卫);
- `packages/components/src/renderers/complex/data-table.tsx` 的同名函数 ——
  同时管 data-table 的行菜单项与 `planDataTableRowMenu` 的计数。

派发单按 #3758 正文/分诊(锚 `b1204af`)描述为**三个**门位;PR #3761
(`1a33b1aba`)此后已把 plugin-grid 的 `:130`/`:219` 两处收敛成一处共享函数,
与 #3756 对 data-table 做的相同。缺陷与修法不变,门位数由 3 变 2。

行为变化面刻意窄:只有 `visible` 为字面布尔 `false`(或其它非空 falsy 值)的动作
变化 —— 从渲染变隐藏,即声明所要求的。`visible: true` 照旧渲染,`''` 与未声明
照旧不算门,表达式取值的 `visible` verdict 一律不变。`ActionSchema.visible` 是
`ExpressionInputSchema`(无 boolean 成员),`objectstack build` 产不出这个形状;
手写视图 JSON 与进程内构造 def 可以。

测试:三个观测面各一条 `visible: false` 钉子(plugin-grid overflow 项、inline
按钮、data-table 菜单项),外加守卫/plan 层三条与「⋮」DOM 两条;每条都配
`visible: true`(声明门、恒真)与 `''`/未声明(不算门)的对照断言 —— 否则
「声明检测」被写成「恒隐藏」时钉子仍会全绿。`predicate-surface-parity` 新增四条
非表达式形状的三面一致用例(此前其文件头注释把这条差异记为「still-open」)。
`RowActionMenu.emptyGuard.test.tsx` 里钉住旧真值行为的那条 fixture 整条替换:
它的期望值(`['ghost']` / `1`)现在是错的verdict,不是拼写问题。

Co-authored-by: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 8, 2026 5:27pm

Request Review

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-DT3Qsxyb.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 481.72KB 106.05KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 9.85KB 3.18KB
data-objectstack (index.js) 139.51KB 35.97KB
fields (index.js) 230.82KB 56.70KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.53KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 117.21KB 30.27KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 232.99KB 57.58KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.10KB 27.10KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 187.74KB 49.69KB
plugin-kanban (index.js) 48.30KB 13.28KB
plugin-list (index.js) 105.12KB 25.48KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

✅ 验收通过(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt)—— undraft + auto-merge。

门位地理更正采纳:派发锚(b1204af)后 PR #3761 已把 RowActionMenu 两门位收敛为共享 isCustomRowActionVisible —— dev 按现状改两处门而非派发词的三处,且因 plan 函数同读该门,「⋮」触发器守卫(#3562)自动一致 —— 对在飞期间基面变化的正确响应。

门语义:声明检测(pred == null || pred === '' → 未声明)+ 布尔交 evalRowActionVisibility 既有短路 —— #3492 不变量(hasVisibilityGate 注释原文)的恢复,非新决定。18 条钉子覆盖 6 观测点 × 3 形状;反向验证 6 红逐条命中,且对称断言 12 条改前改后皆绿被如实标注为护栏而非方向证据(防「声明检测」被写成「恒隐藏」)—— 证据分类纪律到位。前置三面读数(改前:行菜单 true/选择栏 false/页头 false → 改后一致)是收敛的直接证明。

残留不对称(visibleWhen: '' 仍 fail-closed vs visible: '' 不算门)按 issue 正文边界刻意保留并加注防误删 —— 采纳。行为变化面(字面布尔 false 从渲染变隐藏)已在 changeset 声明,可达性论证成立(spec 产不出该形状)。19 项 CI 全绿(dev 复核 + 本验收前独立读数一致);fixture「整条替换」处置合规。衍生 #3812(action renderers 8 处同形真值门,两类可达性)另行判级。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 8, 2026 17:35
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 97b63d7 Aug 8, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3758-visible-false-gate branch August 8, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

行动作声明 visible: false 仍然渲染该菜单项——可见性门用真值判断(#3492 在行菜单上的未收口部分)

2 participants