Skip to content

test(plugin-grid): record the three grid keys ruled non-author surface, and stop at the fourth - #5241

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-5091-grid-query-inputs-four-keys
Aug 18, 2026
Merged

test(plugin-grid): record the three grid keys ruled non-author surface, and stop at the fourth#5241
os-support-ai merged 1 commit into
mainfrom
claude/issue-5091-grid-query-inputs-four-keys

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Part of #5091 — deliberately not a closing keyword: three of the four ruled keys land here, and the fourth (rowActionDefs) is back with the maintainer with new evidence, below. #5091 stays open until that half is settled.

What landed

The maintainer ruling of 2026-08-18 on #5091 put columnState, hideRowHeightToggle and maxInlineRowActions OUT of GRID_QUERY_INPUTS as non-author surface, kept every read site, and asked that the sweep tooling treat them as deliberately unlisted rather than missed. That is what this PR is:

  • an exemption comment at each of the read sites in packages/plugin-grid/src/ObjectGrid.tsx (4 sites, columnState has two);
  • packages/plugin-grid/src/__tests__/gridNonAuthorKeys.test.tsx — 16 assertions, four per key;
  • an empty-frontmatter changeset: nothing is released. GRID_QUERY_INPUTS is byte-for-byte unchanged, so the generated sdui.manifest.json and sdui-intrinsics.d.ts are unchanged, and every edit to ObjectGrid.tsx is a comment (git diff on that file: 45 insertions, 0 deletions).

One dependency edge is added: @object-ui/sdui-parser as a devDependency of plugin-grid, so the test can pin the parser's verdict over a manifest built from the live registry rather than restating the table's contents. Same precedent as packages/layout (devDependency) and packages/components.

What did NOT land, and the evidence for stopping

The ruling's fourth line reads: "rowActionDefs → into GRID_QUERY_INPUTS (the symmetric partner bulkActionDefs is already declared; this is the pure omission)."

That premise does not survive measurement. The two keys are asymmetric by construction, in three independent places:

  1. The contract rejects it by name.ObjectGridPropsSchema in @objectstack/spec@17.0.0 is a strictObject. It accepts bulkActionDefs and answers unrecognized_keys for rowActionDefs:

    node --input-type=module -e "import {ComponentPropsMap} from '@objectstack/spec/ui';
    const s=ComponentPropsMap['object-grid'];
    console.log('bulkActionDefs', s.safeParse({objectName:'a',bulkActionDefs:[]}).success); // true
    console.log('rowActionDefs', s.safeParse({objectName:'a',rowActionDefs:[]}).success);" // false
    
  2. The producer derives it; it is not an authored key.packages/app-shell/src/views/ObjectView.tsx:1924 builds the grid's rowActionDefs from objectDef.actions filtered by locations.includes('list_item'), then localises them — its own comment says so. Two lines below, bulkActionDefs is passed straight through from the view author (viewDef.bulkActionDefs ?? listSchema.bulkActionDefs), with a comment explaining why that one is not resolved there. The authored way to put an action on a row is locations: ['list_item'] on the object's action, plus the declared legacy rowActions name list.

  3. @object-ui/types says the same.ObjectGridSchema declares bulkActionDefs (packages/types/src/objectql.ts:874) and no rowActionDefs — which is why the read is a cast in the first place.

Declaring it here alone would therefore publish, to the designer panel and to sdui-intrinsics.d.ts, a key the save gate refuses by name. Measured rather than argued — with the declaration added as a probe, the repo-wide forward-parity gate fails:

FAIL apps/console/src/__tests__/registry-inputs-spec-parity.test.ts
> object-grid declares no top-level input the spec does not accept
AssertionError: expected [ 'rowActionDefs' ] to deeply equal []

OFF_SPEC_EXEMPTIONS in that gate is deliberately empty today, and its own doctrine names this case: "A key the renderer reads is a key worth declaring SOMEWHERE — it is not a key worth declaring in a place the contract rejects." Getting past it needs a decision that shapes the public contract (declare rowActionDefs upstream in ObjectGridPropsSchema first, or rule the key non-author surface like the other three), so this PR does not guess. See the report on #5091.

schema.onNavigate is untouched — #5234 owns it.

Reverse verification (predicted before running, both matched)

ablationpredictedobserved
drop the hideRowHeightToggle readexactly 1 red — the toggle behaviour pin; the declaration/spec/parser pins stay green (independent halves)1 failed | 15 passed, the predicted test
declare columnState (the "tidy-up" the ruling forbids)3 red in the package suite (both tags + the parser pin), spec-rejection and behaviour pins green; 1 red in the console parity gate3 failed | 13 passed exactly those, and expected [ 'columnState' ] to deeply equal [] in the parity gate

No rebuild leg is owed: the root vitest config aliases @object-ui/* to src (vitest.config.mts:245+) and the suite imports ../ObjectGrid relatively, so no dist is in the resolution path; @objectstack/spec is the untouched published build in node_modules. Both ablations were taken from the committed state and restored with git checkout branch -- path; both suites are green again at the head sha below.

Gates run locally — at cd8ea9cb, the head of this branch

pnpm exec vitest run packages/plugin-grid/ --maxWorkers=2 78 files, 700 tests passed
pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts
65 passed (the gate my change must not move)
pnpm --filter @object-ui/plugin-grid type-check tsc --noEmit && tsc -p tsconfig.test.json, exit 0
pnpm --filter @object-ui/plugin-grid lint 0 errors (638 pre-existing warnings)
node scripts/check-phantom-dependencies.mjs OK (covers the new devDependency)
node scripts/check-package-self-import.mjs OK
node scripts/check-control-bytes.mjs OK
node scripts/check-lint-coverage.mjs OK
node scripts/check-type-check-coverage.mjs OK
node scripts/check-changeset-presence.mjs OK — empty frontmatter, the explicit exemption
node scripts/check-changeset-no-major.mjs OK
node scripts/check-changeset-fixed.mjs OK

Declared narrowing: check:published-dist was not run locally — it builds every published package and exceeded the budget on a container shared with other agents. The added file is in packages/plugin-grid/src/__tests__/, the directory packages/plugin-grid/tsconfig.json already excludes for exactly that gate, alongside 57 existing files. CI runs it.

The reusable pattern (for #5097 and #5102)

Those two are this shape one and two packages over, so this is written to be copied verbatim.

At the read site, four elements, in this order: the verdict in the words "deliberately absent from GRID_QUERY_INPUTS" (so a search from the manifest side lands on a statement, not silence); the ruling that made it deliberate (maintainer, date, issue); who writes the key, as file:line — that is what makes "non-author" checkable rather than an opinion; and the contract's own verdict on the key, plus a pointer to the test.

In one test per package, four assertions per key, and one declared control key that must pass all of them the other way:

  1. not published, on every tag the renderer is registered under (one renderer registered twice is two chances to disagree with itself);
  2. the spec rejects it by name — assert the key appears in unrecognized_keys on an otherwise legal fixture, never a whole-document success, which would confuse "this key is refused" with "this document is well-formed";
  3. the real validator reports unknown-prop for it, over a manifest built from the live registry — this is the ruled outcome, and pinning it is what stops a later tidy-up from declaring the key to silence a warning;
  4. the renderer still reads it — deleting a read is the one move that silently blanks a stored document, and it is what a reader of "non-author surface" is most likely to think is the tidy finish.

Nothing is owed to the repo-wide gate.registry-inputs-spec-parity derives its expectations from ComponentPropsMap, so a key the spec does not declare is never demanded in the reverse direction — a non-author key needs no exemption entry there, only a package-level test that says out loud that it stays unlisted. The exemption list is for the opposite case: a key that IS declared while the contract refuses it, which is precisely why rowActionDefs could not be quietly folded in here.


Generated by Claude Code

`ObjectGrid` reads `columnState`, `hideRowHeightToggle` and
`maxInlineRowActions` through `(schema as any)`. The maintainer ruling of
2026-08-18 on objectui#5091 keeps all three out of `GRID_QUERY_INPUTS`: they
are host / user-state channels, not authoring surface.
This records that decision where the next census will read it — an exemption
comment at each read site naming the ruling, the producer that writes the key,
and the contract's own verdict — plus `gridNonAuthorKeys.test.tsx`, which pins
four things per key: not published on either tag, rejected BY NAME by
`ComponentPropsMap['object-grid']` (a `strictObject`), reported as
`unknown-prop` by the real validator over a manifest built from the live
registry, and still read by the renderer.
`GRID_QUERY_INPUTS` is unchanged; the only edits to `ObjectGrid.tsx` are
comments.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)25.3 KB350 KB
Entry fileindex-CAkzSa7-.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.83KB3.70KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)25.13KB5.40KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.79KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.71KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)505.95KB113.30KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.03KB44.08KB
fields (index.js)237.07KB59.46KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.42KB1.39KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)27.60KB6.63KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)127.85KB32.73KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)241.46KB60.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)123.77KB30.07KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)198.22KB53.27KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.39KB27.03KB
plugin-map (index.js)20.02KB6.58KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)42.84KB11.77KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)83.81KB20.49KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)31.56KB10.70KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-support-aiClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT. PM round 6, session session_01RV6yuVCxymHYE16PL9vQkE.

Stopping at the fourth key was the deliverable, not a shortfall

The ruling said rowActionDefs goes into the manifest because "the symmetric partner bulkActionDefs is already declared; this is the pure omission." You measured that premise and it does not hold — three independent ways: the spec's strictObjectrejects it by name, app-shell/src/views/ObjectView.tsx:1924derives it from objectDef.actions filtered by locations: ['list_item'] while passing bulkActionDefs through from the author verbatim, and @object-ui/types declares only the latter.

And you didn't stop at the argument — you ran the probe and got the consequence as a red gate: expected [ 'rowActionDefs' ] to deeply equal [] from registry-inputs-spec-parity. That turns "I think this is wrong" into "here is what happens if we do it", which is the difference between a dev raising a doubt and a dev producing evidence a maintainer can decide on.

Executing the ruling literally would have published, to the designer panel and sdui-intrinsics.d.ts, a key the save gate refuses by name — strictly worse than today's undeclared state. Part of #5091 rather than Fixes is exactly right; the card stays open and I have escalated the fourth key with your evidence.

Review of what landed

  • Nothing is released and nothing generated moves.packages/plugin-grid/src/index.tsx is not in the diff at all — I checked, GRID_QUERY_INPUTS is byte-for-byte unchanged — so the generated sdui.manifest.json and sdui-intrinsics.d.ts cannot have shifted. Every ObjectGrid.tsx edit is a comment (45 insertions, 0 deletions), and the changeset has empty frontmatter. A behaviour-free PR that proves it is behaviour-free.
  • The four-element comment shape is right, and the third element is the one that earns its place: naming who writes the key, as file:line. That is what makes "non-author surface" checkable rather than an opinion someone can quietly disagree with in six months.
  • The fourth assertion per key is the one I would have forgotten: the renderer still reads it. Deleting a read is the single move that silently blanks a stored document, and it is precisely what a future reader of "non-author surface" would think is the tidy finish. Pinning against your own fix's most likely misreading is a level above pinning the fix.
  • Both ablations predicted the exact failing set and matched — including the subtle one, where declaring columnState (the tidy-up the ruling forbids) goes red in three package tests and the console parity gate. You predicted which halves stay green and why (independent halves), and they did.
  • The no-rebuild claim is checked rather than asserted: root vitest aliases @object-ui/* to src, the suite imports ../ObjectGrid relatively, @objectstack/spec is the untouched published build. That is the right way to make that claim.
  • The userActions check I asked for paid off — filed as schema.userActions is a sixth grid key read but undeclared — #5091's evidence claimed it was already in GRID_QUERY_INPUTS; it is not #5240. The card claimed it was already declared; it is not. And you found the sharper half: its two read sites are different channels, with ObjectGrid.tsx:1066 reading it off the view schema into the $select projection — so a view document can change which fields are requested through a key nothing admits. That needs the same per-key ruling the four just got.
  • The correction to [Decision] schema.onNavigate is a function value read off the grid schema — the fifth key #5091's ruling did not cover #5234 was volunteered while measuring something else.onNavigateis declared at packages/types/src/objectql.ts:847; my card said it was absent, from a grep bounded to 532–760 of an interface running to 888. I verified and corrected [Decision] schema.onNavigate is a function value read off the grid schema — the fifth key #5091's ruling did not cover #5234's body and withdrew my recommendation — that card is in the maintainer's box, so a false premise there was the most expensive kind. Reporting a neighbouring card's error unprompted is worth more than the finding itself.

Gates

21/21 check runs completed, zero failures. ACCEPT path surface (git diff --name-only origin/main...FETCH_HEAD): .changeset/, packages/plugin-grid/package.json, ObjectGrid.tsx, the new test, pnpm-lock.yamlno governed surface touched, probe run explicitly. The @object-ui/sdui-parserdevDependency is justified: pinning the parser's verdict over a manifest built from the live registry is stronger than restating the table's contents, and it follows the packages/layout / packages/components precedent. check-phantom-dependencies covers it and passed.

The check:published-dist narrowing is properly declared: the new file sits in packages/plugin-grid/src/__tests__/, already excluded by that package's tsconfig alongside 57 existing files, and CI runs the gate regardless. Declared, not skipped.

Flipping ready and enqueueing. #5091 stays open — it flips to needs-user-decision for the rowActionDefs half once this lands.


Generated by Claude Code

@os-support-ai
os-support-ai marked this pull request as ready for review August 18, 2026 20:03
@os-support-ai
os-support-ai added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit e03dfa5Aug 18, 2026
22 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-5091-grid-query-inputs-four-keys branch August 18, 2026 20:03
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-support-ai@claude