Skip to content

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

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-10881-grouping-needs-header-query
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-10881-grouping-needs-header-query

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10881
Clause-②: yes

Maintainer ruling F (5862401217), executed as ruled. A grouped grid over a data source that declares no queryGroupHeaders refuses grouping loudly, naming that member, and issues no row query; ListView makes the same refusal before it mounts such a grid; the Partial marker is retired everywhere. Rows handed in WHOLE still group in the browser, exactly (useGroupedData unchanged 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

Item Where What it does now
(1) ObjectGrid, no header query ObjectGrid.tsx, groupingNeedsHeaderQuery !hasInlineData and objectName and a dataSource whose queryGroupHeaders is not a function and a grouping entry the grid would group by (same entries as serverGroupedFetch, less the readability gate). The load effect returns before Step 2 (no find; 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", heading grid.errorLoading, sentence grid.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.
(2) ListView, no header query ListView.tsx, groupingNeedsHeaderQuery grid view, a grouping entry with a named field (collectGroupingFieldRefs), rows NOT inline (schema.data neither an array nor a value provider), objectName, a dataSource without queryGroupHeaders. The fetch effect stands down (and clears held rows), a DataErrorState (list.loadErrorTitle plus 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.
(3) marker retired ObjectGrid.tsx, GroupRow.tsx, ten packs, tests, docs the three grid.grouping.partial* keys (defaults map and all ten packs), the groupingIsPartial computation and notice, GroupRow's partialLabel / partialTitle props, groupedPartialDisclosure-7189.test.tsx; the "Two cases still group in the browser" section rewritten in plugin-grid.mdx and the plugin-grid README.
(4) dated notes .changeset/7189-grouped-grid-partial-disclosure.md, .changeset/7189-server-side-grid-grouping.md PR objectui#10878 already added the note review ③ asked for; this PR's change falsified that note's last sentences, so a second dated note, 2026-09-28, names them. 7189-server-side-grid-grouping.md gets one too, for its two sentences this PR falsified.
(5) a host-declared window (round 2, B) ObjectGrid.tsx, groupingNeedsWholeRows rows handed in (hasInlineData) with the external-pagination props declared (manualPagination, onPageChange, a numeric rowCount) and rowCount above the rows handed (counted on the rows as handed, not on the data state 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 implements queryGroupHeaders. 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). No rowCount, or one not above the rows handed, is whole rows and groups as before (pinned).

Two new keys, grid.grouping.needsHeaderQuery and grid.grouping.needsWholeRows, in GRID_DEFAULT_TRANSLATIONS and all ten packs (the first also in LIST_DEFAULT_TRANSLATIONS); queryGroupHeaders stays untranslated inside each sentence.

New changeset .changeset/10881-grouping-needs-header-query.md: minor for @object-ui/plugin-grid, @object-ui/plugin-list, @object-ui/i18n, with the breaking semantics (including the host-declared window) and the migration (implement queryGroupHeaders and let the grid fetch, or hand the rows in whole). @object-ui/types and @object-ui/react are touched in doc comments only (the DataSource.queryGroupHeaders JSDoc sentence that said such a grid "keeps grouping the rows it fetched", and NonGridRowCeilingNote'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: ListView over the objectstack adapter while a toolbar search is active still hands a grouped grid its window (objectstack#20358); ValueDataSource gains no queryGroupHeaders.

The PM's assumptions, measured

  1. Census. Readers of groupingIsPartial / grid.grouping.partial* / partialLabel / partialTitle / the two marker test ids across packages, apps, content, examples, e2e, scripts, JSON: ObjectGrid.tsx (defaults, computation, two render sites), GroupRow.tsx, the ten packs (key rows, plus a rowCeilingNote comment in each pack that cited grid.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 same git grep over the tree (CHANGELOGs and .changeset/ excluded) hits only the new i18n pin, which is the control that the grep can hit.
  2. Whole versus window in ListView. Decidable from what ListView knows: its fetch effect returns early, before any find, when schema.data is an array or a value provider (searched rows are filtered in memory, still the whole matching set); every other shape reaches dataSource.find with $top: effectivePageSize and hands that window down as data. The refusal keys on exactly that split, so truly whole rows never refuse (pinned, and the pin goes red when the value exclusion is ablated, below). Does ListView ever take the round-2 path? No: it hands a grid host paging only when paginate && serverTotal != null, and paginate is 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 no manualPagination / rowCount, against a lit control where the same source ungrouped is handed manualPagination: true and rowCount: 186 (pinned).
  3. The error panels. The grid's existing load-error idiom, the i18n path through GRID_DEFAULT_TRANSLATIONS / LIST_DEFAULT_TRANSLATIONS (byte-identical to en, which defaults-maps-mirror-en-pack enforces, green) and the ten packs; no row query, proven by the find call count against a lit control where the same find-only source, ungrouped, is queried.
  4. PR objectui#10278 (head eab4c8e52). Its hunks in ObjectGrid.tsx: the spec import, the DEFAULT_* constants, the groupedPageSize / serverPageSize / fetchWindow state, the $top / $skip params, 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 renames serverPageSize to fetchWindow there; this PR appends groupingNeedsHeaderQuery; the line already differs from its base since PR objectui#10878) and groupingPartialWindowFull (it re-points it to fetchWindow; this PR deletes the whole marker computation, as ruled). Round 2 touched none of its hunks: the new predicate sits below groupingNeedsHeaderQuery, and the moved panel sits after the spinner branch. groupedPagination.test.tsx is untouched; in ObjectGrid.pageSizeNonPositive-9853.test.tsx only groupedSchema changed, which none of its hunks holds. For its merge of main: 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 grouped fetchWindow branch is reached only where a grouped grid still fetches a flat window.
  5. Docs. The section lived in content/docs/plugins/plugin-grid.mdx and packages/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: the grouping row in content/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).
  6. Changeset. As above: three packages, not five, with the reason; round 2 corrected its whole-rows sentence to rows handed in WHOLE and added the host-declared window to the breaking semantics and the migration.

Pins (new files) and their red on base

  • packages/plugin-grid/src/__tests__/groupingNeedsHeaderQuery-10881.test.tsx (12):
    • a grouped grid over a find-only source renders the refusal naming queryGroupHeaders and find is 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 (a value provider, and a data prop) group as 86/61/31/7/1 with no refusal and no query;
    • round 2: 100 rows handed with rowCount: 186 render the whole-rows refusal, draw no group and query nothing; the same window over a source that DOES declare queryGroupHeaders is refused the same way and the header query is never asked; CONTROL 186 rows with rowCount: 186 group exactly as 86/61/31/7/1; CONTROL 100 rows with no rowCount group as handed (86, 14), unrefused;
    • round 2: a masked-only grouping key shows Loading grid…, never the refusal, while the definition read is in flight, then the flat rows;
    • re-homed from the retired file: under 768px a grouped grid draws its groups and tables, not cards; CONTROL the same rows ungrouped at that width are cards with no table.
  • packages/plugin-list/src/__tests__/ListView.groupingNeedsHeaderQuery-10881.test.tsx (4): the refusal naming queryGroupHeaders, no grid mounted, no find, 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, no find; 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 a grid.grouping.partial* key; CONTROL the same walk finds grid.grouping.needsHeaderQuery, naming queryGroupHeaders, 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 at b2683a2c0, restored from HEAD after (hash-compared, git diff HEAD empty): 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 with ObjectGrid.tsx, ListView.tsx and the ten packs at 638a8250c (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 the ListView measurement, which pin behaviour kept. Green on head: grid pin file Tests 12 passed (12), ListView pin file Tests 4 passed (4).

Ablations (ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty)

  • A1 ObjectGrid header refusal without !hasInlineData: both whole-rows pins red (2 failed | 3 passed).
  • A2 ObjectGrid load-effect guard deleted: the refusal pin red on the find count (1 failed | 4 passed); the panel alone does not satisfy it.
  • A3 ListView refusal without the value exclusion: 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 at 638a8250c with the same result.
  • A4 ListView fetch-effect stand-down disabled: the refusal pin red on the find count (1 failed | 2 passed).
  • R2-A1 the whole-rows guard neutralized (groupingNeedsWholeRows forced false), at 156eb5103: both window pins red (2 failed | 10 passed).
  • R2-A2 its strict "above" loosened to "at least": the rowCount-equal control red (1 failed | 11 passed), so the control can fail.
  • R2-A3 the card view's !isGrouped exclusion 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 pinned ListView handing 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 answer queryGroupHeaders, 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, so ListView still fetches the window those pins read (the rows a searched grouped view is handed, and what the chip counts and export read).
  • Two test headers citing the retired file re-pointed.

Local verification

  • At 156eb5103 (the round-2 code; the later 94df46014 only 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-check for plugin-grid, plugin-list and i18n after building their dependency closure: all Done; plugin-list re-run at 94df46014.
  • At 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.
  • Round 1, unchanged by round 2 and not re-run: packages/plugin-view/ with the app-shell widget-dom-leak-sweep and ObjectView.rowColorRelay-7218, packages/react/src/utils, packages/types/ (Test Files 318 passed (318) at dfd9bb187), and the types / react type-checks.
  • Lint, narrowed and declared: eslint --no-inline-config --format json over the changed .ts / .tsx files that exist at HEAD (the population is git 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 against 638a8250c); the new test files carry no-explicit-any warnings in the idiom of their neighbours. The config enables no type-aware linting (no parserOptions.project), so this diff cannot move a verdict in an untouched file.

NOT MEASURED locally, declared to CI: check:readme-exports (prerequisite: every package's dist; 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-wide pnpm lint.

Acceptance notes

  • A host that hands a grouped grid rows AND declares them one page of more is refused (item 5). This was an open question in round 1; the seat decided B on the contract review's measurement (5863494567, ③ flag 1: 100 of 186 rows with rowCount: 186 drew 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: ListView hands a grouped grid no host paging (item 2, pinned).
  • Over a source that DOES declare 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.
  • A grouping whose only key is a masked type: over a source without the member, the grid now shows its spinner until the object definition lands, then the flat grid (pinned). Over rows a host handed as a declared window, there is no read to wait behind, so the whole-rows refusal shows until the definition lands and then withdraws; no in-repo producer.
  • The first five commits carry a Co-Authored-By trailer 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

… 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
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 30 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/3917-retire-action-condition-branch.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • ActionCondition is removed from @object-ui/types (and from the barrel export). - ActionSchema.condition is retyped to the predicate the runtime actually honours: boolean | string | { dialect?: string; source: string } — the same three arms ActionRunner's own ActionDef.condition carries, and the same vocabulary visible and disabled use. - ActionConditionSchema is removed from @object-ui/types/zod (and from the zod barrel); the condition key now validates against that predicate union. - The two teaching sites (content/docs/core/enhanced-actions.mdx Conditional Execution, content/docs/api/schema-reference.md ActionSchema table) are rewritten to the live vocabulary: condition is a gate; a branch is expressed as separate actions with mutually exclusive conditions.

.changeset/4730-retire-dead-locale-key-batch.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    • Superseded twin vocabularies. cellRender.* and rowAction.* duplicated a grid.* vocabulary that won. RowActionMenu.tsx is fully i18n-wired and reads grid.openMenu / grid.edit / grid.delete; ObjectGrid.tsx reads grid.empty / grid.yes / grid.no / grid.systemFields. The twins had no reader on either side. - Labels that outlived their control. calendar.agenda labelled a view mode b55a34647 retired from CalendarViewMode (now 'month' | 'week' | 'day'). home.quickActions.createApp*, layout.systemNav.createApp, actionDialog.defaultActionTitle / .ok and grid.bulk.selectPlaceholder sit in namespaces whose consumers are live and wired but demonstrably read other siblings. - Surfaces that left the product. map.* is the strongest form: @object-ui/plugin-map declares no @object-ui/i18n dependency and contains no t() call at all, so it cannot consume a locale string. home.stats.* and recordDetail.viewersTooltip name surfaces nothing renders.

.changeset/6170-retire-timeline-dead-keys.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Also in this change: the in-repo example packages/types/examples/data-display-examples.json (its timeline node authored all three) is migrated to items / variant; the two content/docs/api/schema-reference.md snippets that authored events: [] now author items: []; and the plugin-timeline docs callout says retired rather than deprecated.

.changeset/6597-retire-fieldmeta-referenceto.md

  • names plugin-grid/src/ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    No authoring story survived the search either. ObjectGrid's own relational-meta pass-through (applyRelationalMeta, plugin-grid/src/ObjectGrid.tsx) copies reference_to / reference / display_field / etc. from the SCHEMA field def only, at all three of its call sites — never from an authored column override. No doc, example, or fixture in this repo shows a table column pinning a lookup's target away from what its schema field already says. Under the maintainer's standing startup-stage rule (2026-08-27: deprecated/alias spellings retire immediately, no transition windows), no measured demand selects withdraw.

.changeset/6650-listcolumn-wrap-implemented.md

  • names packages/plugin-grid/README.md → packages/plugin-grid/README.md — edited by this change

    @objectstack/spec declares ListColumn.wrap and describes it to authors as "Allow text wrapping", and packages/plugin-grid/README.md shows it in its authored-column example. No renderer anywhere implemented it. Long cell text stayed clipped to one line, with no error, no warning and no feedback of any kind — a promise made at authoring time and silently broken at render time.

.changeset/6661-app-launcher-nav-menu-renderers.md

  • names en.ts → packages/i18n/src/locales/en.ts — edited by this change

    Three new strings — the launcher's and the menu's accessible names, and the menu's empty state — are declared under console.nav in en.ts and its nine sibling packs. An inline defaultValue alone is not a fix: it renders English at one call site and leaves the string untranslatable everywhere (objectui#3517).

.changeset/6896-retire-chart-inline-data.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ One correction to the record the ruling rests on. The ruling states zero authorship of a populated series[].data outside tests across packages/ / apps/ / examples/. The re-measurement finds one such site inside those roots — packages/types/examples/data-display-examples.json (2 series) — plus four outside them, in documentation: content/docs/api/schema-reference.md (3) and content/docs/core/report-schema.mdx (1).

.changeset/6939-kanban-column-cards.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Breaking, deliberately. KanbanColumn declared its card list as items in complex.ts and in the zod mirror complex.zod.ts. Every board reads cards. Measured on origin/main 78a3cc238: KanbanImpl.tsx reads .cards on 12 lines, KanbanEnhanced.tsx on 8, and bucketCardsIntoColumns twice more as col.cards || []; .items had zero read sites in either board (a same-shaped .title control on the same two files returns 8 and 3, so those zeros are readings and not a mis-shaped probe). Both catalog entries, the plugin docs and content/docs/api/schema-reference.md all author cards.

.changeset/6951-tree-view-data-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What was measured, on this branch's base. TreeViewSchema declared two spellings for its one inline-nodes slot — nodes (read second) and data (read third: boundData || schema.nodes || schema.data || [] at renderers/data-display/tree-view.tsx:105), both declared by objectui#6150. data had been REQUIRED until 777e5c6f4 (PR fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) #7533) made it optional, so this retirement starts from a declared-and-optional member on both faces. The in-repo corpus at the retirement: seven tree-view nodes under examples/schema-catalog and packages/types/examples plus one content/docs fence — six on nodes, two on data (packages/types/examples/data-display-examples.json and content/docs/api/schema-reference.md), both rewritten; no package source authored either spelling.

.changeset/7070-no-invented-gantt-date-fields.md

  • names plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7499-gantt-non-axis-floors-omitted.md

  • names plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7664-kanban-arm-plugin-dialect.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Migration. Author boards in the plugin dialect — objectName + groupBy for an object-bound board, or columns[].cards[] with badges for a static one. Replace DeclarativeKanbanSchema imports with KanbanSchema (from @object-ui/types, or the Zod KanbanSchema from @object-ui/types/zod; @object-ui/plugin-kanban re-exports the same KanbanSchema type). Delete draggable (drag-and-drop is always on) and column color (style a lane through className). content/docs/api/schema-reference.md's kanban section now documents this dialect.

.changeset/7762-object-grid-export-options-bare-array-refusal.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    What was measured, on this branch's base. The mirror declared NO exportOptions member at all, and BaseSchema is .passthrough(), so ObjectGridSchema.safeParse({ type: 'object-grid', objectName: 'accounts', exportOptions: ['csv', 'xlsx'] }) returned success: true with the array back VERBATIM — as did { formats: ['csv', 'pdf'], compression: 'gzip' }. Nothing on the render path parses, and ObjectGrid.tsx reads schema.exportOptions?.formats and only that, so the authored array then lost SILENTLY to the ['csv', 'json'] default: the useEffect that warns about dropped formats reads .formats too and returns early when it is absent, while !!schema.exportOptions kept the export button on screen. An author declared ['csv', 'xlsx'] and got csv/json with no error, no warning and no console line. The two authoring faces disagreed in the direction opposite to objectui#6956's: the TypeScript interface already declared the object form only, so TS refused what zod admitted.

.changeset/7773-kanban-adapter-groupfield-write.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    Who is NOT affected — the boundary is node-local. Every VIEW-LEVEL groupField read is untouched and still live: it is a legacy alias of the spec's groupByField on the kanban view config, mapped by normalize-list-view.ts, and both adapters still resolve lanes through it (ObjectView.tsx's kanbanCfg.groupField ||, ListView.tsx's groupByField || groupField). Authoring options.kanban.groupField on a list-view or object-view keeps working exactly as documented in packages/plugin-list/README.md. groupField is dead only on the generated object-kanban NODE.

  • names packages/plugin-list/README.md → packages/plugin-list/README.md — edited by this change

    Who is NOT affected — the boundary is node-local. Every VIEW-LEVEL groupField read is untouched and still live: it is a legacy alias of the spec's groupByField on the kanban view config, mapped by normalize-list-view.ts, and both adapters still resolve lanes through it (ObjectView.tsx's kanbanCfg.groupField ||, ListView.tsx's groupByField || groupField). Authoring options.kanban.groupField on a list-view or object-view keeps working exactly as documented in packages/plugin-list/README.md. groupField is dead only on the generated object-kanban NODE.

.changeset/7780-object-kanban-record-source.md

.changeset/7997-detail-view-related-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Documentation. packages/plugin-detail/README.md and content/docs/api/schema-reference.md stop teaching the retired array and gain a migration block each.

.changeset/8268-testid-emitted-as-data-testid.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    The other direction — retiring the promise — was considered and declined. It is what c1fe272ad did for BaseSchema.hidden, but that key had a working behaviour to describe and zero named consumers, and the ruling's decline turned on exactly that. This promise already has carriers outside the type declaration: content/docs/api/schema-reference.md states it as a table row and authors testId in that page's own base-schema example, @object-ui/cli's OBJECTUI_STRUCTURAL_KEYS identifies a file as an ObjectUI schema node by this key, ObjectGridSlotKey / ObjectFormSlotKey pin it, SchemaBuilder.testId() writes it, and ADR-0054 C4 — shipped — reads "the renderer emits data-testid … derived from metadata".

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/plugin-list/src/ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    • packages/plugin-list/src/ListView.tsx generates the node as groupBy: laneField. objectDef loads asynchronously, so laneField is undefined on every load until it lands, and stays undefined whenever the object offers no stageField hint and none of status / stage / state / phase. The renderer serves that node; both published faces refused it. - content/docs/utilities/data-objectstack.mdx documents an object-kanban node that is exactly { type, dataSource }, with no groupBy. ⚠️ This one is weaker and is cited for what it is: that fragment is still refused after this change, at RECORD_SOURCE_REQUIRED, because dataSource is not a rung of the record-source ladder. It shows a lane-less board is a documented authoring; it is not a document this change admits.

.changeset/9187-record-highlights-layout-two-values.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ The census behind this narrowing covers this repository only, and it found no in-repo authoring to migrate: every in-tree layout: 'grid' belongs to a different component (detail-view in content/docs/api/schema-reference.md and phase2-schemas.test.ts, ai-recommendations in packages/plugin-ai/README.md), and the one in-repo consumer of this interface that writes a layout (p1-spec-alignment.test.ts) writes 'horizontal'. So no document in this repository stops type-checking. A TypeScript consumer outside this repo that wrote grid is not observable from here and gets a compile error (TS2322) naming the key — which is why the FROM/TO is spelled out above.

.changeset/9309-object-gallery-filter-destination-typed.md

  • names data.ts → packages/types/src/data.ts — edited by this change

    It was filter?: unknown under the docblock "Query filter, forwarded verbatim as $filter", and $filter is RecordANGLE-BRACKETS(string, any) | FilterArray (QueryParams, data.ts). The declaration therefore named a destination it did not type: an author told to forward the value verbatim got a type error on the key the sentence had just told them to forward, and the way through was as — which un-checks the destination's real type at that call site too. The declaration is now an INDEXED ACCESS on QueryParams, not a copy of its arms, so the two cannot drift.

.changeset/9722-bulk-executor-datasource-face.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    • ObjectGrid.tsx → RecordDetailPanel: removing it type-checks GREEN. It was paying for nothing — both sides already declare the same DataSource | undefined from @object-ui/types. Removed outright. - ObjectGrid.tsx → BulkActionDialog: removing it reddens, and it was paying for TWO things at once — the optional-vs-required arm (the grid declares dataSource?: DataSource, the dialog demands one) AND the bulk-door contravariance. Only the first still needs erasing, so it is now a non-null assertion: today's runtime behaviour is preserved exactly, and any future drift of the face reddens at that site instead of passing silently.

.changeset/9853-grid-non-positive-page-size.md

  • names ObjectGrid.tsx → packages/plugin-grid/src/ObjectGrid.tsx — edited by this change

    Refuse a non-positive pageSize at all three of ObjectGrid.tsx's read points, instead of giving two different answers for one authored value (objectui#9853).

.changeset/calendar-view-schema-converge.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Runtime renderer behaviour is unchanged. @object-ui/plugin-calendar's README and content/docs/api/schema-reference.md are repaired to the converged surface in the same change, so no copy of the old contradiction survives.

.changeset/issue-5373-retire-crud-schema.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    crud had four declaration faces and no registered renderer, for the whole life of the key: the TS interface (packages/types/src/crud.ts), the zod mirror (packages/types/src/zod/crud.zod.ts), a dedicated branch in validateSchema that affirmatively PASSED it, and CRUDBuilder in @object-ui/core. A node spelling it painted the OBJUI-001 "Unknown component type" panel, and content/docs/api/schema-reference.md published it as reference material — so a reader (or an AI author) who copied the page got a red panel.

  • names api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Authoring crud is now REFUSED BY NAME rather than passed or silently ignored. validateSchema returns an error with code: 'RETIRED_TYPE' on schema.type — at any depth, since it is what validateChildren recurses with — so assertValidSchema throws and isValidSchema answers false. The message names the migration: object-grid for the record table with its toolbar, filters, pagination and row/batch actions, object-form for the create/edit form, and detail for the record view. api/schema-reference.md is rewritten around those shapes.

.changeset/list-user-actions-collision-5398.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    The harvest now reads the object block only. Both userActions read sites in ListView.tsx carry a comment naming the collision, and __tests__/ListView.userActionsCollision.test.tsx pins each clause of it: the two shapes, a producer that manufactures the view one, the harvest's blindness to it, and the projection that must keep the object's operand with a toolbar block — or an empty block — present on the view.

.changeset/listview-comment-pair-4559.md

  • names ListView.tsx → packages/plugin-list/src/ListView.tsx — edited by this change

    Two comment corrections in ListView.tsx (objectui#4559, objectui#4966). No runtime behaviour changes and the emitted bundle is byte-identical; the published .d.ts does change, which is why this is a patch rather than an empty frontmatter.

.changeset/object-view-unmirrored-keys-7779.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    listViews stays unmirrored, on the ruling's own fallback clause. The declaration's value is the local NamedListView — 64 declared top-level members (⚠️ re-taken at objectui#8980, which added the seventeen the protocol declares on this surface to the 47 this entry first measured), of which the renderer reads 21 off a named view. data is one of the 21 now: it used to reach the renderer through an as any cast on the named-view config in packages/plugin-view/src/ObjectView.tsx and be declared nowhere, and the objectui#8980 ruling declared it by name — objectui#7928's open half, answered. The spec's ViewSchema.listViews is a record of the STRICT ObjectListViewSchema, which requires columns and refuses options, ObjectQL tuple filters and default — that is, it refused the named views this package's own README and content/docs/api/schema-reference.md taught when this entry was written ({ label: 'All Users' } fails at columns; filter: [["owner", "=", "..."]] fails at filter.0), and objectui#8255 has since rewritten them in the spec shape. Mirroring the spec value would have lost documented behaviour; mirroring the local value would enforce 43 unread members (64 declared, minus the 21 that are both declared and read) into the contract — the very thing ruling B refused for the six local keys. The key therefore stays in the parity ledger with that measurement, pinned, until the maintainer decides its value type. It is not papered over with z.any().

.changeset/table-renderer-declared-column-contract-5350.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What makes this site different from its three siblings is that the alias was not merely tolerated, it was published. content/docs/api/schema-reference.md §TableSchema shipped a copyable { "name": "id", "label": "#" } example and a property row reading "Column definitions with name, label, …", while packages/types declared the opposite pair. Docs and type disagreed about one type they both call TableColumn, each internally consistent. Retiring the alias without correcting the page would have turned a documented, working example into a silently broken one, so both halves land together: the page now authors accessorKey/header. The same row also advertised a render property that TableColumn has never declared — the renderer's hook is cell — and that claim is dropped rather than re-spelled.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 6a7f24e92 (merge-base with origin/main): 36 file(s) changed outside .changeset/, read against 1661 pending declaration(s) that publish a body (2255 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3095.3 KB 3104.5 KB
Main entry chunk (gzip) 148.5 KB 350 KB
Entry file index-DrSxhPSO.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.57KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 83.99KB 22.86KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.82KB 36.44KB
plugin-designer (index.js) 216.42KB 44.47KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 227.55KB 62.43KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.00KB 9.09KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 87.90KB 22.06KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.52KB 1.36KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 638a8250c9850113f566d10f6d9878bdf477822e

Read: card objectui#10881 body and its three comments via REST (ruling 5862401217 letter F, claim 5862586641, the dev report 5863269105 read only for the claims it makes); objectui#7189 ruling 5509036555 and triage 5861161215; PR #10878 (merged, K) and its contract review 5862150828; PR #10278 at its head eab4c8e52; PR #10896 body, its 39-file list and the full diff b2683a2c0..638a8250c (merge-base b2683a2c0; origin/main is now 35d68c4ce, five commits on, none touching a file this PR touches); head sources of ObjectGrid.tsx, ListView.tsx, GroupRow.tsx, useGroupedData.ts, data.ts, nonGridRowCeiling.tsx, view-states.tsx, normalize-list-view.ts, grouping-fields.ts; the retired groupedPartialDisclosure-7189.test.tsx at base; the three new pins, the twelve re-decided files, the ten packs, the three changesets, the two docs pages, the two READMEs and schema-reference.md; every pending .changeset/*.md naming grouping, the marker, queryGroupHeaders or nonGridRowCeiling (36 files); check-changeset-presence.mjs and defaults-maps-mirror-en-pack.test.tsx headers; the head's check-runs polled twice. Ran, in a throwaway git worktree add --detach under this review's scratch dir (offline frozen install, removed and pruned afterwards): the three new pins plus the two re-decided 7189 files at head, Test Files 5 passed (5), Tests 44 passed (44); the two refusal pins with ObjectGrid.tsx, GroupRow.tsx and ListView.tsx at b2683a2c0, 2 failed | 6 passed — exactly the two refusal cases red, every control and whole-rows pin green; and one probe of the host-window shape (③). No gate or suite re-run, no GitHub write but this comment, no MCP GitHub tool, no edit under the shared checkouts.

① Derived judgments

  1. The grid refusal — predicate: RIGHT, with one missed window that is the seat decision (③). groupingNeedsHeaderQuery (ObjectGrid.tsx 2126–2131) is !hasInlineData, objectName, a dataSource whose queryGroupHeaders is not a function, and a usable grouping entry not refused on a KNOWN masked type. It is disjoint from serverGroupedFetch by the typeof test, so no shape is both. hasInlineData is dataConfig?.provider === 'value', and dataConfig (1607–1615) coerces the data prop into a value provider, so both whole-rows shapes clear it — no false refusal on inline rows, pinned both ways and run here. Same entry set as serverGroupedFetch less the readability gate: right, since over such a source an unreadable key has no page to fall through to. No dataSource at all: no refusal and no fetch, the pre-existing DataSource required path. No row query: PROVEN. The guard if (groupingNeedsHeaderQuery) return; (2195) sits after Step 1 inside the try, so finally still clears loading; the render order is filter refusal (4028) → this panel (4045) → load error → spinner, so nothing shadows it. The pin asserts find not.toHaveBeenCalled() after getObjectSchema resolved and a five-tick settle, beside a control where the same find-only source, ungrouped, is queried once and draws; red on base in my run. Object definition still read, masked-only key lifts: SOUND. groupingRefusedKnownMasked is the KNOWN-masked set (the warned half of maskedGroupingSignature, computed with objectTypesPending false), so a key whose column carries no authored type is not refused until the definition lands; then the predicate flips, the load effect (which names it in its dependency array) re-runs and the flat window goes out. What the user sees on that one shape (masked-only grouping, find-only source, no host objectFields): a red grid.errorLoading panel for one schema round-trip, then the flat grid. Disclosed in the PR; no in-repo producer; if it is ever worth a line, placing the panel after the spinner branch trades the flash for one spinner. Panel: RIGHT. role="alert", heading grid.errorLoading, sentence grid.grouping.needsHeaderQuery naming queryGroupHeaders in GRID_DEFAULT_TRANSLATIONS and all ten packs, data-testid="grid-grouping-needs-header-query".

  2. The ListView refusal — the whole-versus-window split is TRUE for every on-contract shape. The fetch effect returns before any find on a value provider with an array items (2218–2233) and on an array schema.data (2237–2249); ganttOwnsData is gantt-only; the refusal's early return (2265–2270) sits before the objectDefLoaded gate, so it fires on the first run and clears held rows. Every other shape reaches dataSource.find(schema.objectName, findParams) (2646) with $top: effectivePageSize. Two off-contract shapes the claim's "exactly when" overstates: (a) a value provider whose items is not an array is excluded from the refusal (provider !== 'value') yet falls through the early return and fetches a window; (b) currentView is schema.viewType as normalizeListViewSchema wrote it (a missing or list kind becomes grid, so the bare list view is covered), but an unrecognised kind reaches the default: grid branch with neither gridOwnsGroupedFetch nor this refusal firing — the gate shape feat(plugin-grid): server-side grid grouping — group headers from the query, rows paged per group (objectui#7189) #10878 shipped. Neither reaches a spec-valid schema; nits, no card. The two predicates count the same entries: usableGroupingFields and collectGroupingFieldRefs both admit an object with a non-empty string field. Grid never mounted: PINNED (lastGridProps null, no stub in the document); surfaceDrawsFetchedRows is false so no record-count bar; the refusal replaces only the body ternary at 4994 and the toolbar renders above it, so it stays. DataErrorState spreads ...props onto its root (view-states.tsx 127), so role="alert" and the test id land. No Retry: right, the verdict is a property of the source. A toolbar search does not lift it: right.

  3. Marker retirement — every reader gone. git grep over the head tree for the three keys, groupingIsPartial, partialLabel, partialTitle, the two test ids and the retired file's name (CHANGELOGs and .changeset/ excluded) hits once: the historical prose in gridArrayArmOrderby-8973's header, which this PR re-pointed. GroupRow's props and render are gone; the nonGridRowCeiling.tsx and useGroupedData.ts doc sentences are rewritten; the plugin-grid.mdx and README hunks are byte-identical; the schema-reference.md row and the plugin-list README are brought true; no docs page or README describes the marker. Ten packs: CONSISTENT. Each drops the three keys and adds needsHeaderQuery; the rowCeilingNote comment that cited partialNotice is rewritten in each. ar, de, es, fr, ja, ko, pt, ru and zh are genuine translations with queryGroupHeaders untranslated; read each — no error found (zh uses full-width punctuation, as its neighbours do). GRID_DEFAULT_TRANSLATIONS and LIST_DEFAULT_TRANSLATIONS carry the sentence byte-identical to en; the mirror test names grid.* rows in the list map as borrowed pack keys, green.

  4. The twelve re-decided files — each re-decision honest, no assertion weakened, no refusal converted to silence. serverGrouping-7189: the control now pins the refusal, find never called and zero group rows; its two marker-absence lines are dropped because nothing is left to be absent, and the i18n pin carries the absence. ListView.groupedGridOwnsFetch-7189: the control now pins no grid mounted and no record-count bar. groupingProjection-7179, gridGroupingMembers-8071, badgeHexCrossSurface, maskedColumnSurfaces-10583: the doubles gain queryGroupHeaders, and the assertion under test (the $select projection, the header label, the hex, the masked key dropped from the query) is read off the group row query or the server headers — the same assertion, one path over. expandFls-7215 PIN 7: the member is declared answering no groups; an unreadable-only key keeps serverGroupedFetch false, so that grid fetches its flat window — the pre-existing path, exercised as before. ObjectGrid.pageSizeNonPositive-9853: grouped cases hand the seven rows in whole; the page-of-groups arithmetic is identical. The three ListView projection pins: the member is declared, so the window fetch they read still goes out (the feat(plugin-grid): server-side grid grouping — group headers from the query, rows paged per group (objectui#7189) #10878 review's "extra request"); a follow-up that gates that fetch will re-decide them again. Two header re-points. The retired file's thirteen cases: the marker and notice cases retire with the marker; "does not mark rows handed to it inline" becomes the two whole-rows pins; the ungrouped control is subsumed by the new ungrouped control. Two cases have no successor: PIN 4 "reads the total a HOST supplies (the ListView paging shape)" (③), and "grouping never reaches the mobile card view" — the source pin on the card-view branch (useCardView, rows present, !isGrouped) is now pinned nowhere (minor; re-home it in the follow-up).

  5. Unchanged paths: EXACTLY as after PR feat(plugin-grid): server-side grid grouping — group headers from the query, rows paged per group (objectui#7189) #10878. The diff's only ObjectGrid.tsx edits are the defaults map, the predicate, the Step 2 guard, the dependency array, the panel and the marker deletion; serverGroupedFetch, useServerGroupHeaders, useServerGroupRows, groupRowQuery, useGroupedData with server, and useServerPagination = !hasInlineData && (!isGrouped || serverGroupedFetch) are untouched; serverGrouping-7189's ten positive cases pass with their assertions unchanged (only the two marker lines removed).

  6. Clause-②: yes is RIGHT. A published component that grouped any source's fetched page now refuses on one class of source: a narrowing, declared, review owed.

② Semver level

minor on @object-ui/plugin-grid, @object-ui/plugin-list and @object-ui/i18n: RIGHT under objectui AGENTS.md (a break ships as minor with the semantics stated); Changeset Bump Policy, Changeset Fixed Group Check, Changeset Declaration and Changeset Claim Re-read are green on the head. @object-ui/types and @object-ui/react with no changeset: RIGHT — the changed lines in data.ts and nonGridRowCeiling.tsx are all comment lines (checked mechanically: the diff minus comment lines is empty), no export, key or behaviour moves, and the presence gate is family-wide (fixed group), so the one changeset satisfies it. Changeset sentences: each TRUE. The migration line's two routes are the two doors in the code (typeof queryGroupHeaders === 'function', or hasInlineData); "retired everywhere before any release carried it" holds (the #7226 changeset is still pending on main); "grouped the page it fetched in the last release" holds; the objectstack#20358 sentence holds. Dated notes: each TRUE, and the sentences they name exist verbatim in their entries — 7189-server-side-grid-grouping.md: "a source with no header query still groups the page it fetched and still marks it partial" and "remains only for the data sources that cannot answer the header query"; 7189-grouped-grid-partial-disclosure.md: "the grid still groups the page it fetched in the browser, as described above, and the marker and the notice are kept there". Both follow the repo's dated-note precedent; Changeset Overwrite Report green. One understatement: "a data prop from a parent view — are still grouped in the browser, exactly" reads as whole rows; a parent that hands a window with rowCount gets it grouped too, and not exactly (③).

③ Boundary flags

  • Seat decision A (a host that hands rows AND declares external manual pagination) — measured at head: a grouped grid handed 100 of 186 rows with manualPagination, rowCount: 186, page, pageSize and onPageChange, over a find-only source, draws TWO groups (86, 14), three units missing, no refusal, no marker, no "186" anywhere in the document, no find. The retired PIN 4 pinned the notice with "186" on that very render, through hostRowCount. A is sound by the letter of the ruling's Kept line, which names hasInlineData as the kept case, and the shape has no in-repo producer (ListView passes host paging only when ungrouped) and grouped silently in the last release (the marker never shipped). A's stated reason is not sound: the ruling put the refusal on ListView because the grid cannot tell a window from whole rows, and on this shape the grid can (externalManualPagination with hostRowCount above data.length). On the merits it contradicts 「everything else needs the header query」 and keeps a second, silent mode on a public prop set (ObjectGridExternalPaginationProps). Not a defect against the ruling's pins or scope; a follow-up card is owed: refuse (or at least disclose) when isGrouped && externalManualPagination and hostRowCount exceeds data.length, with a sentence of its own — the queryGroupHeaders sentence is wrong there (the host owns the fetch, and the source may even declare the member), which is why B needs a key. Until then the docs' "The grid takes rows a host hands it to be the whole set" is the only disclosure.
  • Unreadable-key grouping over a capable source still groups a fetched page: verified as the feat(plugin-grid): server-side grid grouping — group headers from the query, rows paged per group (objectui#7189) #10878 state, not moved here; out of scope as the PR notes.
  • The refusal flash on a masked-only key: as ① item 1; acceptance note.
  • PR feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278 overlap — mapped at its head eab4c8e52 (its merge-base with main is 88a4ef616, before feat(plugin-grid): server-side grid grouping — group headers from the query, rows paged per group (objectui#7189) #10878). Its ObjectGrid.tsx hunks: the spec import, the DEFAULT_* constants, groupedPageSize, serverPageSize / fetchWindow, the $top / $skip params, the load effect's dependency array, pageSize, groupingPartialWindowFull, the group pager's select. Merge probes (git merge-tree): against this PR's base it already conflicts in one ObjectGrid.tsx region (the dependency array) and once each in plugin-grid.mdx and the README; against this head there is one more ObjectGrid.tsx region — its groupingPartialWindowFull edit against this PR's deletion of the whole block. The PR body names exactly those two sites, and the docs sentence feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278 will need to drop; pageSizeNonPositive-9853 auto-merges.
  • PR body: read sentence by sentence; nothing false found. The local-verification counts were not re-run; the head's CI stands for them.
  • Pending changesets: 36 name grouping, the marker, queryGroupHeaders or nonGridRowCeiling; the sentences in 7179, 9853, 10583, 10657, 7210 and 7508 still hold at the head; none is made false.
  • The five commits' Co-Authored-By trailers name a model, as the dev disclosed; a process note for the dispatch contract, not a code defect.
  • Off-contract shapes (value provider without an array items; an unrecognised viewType): ① item 2, nits, no card.
  • CI on the head, read from its check-runs: poll 1 at 04:20Z — 42 checks, 27 success, 12 in progress; poll 2 at 04:30Z — 43 checks, all completed: 40 success (Action Ref Convention, Build & E2E, Build Docs, Bundle Analysis, Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Control Byte Scan, Doc Component Type Check, Doc Example Id Check, Doc Fence Language Check, Doc Snippet Type Check, Docs Route Eager Closure Check, Governed Surface Queue Guard, Inert vi.mock Specifier Check, Internal Docs Link Check, Line Citation Gate, Lint, Live E2E (informational), Pre-Install Import Graph Check, README Export Check, Shell Escape Residue Scan, Skill Eval Token Check, Skill Example Check, Skill Guide Path Check, Spec Main Shape Gate, Test, Test (dist pins), Test (shard 1/8) through Test (shard 8/8), Type Check, label) and 3 skipped by design (dependabot, Test (coverage), the unexpanded coverage-shard matrix entry). The two the dev declared to CI, README Export Check and Docs Route Eager Closure Check, are among the green. No failure, no annotations to read.
  • Read-only held: no GitHub write but this comment, no MCP GitHub tool, no edit under /home/user/objectui or /home/user/objectstack; the throwaway worktree under this review's scratch dir was removed with git worktree remove and pruned, and the shared checkout is clean.

Implemented-by: claude/issue-10881-grouping-needs-header-query
Reviewed-by: session_01DuWo5bdP9SdVebamn99GGk

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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3095.6 KB 3104.5 KB
Main entry chunk (gzip) 148.5 KB 350 KB
Entry file index-B942HzBp.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.57KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 83.99KB 22.86KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.82KB 36.44KB
plugin-designer (index.js) 216.42KB 44.47KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.00KB 9.09KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.42KB 22.18KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.52KB 1.36KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3095.6 KB 3104.5 KB
Main entry chunk (gzip) 148.5 KB 350 KB
Entry file index-MMcUZBwy.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 83.99KB 22.86KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.82KB 36.44KB
plugin-designer (index.js) 215.03KB 44.21KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.00KB 9.09KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.42KB 22.18KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 94df460146e9fb82958386e622ce44aa8ec15cb4

Delta review of patch round 2, after the PASS record 5863494567 on 638a8250c. Read: the PR body as rewritten for round 2; the two new commits 156eb5103 and 94df46014 and the delta diff 638a8250c..94df46014 (17 files: ObjectGrid.tsx, useGroupedData.ts, the ten packs, the changeset, plugin-grid.mdx, the plugin-grid README, the two pin files); the head sources of ObjectGrid.tsx and ListView.tsx at every hostRowCount / rowCount read site, the dataConfig derivation, ObjectGridExternalPaginationProps, and the render's early returns; PR #10278 at eab4c8e52 re-probed against the new head; origin/main (now 6a7f24e92, nine commits past the merge-base) probed for overlap and merge; the head's check-runs polled eight times until settled. Ran, in a throwaway git worktree add --detach under this review's scratch dir (offline frozen install, removed and pruned afterwards): the two behaviour pin files at head, Test Files 2 passed (2), Tests 16 passed (16); the same two files with ObjectGrid.tsx at 638a8250c, Tests 3 failed | 13 passed (16) — exactly the two window refusals and the spinner pin red, every round-1 pin and every control green. No gate or suite re-run, no GitHub write but this comment, no MCP GitHub tool, no edit under the shared checkouts.

① Derived judgments

  1. The whole-rows predicate: EXACTLY RIGHT. groupingNeedsWholeRows (ObjectGrid.tsx 2149–2153) is externalManualPagination (the pre-existing manualPagination === true, numeric rowCount, function onPageChange, line 1629), an array handedRows, hostRowCount strictly above handedRows.length, and a usable grouping entry not refused on a KNOWN masked type. handedRows is dataConfig.items (2148), and dataConfig (1598–1616) is built from the props — passedData (the data prop), else boundData, else the page schema's value block — so it is the rows AS HANDED, decided synchronously in the same render as the props: whole rows never flash it. No refusal on whole rows: pinned with a rowCount equal to the 186 rows handed (the comparison is strictly "above"; the dev's ablation to "at least" reddens that control) and with no rowCount at all, both grouping as before. No other rowCount path is missed: hostRowCount has exactly two read sites in the grid, externalManualPagination (1631) and resolvedTotalMatching (5024, itself gated on externalManualPagination), so every way the grid can know it holds one page of more passes through the predicate's own gate; serverPage.count (5367) is the server-grouped per-group pager, not a host count. A host that passes rowCount without onPageChange or manualPagination was never a window by the grid's own definition (it client-sliced and never paged it), so treating it as whole rows is consistent with the mode's contract. One flash remains, disclosed in the body: a masked-only grouping key over a DECLARED window shows this refusal until the object definition lands, then withdraws — there is no read to wait behind; no in-repo producer, and a host that hands objectFields as ListView does never sees it. Panel: role="alert", heading grid.errorLoading, its own sentence grid.grouping.needsWholeRows, data-testid="grid-grouping-needs-whole-rows"; placed before the load-error and spinner branches, right, since nothing is in flight for it.

  2. Refusing over a capable source: RIGHT. With hasInlineData the grid never fetches, serverGroupedFetch is false whatever the source declares, and the host's window carries a filter and search the grid does not know, so the source's queryGroupHeaders is unreachable from those rows. The sentence names the two real doors — hand every record, or let the grid fetch from a source that implements the member — and the header-query sentence would have been false there. Pinned: the same window over a source declaring the member is refused the same way and queryGroupHeaders is never called.

  3. The ten packs: CONSISTENT and CORRECT. Each adds needsWholeRows beside needsHeaderQuery; ar, de, es, fr, ja, ko, pt, ru and zh are genuine translations of the English sentence with queryGroupHeaders untranslated — read each; no error found. GRID_DEFAULT_TRANSLATIONS carries it byte-identical to en; it is rightly NOT added to LIST_DEFAULT_TRANSLATIONS, which ListView never reads it from. Nit, no card: the i18n pin's presence control still walks only needsHeaderQuery; the second key's presence in all ten packs rests on the key-parity gates (check:i18n-keys, check:i18n-drift), green.

  4. The spinner reordering: SAFE for every other load state. The render's early returns are now filter refusal (4049) → whole-rows refusal (4071) → load error (4088) → spinner (4100, condition unchanged: loading && data.length === 0, or the server-grouped first load) → header-query refusal (4132). The header-query refusal requires !hasInlineData, so the load effect always runs for it: setLoading(true) at entry, the definition read, the Step 2 guard's return inside the try, finally clearing loading; data stays empty since nothing is fetched, so the spinner shows for exactly the read in flight and the panel follows. The effect's other early returns precede setLoading(true) and belong to shapes the panel does not reach (hasInlineData; schemaFilterRefusal, whose own panel is first). A throwing getObjectSchema sets error and draws the load-error panel ahead of it, right. Nothing else moved: the spinner's condition and position are the round-1 lines. Pinned: a masked-only key shows Loading grid…, never the refusal, then one find and the flat rows.

  5. ListView never hands a grouped grid host paging: TRUE. paginate = currentView === 'grid' && !(groupingConfig?.fields?.length) (1504); the only hand-down of manualPagination / rowCount is at 5167, gated paginate && serverTotal != null. Pinned on the one shape that hands a grouped grid a window (a capable source with a toolbar search active): rows handed, no manualPagination, no rowCount, against a lit ungrouped control handed manualPagination: true and rowCount: 186; initialSearchTerm is a real prop (330).

  6. Every sentence TRUE. Changeset: "a data prop from a parent view that declares no larger rowCount", the host-declared-window bullet, "It used to have that page grouped as if it were whole" (measured in round 1: 86 and 14), the i18n bullet's two keys, the third breaking-semantics sentence "whatever its data source" (pinned), and the migration line's two routes — each holds at the head. Docs and README: identical hunks, each sentence matches the predicate. useGroupedData doc: true, including that an undeclared window (the searched ListView case) is still grouped. PR body round-2 items 1 to 5, the ListView measurement, the ablations and "round 2 touched none of PR feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278's hunks": each read; nothing false found.

  7. No regression. The delta's only source edits are the new key, the new predicate, the moved header-query panel and the new whole-rows panel; groupingNeedsHeaderQuery, its Step 2 guard, ListView's refusal, serverGroupedFetch, useServerGroupHeaders, useServerGroupRows and useGroupedData's body carry no diff line. My run: all 13 round-1 pins and controls green with ObjectGrid.tsx at 638a8250c and at head; CI's eight Test shards and Test (dist pins) green.

  8. Clause-②: yes still right; the seat's recorded reversal to B is sound on the round-1 measurement and closes the second silent mode.

② Semver level

Unchanged and RIGHT: minor on @object-ui/plugin-grid, @object-ui/plugin-list, @object-ui/i18n; the delta touches no file under packages/types or packages/react; the i18n bullet now names both keys; the breaking semantics add the host-declared window with its three props; Changeset Bump Policy, Changeset Fixed Group Check, Changeset Declaration, Changeset Claim Re-read and Changeset Overwrite Report green on the head.

③ Boundary flags

  • PR feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278: round 2 touches none of its hunks (the new predicate sits under groupingNeedsHeaderQuery, the moved panel under the spinner, both outside its ranges). Re-probed with git merge-tree at the new head: the same three files conflict as at 638a8250c, ObjectGrid.tsx in the same two regions (the dependency array, pre-existing since feat(plugin-grid): server-side grid grouping — group headers from the query, rows paged per group (objectui#7189) #10878; the groupingPartialWindowFull block this PR deletes). No new region.
  • origin/main drift: nine commits since the merge-base now touch the ten packs, ObjectGrid.tsx and ListView.tsx — one-line issue-citation edits in the two components and the app wizard's locale keys. git merge-tree origin/main with this head: clean, no conflict.
  • Refusal flash on a masked-only key over a declared window: acceptance note, as ① item 1.
  • i18n pin not extended to needsWholeRows: nit, as ① item 3.
  • The round-2 commits carry the dispatch contract's model-free trailer pair, as the body says; the first five commits are as noted in the round-1 record.
  • CI on the head, read from its check-runs: first poll 05:14Z — 42 checks, 23 success, 16 in progress; settled 05:24Z — 43 checks, all completed: 40 success (Action Ref Convention, Build & E2E, Build Docs, Bundle Analysis, Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Control Byte Scan, Doc Component Type Check, Doc Example Id Check, Doc Fence Language Check, Doc Snippet Type Check, Docs Route Eager Closure Check, Governed Surface Queue Guard, Inert vi.mock Specifier Check, Internal Docs Link Check, Line Citation Gate, Lint, Live E2E (informational), Pre-Install Import Graph Check, README Export Check, Shell Escape Residue Scan, Skill Eval Token Check, Skill Example Check, Skill Guide Path Check, Spec Main Shape Gate, Test, Test (dist pins), Test (shard 1/8) through Test (shard 8/8), Type Check, label) and 3 skipped by design (dependabot, Test (coverage), the unexpanded coverage-shard matrix entry). No failure, no annotations to read.
  • Read-only held: no GitHub write but this comment, no MCP GitHub tool, no edit under /home/user/objectui or /home/user/objectstack; the throwaway worktree was removed with git worktree remove and pruned, and the shared checkout is clean.

Implemented-by: claude/issue-10881-grouping-needs-header-query
Reviewed-by: session_01DuWo5bdP9SdVebamn99GGk

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 05:29
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 244d516 Sep 28, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10881-grouping-needs-header-query branch September 28, 2026 05:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants