Skip to content

fix(components,plugin-detail): 动作 disabled 的「已声明」判定剩余五处改读 hasDeclaredVisibilityGate (#3849) - #3861

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3849-disabled-gate-five-sites
Aug 8, 2026
Merged

fix(components,plugin-detail): 动作 disabled 的「已声明」判定剩余五处改读 hasDeclaredVisibilityGate (#3849)#3861
yinlianghui merged 1 commit into
mainfrom
claude/issue-3849-disabled-gate-five-sites

Conversation

@yinlianghui

@yinlianghui yinlianghui commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #3849

基于 origin/main @ 993336f7c481e1fd724512363b8b2f15b305da86(即 #3842 / PR #3851 的合并点)。

落点核对(逐处,不整批假设)

开工前在该 sha 上逐处核过现拼法,issue 正文的五处落点表全部成立,五处现拼法一律是 disabled != null

# 文件:行(基线 sha) 组件 现拼法 enabled
1 packages/components/src/renderers/action/action-icon.tsx:97 action:icon disabled != null
2 packages/components/src/renderers/action/action-group.tsx:106 InlineActionButton disabled != null
3 packages/components/src/renderers/action/action-group.tsx:148 DropdownActionItem disabled != null
4 packages/components/src/renderers/action/action-menu.tsx:98 ActionMenuItem disabled != null
5 packages/plugin-detail/src/renderers/record-quick-actions.tsx:208 QuickActionButton disabled != null

三个 packages/components 文件此前已经导入了 hasDeclaredVisibilityGatevisible 门在 #3812 / #3823 就改过),所以是同一文件同一按钮上 visible 问「已声明」、隔一行的 disabled!= null —— 一个问题两种拼法。record-quick-actions.tsx@object-ui/components barrel 新增导入(#3835 开的跨包路子)。

SchemaRenderer.tsx:323 的第三种拼法(!== undefined)确认存在但不在本单文件面,未动,见下方「越界发现」。

机理(与 #3842 逐字相同,方向是关键)

toPredicateInput('')undefinedevaluateCondition(undefined)true

  • visible 上这个 true 意味着「显示」,与过宽的「已声明」判定互相抵消 —— 所以 visible: '' 前后都渲染,'' 在那一侧不是变异探测器。
  • disabled 上同一个 true 意味着「禁用」,两个错误叠加:空谓词从「没有门」变成「永久置灰」,作者写什么都解不开。

五处统一改读 hasDeclaredVisibilityGate!= null && !== '')。按 #3842 裁定:不重命名、不加别名、不上提 core;包内三处从 ./visibility-gate 相对取;每处落点就地注释说明名称系历史、语义为谓词通用。

行为变更面(刻意窄)

只有 disabled: '' 从「置灰」变「可点」—— 这正是「没有谓词」本来要求的结果。disabled: true 仍置灰,disabled: false 与未声明仍不置灰,表达式取值的 verdict 一律不变。

四处带 legacy enabled 腿的落点多一条推论:空 disabled 现在落到 enabled 腿,而不再在空谓词上短路,所以同时写 disabled: '' + enabled: true 的动作变为可点。record:quick_actions 没有 enabled 腿,它的链条就是单个门。

enabled 腿本身取反(disabled = !isEnabled),空谓词的 true 到达时已是 !true = 「不置灰」,与「没声明门」同 verdict —— 四形状在新旧判定下完全一致,属推演等价而非修复#3842 的四形状推演表连同「没有任何 enabled 用例能因还原该腿而变红」这句话一并抄在钉子旁,如实说明而不包装成覆盖率。

测试

新增两个钉子文件,形状抄 PR #3851action-disabled-declared-gate.test.tsx 保持家族一致:

  • packages/components/src/renderers/action/__tests__/action-member-disabled-declared-gate.test.tsx —— 四处落点 × ('' / true / false / 未声明 / 表达式双向) + enabled 腿五形状,表驱动。宿主按各自真实路径挂载:action:iconaction:bar 的成员方式把整个 action 摊到叶子自己的 schema 上;InlineActionButton 走真实 action:group 宿主;两个下拉叶子放在 controlled-open 菜单里(沿用 action-member-visible-gate.test.tsx 的 harness)。
  • packages/plugin-detail/src/renderers/__tests__/record-quick-actions.disabled-declared-gate.test.tsx —— 真实驱动宿主(useActionEnginelocations 过滤 → 渲染器 map 出 QuickActionButton),useCondition / toPredicateInput 一律用真的,不 stub —— 空谓词链条本身就是被测机理。

判定探针按 DOM 形状区分:原生 button 元素读它的 disabled DOM 属性;Radix DropdownMenuItem 渲染成 role="menuitem" 的 div,禁用时带的是 data-disabled(沿用 action-group-dropdown-visible.test.tsx#1885 立的探针)。

实跑(仓根、flock 串行、--maxWorkers=2):

  • 两个新文件:Test Files 2 passed (2) / Tests 45 passed (45)
  • 消费半径扫面(renderers/action/__tests__ 全量 + components 的 action-bar / action-group + plugin-detail/renderers/__tests__ 全量 + DeclaredActionsBar + actionPredicate.parity + console 的 record-block-record-reach):Test Files 17 passed (17) / Tests 243 passed (243)
  • 末次编辑后全仓 turbo run type-check --concurrency=2Tasks: 78 successful, 78 total

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

预判写在跑之前:单还原 action-icon.tsx 一处的 != null,该处 '' 钉子必须红且点名该处;其余四处与 record:quick_actions 保持绿。

实跑与预判一致 —— 恰好两条红,都在 'action:icon' 名下:

FAIL  'action:icon' — declared `disabled` gate (objectui#3849) :
      an empty-string `disabled` is not a declared gate — the action stays clickable
AssertionError: expected true to be false
FAIL  'action:icon' — legacy `enabled` leg (objectui#3849) :
      an empty `disabled` falls THROUGH to the legacy `enabled` leg
AssertionError: expected true to be false
Tests  2 failed | 43 passed (45)

第二条红是预判到的同一处的另一面:disabled: '' 一旦重新算作「已声明」,链条就在空谓词上短路,作者的 enabled: true 根本没被读到。还原后已复原为 hasDeclaredVisibilityGate

越界发现

packages/react/src/SchemaRenderer.tsx:322-334newSchema.disabled !== undefined(第三种拼法,范围比 != null 再宽一格 —— disabled: null 也会置灰)确认存在于 origin/main,不在本单文件面,本 PR 未动。已立单 #3862(未认领,附 file:line 取证:该值经 SchemaRenderer.tsx:461disabled: __disabled || undefined 转发成组件 prop,不是死标记;修法涉及「共享定义放哪一层」的跨包裁定,建议与 #3850 同批裁),交 PM 分诊。


Generated by Claude Code

…VisibilityGate (#3849)

#3842 / PR #3851 只修了 action:button 与 app-shell 的 DeclaredActionsBar,同形
的另外五处仍停在 `!= null`:同一个组件里 visible 门问「已声明」,隔一行的
disabled 门问 `!= null`,一个问题两种拼法。

- packages/components:action:icon、action:group 的内联按钮与下拉项、
  action:menu 的菜单项(四处,均含 legacy `enabled` 腿)
- packages/plugin-detail:record:quick_actions 的 QuickActionButton(无
  `enabled` 腿)

机理与 #3842 逐字相同:`toPredicateInput('')` 为 `undefined`,
`evaluateCondition(undefined)` 为 `true`。这个 `true` 在 visible 上意味着「显示」
(与过宽的「已声明」判定互相抵消),在 disabled 上意味着「禁用」——于是两个错误
叠加,`disabled: ''`(空谓词,等于没声明)变成永久置灰,作者写什么都解不开。

五处统一改读 hasDeclaredVisibilityGate(`!= null && !== ''`):包内三个文件从
`./visibility-gate` 相对取,plugin-detail 从 @object-ui/components barrel 取
(#3835 开的跨包路子)。按 #3842 裁定不重命名、不加别名、不上提 core,每处落点
就地注释说明。

行为变更面刻意窄:只有 `disabled: ''` 从「置灰」变「可点」。`disabled: true` 仍
置灰,`disabled: false` 与未声明仍不置灰,表达式取值的 verdict 一律不变。四处带
legacy `enabled` 腿的落点多一条推论:空 `disabled` 现在落到 `enabled` 腿而不再在
空谓词上短路。`enabled` 腿本身取反,四形状在新旧判定下同 verdict,属推演等价而非
修复(#3842 的推演表连同「无任何 enabled 用例能因还原该腿而变红」一并抄在钉子旁)。

反向验证:单还原 action-icon 一处的 `!= null` 后,恰好两条钉子变红且都点名
action:icon(`disabled: ''` 与「空 disabled 落到 enabled 腿」),其余四处与
record:quick_actions 全绿——方向与 issue 的预判一致。
@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 10:07pm

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--Vewblue.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) 482.39KB 106.34KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
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) 236.17KB 58.82KB
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.63KB 49.66KB
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

✅ 验收(PM,session session_01GTRjn8xBqp75dk7kFupVRt)

实物核验:origin/claude/issue-3849-disabled-gate-five-sitesd10d1a20e,单提交 7 文件 +433/−10;五处源 diff 逐行读过 —— 全部改读 hasDeclaredVisibilityGate,不重命名不加别名不上提 core,包内相对取、plugin-detail 走 barrel(#3835 路子);trailer 0 命中;与在飞 #3546s6/#3735/#3746/#3741 零相交。
CI 终态:19 检查全部 completed —— 17 success + 2 skipped,零失败。

裁定要点:

转 ready 并挂 auto-merge。#3862 归分诊席定级。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 8, 2026 22:19
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit f0a625a Aug 8, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3849-disabled-gate-five-sites branch August 8, 2026 22:20
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.

disabled 的「已声明」判定还有五处停在 != null(action-icon / action-group ×2 / action-menu / record-quick-actions),#3842 只修了两处

2 participants