fix(plugin-grid): object-grid publishes the filter key it reads — filter, singular (#4041) - #4081
Merged
Conversation
…lter`, singular (#4041) The registration declared plural `filters` while `ObjectGrid` reads singular `schema.filter`, and `schema.filters` had zero read points. An author following the published vocabulary wrote `filters: [...]`, the save gate accepted it (`sdui-parser` validates props against `inputs`), the renderer dropped it, and the grid answered with the whole table — no error anywhere. The spelling that worked was reported as `unknown-prop`. Per the maintainer ruling (2026-08-10, option A) the declaration becomes singular `filter` on both `object-grid` and its `view:grid` alias, aligned with the renderer's read point, with `list-view`, and with the spec's own `ListView.filter`. The plural is deleted rather than taught to the renderer: it has zero read points, so no working usage depends on it. The read point now lowers through `toFilterNode`, the repo's single hop before the wire. `ListView.filter` is spec-declared `ViewFilterRule[]` and rejects AST triples, so the newly-reachable key delivers rule objects — which sent verbatim are refused as `400 INVALID_FILTER`. Declaring the key without the hop would have traded a silent wrong answer for a guaranteed failure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4041
object-gridpublished pluralfiltersas an authoring input whileObjectGridreads singular
schema.filter, andschema.filtershad zero read points inthe renderer. Both halves failed silently, in opposite directions: an author
following the published vocabulary wrote
filters: [...], the save gate acceptedit (
sdui-parser/src/validate.tswalks a node's props against the block'sinputs, andfilterswas there), the renderer never read it, and the gridanswered with the whole table — no error at authoring time and none at runtime.
Meanwhile the spelling that actually worked,
filter, was undeclared, so writingit was reported as
unknown-prop. Published word and runtime read pointed atopposite keys on a shipped surface.
Premise re-verified on current
origin/mainThe card was filed against
65bb513dc;mainis now8aad9fd50. All threeanchors still hold, plus one the card did not have:
8aad9fd50filtersdeclared twicepackages/plugin-grid/src/index.tsx:96(object-grid),:113(view:grid)schema.filtersread pointsfiltershits inObjectGrid.tsxare the header comment (:18) and the unrelatedlookup_filtersfield-prop list (:226)list-viewdeclares the singularpackages/plugin-list/src/index.tsx:52and:93,{ name: 'filter', type: 'array', label: 'Filter' }ListViewSchema.shapecarriesfilter(notfilters); probed live against the pinned@objectstack/spec/uiWhat changed
1. The declaration converges on the implementation (maintainer ruling
2026-08-10 on the card, option A). Both registrations now declare
{ name: 'filter', type: 'array', label: 'Filter' }, and they share oneGRID_QUERY_INPUTSconstant so theview:gridalias cannot drift from the blockit aliases. The plural is deleted rather than also taught to the renderer: with
no read point on any ref there is no working in-the-wild usage to keep
compatible, and reading it too would harden a misspelling into a second de-facto
contract for one concept (AGENTS.md #0.1).
2. The read point lowers through
toFilterNode. This is the constraintrecorded on the source thread (objectstack#7119, comment of 2026-08-09T20:26Z),
quoted verbatim as filed:
Until now the only value that could arrive at
schema.filterwas an ObjectQL ASTsynthesized by
ElementDataSourceGate, and copying that onto$filterverbatimwas correct. An author writes the spec's view vocabulary instead —
ViewFilterRule[],[{ field, operator, value }]— and that shape byte-copiedonto
$filteris refused on the wire:isFilterASTis false for an array ofobjects and the data API answers
400 INVALID_FILTER(measured against a realbackend in objectui#3431).
toFilterNodeis the repo's single lowering hopbefore the wire;
plugin-list'sbuildEffectiveFilter,plugin-view'sObjectViewandplugin-detail'sRelatedListalready go through it, and thisread point was the last consumer on the chain that did not.
That hazard is not merely prospective — it is reachable today.
packages/react/src/spec-bridge/bridges/list-view.ts:85builds a node oftype: 'object-grid'and copiesspec.filterstraight ontonode.filter. Andthe spec's
ListView.filteracceptsViewFilterRule[]and rejects ASTtriples — probed against the pinned spec:
So a spec
ListViewbridged onto this block already delivers rule objects to theread point, and they already went out raw. The rename is what makes that path
author-reachable; the lowering is what makes it correct.
Two narrow behaviour changes ride along at that read point, both toward the
shared sink's documented contract: a MongoDB-style object
filteris convertedinstead of silently dropped (the old
Array.isArrayguard read false for it, sothe grid returned every record — the same defect
buildEffectiveFilterfixed onepackage over), and a declared-but-empty
filter: []skips$filterrather thansending an empty one. The fetch and the server-side export read the same lowered
value, so the downloaded file cannot disagree with the screen.
The pin
packages/plugin-grid/src/__tests__/gridFilterInputSpelling.test.tsx— 10 cases.Every behavioural case reads the key name out of the registration
(
declaredFilterInput()) instead of spelling'filter'itself: a test thatrestated the key would keep passing through exactly the drift this card is about.
array-typed, on both tags, and the twotags agree with each other;
filtersis gone from both declarations;$filter(the card's pin), onobject-gridand onview:grid;ViewFilterRule[]arrives lowered —[['stage', 'equals', 'won']],with a second assertion that no bare rule object survives into the outgoing
filter, since that is the half that would rot silently;
ElementDataSourceGatepath must not be lowered twice);
$filter;declaration invited (
filters: [...]) produced no$filterat all — which isthe evidence that option A deletes a key with no users rather than a contract.
Reverse verification
Predicted directions before running, both confirmed, each taken out with
git checkout/ a scratch patch (nevergit stash):filters)expected 'filters' to be 'filter'andexpected undefined to deeply equal [ [ 'stage', '=', 'won' ] ]toFilterNodehop only (rename kept)expected [ { field: 'stage', …(2) } ] to deeply equal [ [ 'stage', 'equals', 'won' ] ]andexpected [] to be undefinedThe second row is the interesting one: the failure message shows the raw rule
objects sitting on
$filter— literally the payload the wire face answers400to. The two halves of this PR fail independently, so neither is carried by the
other.
Generated artifacts and adjacent gates
sdui.manifest.json/sdui-intrinsics.d.tsare not tracked in this repo—
packages/sdui-parser/scripts/gen-manifest.tswrites them into a buildoutput dir, and
scripts/dump-public-manifest.mjswrites intopackages/console/dist/.git ls-filesshows neither. Nothing to regenerate;the rename flows into both on the next build.
apps/console/src/__tests__/registry-inputs-spec-parity.test.tsdoes not judgethese tags:
coveredis derived fromComponentPropsMap, andobject-grid/view:gridare not entries in it (verified by probing the pinned spec).public-contract.test.tspins block tags, not input names.public-block-binding-reach.test.tsxasks only whether a declaredobjectNamereaches the data layer; it synthesizes a plausible value per declared input and
still reaches.
packages/app-shell/.../previews/block-config.tsdeclares no filter field forobject-gridat all — itsfiltershit is prose in a comment — so nothing ismechanically forced there and it is left alone.
All four console gates were run anyway rather than reasoned about (below).
Verification
Downstream consumer sweep — prefix filter, i.e. the packages that DEPEND ON
plugin-grid:
pnpm --filter '...@object-ui/plugin-grid' type-check→ 8 of 46projects, all
Done(plugin-grid,plugin-report,plugin-designer,plugin-view,app-shell,console,console-starter,byo-backend-console;apps/siteis the ninth consumer and has notype-checkscript). The first twoattempts at this sweep failed with
Cannot find module '@object-ui/plugin-grid'and friends — cold
distin a fresh worktree, the AGENTS.md section 9stale-artifact trap, not the change; building the console's dependency closure
first cleared every one of them.
A
.changeset/*.mdscores@object-ui/plugin-gridas patch, argued from thefact the ruling rests on: the removed key never reached the query on any released
version, so nothing that worked stops working — what changes is that a filter
written under the published name now takes effect.
Generated by Claude Code