Repository navigation
fix(plugin-grid,plugin-list): resolve context tokens in a directly authored object-grid or list-view filter (objectui#10607) - #10656
Conversation
…t-grid filter (objectui#10607) The node's own `filter`, and its deprecated alias `defaultFilters`, are resolved once through @object-ui/core's shared resolveFilterPlaceholders against useFilterScope(), and held against their inputs (compared by structure, the scope's members one by one) so an equal filter keeps its reference and does not refetch. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
…view filter (objectui#10607) The node's own `filter` is resolved once through @object-ui/core's shared resolveFilterPlaceholders against useFilterScope(), and held against its inputs (compared by structure, the scope's members one by one). The fetch, the page-reset signature, the self-querying views, the child view's node, the export and the empty-state copy all read the held value. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
Conflicts in packages/plugin-list/src/ListView.tsx resolved as a union: the @object-ui/react import keeps useDataInvalidation (objectui#10572) and adds useFilterScope; the fetch effect's dependency list keeps invalidationNonce and reads authoredFilter in place of schema.filter. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…uthored grid or list skips the resolver (objectui#10607) `.changeset/10506-named-view-context-tokens.md`: one sentence, prose only, front matter byte-identical. After objectui#10607 both nodes call the shared resolver, so "(A directly authored `object-grid` or `list-view` node still does not call it.)" becomes "(A directly authored `object-grid` or `list-view` node calls it too, since objectui#10607.)". `.changeset/10607-grid-list-filter-context-tokens.md`: the sentence that pointed at the gap the 10506 entry used to note now says what is true of both entries. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
Brings in f99f9cd (plugin-form only). No conflicts. Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsRead: card objectui#10607 (body and 5 comments), triage Diff shape. 6 files, +726/−22: 1. One shared resolver, no second resolver — RIGHT. Triage 2. The hold — RIGHT, and it never keys on a per-render identity (AGENTS.md #10). Refetch-loop check (brief item). The grid's load effect now keys on 3. The local comparator 4. 5. 6. 7. 8. Hook order. 9. Missing asks — none. Reproduce-first: the pins' token cells read the literal on base by construction (base has no resolver read, so 10. Done that was not asked — two items, both judged above. The alias resolution (item 5) and the effect dependency (item 6). Nothing else: no docs, no other node, no other package. 11. Existing pins edited or deleted — none. The diff touches no existing test file. The 10506 plugin-view pin mocks 12. New pins can fail; lit control and negative leg present. Lit control: 13. Prose elsewhere. Out-of-scope finding, not a stop (noted for a card, not filed here): ② Semver level
Changeset 10607, sentence by sentence (all TRUE at head):
Pending 10506 changeset (the amended claim's one sentence). The diff to that file is exactly one line (line 22): 「(A directly authored The 16 other pending changesets naming Docs/README prose added by the diff: none (the diff adds no file under ③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #10607
Clause-②: no — the documented context tokens resolve on two more hosts, through the one shared resolver; no schema, export or accept set moves.
What changed
A directly authored
object-gridorlist-viewnode, with noobject-viewabove it, sentfilter: [['owner', '=', '{current_user_id}']]on$filteras the literal token. Neitherplugin-gridnorplugin-listreadresolveFilterPlaceholdersoruseFilterScope. Each host now resolves its own authored filter ONCE, through@object-ui/core's sharedresolveFilterPlaceholders, againstuseFilterScope()from@object-ui/react. This is the shape PR objectui#10577 used forobject-view. There is no second resolver, and neitherfilter-tokens.tsnoruseFilterScope.tsis touched.The hold. It is the pattern of plugin-view's
useResolvedFilterSegments, one per host. The resolved value is held in state, keyed on the authored filter (compared by structure) and on the scope's three members (currentUserId,currentOrgId,onUnresolved), each compared on its own and never by the scope object's identity (AGENTS.md #10). A set during render re-resolves when an input changes. Each host has one resolved value, and every read site reads it.@object-ui/plugin-grid,ObjectGrid.useResolvedGridFiltersresolves the node'sfilterand its deprecated aliasdefaultFiltersin one call.schemaFilterSource(the lowering both the query and the server-side export read) now reads the resolvedfilter.defaultFiltersleg reads the resolved alias.schemaDefaultFilters). It readschema.defaultFilterswithout keying on it, so a new signed-in user would not have re-queried a token carried only there.@object-ui/plugin-list,ListView.useResolvedAuthoredFilterresolvesschema.filteronce, right after the schema is normalized. The fetch and its dependency list, the page-reset signature, the self-querying views (gantt, tree, chart), the child view's node (filter:), the export and its dependency list, and the empty-state "filtered" test all readauthoredFilter. After this change the only rawschema.filterread left in code is the hold's own input.isSameAuthoredFiltersits in each file. It follows the same rules as plugin-view'sisStructurallyEqual: primitives byObject.is, aDateby instant, arrays in order, plain objects by key set, and everything else by reference. It is local because plugin-view depends on plugin-grid, plugin-list depends on neither, and a shared export would move an export set..changeset/10607-grid-list-filter-context-tokens.md, with'@object-ui/plugin-grid': patchand'@object-ui/plugin-list': patch. One sentence of the pending.changeset/10506-named-view-context-tokens.mdis corrected too (round 2, below).Files:
packages/plugin-grid/src/ObjectGrid.tsxpackages/plugin-list/src/ListView.tsxpackages/plugin-grid/src/__tests__/ObjectGrid.filterContextTokens-10607.test.tsxpackages/plugin-list/src/__tests__/ListView.filterContextTokens-10607.test.tsx.changeset/10607-grid-list-filter-context-tokens.md.changeset/10506-named-view-context-tokens.mdRound 2 (
80eca8f, on amended claim 5834409941). Prose only..changeset/10506-named-view-context-tokens.md: one sentence, and the front matter is byte-identical (md58212bf42dc2f6ba4025ef7ced79c6d6dbefore and after). 「(A directly authoredobject-gridorlist-viewnode still does not call it.)」 becomes 「(A directly authoredobject-gridorlist-viewnode calls it too, since objectui#10607.)」.object-viewhas done this since objectui#10506; these two nodes, authored directly, had not."origin/mainf99f9cd(plugin-form only), with no conflict. Betweene4aa578and80eca8f, no file underpackages/plugin-gridorpackages/plugin-listchanges.80eca8f, both pins: 19 passed, exit 0.80eca8f, each exit 0:check:changeset-claims,check:pending-changeset-literals,check-changeset-presence,check-changeset-overwrite(1 added, 1 modified: the 10506 file, declaringplugin-view: patchandcore: patchat base and now),check-changeset-no-major,check-changeset-fixed,check:control-bytesandcheck:new-line-citations(0 new).Measured
The code was measured at
e4aa578, which is the merge oforigin/main4a3d500into the branch. The branch was cut atdddbc27. Round 2 then mergedorigin/mainf99f9cd(plugin-form only) and changed no code in either package; its own readings are in the Round 2 paragraph below.The pins. Each renders the registered node under
FilterScopeProvider(userusr_42, orgorg_7) and readsdataSource.find's$filter.{current_user_id}and{current_org_id}.defaultFiltersalias.filterand ondefaultFiltersalone.4a3d500blob, the pins as committede4aa578On base, the token cells fail reading the literal (
expected [ [ 'owner', '=', '{current_user_id}' ] ] …). The cells that pass on base are the CONTROL, the same-node count and the already-resolved seam, as they should.H4, the hold, by ablation.
ablation-replace.mjsin WRAP mode swapped each hold call for a bare per-renderresolveFilterPlaceholders(…). The anchor hit once, and the blob moved.5 failed | 5 passed. The five hold cells time out at 15000ms: a copy minted every render re-queries without end. The five token cells stay green.4 failed | 5 passed. Two cells time out at 15000ms. Two readexpected 0 to be greater than 0: nofind()landed inside the 1000ms wait while the list kept re-rendering.blob after restoreequalsblob at HEAD, andgit diff HEADis empty.src/(../index,../ListView), so nodist/leg exists here.H3, the object-view path, measured once and not kept as a file. A temporary plugin-view test drove the real
ObjectViewinto the realObjectGridunder the same scope, then re-rendered twice (the same schema, then a spread copy).ObjectGrid.tsx: fetches$filter(identical in both)table.filter[["owner","=","usr_42"]]defaultFilters)[["owner","equals","usr_42"]]Resolving twice is a no-op, and it adds no fetch. The base file was swapped in and restored with a hash check against HEAD. The temporary test was deleted, and
git statusis clean.Suites and gates at
e4aa578, each run through the shared verify lock:pnpm exec vitest run packages/plugin-grid/: 157 files, 1497 tests passed, exit 0.pnpm exec vitest run packages/plugin-list/: 99 files, 1156 tests passed, exit 0.pnpm exec vitest run packages/plugin-view/(the idempotence leg): 49 files, 477 tests passed, exit 0.pnpm turbo run type-check --filter @object-ui/plugin-grid --filter @object-ui/plugin-list --concurrency=2: 16 tasks successful, exit 0, with the dependency closure rebuilt after the merge. Each package'stsconfig.test.jsonlists its new pin (--listFilesOnly, 1 hit each).Gates (each exit 0):
check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape.check:new-line-citations(0 new) andcheck:pending-changeset-literals.check-changeset-presence(4 source files of 2 released packages, 1 changeset),check-changeset-overwrite(1 added, 0 modified),check-changeset-no-majorandcheck-changeset-fixed.check:phantom-deps,check:unreferenced-sources,check:self-import,check-type-check-coverageandcheck:side-effects-array.check-governed-queue-guard --teston the five paths: NOT GOVERNED.The changeset-claims gate (
check:changeset-claims) is report-only and exited 0. It lists 12 pending changesets that nameObjectGrid.tsxorListView.tsx. Each paragraph was re-read, and none concerns the filter read: they cover i18n keys,reference_to, gantt date fields, export options, kanbangroupFieldand record source, the bulk executor's data source, the page size,userActions, and comment corrections.eslint on the four changed files, plain form: exit 0, 0 errors.
4a3d500against head, are unchanged in both sources:ObjectGrid.tsx207 → 207 andListView.tsx184 → 184, with 0 rules differing.NOT MEASURED:
check:eager-closureandcheck:sdui-registration-pins. Both need a full@object-ui/consoleproduction build; the second exits 2 ("No console build to weigh"), which is a missing prerequisite, not a verdict. CI's Bundle Analysis runs them. The only new import isresolveFilterPlaceholdersfrom@object-ui/core, which the console already loads eagerly through its app-shell callers.pnpm lintand the full test farm belong to CI.Acceptance notes
object-gridorlist-viewnode still did not call the resolver. That was false once this PR lands, so round 2 corrects that one sentence (see Round 2).check:changeset-claimswould not have flagged it, because it names the symbols, not the files.ObjectViewhands a named view's filter toObjectGridindefaultFilters. It re-resolves that filter when the user changes, and it does not remount the grid (its key carries no user). On base the grid's load effect did not key ondefaultFilters, so it did not re-query in place. The newschemaDefaultFiltersdependency covers that too.{today}, …) are now resolved on the client for these two nodes, in the browser's local time. That is the same callobject-view, the app-shell host,ObjectChartand the dashboards make. Before, they reached the server as literals, and the ObjectStack server resolved them in the tenant's configuredlocalization.timezone(UTC by default). A browser whose local day differs from the server's can now get a different day. They are held until the scope or the filter changes, so a refetch for another reason reuses the held value.{current_user_id}for a signed-in request, and refuses the request otherwise. So this is defence in depth there (triage graded it p3), and a real gap on a backend with no resolver of its own.ObjectGrid's load effect still keys onschemaFilter, which comes out of auseMemo(toFilterNodeSafely). That is an identity dependency AGENTS.md [WIP] Enhance every detail of the designer #10 names, and this PR feeds it a held input without moving it.ListView'sselfQueryFilterRef(a JSON-key hold) is also unchanged, and it now receives the resolved filter.content/docs/api/schema-reference.mdlists the resolver's callers without these two hosts. The list is now incomplete, but it is not false, and no docs sentence states that these two hosts skip the resolver.ListView.tsxduring this run. The merge conflicted in two places, both inside this PR's hunks, and was resolved as a union: the@object-ui/reactimport keepsuseDataInvalidationand addsuseFilterScope, and the fetch effect's dependency list keepsinvalidationNonceand readsauthoredFilter. objectui#10278 (a draft on theObjectGridpage size) touches other regions.The session behind this change is
https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy(domain:ui seat 5, dispatched dev).Generated by Claude Code