Skip to content

fix(plugin-grid): over the 5000-record cap, the select-all-matching bulk bar says only the first 5000 matching records will be processed - #28606

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-u5if71
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-u5if71

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #28552

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:列表「选择全部匹配记录」的批量栏,在匹配数超过 5000 时,横幅、「选择全部」按钮、已选计数和选区右键菜单都改为点名上限,例如「将处理前 5000 条(共匹配 7200 条记录)。」;5000 及以下文案不变。上限与实际取数(resolveBulkRows)读同一个常量 BULK_SELECT_ALL_MATCHING_CAP。
  • 为什么改:维护者裁决([Decision] wave-3 checklist: five held items need a ruling before their ledger edits (#27377) #28345 第 ④ 条)。原来横幅按服务器总数说「已选择全部 7200 项」,批量删除实际只处理前 5000 条,屏幕上什么都不说,是破坏性路径上的静默部分执行。
  • 风险与回滚:低。只改 plugin-grid 的三处文案选择和十个语言包各加三条 grid.* 文案;取数逻辑、上限数值、接口都不变。回滚即还原本 PR。
  • 先看:ui/packages/plugin-grid/src/components/BulkActionBar.tsx(overCap 三处分支)、ui/packages/plugin-grid/src/bulkSelectAllMatchingCap.ts、BulkActionBar.selectAllMatchingCap-28552.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-grid/src/__tests__/BulkActionBar.selectAllMatchingCap-28552.test.tsx packages/plugin-grid/src/__tests__/bulkFanoutHostParams.test.tsx,预期 2 个文件、16 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

  • One constant. ui/packages/plugin-grid/src/bulkSelectAllMatchingCap.ts holds BULK_SELECT_ALL_MATCHING_CAP (5000) and exceedsBulkSelectAllMatchingCap(totalMatching). resolveBulkRows in ObjectGrid.tsx stops its paged fan-out at that constant (it was a local literal), and every string below reads the same constant. The module is not exported from the package entry.
  • Over the cap, the bar names it (BulkActionBar): the select-all button reads grid.bulkSelectAllMatchingCapped ("Select the first 5000 of 7200 matching"), the banner after selecting reads grid.bulkAllMatchingSelectedCapped ("Only the first 5000 of 7200 matching records will be processed."), and the count chip reads grid.bulkSelectedAllMatchesCapped ("5000 selected (of 7200 matches)").
  • The selection's context menu (SelectionContextMenuItems in RowContextMenu.tsx) counted totalMatching with the same grid.bulkSelectedAllMatches key; over the cap it now reads the same capped key. This is the same defect on the second surface that renders the count, so it is fixed here.
  • At or under the cap, nothing changes: the three existing keys, with count, as before.
  • Strings (@object-ui/i18n, grid namespace, all ten built-in packs): three new keys interpolating cap and total. They take no count, so they are not i18next count families. In ru and ar the total stands alone (in parentheses or after a preposition with no noun) so no noun has to agree with it; the cap is always 5000.
  • One root changeset: @object-ui/plugin-grid patch, @object-ui/i18n patch. No packages/spec/**, no server change, no metadata key.

Why

Ruling 6545938831 on #28345, item ④, verbatim: 「bulk-select-all-matching: file a product defect card — the banner names the cap (e.g. 「将处理前 5000 条」); the checklist gains one criterion pinning it.」 The checklist criterion rides #28551, not this PR.

Clause-② reading (measured on the built declaration closure, objectstack a2e75562, turbo run build --filter='@object-ui/plugin-grid^...', 49/49 tasks): plugin-grid exports no language pack (its package.json has only the . entry). The grid strings live in @object-ui/i18n, which publishes ./locales and ./locales/* and re-exports en and type TranslationKeys (typeof en) from its . entry; the built dist/locales/en.d.ts declares each leaf as a readonly string literal. So the three new keys widen the published declaration of en and TranslationKeys: Clause-②: yes (widening), as amended on the card's Claim. Nothing is narrowed and no metadata author surface moves: Metadata-protocol: none, patch.

Tests

  • New ui/packages/plugin-grid/src/__tests__/BulkActionBar.selectAllMatchingCap-28552.test.tsx, nine cases, strings read from the packs and the cap from the constant:
    • the constant is the 5000 the fan-out pin already holds;
    • over the cap (7200 matches, a 50-row page): the button reads the capped key and not today's; after selecting, the banner reads the capped key and contains the cap, and the chip reads the capped key; under zh the banner reads the zh pack; the selection's context menu reads the capped key;
    • exactly at the cap (5000) and under it (1200): the button, the banner and the chip read today's three keys.
  • bulkFanoutHostParams.test.tsx, "still honours HARD_CAP over a match set larger than the cap": in the same ObjectGrid run that delivers 5000 of 6000 records to the bulk delete, the banner now reads the capped key filled with BULK_SELECT_ALL_MATCHING_CAP and 6000, and the delivered length equals the constant. So the number shown and the number delivered are pinned in one run.

Acceptance notes

  • The numbers are shown unformatted (5000, not 5,000), the same as every existing count in these strings.
  • The banner is only reachable when the host passes find params (canOfferSelectAllMatching), unchanged.

Verification

All at head a9224125 (this branch merged origin/main cd065d36; the merge touched none of this diff's packages, and the delta against main is still these 17 files, +246 / -15), in a worktree, every build and test under scripts/pm/os-verify-lock.sh.

Tests, ablations, type-check, lint
  • New file alone, verbose: Tests 9 passed (9).
  • Ablation 1 (node scripts/ablation-replace.mjs, BulkActionBar.tsx, anchor const overCap = exceedsBulkSelectAllMatchingCap(totalMatching); replaced by const overCap = false && exceedsBulkSelectAllMatchingCap(totalMatching);, anchor x1 to x0) over the new file and bulkFanoutHostParams.test.tsx: Tests 4 failed | 12 passed (16), the three bar cases over the cap and the fan-out case; the context-menu case stays green because it reads the cap on its own. Restored: blob equal to HEAD.
  • Ablation 2 (RowContextMenu.tsx, anchor ? exceedsBulkSelectAllMatchingCap(totalMatching) replaced by ? false && exceedsBulkSelectAllMatchingCap(totalMatching)) over the new file: Tests 1 failed | 8 passed (9), the context-menu case. Restored: blob equal to HEAD.
  • pnpm --dir ui exec vitest run packages/plugin-grid/ (before the merge of main, at 08c376a5): Test Files 270 passed (270), Tests 2349 passed (2349).
  • pnpm --dir ui exec vitest run packages/i18n/ (at 08c376a5): Test Files 94 passed (94), Tests 1438 passed | 13 skipped (1451); this includes count-families-11445 and all-locales-key-parity.
  • After the merge, at a9224125: the new file, bulkFanoutHostParams, BulkActionBar and rowContextMenu-28434: Test Files 4 passed (4), Tests 41 passed (41).
  • pnpm --filter @object-ui/plugin-grid run type-check && pnpm --filter @object-ui/i18n run type-check: VERDICT command-exit 0; plugin-grid's tsconfig.test.json lists both touched test files.
  • eslint on the five plugin-grid files and the en and zh packs: 0 errors; the 30 warnings are existing anys, and this diff adds no any.
  • pnpm check:new-line-citations (from ui/): 0 new citation(s).
Derived dispatch gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 35 commands over the 17 paths, the same list before and after the merge of main; every one exited 0, the 29 that are neither a suite nor a PR-body gate re-run at a9224125. --ran: 35 derived, 35 run, 0 NOT-MEASURED. The PR-body gates (check-closing-keyword-parity, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main) ran against this body through GITHUB_EVENT_PATH.
  • pnpm --dir ui exec vitest run scripts/ (at 08c376a5; the merge of main changed no ui script): Test Files 169 passed | 2 skipped (171), Tests 4900 passed | 2 skipped (4902).
  • node scripts/check-sdui-manifest.mjs: present and intact (106 components); CI's Build & E2E UI regenerates and compares it.
  • pnpm check:type-check-debt (at a9224125): 0 raw tsc error(s) total, none above its recorded number.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jTrUjyA3Jhqgsi86rz


Generated by Claude Code

…ord cap when the match set is larger

resolveBulkRows and the bulk bar now read one constant,
BULK_SELECT_ALL_MATCHING_CAP. Over it, the select-all button, the banner,
the count chip and the selection's context-menu label name the cap and the
total; at or under it they keep today's text. Three new grid.* strings in
all ten packs.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTrUjyA3Jhqgsi86rz
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Nov 1, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

Contract review

Served-tier: TIER_DEFAULT
Head-sha: a92241259f9571a14f9ff1503effbe1c1fde0095
Local-runs: none

① Derived judgments

  • Face: the Clause-②: yes (widening) declaration alone. The committed tree touches no packages/spec/src/** and no governed path (17 files: .changeset/, ui/packages/i18n/src/locales/*.ts, ui/packages/plugin-grid/src/**).
  • Published surface moved: @object-ui/i18n gains three leaves under grid in every pack — bulkSelectAllMatchingCapped, bulkAllMatchingSelectedCapped, bulkSelectedAllMatchesCapped — each interpolating cap and total. That widens the published en and TranslationKeys (typeof en). Correct as declared: additive, no key removed or renamed, no existing value changed.
  • @object-ui/plugin-grid: the new module bulkSelectAllMatchingCap.ts is not re-exported from the package entry, so BULK_SELECT_ALL_MATCHING_CAP and exceedsBulkSelectAllMatchingCap are internal. No prop, parameter or return type of an exported component changes (BulkActionBar and SelectionContextMenuItems keep their props; totalMatching was already a prop). No accept set moves.

② Semver level

  • Changeset: @object-ui/plugin-grid patch, @object-ui/i18n patch, with Clause-②: yes (widening) and Metadata-protocol: none line-initial in the changeset body and the PR body. none takes patch (AGENTS.md Post-Task Checklist step 3): consistent.

③ Boundary flags

  • The dev raised one flag: the Clause-② reading, measured on the built declaration closure at a2e75562; the seat amended the claim to match before pr_create. Answered.
  • No open_questions. PR author is objectstack-fleet[bot] (relay write).

Implemented-by: claude/project-thread-u5if71
Reviewed-by: session_01QsR2jR7ZFkpqmm4U8S1f9y

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

席位核对 · ACCEPT · 2026-11-01T08:02Z

Head-sha: a92241259f9571a14f9ff1503effbe1c1fde0095

  • Shape: draft → main, first content line Fixes #28552, no other card number beside a closing keyword. 17 files, +246 / −15, no governed path (check-governed-merges --pr 28606: 0 of 17 hit the register; 261 lines ≤ 3000). Author objectstack-fleet[bot]. Declaration lines Clause-②: yes (widening) / Metadata-protocol: none line-initial in the body and the changeset, as amended on the card.
  • Premise, re-measured: at origin/main resolveBulkRows held a local HARD_CAP = 5000 and the three banner strings read the uncapped totalMatching. The diff makes the fan-out and every capped string read one constant (bulkSelectAllMatchingCap.ts, not re-exported). The zh banner reads 「将处理前 5000 条(共匹配 7200 条记录)。」 once filled, the ruling's example shape (6545938831 ④).
  • Gates (check-runs on this head): 44 success, 11 skipped, 0 failed or pending, including all ten required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Governed Surface Queue Guard, Lint UI, Type Check UI, Test UI, Build & E2E UI, Live E2E UI success; Build Core skipped by its path filter on a ui-only diff).
  • Accepted deviation: the selection context-menu label (RowContextMenu.tsx SelectionContextMenuItems) is fixed in the same PR. Same defect class (the count reads uncapped totalMatching with the same key), mechanical, inside the claimed ui/packages/plugin-grid/** surface, unclaimed elsewhere, same gate family, and named with its test in the PR body.
  • Changeset sentences checked against the diff: "With 7200 matches it read "All 7200 matching records are selected."" (the old grid.bulkAllMatchingSelected path); "the select-all button reads "Select the first 5000 of 7200 matching"" (en.grid.bulkSelectAllMatchingCapped); "The fan-out and every one of those strings read one constant" (ObjectGrid.tsx HARD_CAP = BULK_SELECT_ALL_MATCHING_CAP). All three hold.
  • Out of scope: none reported. Skills-docs: not owed (kind none, no narrowing arm).
  • Contract review: TIER_DEFAULT PASS on this head, comment 6555150955.
  • Landing: ordinary queue landing — pr_ready then automerge_enable. Fixes #28552 closes the card on merge; the seat clears pm:dispatched and the stale finding label in the same stroke.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 09:04
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 09:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit d5da45b Nov 1, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-u5if71 branch November 1, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

grid: "select all matching" banner counts every match, but the bulk action processes only the first 5000

1 participant