Repository navigation
feat(plugin-grid,plugin-list,i18n): a grouped grid over a data source with no group header query refuses grouping; the Partial marker is retired (objectui#10881) - #10896
Conversation
… with no group header query refuses grouping; the Partial marker is retired (objectui#10881) Maintainer ruling F on objectui#10881. A grouped grid that fetches its own rows over a data source declaring no `queryGroupHeaders` renders an error panel naming that member and issues no row query, instead of fetching a window and bucketing it. ListView makes the same refusal before it mounts a grouped grid over such a source, since it would otherwise hand the grid a window the grid takes as whole. Rows handed in whole still group in the browser, exactly. The Partial marker is retired everywhere: the three grid.grouping.partial* keys (ObjectGrid defaults and the ten packs), GroupRow's partialLabel / partialTitle, and groupedPartialDisclosure-7189.test.tsx. One new key, grid.grouping.needsHeaderQuery, carries the refusal in all ten packs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…a find-only source (objectui#10881) Ruling F refuses a grouped grid that fetches its own rows over a data source with no group header query, so six files whose doubles were find-only now reach the path each one pins another way: - groupingProjection-7179, gridGroupingMembers-8071, badgeHexCrossSurface and maskedColumnSurfaces-10583: the double answers queryGroupHeaders, and the projection / group label is read off the server-grouped path. - expandFls-7215: the double declares queryGroupHeaders; PIN 7 groups only by an unreadable key, so its grid still fetches the flat window. - ObjectGrid.pageSizeNonPositive-9853: the grouped cases hand their seven rows in whole; the page of groups is the same on either path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…nd-only source (objectui#10881) Ruling F: ListView refuses a grouped grid over a data source with no group header query before it fetches anything. ListView.groupingProjection-7179, ListView.expandFls-7215 and ListView.speculativeFls-7216 read the grouping projection off ListView's window fetch, so their doubles now declare queryGroupHeaders; over such a source the window is still fetched (the rows a searched grouped view is handed, and what the chip counts and the client export read). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…fuses grouping; dated notes on the two 7189 entries (objectui#10881) One minor changeset for @object-ui/plugin-grid, @object-ui/plugin-list and @object-ui/i18n, with the breaking semantics and the migration (implement queryGroupHeaders, or hand the rows in whole). The pending 7189-grouped-grid-partial-disclosure and 7189-server-side-grid-grouping entries each gain a dated note naming the sentences ruling F falsified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…y cast (objectui#10881) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Read: card objectui#10881 body and its three comments via REST (ruling ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…e page of more; the header-query panel waits behind the spinner (objectui#10881) Patch round 2, the seat's decision B. A grouped grid handed rows by a host that also declares them one page of a larger set (manualPagination, onPageChange and a rowCount above the rows handed) now refuses grouping in the load-error panel idiom, with its own key grid.grouping.needsWholeRows in all ten packs: grouping needs every record, so hand the rows in whole or let the grid fetch them. Rows handed with no rowCount, or one not above them, still group exactly. The header-query refusal now renders after the spinner branch, so a grouping whose only key turns out to be a masked type shows loading, not an error it withdraws. The card-view exclusion pin, lost with the retired disclosure file, is re-homed in the grid pin file; ListView is measured to hand a grouped grid no host paging. Changeset and docs updated. Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…ectui#10881) Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Delta review of patch round 2, after the PASS record ① Derived judgments
② Semver levelUnchanged and RIGHT: ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10881
Clause-②: yes
Maintainer ruling F (
5862401217), executed as ruled. A grouped grid over a data source that declares noqueryGroupHeadersrefuses grouping loudly, naming that member, and issues no row query;ListViewmakes the same refusal before it mounts such a grid; thePartialmarker is retired everywhere. Rows handed in WHOLE still group in the browser, exactly (useGroupedDataunchanged for that case). Builds on PR objectui#10878 (option K, merged).Patch round 2 adds the seat's decision B (contract review
5863494567, ③ flag 1): rows a host hands in while DECLARING them one page of more are a window, not whole rows, and a grouped grid refuses them too, with a sentence of its own.What changed
ObjectGrid, no header queryObjectGrid.tsx,groupingNeedsHeaderQuery!hasInlineDataandobjectNameand adataSourcewhosequeryGroupHeadersis not a function and a grouping entry the grid would group by (same entries asserverGroupedFetch, less the readability gate). The load effect returns before Step 2 (nofind; the object definition is still read, so a KNOWN masked-only grouping key flips the refusal off and the flat fetch then goes out). Render: the load-error panel's own shape (role="alert", headinggrid.errorLoading, sentencegrid.grouping.needsHeaderQuery),data-testid="grid-grouping-needs-header-query", placed AFTER the spinner branch, so while the definition read is in flight the grid shows it is loading rather than an error it may withdraw.ListView, no header queryListView.tsx,groupingNeedsHeaderQueryfield(collectGroupingFieldRefs), rows NOT inline (schema.dataneither an array nor avalueprovider),objectName, adataSourcewithoutqueryGroupHeaders. The fetch effect stands down (and clears held rows), aDataErrorState(list.loadErrorTitleplus the grid's own sentence, no Retry) replaces the grid, which is never mounted, and no record-count bar. The toolbar stays, so removing the grouping lifts it. A toolbar search does not lift it.ObjectGrid.tsx,GroupRow.tsx, ten packs, tests, docsgrid.grouping.partial*keys (defaults map and all ten packs), thegroupingIsPartialcomputation and notice,GroupRow'spartialLabel/partialTitleprops,groupedPartialDisclosure-7189.test.tsx; the "Two cases still group in the browser" section rewritten inplugin-grid.mdxand the plugin-grid README..changeset/7189-grouped-grid-partial-disclosure.md,.changeset/7189-server-side-grid-grouping.md7189-server-side-grid-grouping.mdgets one too, for its two sentences this PR falsified.ObjectGrid.tsx,groupingNeedsWholeRowshasInlineData) with the external-pagination props declared (manualPagination,onPageChange, a numericrowCount) androwCountabove the rows handed (counted on the rows as handed, not on thedatastate that mirrors them a render later, so whole rows never flash it), and a grouping entry the grid would group by. Refused in the same panel idiom (role="alert",grid.errorLoading) with its own sentence,grid.grouping.needsWholeRows: grouping needs every record, so hand the rows in whole or let the grid fetch them from a source that implementsqueryGroupHeaders.data-testid="grid-grouping-needs-whole-rows". The header-query sentence would be wrong there: the host owns the fetch, and its source may declare the member (pinned). NorowCount, or one not above the rows handed, is whole rows and groups as before (pinned).Two new keys,
grid.grouping.needsHeaderQueryandgrid.grouping.needsWholeRows, inGRID_DEFAULT_TRANSLATIONSand all ten packs (the first also inLIST_DEFAULT_TRANSLATIONS);queryGroupHeadersstays untranslated inside each sentence.New changeset
.changeset/10881-grouping-needs-header-query.md:minorfor@object-ui/plugin-grid,@object-ui/plugin-list,@object-ui/i18n, with the breaking semantics (including the host-declared window) and the migration (implementqueryGroupHeadersand let the grid fetch, or hand the rows in whole).@object-ui/typesand@object-ui/reactare touched in doc comments only (theDataSource.queryGroupHeadersJSDoc sentence that said such a grid "keeps grouping the rows it fetched", andNonGridRowCeilingNote's JSDoc citing the retired key); neither package's behaviour or keys move, so neither is named in the changeset. The presence gate is satisfied by the one changeset.Out of scope as ruled and untouched:
ListViewover the objectstack adapter while a toolbar search is active still hands a grouped grid its window (objectstack#20358);ValueDataSourcegains noqueryGroupHeaders.The PM's assumptions, measured
groupingIsPartial/grid.grouping.partial*/partialLabel/partialTitle/ the two marker test ids acrosspackages,apps,content,examples,e2e,scripts, JSON:ObjectGrid.tsx(defaults, computation, two render sites),GroupRow.tsx, the ten packs (key rows, plus arowCeilingNotecomment in each pack that citedgrid.grouping.partialNotice),nonGridRowCeiling.tsx(JSDoc citation),useGroupedData.ts(doc sentence),serverGrouping-7189.test.tsx,groupedPartialDisclosure-7189.test.tsx, and two test headers citing the retired file (gridArrayArmOrderby-8973,gridGroupingMembers-8071).apps,content,examples,e2e: none. After this PR the samegit grepover the tree (CHANGELOGs and.changeset/excluded) hits only the new i18n pin, which is the control that the grep can hit.ListView. Decidable from whatListViewknows: its fetch effect returns early, before anyfind, whenschema.datais an array or avalueprovider (searched rows are filtered in memory, still the whole matching set); every other shape reachesdataSource.findwith$top: effectivePageSizeand hands that window down asdata. The refusal keys on exactly that split, so truly whole rows never refuse (pinned, and the pin goes red when thevalueexclusion is ablated, below). DoesListViewever take the round-2 path? No: it hands a grid host paging only whenpaginate && serverTotal != null, andpaginateis false whenever a grouping is set. Measured on the one shape in which it hands a GROUPED grid a window (a source that answers the header query, a toolbar search active): the grid is handed the rows and nomanualPagination/rowCount, against a lit control where the same source ungrouped is handedmanualPagination: trueandrowCount: 186(pinned).GRID_DEFAULT_TRANSLATIONS/LIST_DEFAULT_TRANSLATIONS(byte-identical toen, whichdefaults-maps-mirror-en-packenforces, green) and the ten packs; no row query, proven by thefindcall count against a lit control where the same find-only source, ungrouped, is queried.eab4c8e52). Its hunks inObjectGrid.tsx: the spec import, theDEFAULT_*constants, thegroupedPageSize/serverPageSize/fetchWindowstate, the$top/$skipparams, the load effect's dependency array,pageSize,groupingPartialWindowFull, the group pager's select. Lines of its hunks this PR had to touch: the load effect's dependency array (it renamesserverPageSizetofetchWindowthere; this PR appendsgroupingNeedsHeaderQuery; the line already differs from its base since PR objectui#10878) andgroupingPartialWindowFull(it re-points it tofetchWindow; this PR deletes the whole marker computation, as ruled). Round 2 touched none of its hunks: the new predicate sits belowgroupingNeedsHeaderQuery, and the moved panel sits after the spinner branch.groupedPagination.test.tsxis untouched; inObjectGrid.pageSizeNonPositive-9853.test.tsxonlygroupedSchemachanged, which none of its hunks holds. For its merge ofmain: its docs sentence "A grouped view separately fetches a larger batch of rows to group" no longer describes the grid (a server-grouped grid strips$top/$skip, a refused one fetches nothing, inline rows fetch nothing), and its groupedfetchWindowbranch is reached only where a grouped grid still fetches a flat window.content/docs/plugins/plugin-grid.mdxandpackages/plugin-grid/README.md(identical text); rewritten there as ruled, and in round 2 its whole-rows paragraph names the host-declared window and its refusal. Also brought true: thegroupingrow incontent/docs/api/schema-reference.md, a new paragraph in the plugin-list README "Grouping Records" section, and the plugin-list README's sort paragraph (its "the grouped view, which holds every row it groups" was already false for a server-grouped view and names no remaining case).Pins (new files) and their red on base
packages/plugin-grid/src/__tests__/groupingNeedsHeaderQuery-10881.test.tsx(12):queryGroupHeadersandfindis never called; CONTROL the same source ungrouped is queried once and draws; CONTROL the same grouping over a source declaring the member is not refused; rows handed in whole (avalueprovider, and adataprop) group as 86/61/31/7/1 with no refusal and no query;rowCount: 186render the whole-rows refusal, draw no group and query nothing; the same window over a source that DOES declarequeryGroupHeadersis refused the same way and the header query is never asked; CONTROL 186 rows withrowCount: 186group exactly as 86/61/31/7/1; CONTROL 100 rows with norowCountgroup as handed (86, 14), unrefused;Loading grid…, never the refusal, while the definition read is in flight, then the flat rows;packages/plugin-list/src/__tests__/ListView.groupingNeedsHeaderQuery-10881.test.tsx(4): the refusal namingqueryGroupHeaders, no grid mounted, nofind, no record-count bar; CONTROL ungrouped is queried and handed its window WITH host paging; CONTROL rows handed in whole reach the grouped grid whole, no refusal, nofind; round 2: a grouped grid handed a searched window is handed no host paging.packages/i18n/src/__tests__/groupingPartialRetired-10881.test.ts(21): no pack carries agrid.grouping.partial*key; CONTROL the same walk findsgrid.grouping.needsHeaderQuery, namingqueryGroupHeaders, in all ten.Red on base, round 1: the three files run with the behaviour-carrying sources (
ObjectGrid.tsx,GroupRow.tsx,ListView.tsx, ten packs) checked out atb2683a2c0, restored fromHEADafter (hash-compared,git diff HEADempty):Tests 22 failed | 7 passed (29); the 22 are both refusal pins and the 20 locale rows, the 7 are lit controls and whole-rows pins F keeps. Red on base, round 2: the two behaviour files run withObjectGrid.tsx,ListView.tsxand the ten packs at638a8250c(the round-1 head), restored the same way:Tests 3 failed | 13 passed (16); the 3 are the two window refusals and the spinner-not-refusal pin; the 13 are the round-1 pins, the round-2 controls, the card-view pair and theListViewmeasurement, which pin behaviour kept. Green on head: grid pin fileTests 12 passed (12),ListViewpin fileTests 4 passed (4).Ablations (
ablation-replace.mjs, each restored to theHEADblob withgit diff HEADempty)ObjectGridheader refusal without!hasInlineData: both whole-rows pins red (2 failed | 3 passed).ObjectGridload-effect guard deleted: the refusal pin red on thefindcount (1 failed | 4 passed); the panel alone does not satisfy it.ListViewrefusal without thevalueexclusion: the whole-rows control red (1 failed | 2 passed). Declared: the first attempt was an anchor miss (a quoting slip left the newlines literal) and the second was refused by the tool because the replacement text was a suffix of the anchor; neither wrote anything. The third, with a marker in the replacement, landed; it was re-run at638a8250cwith the same result.ListViewfetch-effect stand-down disabled: the refusal pin red on thefindcount (1 failed | 2 passed).groupingNeedsWholeRowsforced false), at156eb5103: both window pins red (2 failed | 10 passed).rowCount-equal control red (1 failed | 11 passed), so the control can fail.!isGroupedexclusion removed: the re-homed grouped-mobile pin red (1 failed | 11 passed).Existing tests re-decided (none deleted blindly)
groupedPartialDisclosure-7189.test.tsx— retired as ruled. Its marker and notice cases pin what F retires. Its "does not mark rows handed to it inline" case is re-decided into the new whole-rows pins; its "reads the total a HOST supplies" case is re-decided into the round-2 window refusal (the same render, now refused); its card-view case is re-homed as a behavioural pin with a lit control; its ungrouped control asserted only the marker's absence beside a fetch other files pin.serverGrouping-7189.test.tsx— the two marker-absence assertions dropped (nothing left to be absent: a phantom check); its CONTROL, which pinned page grouping plus two markers over a find-only source, now pins the refusal and no row query; header text says why.ListView.groupedGridOwnsFetch-7189.test.tsx— its CONTROL, which pinnedListViewhanding a find-only source's window to the grid, now pins that no grid is mounted.groupingProjection-7179,gridGroupingMembers-8071,badgeHexCrossSurface,maskedColumnSurfaces-10583(plugin-grid): each grouped over a find-only double, which F refuses; the doubles now answerqueryGroupHeaders, and the projection / header label is read off the server-grouped path (its row query carries the grid's own$select/$expand).expandFls-7215(plugin-grid): its PIN 7 groups only by an unreadable key; the double declares the member so the grid still fetches its flat window, the projection under test.ObjectGrid.pageSizeNonPositive-9853: its grouped cases hand their seven rows in whole; the page of groups is the same on either path.ListView.groupingProjection-7179,ListView.expandFls-7215,ListView.speculativeFls-7216: the doubles declare the member, soListViewstill fetches the window those pins read (the rows a searched grouped view is handed, and what the chip counts and export read).Local verification
156eb5103(the round-2 code; the later94df46014only types one test callback, and that file was re-run there:Tests 4 passed (4)):pnpm exec vitest run packages/plugin-grid/:Test Files 164 passed (164),Tests 1535 passed (1535).pnpm exec vitest run packages/plugin-list/ packages/i18n/:Test Files 178 passed (178),Tests 2380 passed (2380).pnpm exec vitest run scripts/__tests__/:Test Files 177 passed | 2 skipped (179).defaults-maps-mirror-en-pack(app-shell):Tests 15 passed (15).type-checkfor plugin-grid, plugin-list and i18n after building their dependency closure: allDone; plugin-list re-run at94df46014.94df46014:check:i18n-keys,check:i18n-drift,check:i18n-dead-keys,check:control-bytes,check:new-line-citations(0 new),check:changeset-claims(report),check:test-path-roots,check:vi-mock-specifiers,changeset:check,check-changeset-presence: exit 0.packages/plugin-view/with the app-shellwidget-dom-leak-sweepandObjectView.rowColorRelay-7218,packages/react/src/utils,packages/types/(Test Files 318 passed (318)atdfd9bb187), and thetypes/reacttype-checks.eslint --no-inline-config --format jsonover the changed.ts/.tsxfiles that exist atHEAD(the population isgit diff --name-only --diff-filter=d, not a guess): 0 errors; per-file warning counts equal to the previous head for every modified file (round 2: 14 files against638a8250c); the new test files carryno-explicit-anywarnings in the idiom of their neighbours. The config enables no type-aware linting (noparserOptions.project), so this diff cannot move a verdict in an untouched file.NOT MEASURED locally, declared to CI:
check:readme-exports(prerequisite: every package'sdist; neither touched README gained or lost a fenced block, and no export name moved),check:eager-locale-catalogues(prerequisite: a console build; this PR moves no import), the repo-widepnpm lint.Acceptance notes
5863494567, ③ flag 1: 100 of 186 rows withrowCount: 186drew two groups, 86 and 14, three units missing, with nothing on screen saying so, a render the retired disclosure pin had marked). No in-repo host takes this path:ListViewhands a grouped grid no host paging (item 2, pinned).queryGroupHeaders, a grouping whose every key the principal may not read is not asked of the server and still groups a fetched page (since PR objectui#10878); unchanged here.Co-Authored-Bytrailer copied from the harness reminder; the round-2 commits carry the dispatch contract's model-free pair. History not rewritten (no force-push).The session that wrote this:
https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk.Generated by Claude Code