… it grouped a page
`useGroupedData` buckets the rows the browser already holds and computes every
per-group aggregate from that same array, so both the set of groups and every
number in a group header are properties of the fetched page, not of the query.
The algorithm is correct for what it does and is untouched here; what was
missing is any statement that client-side grouping is what you are looking at.
Measured on today's `main` (a6d8b8d) over a 186-record store distributed
86/61/31/7/1 across five business units with a 100-row page: contiguous rows
rendered TWO group headers (86, 14) with three units absent from the screen
entirely; interleaved rows rendered all five with every count a page slice
(31/31/30/1/7). The document contained no "186", no "partial", no "loaded".
The disclosure goes where the authoritative-looking number is: a short
`Partial` marker beside every group count at every depth, carrying the whole
sentence as its title and accessible name, plus one line directly above the
group list inside the grouped region — not in the paging footer, which is not
a statement about what was grouped.
The trigger never outruns what the component knows. A real match total
(`resolvedTotalMatching`, the one derived value the pager and both bulk-bar
sites already read) states the fact with both numbers; a full window with no
total may only say "more may match", the same inference plugin-list's own
footer draws. Inline rows are not a page and are never marked, and a grouped
grid whose result set fits in one page shows nothing at all.
Server-side grouping is deliberately out of scope — an API-surface decision
still open on objectui#5560. Nothing here changes the fetch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
Part of #7189 — the disclosure half only.
A grouped grid now states, where the group counts are, that it grouped a page.
The card's numbers were stale, so here is today's
mainobjectui#7179 landed as
a6d8b8d44after the card was written, and it changedwhat the card measures: the projection now carries the grouping field, so the
groups resolve and the
(empty)collapse is gone. The card's table (fourgroups for five units, 33 / 3 / 46 / 18) cannot be reproduced and nothing
here was built against it.
Re-measured on
a6d8b8d44— 186 records over five business units sized86 / 61 / 31 / 7 / 1,
$top: 100,$selectconfirmed to carrybusiness_unit:In both, the document contained no
186, no "partial", no "loaded". Whichshape you see is a property of row ordering, not of the tree — the defect is
the same one either way, and the disclosure has to be true of both.
The paging footer the card quotes is not the grid's:
Showing first N records. More data may be available.lives inpackages/plugin-list/src/ListView.tsx(list.dataLimitReached), a differentcomponent in a different package. A standalone grouped grid renders no footer
at all.
What this adds
useGroupedData's grouping and aggregation algorithm is untouched — it iscorrect for what it does, and the counts above are still page slices
afterwards. What was missing is any statement that client-side grouping is
what you are looking at.
Partialmarker beside every group count, at every nestingdepth, carrying the whole sentence as its
titleand its accessible name;than in the paging footer, which is not a statement about what was grouped.
Is a result-set total reachable where the group headers render? Yes
resolvedTotalMatching(ObjectGrid.tsx:3769) is in scope at the grouped rendersite (~4688 / ~4755), and it is the same ONE derived value the pager and both
bulk-bar sites already read — deliberately not re-spelled here, per the note
objectui#4464 left on it. It resolves from the grid's own
result.totalwhenthe grid owns the fetch, and from a host's
rowCountwhen a host does. Bothpaths are pinned.
So the strong wording is supported, and the trigger never outruns what the
component knows:
plugin-list's own footer draws when no total is knownThat last row is the point of the whole thing: a marker that is always on says
nothing.
Deliberately NOT here
Server-side grouping. It is the card's own preferred durable fix, an
API-surface decision open on #5560, and out of scope for this branch: nothing
builds toward it, no flag anticipates it, the fetch is unchanged. That half of
#7189 stays open.
No metadata contract was widened
No schema key was added — the condition is derived from data the grid already
holds.
GroupRow(exported from this package) gains two optional props,partialLabelandpartialTitle;@objectstack/specis untouched.Tests — 13 new pins, and an ablation in both directions
packages/plugin-grid/src/__tests__/groupedPartialDisclosure-7189.test.tsx.Prediction was written before either leg ran and both matched exactly.
groupingIsPartial = false(disclosure removed)groupingIsPartial = isGrouped(marker unconditional)Leg B goes red on the controls only — fits-in-one-page, inline rows,
short window — which is what makes the eight positive pins worth anything.
Each leg: mutation proved on disk by injected-marker line count (1) and
removed-expression line count (0) and a blob hash moved off
HEAD:packages/plugin-grid/src/ObjectGrid.tsx(
eaab121121273aea4e6304d96d82f6fa0da6b06d); restore bygit checkout HEAD -- ABSOLUTE_PATHand proved by state —git diff HEAD,git diff --cachedandgit status --shortall empty and the blob hash backto HEAD's — with the restore trapped on
EXIT INT TERM. No rebuild was neededor performed:
vitest.config.mts:288aliases@object-ui/plugin-gridtopackages/plugin-grid/src, verified by reading the alias table.jsdom caveat, closed rather than assumed. jsdom applies CSS media-query
rules irrespective of
innerWidth, so a width-dependent reading in it is not ameasurement. This disclosure is not width-dependent: it carries no responsive
visibility utility, and grouping never reaches the width-driven branch —
if (useCardView && data.length > 0 && !isGrouped). The last pin assertsthat gate in source rather than trusting the reading.
Verification, all at
1d8fd9e65(the final commit)pnpm exec vitest run packages/i18n/ packages/plugin-grid/— 166 files,1912 tests, all passed. Includes
all-locales-key-parity, which owns theten-pack key sets the three new
grid.grouping.*strings had to satisfy.turbo run type-check --filter @object-ui/plugin-grid --filter @object-ui/i18n— 15/15 tasks.tsc -p tsconfig.test.json --listFilesconfirms the new test file and both edited sources are in that program, so
"typecheck clean" actually covers them.
@object-ui/plugin-grid(namedexplicitly, because the
...prefix means opposite things in pnpm andturbo): app-shell, console, plugin-designer, plugin-report, plugin-view,
plugin-list —
turbo run type-check, 41/41 tasks.check:i18n-keys,check:i18n-drift(0 en values changed, 3 keys added),check:control-bytes,check:doc-types,check:doc-fences,check:changeset-presence— all exit 0 at1d8fd9e65..ts/.tsxfiles: 0 errors; thenew test file is 0 errors / 0 warnings. Type-aware linting is not enabled in
eslint.config.js(noparserOptions.project), so this diff cannot move averdict in a file it did not touch — the narrowing is a measurement, not a
skip. The repo-wide scan is CI's.
check:doc-snippetsandcheck:readme-exports. Both exit on an explicit precondition — 21packages unbuilt in a fresh worktree — and both judge only fenced code
blocks. This diff adds zero fenced blocks to any document (measured:
zero added lines carrying a fence). CI builds and runs them.
Changeset
@object-ui/plugin-gridand@object-ui/i18n, bothminor. The docs editsride along in the same PR, so no separate empty-frontmatter changeset is owed —
that form is for a docs-only change, where a
patchwould falsely claim areleased package moved.
Generated by Claude Code