Skip to content

fix(grid): 批量删除 / 按名字派发也要清掉行复选框 (#3056) - #3058

Merged
os-zhuang merged 1 commit into
mainfrom
claude/bulk-selection-desync
Jul 30, 2026
Merged

fix(grid): 批量删除 / 按名字派发也要清掉行复选框 (#3056)#3058
os-zhuang merged 1 commit into
mainfrom
claude/bulk-selection-desync

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #3056.

症状

批量删除成功后(或按名字派发到消费方注册的 runner handler 后),选择栏消失了,但每行复选框还全勾着。用户面对的是「一堆勾选的行 + 没有任何批量操作入口」,只能逐行取消或刷新页面。

根因

ObjectGrid 有两套必须同步移动的选择状态:

  • selectedRows(自己的 state)→ 驱动 BulkActionBar
  • data-table 内部的 selectedRowIds → 驱动行复选框,只有宿主 bump selectionResetKey 时才清空

handleBulkDialogClose(富 def 那条路)两个都清;dispatchBulkAction 只清了第一个,两条分支都漏——走 onBulkDelete 的删除分支,和按名字派发的分支。

这个不变量其实早就写在 handleBulkDialogClose 的注释里了,只是另一条路径没遵守:

Both selection sources must move together, or the checkboxes (table-internal) and the toolbar (our selectedRows) drift out of sync — ticked rows with no toolbar.

改动

三处调用点统一走一个 resetSelection() helper,把规则做成结构性的,而不是靠三个地方各自记得保持一致。失败语义没动:action 返回 success: false 时两套状态都保留,用户可以修好原因后重试同一批行。

验证

  • 反向对照:只回退 ObjectGrid.tsx 后,两条新增的正向断言全部失败——按名字 action 成功后、批量删除后,header 复选框仍是 checked(应为 unchecked)。
  • 新增 3 条测试:按名字成功清空、按名字失败保留(防止矫枉过正)、批量删除清空。
  • plugin-grid 44 文件 / 354 测试全绿;改动文件 0 lint error。

注:#3002 之后这条路径变窄了——能解析到对象 action 的名字现在会被提升成 def 走对话框,所以仍然到达 dispatchBulkAction 的是消费方注册的 handler 和内建的 'delete'。删除是其中最常见的一条。

🤖 Generated with Claude Code

…oo (#3056)

`ObjectGrid` carries two selection sources that must move together:
`selectedRows` (ours — drives the toolbar) and the data-table's internal
`selectedRowIds` (drives the checkboxes, cleared only when the host bumps
`selectionResetKey`). `handleBulkDialogClose` reset both; `dispatchBulkAction`
reset only the first, on BOTH of its branches — the bulk-delete route through
`onBulkDelete` and the by-name runner dispatch.

So a successful run left the toolbar gone and every row still ticked, with no
affordance to act on them. The invariant was already written down in
`handleBulkDialogClose`'s comment ("ticked rows with no toolbar"); it just was
not upheld on the other path.

All three call sites now go through one `resetSelection()` helper, so the rule
is structural instead of three places remembering to agree. Failure semantics
are unchanged: an action reporting `success: false` keeps both sources so the
user can retry the same rows.

Reverse-control: with only ObjectGrid.tsx reverted, the two new positive
assertions fail (checkboxes stay 'checked' after a successful by-name action
and after a bulk delete).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 30, 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 Jul 30, 2026 2:39pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
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.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
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) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
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) 458.88KB 100.29KB
core (index.js) 2.16KB 0.78KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 134.67KB 34.24KB
fields (index.js) 222.07KB 54.35KB
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.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.45KB 10.67KB
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.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.76KB 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 (retry.js) 3.48KB 1.61KB
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.90KB 12.35KB
plugin-charts (index.js) 60.52KB 17.11KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 111.59KB 28.74KB
plugin-designer (index.js) 210.51KB 42.50KB
plugin-detail (index.js) 221.42KB 54.12KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 106.54KB 25.88KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 183.80KB 48.21KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 102.39KB 24.18KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 40.32KB 10.53KB
plugin-timeline (index.js) 25.75KB 7.32KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.95KB 21.02KB
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
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 3.47KB 1.54KB
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 (index.js) 2.07KB 0.99KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
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.04KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 1.08KB 0.64KB
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

@os-zhuang
os-zhuang merged commit a149e90 into main Jul 30, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/bulk-selection-desync branch July 30, 2026 14:46
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.

Bulk delete / by-name bulk action clears the toolbar but leaves the row checkboxes ticked

1 participant