Repository navigation
fix(plugin-view): resolve context tokens in the filters ObjectView hands on (objectui#10506) - #10577
Conversation
…nds on
ObjectView had no read of @object-ui/core's resolveFilterPlaceholders, so a
named list view filtering `owner` on `{current_user_id}` reached the query
with the literal token on the registered object-view node, while the
app-shell host resolved the same view.
The three authored filter segments (the active view's filter, table.filter,
table.defaultFilters) are now resolved once through the shared resolver,
against the session scope the host provides through useFilterScope, and the
non-grid find(), the object-grid schema and the delegated list-view schema
all read the resolved segments. The resolved value is held against its raw
inputs (compared by structure) and the scope's members, so ObjectGrid and
ListView, which key their fetch on the filter's identity, do not refetch per
render, and a `{now}` macro cannot mint a new value per render.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
The schema reference said resolving `{current_user_id}` on the object-view
node path was still open; it is resolved there now. The plugin-view README
and plugin page gain one paragraph beside the filter-tier precedence saying
every tier's filter goes through resolveFilterPlaceholders, where the scope
comes from, and what an unresolvable token does. Adds the plugin-view patch
changeset.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
tsc -p tsconfig.test.json refused the implicit any (TS7006). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
…t a ref Reading and writing a ref during render drew eleven react-hooks/refs warnings, which the lint config keeps only for code that predates the rule. React's own pattern for a value derived from previous renders is a state pair set during render; ObjectView.tsx lints at its base warning count again. Also types the view segment through NamedListView rather than a bare any. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
|
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
|
resolveContextTokens and resolveDateMacros rebuilt every object from its own keys, so a Date comparand, which the spec admits, came back from resolveFilterPlaceholders as an empty object. Routing plugin-view's filters through the shared resolver made that reachable on the object-view node. Only a plain object (prototype Object.prototype or null) is walked now; anything else is returned as the same instance. Pins: a core test (a Date survives as the same instance through resolveFilterPlaceholders and through each walk alone; plain and null-prototype objects carrying a token are still resolved) and one more plugin-view cell (a Date in a named-view filter reaches find() as a Date). The changeset gains the core patch line. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ 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 judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…e review Prose only, from the contract review of the head before this commit: - the date macros resolve "as they already did" only on the surfaces that call the resolver (app-shell host, ObjectChart, dashboard widgets), not on every surface; the server-side UTC difference is stated; - an unresolved token never widens the filter, and what the literal selects depends on the backend (ObjectStack resolves it when signed in, refuses it with a 400 otherwise; a backend with no resolver matches no record); - the hold compares three scope members, not two ids; - a non-plain object, a class instance included, is a leaf of both walks; - the console's app-shell renderListView resolves the view's own filter itself, so its date macros and signed-out fallback are unchanged, and the one difference there is a warning from this component's pass. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ 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 judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Exactly what stops it: the landing prose states two backend facts that are not true at objectstack Generated by Claude Code |
…ectly Prose only, from the delta review of the previous head: - a signed-out request is refused at the REST auth gate with a 401 before the filter is read; the resolver's 400 is reachable only where a guest context reaches the engine; - the server resolves date macros in the tenant's configured localization.timezone, UTC only by default; - the changeset names the views prop as the route by which the console's active-view filter still reaches ObjectView. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ 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 judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #10506
Clause-②: no — a renderer stops sending a spec context token literally; no export, schema or accept set moves.
What changed
@object-ui/plugin-view—ObjectView. It now sends the spec's context tokens ({current_user_id},{current_org_id}) as real values in the authored filters it passes on. It uses the ONE sharedresolveFilterPlaceholdersfrom@object-ui/core, with the session scope the host provides throughuseFilterScope()from@object-ui/react. The console shell mountsFilterScopeProviderfromuseAuth()'s user and active organization, the same two sources the app-shellObjectViewuses for its ownfilterScope. No second resolver and no fallback: a token the scope cannot resolve is left as written, and the resolver warns once, which is the resolver's own rule. The filter is therefore never widened.ObjectView.tsx,useResolvedFilterSegments, resolves the three authored filter segments once, in one call: the active view's filter (a namedlistViewsentry, else the hostviewsentry),table.filter, andtable.defaultFilters.find(), theobject-gridschema handed toObjectGrid, and thelist-viewschema handed to a host'srenderListView.isStructurallyEqual) and on the scope's three members (currentUserId,currentOrgId,onUnresolved), each compared on its own, never the scope object's identity (AGENTS.md [WIP] Enhance every detail of the designer #10). The reasons:ObjectGridandListViewrefetch whenever the filter object's identity changes, and a{now}macro gives a new value on every call.@object-ui/core— the resolver's two object walks (the Date repair).resolveContextTokensinfilter-tokens.tsandresolveDateMacrosindate-macros.tsused to rebuild every object from its own keys. ADatehas no own keys, so aDatecomparand came back fromresolveFilterPlaceholdersas{}, even though the spec admitsDate(ACCEPTED_FILTER_COMPARAND_TYPES).Object.prototypeornull. Any other object is a leaf, returned as the same instance: aDate, and equally a class instance. A class instance with own keys carrying a token is therefore no longer copied and resolved. The contract review of this change found no in-repo caller that passes one.ObjectView,ObjectDataPage,useOpenRecordList; plugin-chartsObjectChart; the plugin-dashboard widgets) all get the repair. The plugin-view change above would otherwise have been a tenth door onto it.Changeset:
.changeset/10506-named-view-context-tokens.md, with'@object-ui/plugin-view': patchand'@object-ui/core': patch.Prose this change would make false, or that AGENTS.md #2 requires:
content/docs/api/schema-reference.md: its sentence saying resolution on theobject-viewnode was still tracked in this card is corrected.packages/plugin-view/README.mdandcontent/docs/plugins/plugin-view.mdx: each gains one paragraph beside the filter-tier precedence.Commits:
2218bbbbbfix(plugin-view): resolve the tokens.fa4f23b3fdocs and changeset.ec06abc38test typing.bf6a8fbe9hold in state, not a ref.f0173ec3ffix(core): non-plain objects are leaves.57eda69bedocs: changeset and docs prose made true to the contract review; prose only.53aa3660cdocs: the ObjectStack auth (401 at the REST auth gate) and timezone (tenantlocalization.timezone) facts corrected; prose only.Measured
The code was measured at
f0173ec3f, base9cbe4dbca.57eda69beand53aa3660cchange only the changeset and the three docs files:git diff --stat f0173ec3f..53aa3660clists exactly those four.packages/plugin-view/src/__tests__/ObjectView.namedViewContextTokens-10506.test.tsx, 14 cells.{current_user_id}whereusr_42was expected.bf6a8fbe9(expected {} to be an instance of Date).packages/core/src/utils/__tests__/filter-tokens.dateLeaf-10506.test.ts, 5 cells.bf6a8fbe9, the 3 Date cells fail (expected {} to be 2026-01-01T00:00:00.000Z). The 2 controls pass: a plain object and a null-prototype object carrying a token are still resolved.ablation-replace.mjs --deleteof the guard line, each file on its own):4 failed | 15 passedfor each, with the controls green. Both files were restored: blob equals HEAD andgit diff HEADis empty.ObjectView.tsxput back:12 failed | 1 passed.4 failed | 9 passed.f0173ec3f:packages/core/:172 passed,3491 passed.packages/plugin-view/:49 passed,477 passed.73 passed,677 passed.views/ObjectView.*and Studio:85 passed,622 passed.53aa3660c, each exit 0:check-changeset-presence,check-changeset-overwrite,check-changeset-fixed,check-changeset-no-major,check:new-line-citations(0 new),check:control-bytes,docs:check-linksandcheck:doc-fences.check-changeset-claimsis report-only. The 18 listed paragraphs are unchanged in meaning by this prose, and this changeset negates neither package it declares.f0173ec3f: 43 check runs, 40 success, 3 skipped, 0 failure.Acceptance notes
{today},{current_quarter_start}, …) are now resolved on the client for this node, in the browser's local time.ObjectChartalready make; a directly authoredobject-gridorlist-viewnode still does not make it.localization.timezone, UTC by default: REST reads it throughresolveLocalizationContextinto the execution context, and objectql'sresolveWhereTokenshands that context toresolveFilterTokens. So a browser whose local day differs from the server's day can now get a different day.{now}and{today}are resolved once and held until the user, the organization or a segment changes. ArefreshKeyre-query reuses the held value. This governs the non-gridfind(), the grid, and anyrenderListViewhost that queries with the filter it is handed.renderListViewresolves the active view's own filter itself (viewDef.filter ?? listSchema.filter, and the schema it gives this component carries no filter segment). So on the console's delegated grid path the date macros are still resolved by the app-shell on each of its renders, as before. The active view's filter still reaches this component there, through theviewsprop, so this component's pass still runs on it.{current_user_id}for a signed-in request, and refuses the request otherwise: the REST face answers 401 at its auth gate before the filter is read, and where a guest context reaches the engine the resolver answers 400. So against that backend, the literal token this node used to send was expanded, or the request was refused, before the database saw it. This is a source reading, not an end-to-end measurement. The gap this PR closes is on the client, and it still matters for any backend that has no such resolver, where a literal token matches no record.'current-user'(FALLBACK_USER) silently, as before, so the console's query is unchanged from base and the only difference is one new warning. A host that resolves the handed value again changes nothing: a resolved id or date no longer matches the whole-token pattern (pinned).object-gridorlist-viewnode still sends its ownschema.filtertokens unresolved (plugin-gridandplugin-listhave zero reads of the resolver). The contract review calls this a real defect of the same class. The seat filed it as objectui#10607; it is not fixed in this PR.Generated by Claude Code