Blocked-by: objectstack-ai/objectstack#14556
RULED 2026-09-02 (maintainer via the director seat, batch #7, verbatim 「7189 A 其他同意」, comment 5509036555): option A — grouping on a list view is server-side. The set of groups and every number in a group header are properties of the query, not of the fetched page; rows inside a group are paged. Three halves in order: spec (objectstack#14556, domain:spec), platform (blocked on the spec half, tracked there), then this card (plugin-grid consumes server group headers, useGroupedData stops grouping the page, the grouped exclusion in useServerPagination goes, the "Partial" marker from PR #7226 is retired once counts are server-true, the "Grouping is page-scoped" docs section is rewritten). Acceptance: the 186-row, five-unit, $top: 100 fixture renders five headers reading 86/61/31/7/1 regardless of row order; every row is reachable through the UI.
Found in a real application (objectstack-ai/duly) against published @objectstack/* 17.2.0, on a grid grouped by a lookup with 186 rows across 5 distinct values. A capability gap rather than a defect in the current code — the client-side implementation is correct for what it does; nothing tells an author it is page-scoped, and the failure is silent.
Measured
A grid with grouping: { fields: [{ field: 'business_unit' }] }, default page size 100, over a store holding 186 rows distributed 86 / 61 / 31 / 7 / 1 across five units:
Business unit ▸ Northgate Operations 33
Business unit ▸ Northgate Plant 3
Business unit ▸ Northgate Quality 46
Business unit ▸ Riverside Plant 18
───
100 "100 records · Showing first 100 records."
Four groups for five units. One business unit does not appear at all, and nothing on screen says a group is missing. The counts shown are page slices, not totals — the largest unit holds 86 rows in the store and its header reads 46.
Where it comes from
packages/plugin-grid/src/useGroupedData.ts groups over data, the rows already in the browser, and computes per-group aggregations from that same array:
constagg=aggregations&&aggregations.length>0
? computeAggregations(entry.rows,aggregations)
: [];
So both the set of groups and every number in a group header are properties of the page, not of the query. That is a reasonable implementation of client-side grouping; the problem is that nothing distinguishes it from the server-side kind at the point where an author writes grouping: {…}.
Why it reads as data rather than as a bug
The footer does disclose "Showing first 100 records. More data may be available." But the number a reader treats as authoritative is the one in the group header, next to the group's name — and a missing group is worse than a wrong count, because a wrong number invites a second look and an absent row invites none. On a grid used as an org lens ("what is outstanding, per business unit"), a unit silently absent is a wrong answer that looks like good news.
This is the same shape as objectui#7179, filed from the same app: nothing errors, the screen is internally consistent, and the only symptom is that the answer is wrong.
Suggested direction
Server-side grouping is the durable fix, and the platform demonstrably has the capability already — the same app's dashboard groups the same field over the same data through a dataset and gets true totals. It is the grid surface that lacks it.
Short of that, two things would each remove the silent half:
- Say it on screen. A grouped grid whose result set exceeds the page should mark the grouping as partial in the group header area, not only in the row-count footer. Even "grouped over the first 100 of 186" would prevent the wrong reading.
- Name it at author time.
grouping on a paged grid is a different feature from what its name implies. If it is going to stay client-side, the schema doc should say the groups and counts are page-scoped, so an author can decide whether that fits before shipping the view.
Application impact
Low urgency for us — we are scoping the affected lens to open work, which fits one page at this product's realistic scale and is a better lens anyway. But that is a workaround at our data size, not a fix: a deployment with more open rows than a page hits it again, silently, and we have no way to detect it from inside the app.
Blocked-by: objectstack-ai/objectstack#14556
Found in a real application (
objectstack-ai/duly) against published@objectstack/*17.2.0, on a grid grouped by a lookup with 186 rows across 5 distinct values. A capability gap rather than a defect in the current code — the client-side implementation is correct for what it does; nothing tells an author it is page-scoped, and the failure is silent.Measured
A grid with
grouping: { fields: [{ field: 'business_unit' }] }, default page size 100, over a store holding 186 rows distributed 86 / 61 / 31 / 7 / 1 across five units:Four groups for five units. One business unit does not appear at all, and nothing on screen says a group is missing. The counts shown are page slices, not totals — the largest unit holds 86 rows in the store and its header reads 46.
Where it comes from
packages/plugin-grid/src/useGroupedData.tsgroups overdata, the rows already in the browser, and computes per-group aggregations from that same array:So both the set of groups and every number in a group header are properties of the page, not of the query. That is a reasonable implementation of client-side grouping; the problem is that nothing distinguishes it from the server-side kind at the point where an author writes
grouping: {…}.Why it reads as data rather than as a bug
The footer does disclose "Showing first 100 records. More data may be available." But the number a reader treats as authoritative is the one in the group header, next to the group's name — and a missing group is worse than a wrong count, because a wrong number invites a second look and an absent row invites none. On a grid used as an org lens ("what is outstanding, per business unit"), a unit silently absent is a wrong answer that looks like good news.
This is the same shape as objectui#7179, filed from the same app: nothing errors, the screen is internally consistent, and the only symptom is that the answer is wrong.
Suggested direction
Server-side grouping is the durable fix, and the platform demonstrably has the capability already — the same app's dashboard groups the same field over the same data through a dataset and gets true totals. It is the grid surface that lacks it.
Short of that, two things would each remove the silent half:
groupingon a paged grid is a different feature from what its name implies. If it is going to stay client-side, the schema doc should say the groups and counts are page-scoped, so an author can decide whether that fits before shipping the view.Application impact
Low urgency for us — we are scoping the affected lens to open work, which fits one page at this product's realistic scale and is a better lens anyway. But that is a workaround at our data size, not a fix: a deployment with more open rows than a page hits it again, silently, and we have no way to detect it from inside the app.