Skip to content

fix(plugin-grid,plugin-list): resolve context tokens in a directly authored object-grid or list-view filter (objectui#10607) - #10656

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-10607-grid-list-filter-tokens
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-10607-grid-list-filter-tokens

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

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-grid or list-view node, with no object-view above it, sent filter: [['owner', '=', '{current_user_id}']] on $filter as the literal token. Neither plugin-grid nor plugin-list read resolveFilterPlaceholders or useFilterScope. Each host now resolves its own authored filter ONCE, through @object-ui/core's shared resolveFilterPlaceholders, against useFilterScope() from @object-ui/react. This is the shape PR objectui#10577 used for object-view. There is no second resolver, and neither filter-tokens.ts nor useFilterScope.ts is 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. useResolvedGridFilters resolves the node's filter and its deprecated alias defaultFilters in one call.
    • schemaFilterSource (the lowering both the query and the server-side export read) now reads the resolved filter.
    • The load effect's defaultFilters leg reads the resolved alias.
    • The load effect now also lists the resolved alias (schemaDefaultFilters). It read schema.defaultFilters without keying on it, so a new signed-in user would not have re-queried a token carried only there.
  • @object-ui/plugin-list, ListView. useResolvedAuthoredFilter resolves schema.filter once, 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 read authoredFilter. After this change the only raw schema.filter read left in code is the hold's own input.
  • The comparator. isSameAuthoredFilter sits in each file. It follows the same rules as plugin-view's isStructurallyEqual: primitives by Object.is, a Date by 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: .changeset/10607-grid-list-filter-context-tokens.md, with '@object-ui/plugin-grid': patch and '@object-ui/plugin-list': patch. One sentence of the pending .changeset/10506-named-view-context-tokens.md is corrected too (round 2, below).

Files:

file lines
packages/plugin-grid/src/ObjectGrid.tsx +129 / −5
packages/plugin-list/src/ListView.tsx +125 / −16
packages/plugin-grid/src/__tests__/ObjectGrid.filterContextTokens-10607.test.tsx new, 216
packages/plugin-list/src/__tests__/ListView.filterContextTokens-10607.test.tsx new, 215
.changeset/10607-grid-list-filter-context-tokens.md new, 40
.changeset/10506-named-view-context-tokens.md +1 / −1 (one sentence, round 2)

Round 2 (80eca8f, on amended claim 5834409941). Prose only.

  • .changeset/10506-named-view-context-tokens.md: one sentence, and the front matter is byte-identical (md5 8212bf42dc2f6ba4025ef7ced79c6d6d before and after). 「(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.)」.
  • This PR's changeset: the sentence that pointed at the gap the 10506 entry used to note now reads "object-view has done this since objectui#10506; these two nodes, authored directly, had not."
  • Merged origin/main f99f9cd (plugin-form only), with no conflict. Between e4aa578 and 80eca8f, no file under packages/plugin-grid or packages/plugin-list changes.
  • At 80eca8f, both pins: 19 passed, exit 0.
  • At 80eca8f, each exit 0: check:changeset-claims, check:pending-changeset-literals, check-changeset-presence, check-changeset-overwrite (1 added, 1 modified: the 10506 file, declaring plugin-view: patch and core: patch at base and now), check-changeset-no-major, check-changeset-fixed, check:control-bytes and check:new-line-citations (0 new).

Measured

The code was measured at e4aa578, which is the merge of origin/main 4a3d500 into the branch. The branch was cut at dddbc27. Round 2 then merged origin/main f99f9cd (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 (user usr_42, org org_7) and reads dataSource.find's $filter.

  • The grid pin has 10 cells:
    • {current_user_id} and {current_org_id}.
    • The defaultFilters alias.
    • A token-free CONTROL.
    • No scope: the token stays literal and the resolver warns.
    • A user change re-queries, both on filter and on defaultFilters alone.
    • The count holds across same-node re-renders, and across re-renders that rebuild an equal filter.
    • The object-view seam: a filter already resolved upstream is queried once, unchanged.
  • The list pin has 9 cells: the same set, with the child view's handed filter in place of the alias cells.
run grid pin list pin
base: each source swapped to its 4a3d500 blob, the pins as committed 7 failed, 3 passed (exit 1) 6 failed, 3 passed (exit 1)
head e4aa578 10 passed 9 passed (both files in one run: 19 passed, exit 0)

On 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.mjs in WRAP mode swapped each hold call for a bare per-render resolveFilterPlaceholders(…). The anchor hit once, and the blob moved.

  • Grid: 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.
  • List: 4 failed | 5 passed. Two cells time out at 15000ms. Two read expected 0 to be greater than 0: no find() landed inside the 1000ms wait while the list kept re-rendering.
  • Both restored: blob after restore equals blob at HEAD, and git diff HEAD is empty.
  • The pins import the subject from src/ (../index, ../ListView), so no dist/ leg exists here.

H3, the object-view path, measured once and not kept as a file. A temporary plugin-view test drove the real ObjectView into the real ObjectGrid under the same scope, then re-rendered twice (the same schema, then a spread copy).

case head: fetches base ObjectGrid.tsx: fetches $filter (identical in both)
table.filter 1, then 1 1, then 1 [["owner","=","usr_42"]]
named grid view (reaches the grid as defaultFilters) 1, then 1 1, then 1 [["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 status is 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's tsconfig.test.json lists 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) and check: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-major and check-changeset-fixed.
  • check:phantom-deps, check:unreferenced-sources, check:self-import, check-type-check-coverage and check:side-effects-array.
  • check-governed-queue-guard --test on the five paths: NOT GOVERNED.

The changeset-claims gate (check:changeset-claims) is report-only and exited 0. It lists 12 pending changesets that name ObjectGrid.tsx or ListView.tsx. Each paragraph was re-read, and none concerns the filter read: they cover i18n keys, reference_to, gantt date fields, export options, kanban groupField and 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.

  • Warnings per rule, 4a3d500 against head, are unchanged in both sources: ObjectGrid.tsx 207 → 207 and ListView.tsx 184 → 184, with 0 rules differing.
  • The two pins have 0 findings.

NOT MEASURED:

  • check:eager-closure and check:sdui-registration-pins. Both need a full @object-ui/console production 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 is resolveFilterPlaceholders from @object-ui/core, which the console already loads eagerly through its app-shell callers.
  • Repo-wide pnpm lint and the full test farm belong to CI.

Acceptance notes

  • The pending objectui#10506 changeset said a directly authored object-grid or list-view node 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-claims would not have flagged it, because it names the symbols, not the files.
  • Fewer queries, by design. On base both hosts keyed their fetch on the filter's identity, so a host that rebuilt an equal filter inline re-queried on every render. The structural hold now drops those redundant queries, and pins them in the equal-filter cells. A structurally different filter, or a new scope member, still re-queries.
  • The object-view named-view grid, a source reading, not measured. ObjectView hands a named view's filter to ObjectGrid in defaultFilters. 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 on defaultFilters, so it did not re-query in place. The new schemaDefaultFilters dependency covers that too.
  • Date macros ({today}, …) are now resolved on the client for these two nodes, in the browser's local time. That is the same call object-view, the app-shell host, ObjectChart and the dashboards make. Before, they reached the server as literals, and the ObjectStack server resolved them in the tenant's configured localization.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.
  • Against ObjectStack, the server also expands {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.
  • Unchanged here, pre-existing. ObjectGrid's load effect still keys on schemaFilter, which comes out of a useMemo (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's selfQueryFilterRef (a JSON-key hold) is also unchanged, and it now receives the resolved filter.
  • Prose elsewhere. content/docs/api/schema-reference.md lists 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.
  • Overlap. objectui#10620 landed on ListView.tsx during this run. The merge conflicted in two places, both inside this PR's hunks, and was resolved as a union: the @object-ui/react import keeps useDataInvalidation and adds useFilterScope, and the fetch effect's dependency list keeps invalidationNonce and reads authoredFilter. objectui#10278 (a draft on the ObjectGrid page 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

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

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 12 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/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 objectui#5740 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/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/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/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.

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

.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/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/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.

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.

Compared the checked-out tree with 3d17a9761 (merge-base with origin/main): 4 file(s) changed outside .changeset/, read against 1501 pending declaration(s) that publish a body (2085 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3052.0 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-BklAbhyC.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.15KB
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) 546.99KB 130.83KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 260.06KB 65.86KB
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 (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.49KB 14.64KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.60KB 35.41KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.96KB 61.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.20KB 39.05KB
plugin-gantt (index.js) 169.64KB 41.91KB
plugin-grid (index.js) 216.65KB 59.28KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 115.72KB 28.71KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.84KB 21.96KB
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.66KB 3.50KB
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) 116.21KB 38.14KB
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) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
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.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
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.25KB
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.28KB 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

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3052.0 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-CofURL2O.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.15KB
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) 546.99KB 130.83KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 260.06KB 65.86KB
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 (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.49KB 14.64KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.60KB 35.41KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.96KB 61.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.20KB 39.05KB
plugin-gantt (index.js) 169.71KB 41.93KB
plugin-grid (index.js) 216.65KB 59.28KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 115.72KB 28.71KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.77KB 21.99KB
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.66KB 3.50KB
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) 116.21KB 38.14KB
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) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
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.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
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.25KB
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.28KB 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: 80eca8ff1de710f2fa3e8202978dd60aa18c0fd7

① Derived judgments

Read: card objectui#10607 (body and 5 comments), triage 5832409031, claims 5833014372 and 5834409941 (amended), the cited PR objectui#10577 review 5831522007, PR #10656 body and file list, refs/review/pr-10656 = 80eca8ff1de… diffed against merge-base f99f9cde75b… (refs/review/main = 8848ce418f…, 4 commits ahead). The two os-dev-report comments were not used as evidence. The diff consumes no new @objectstack/spec symbol (the only spec-derived input, CONTEXT_TOKENS, is re-exported by @object-ui/core's pre-existing filter-tokens.ts:81); the brief's /home/user/objectui/node_modules/.pnpm/ does not exist in the shared checkout and no other worktree was read.

Diff shape. 6 files, +726/−22: packages/plugin-grid/src/ObjectGrid.tsx +129/−5, packages/plugin-list/src/ListView.tsx +125/−16, two new pins (216 and 215 lines), new .changeset/10607-grid-list-filter-context-tokens.md (40 lines), and .changeset/10506-named-view-context-tokens.md +1/−1. Git grep at merge-base: 0 reads of resolveFilterPlaceholders|resolveContextTokens|useFilterScope in packages/plugin-grid/src and packages/plugin-list/src (exit 1), so the card's fact holds at base.

1. One shared resolver, no second resolver — RIGHT. Triage 5832409031 binds 「resolve the node's own filter through the ONE shared resolveFilterPlaceholders with useFilterScope(), as PR objectui#10577 did; hold the result by structure so the grid does not refetch every render.」 At head the only resolver calls are ObjectGrid.tsx:106 (resolved: resolveFilterPlaceholders(authored, scope) inside resolveGridFilters, 100–108) and ListView.tsx:709 (inside resolveAuthoredFilter, 703–711), both imported from @object-ui/core (ObjectGrid.tsx:57, ListView.tsx:24) and fed by useFilterScope() from @object-ui/react (ObjectGrid.tsx:1875, ListView.tsx:1213). git diff --stat vs merge-base over packages/core and packages/react is empty: packages/core/src/utils/filter-tokens.ts and packages/react/src/hooks/useFilterScope.ts are untouched. No token vocabulary, fallback or substitution logic exists in either host; an unresolvable token is whatever the resolver returns (left intact, filter-tokens.ts:174–181, warn via onUnresolved or console.warn).

2. The hold — RIGHT, and it never keys on a per-render identity (AGENTS.md #10). useResolvedGridFilters (ObjectGrid.tsx:173–190) and useResolvedAuthoredFilter (ListView.tsx:777–794) keep {authored, currentUserId, currentOrgId, onUnresolved, resolved} in useState and re-resolve during render only when held.currentUserId !== scope.currentUserId || held.currentOrgId !== scope.currentOrgId || held.onUnresolved !== scope.onUnresolved || !isSameAuthoredFilter(held.authored, authored) (ObjectGrid.tsx:175–180, ListView.tsx:779–784). The three scope members are compared one by one, never scope itself (the FilterScopeProvider value is a useMemo object, useFilterScope.ts:54–57; keying on it would be exactly the #10 shape). The authored input is compared by structure, so the wrapper object the grid builds inline every render ({ filter: schema.filter, defaultFilters: schema.defaultFilters }, ObjectGrid.tsx:1877) does not re-resolve. Because the key is the INPUT, not the resolver's output, a date macro ({now}) that yields a new value per call cannot churn the hold. This is byte-for-byte the shape of plugin-view's useResolvedFilterSegments (ObjectView.tsx:282–300), which PR objectui#10577 landed and the triage names as the model.

Refetch-loop check (brief item). The grid's load effect now keys on schemaFilter (a useMemo of schemaFilterSource = authoredFilters.filter, ObjectGrid.tsx:1880–1884) and the new schemaDefaultFilters = authoredFilters.defaultFilters (1881, deps at 2484). Both are members of held.resolved, whose reference changes only on an input change, so a find() answer → setData → re-render cycle reaches the hook with equal inputs and gets the same resolved object back; no loop. The one way to loop would be an authored filter the comparator cannot model rebuilt fresh every render (a Map, class instance, function, or nesting past depth 12): that turns into React's "too many re-renders" throw instead of base's endless refetch. plugin-view has had the identical exposure since objectui#10577 with the identical rules and the same bound (stableIdentity.ts:71), spec filters are JSON-shaped, and the card did not ask to guard it. Noted, not a defect.

3. The local comparator isSameAuthoredFilter — a private helper the dependency graph forces, not a second implementation of what the triage forbids. The triage's 「ONE shared」 and 「⛔ No second resolver」 bind the RESOLVER (the token/macro substitution). The comparator is the hold's equality test, a different thing; the triage says only "hold the result by structure" and names no shared helper for it. Facts: plugin-view's isStructurallyEqual is exported from packages/plugin-view/src/stableIdentity.ts:91 but not from plugin-view's package index (grep exit 1), and packages/plugin-view/package.json depends on @object-ui/plugin-grid, so plugin-grid importing from plugin-view would be a cycle; plugin-list (package.json: no dependencies, peers only) depends on neither. No structural-equality helper is exported by @object-ui/core, @object-ui/react, @object-ui/types or @object-ui/components at head (regex grep for deepEqual|isEqual|isStructurallyEqual|shallowEqual|useStableIdentity… exit 0 with no hits). Adding one to core would move a published export set and flip the claim's Clause-②: no. The two copies (ObjectGrid.tsx:111–148, ListView.tsx:714–751) implement exactly plugin-view's rules: Object.is first, MAX_DEPTH 12, Date by instant before the plain-object test, arrays in order, plain (or null-prototype) objects by key count plus presence, everything else equal only by reference; the every form replaces plugin-view's for loops with the same semantics. Each copy carries a docblock naming the source (ObjectGrid.tsx:120–129, ListView.tsx:723–732). Judged RIGHT; the cost is two more copies of a 19-line function that a future core export could fold.

4. ObjectGrid — the node's own filter read (the card's :~1761) — RIGHT. const schemaFilterSource = schema.filter is now const schemaFilterSource = authoredFilters.filter (ObjectGrid.tsx:1880). Downstream is unchanged: toFilterNodeSafely (1882), the query $filter (2249–2250), the export (3607, reads schemaFilter). The only raw schema.filter / schema.defaultFilters reads left in code are the hold's input at 1877 (the other hits at 1981, 2278, 3686–3687 are comments).

5. ObjectGrid — the deprecated alias defaultFilters resolved too — asked in substance, RIGHT. The card's Direction says "the node's own authored filter"; the amended claim's surface says "the node's own authored filter reads, and the held resolved value that feeds them". defaultFilters is the same authored filter under its @deprecated alias, read by the same load effect through the same $filter sink when filter is absent (2249–2251, 2287), and it is the carrier object-view uses for a named view's filter. Leaving it raw would leave a directly authored object-grid with defaultFilters: { owner: '{current_user_id}' } sending the literal, the card's defect on the same node and the same read site. Resolving both in one call (resolveGridFilters) is the tight fix. The alias does not feed the export (3607 reads schemaFilter only), which is pre-existing and not claimed.

6. ObjectGrid — the load effect gains schemaDefaultFilters as a dependency (2484) — not named by the card, but load-bearing for item 5 and RIGHT. At base the effect read schema.defaultFilters inside loadSchemaAndData without listing it (base deps line 2360 has no defaultFilters entry). Once the alias is resolved and held, a user change re-resolves it, and without the dependency the effect would keep the old user's id in $filter — the fix would be resolve-once-and-stale on the alias leg. The behaviour change it adds beyond that (an in-place edit of the alias now re-queries, where base did not) is the correct reading of "the query's inputs changed", and the structural hold means an equal inline rebuild does not trip it. The grid pin's cell re-queries with the new id when the user changes and only defaultFilters carries the token (ObjectGrid.filterContextTokens-10607.test.tsx:150–161) is what fails without this line. No loop, see item 2.

7. ListView — every raw schema.filter read now reads the held value — RIGHT, and every converted site is a read of the node's own authored filter. At merge-base there were 10 code reads of schema.filter (lines 2202, 2711, 2721, 3087, 3090, 3101, 3140, 4023, 4125, 4984–4987), not the three buildEffectiveFilter calls the first claim named. At head all read authoredFilter (ListView.tsx:1214): the fetch (2311) and its dependency list (2820, which also keeps objectui#10620's invalidationNonce), the page-reset signature (2830; a user change now resets to page 1, which is the signature's stated contract for any result-defining input), the self-query filter for gantt/tree/chart (3196, 3199, 3210), the child view node's filter: (3249) and its memo deps (3773, newly listing authoredFilter beside schema, correct because the memo now reads it), the export (4132) and its deps (4234), and the empty-state hasBaseFilter test (5093–5096; resolution never changes array length or key count, so this is behaviour-neutral but keeps the file's rule "never the raw schema.filter"). The only raw read left in code is the hold input at 1214; the remaining hits (663, 1271, 3164–3182, 3245, 3748) are comments. Each site is the same authored value the card named, so the amended surface ("the node's own authored filter reads, and the held resolved value that feeds them") covers all of them; nothing beyond the node's own filter was touched (user filters, appliedFilters, currentSort untouched).

8. Hook order. useFilterScope() and the hold hook are unconditional: no early return precedes ObjectGrid.tsx:1875 (component starts 1384) or ListView.tsx:1213 (starts 1167). useState is already imported in ObjectGrid (:25); ListView uses React.useState.

9. Missing asks — none. Reproduce-first: the pins' token cells read the literal on base by construction (base has no resolver read, so $filter carries '{current_user_id}' verbatim; cells at ObjectGrid…test.tsx:104–108, 110–114, 116–122 and ListView…test.tsx:115–119, 121–125, 127–132). Fix on both hosts: items 1, 4, 7. Hold by structure: item 2, pinned by the SAME node and EQUAL token filter cells (grid 165–197, list 162–194); SchemaRenderer does not settle the node by structure (useSettledSchema is referenced only from its own hook file), so the grid's equal-filter cell exercises the grid's hold, not the renderer. object-view double-resolve measured: the object-view seam cells hand a pre-resolved filter under a DIFFERENT scope user and assert one find() with the handed value (grid 199–215, list 196–214), which is idempotence with one fetch as the claim asked. Pin: the token reaches the query as the scope's user id on both hosts and the count is stable across an equal re-render: present.

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 @object-ui/plugin-grid (ObjectView.namedViewContextTokens-10506.test.tsx:50), so it never renders this grid; the app-shell 9022 placeholder tests assert drill URLs, not a grid or list query. CI's eight test shards are green at head (③), so nothing pre-existing was weakened by observation either.

12. New pins can fail; lit control and negative leg present. Lit control: CONTROL: a token-free filter reaches the query unchanged (grid 124–128, list 134–138). Negative leg: keeps the token literal with no user in scope, and the shared resolver names it (no fallback) (grid 130–135, list 140–145) renders under FilterScopeProvider currentUserId={null} and asserts both the literal $filter and a console.warn naming {current_user_id}. Failure paths: on base the token cells and the equal-filter cells fail (literal reaches find; identity-keyed effect refetches); a per-render resolve makes the hold cells' find count grow. The pins import from src/ (../index, ../ListView), so there is no dist leg. Both files sit under src/__tests__/ matched by each package's tsconfig.test.json include globs (src/**/*.test.tsx).

13. Prose elsewhere. content/docs/api/schema-reference.md:910 says object-view resolves "through the same resolveFilterPlaceholders … that the app-shell host, the charts and the dashboard widgets call": now an incomplete caller list, not a false sentence; no docs sentence at head states that these two hosts skip the resolver (grep for still does not call / directly authored over content/docs and package READMEs: only the corrected changeset). plugin-view.mdx:197–210 and plugin-view/README.md:258–271 speak only of ObjectView's three paths and remain true.

Out-of-scope finding, not a stop (noted for a card, not filed here): packages/plugin-list/src/ObjectGallery.tsx:472 sends $filter: schema.filter raw and keys on it at :541. A directly authored object-gallery node carries the same class of defect; as a child of list-view it now receives the resolved value (ListView.tsx:3249). The card names object-grid and list-view only.

② Semver level

.changeset/10607-grid-list-filter-context-tokens.md declares '@object-ui/plugin-grid': patch and '@object-ui/plugin-list': patch. Both are in .changeset/config.json's fixed group; AGENTS.md 版本号策略 forbids major and asks ordinary changes to move by changeset from the current major; a bug fix that moves no export (no +export/-export line in either source diff, all new symbols module-private) and no accepted key is patch. CI Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report and Changeset Claim Re-read are all success at head. Level: correct. The edit to the pending 10506 changeset keeps its front matter byte-identical (md5 8212bf42dc2f6ba4025ef7ced79c6d6d at base and head, '@object-ui/plugin-view': patch / '@object-ui/core': patch), so it moves no level.

Changeset 10607, sentence by sentence (all TRUE at head):

  • "A directly authored object-grid or list-view node resolves the spec's context tokens in its own filter (objectui#10607)." TRUE (items 1, 4, 7).
  • "filter: [['owner', '=', '{current_user_id}']] … went out on $filter as the literal token: neither package called @object-ui/core's resolveFilterPlaceholders." TRUE at base (grep exit 1).
  • "Each now resolves the node's own filter once through that shared resolver, with the session scope the host provides through useFilterScope (the console shell mounts FilterScopeProvider from the signed-in user and the active organization)." TRUE: ObjectGrid.tsx:1875, ListView.tsx:1213; app-shell/src/console/AppContent.tsx:1029.
  • "On object-grid that is filter and its deprecated alias defaultFilters: the resolved filter feeds the grid's query and its server-side export, and the resolved alias feeds the query when filter is absent." TRUE: 2249–2251, 2287, 3607.
  • "On list-view it is filter, and the resolved value feeds the list's query, the child view it renders, the self-querying views (gantt, tree, chart), the export and the empty-state copy." TRUE: item 7's sites.
  • "object-view has done this since objectui#10506; these two nodes, authored directly, had not." TRUE.
  • "The relative-date macros … resolve in the same call, in the browser's local time, as on the other surfaces that call the resolver." TRUE: resolveFilterPlaceholders = resolveContextTokens(resolveDateMacros(filter, now), scope) (filter-tokens.ts:241–247); local time per review 5831522007.
  • "Where they used to reach a backend that resolves them itself, as the ObjectStack server does in the tenant's configured localization.timezone (UTC by default), a browser whose local day differs from the server's day can now get a different day." TRUE, the wording review 5831522007 established.
  • "A token the scope cannot resolve is left as written and the resolver warns once, which is the resolver's own rule, so the filter is never widened." TRUE: filter-tokens.ts:174–181; once per resolution, and the hold resolves once per input change.
  • "The resolved value is held against its inputs: the authored filter, compared by structure, and the scope's three members (currentUserId, currentOrgId, onUnresolved), each compared on its own." TRUE: item 2.
  • "A re-render that changes none of them does not refetch, including one that rebuilds an equal filter inline; a new signed-in user refetches with the new id." TRUE: the hold cells and the user-change cells.
  • "Date macros are therefore resolved once and held until the scope or the authored filter changes, and a refetch for another reason reuses the held value." TRUE: the hold's key excludes refreshKey, invalidationNonce, sort, search.
  • "object-view already hands these nodes a filter it has resolved (objectui#10506); resolving that value again changes nothing, because a resolved id or date no longer matches the placeholder pattern." TRUE: WHOLE_TOKEN_RE is anchored {token}; seam cells.
  • "On object-grid, the load effect now also refetches when the resolved defaultFilters changes (a new signed-in user, or an in-place edit of the alias). It read that alias without keying on it before." TRUE: base deps 2360 vs head 2484.

Pending 10506 changeset (the amended claim's one sentence). The diff to that file is exactly one line (line 22): 「(A directly authored object-grid or list-view node still does not call it.)」 → 「(A directly authored object-grid or list-view node calls it too, since objectui#10607.)」. Nothing else in the file changes and the front matter hash is unchanged, so the amended surface holds. The new sentence is TRUE at head (both nodes call the resolver, item 1). Every other sentence of that file was re-read: line 35 "ObjectGrid and ListView key their fetch on the filter's identity, so a host that re-renders without changing anything does not trigger a refetch" stays true (the effects still key on the filter reference, now a held one); lines 43–46 (a host resolving a handed value again changes nothing) stay true and are now also pinned by the seam cells. None becomes false. Style only: line 22 is now 107 characters in a file wrapped near 100 (base 95); not re-wrapped, no gate reads line length.

The 16 other pending changesets naming ObjectGrid.tsx or ListView.tsx (4730, 6597, 6921, 7070, 7196, 7201, 7499, 7762, 7773, 7780, 8990, 9722, 9853, gantt-owns-data-comment-7222, list-user-actions-collision-5398, listview-comment-pair-4559) were grepped for filter|resolve|token|current_user|defaultFilters|useFilterScope|placeholder and the four census-style ones (7196, 7201, 7222, 4559) read in full: they concern held schema keys, i18n keys, gantt data ownership, sort-rationale comments, kanban, export options, page size and userActions; none states anything this diff makes false.

Docs/README prose added by the diff: none (the diff adds no file under content/docs or any README).

③ Boundary flags

  • PR body flags, answered. (a) "The pending objectui#10506 changeset … round 2 corrects that one sentence": verified, one line, front matter identical. (b) "Fewer queries, by design": true; base keyed on identity (ListView.tsx:2711 base listed schema.filter; grid keyed on the useMemo of schema.filter), so an inline-rebuilt equal filter re-queried; the hold drops only structurally equal rebuilds. (c) "The object-view named-view grid … the new schemaDefaultFilters dependency covers that too": consistent with item 6 (source reading, not measured here either). (d) "Date macros … now resolved on the client for these two nodes": true and disclosed in the changeset; it is the triage's own instruction (the ONE shared resolver includes the macro walk). (e) "Unchanged here, pre-existing: ObjectGrid's load effect still keys on schemaFilter, which comes out of a useMemo": true (1882 → 2484), an AGENTS.md [WIP] Enhance every detail of the designer #10 identity dependency that predates this PR and now receives a held input; not this card's ask, not a regression. ListView's selfQueryFilterRef JSON hold (3195–3212) is likewise unchanged. (f) "Prose elsewhere … incomplete, but not false": agreed, item 13. (g) "Overlap": git merge-tree --write-tree refs/review/main refs/review/pr-10656 → cadde8202acbdbfb2311ec881d3d8506647a8d59, exit 0, no conflict against current main; the union with objectui#10620's useDataInvalidation / invalidationNonce is intact at ListView.tsx:18 and 2820. (h) The body's "Between e4aa578 and 80eca8f, no file under packages/plugin-grid or packages/plugin-list changes": verified (empty diff-stat); the round-2 commit d325bb2 touches only the two changesets. (i) Body's NOT MEASURED items (check:eager-closure, check:sdui-registration-pins, repo-wide lint, full farm): covered by CI Bundle Analysis, Lint and the test shards, all success. No open questions are listed in the body.
  • CI at head 80eca8ff1de… (three reads): first 42 runs, 29 success / 3 skipped / 10 in progress; second (15:13 UTC) 42 runs, 37 / 3 / 2 in progress; final (15:15 UTC) 43 runs: 40 success, 3 skipped (Test (coverage), Test (coverage shard), dependabot), 0 failure, 0 in progress. Green includes Lint, Type Check, Test (shard 1/8)…(8/8), Test (dist pins), Build & E2E, Bundle Analysis, Spec Main Shape Gate, Line Citation Gate, Inert vi.mock Specifier Check, Governed Surface Queue Guard, all five Changeset gates, README Export Check, Doc Snippet Type Check.
  • Draft: yes (draft: true, mergeable: true, mergeable_state: behind); labels plugin, tests; assignee os-tesla.
  • Clause-②: no holds. No accept set moves (both hosts accept the same schema keys; a resolved value is the same shape as the authored one), and no published export moves: no +export in either source diff; every new symbol (AuthoredGridFilters, HeldGridFilters, resolveGridFilters, isPlainFilterObject, isSameAuthoredFilter, useResolvedGridFilters, HeldAuthoredFilter, resolveAuthoredFilter, useResolvedAuthoredFilter) is module-private; filter-tokens.ts and useFilterScope.ts unchanged; the 10506 front matter unchanged.
  • Main under the PR's files since merge-base: no. refs/review/main (8848ce4) is 4 commits past merge-base f99f9cd (objectui#10625, finding(fields): readFileValue throws URIError for a file URL whose last segment holds a bare % — nameFromUrl calls decodeURIComponent unguarded, on the render path of every file and image cell #10614, finding(plugin-view): an authored event-name onChange on filter-ui / sort-ui (and onViewChange on view-switcher) crashes with "onChange is not a function" through SchemaRenderer #10616, finding(plugin-gantt): a silent re-read that overtakes an in-flight changed query swallows its outcome — the chart keeps the first query's rows with no error and no refreshing state #10633: plugin-detail, fields, plugin-view handler channels, plugin-gantt); git diff --stat f99f9cd refs/review/main -- over the PR's six paths is empty.

Implemented-by: claude/issue-10607-grid-list-filter-tokens
Reviewed-by: session_01KUxVUa7e39aNjhkKi1gsoy

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 15:21
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 0c3f70a Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10607-grid-list-filter-tokens branch September 25, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants