Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

Cross-page "Select all N matching" can never appear in the console — totalMatching is only written by ObjectGrid's own loader, which never runs under ListView's external pagination #4464

Closed

Description

@baozhoutao

Found by a retest of records-forms.bulk-select-all-matching (framework QA run objectstack-ai/objectstack#7439) against the console at pin 6314e87f2d49. The fix under retest — #4140 (the selectionResetKey bump) — is verified working; this is a separate, pre-existing gap that the retest was able to reach for the first time.

Symptom

Selecting an entire page in a genuinely server-paginated grid shows only N selected | Delete | Clear. The cross-page banner never renders: [data-testid=bulk-cross-page-banner] and [data-testid=bulk-select-all-matching] are both absent, for any match-set size. Reproduced on two fresh loads.

Reproduction

  1. As admin, author a paginated view:
    PUT /api/v1/meta/view/showcase_contact.rt5_paged with
    config {type:'grid', data:{provider:'object',object:'showcase_contact'}, filter:[{field:'account',operator:'equals',value:'<Northwind id>'}], pagination:{pageSize:10}}
  2. Open /_console/apps/com.example.showcase/showcase_contact?view=showcase_contact.rt5_paged.
    The grid pages server-side for real — GET /api/v1/data/showcase_contact?top=10&filter=… → total=26, 10 rows, count bar "26 records", pager "Page 1 of 3".
  3. Click the header select-all checkbox to select the whole visible page.

Expected: BulkActionBar shows "All 10 on this page are selected." plus a "Select all 26 matching" button.
Actual: no banner — only 10 selected | Delete | Clear.

Root cause

BulkActionBar gates the banner on:

const showCrossPageAffordance =
  !allMatchingSelected &&
  typeof totalMatching === 'number' &&
  typeof pageSize === 'number' && pageSize > 0 &&
  selectedRows.length >= pageSize &&
  totalMatching > pageSize;

ObjectGrid passes totalMatching={singleSelection ? undefined : totalMatching}, and the only writer of that state is setTotalMatching(...) inside ObjectGrid's own data loader (ObjectGrid.tsx ~:710).

In the console the grid is hosted by ListView, which fetches rows itself and passes manualPagination:true, rowCount:serverTotal, page, pageSize, onPageChange down (plugin-list/src/ListView.tsx ~:2859-2864). So externalManualPagination is true, ObjectGrid's loader never runs, totalMatching stays undefined, and the banner condition is permanently false.

The tell that the data is right there: the pager shows the correct page count because it reads
manualRowCount = externalManualPagination ? rest.rowCount : totalMatching
— the grid has the real total on the external path; it just never reaches BulkActionBar.

Suggested fix: fall back to rest.rowCount when externalManualPagination is on.

This is the same shape as #4138 (fixed): a feature gated on internal-data state that the console's external-data path never populates. Worth a sweep for other totalMatching-style state with a single internal writer.

Consequence for the checklist item

With the affordance unreachable, three further clauses cannot run at all: the collected-id set can't be compared against the filtered API count, the aggregate-vs-per-record distinction over the matching set is undefined, and the HARD_CAP (5000) path is doubly out of reach. Only the clause #4140 addressed is green.

Two scope notes, stated plainly

Fixture correction worth recording

The previous run recorded these clauses blocked(fixture) believing no object exceeds the page window. Half of that is true — the stock console fetches top=100 and showcase_contact's 32 rows fit one page — but the blocker is not a hard 100-row limit: authoring a view with config.pagination.pageSize turns on real server-side paging, which is what made this defect reachable. (The probe view was deleted afterwards; no repo file was touched.)

Source

Retest of records-forms.bulk-select-all-matching at console pin 6314e87f2d49, framework b602d536.

Activity

  1. self-assigned this
    on Aug 12, 2026
  2. yinlianghui commented on Aug 12, 2026

    @yinlianghui
    Contributor

    CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4464-crosspage-totalmatching.

    Ruling (delegated authority; open veto window): fix as the card diagnoses — the same #4138 shape (feature gated on internal-loader state the external path never populates), and the card's own tell is the design: the pager already reads manualRowCount = externalManualPagination ? rest.rowCount : totalMatching. One derived value, one answer: hoist that exact derivation so the pager AND BulkActionBar's totalMatching consume the SAME resolved total — do not write a second conditional beside the first (two copies of the fallback is how the next #4464 happens). singleSelection ? undefined : … gating stays.

    Test half — the card names the trap: ObjectGrid-in-isolation runs the internal loader, precisely the path the console does not take, so unit tests on ObjectGrid alone are structurally blind here. The red-first pin is a ListView-hosted composition test (new file in packages/plugin-list/src/__tests__/): external server pagination (manualPagination, rowCount > pageSize), select the whole visible page → pre-fix [data-testid=bulk-cross-page-banner] and [data-testid=bulk-select-all-matching] absent (capture verbatim); post-fix both present with "Select all N matching" carrying the server total. Must-not-change: the internal-loader path (standalone ObjectGrid) still shows the banner (green both sides); singleSelection still suppresses; selection short of a full page still shows no banner.

    Census (report-only, the card's suggested sweep): grep ObjectGrid for other internal-loader-only state consumed by features reachable on the external path (the #4138 class) — LIST them in the report, fix nothing extra.

    Grading: patch @object-ui/plugin-grid (runtime wiring only; .d.ts-checked both ways). Never major.

    Surface (mutual exclusion): packages/plugin-grid/src/ObjectGrid.tsx + one NEW composition test file in packages/plugin-list/src/__tests__/ + one changeset. ⛔ NOT: plugin-list/src/ListView.tsx SOURCE (in-flight #4408/PR #4495 owns it — your composition test imports it read-only), BulkActionBar's own component file unless measurement demands (report first), chart files (#4466), plugin-calendar (#4492), the sweep file (#4434). If the derivation hoist genuinely requires a ListView.tsx edit, STOP and report.


    Generated by Claude Code


    Generated by Claude Code

  3. added a commit that references this issue on Aug 13, 2026
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