Skip to content

feat(core,react): {record_id} in filter values resolves to the mounted record, and is refused by name without one (objectui#7297) - #11205

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-7297-record-id-filter-token
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-7297-record-id-filter-token

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #7297
Clause-②: yes

What this does

On a type: 'record' page, {record_id} in a filter value now resolves to the id of the record the page shows. With no record in context it is refused by name and left as written. @objectstack/spec 17.5.0 (already installed on main) declares the token as RECORD_CONTEXT_TOKENS (objectstack-ai/objectstack#20003).

  • @object-ui/core, resolveContextTokens: FilterTokenScope gains an optional recordId. {record_id} / ${record_id} resolves from it and from nothing else. When it is missing, null or empty, the token is refused through onUnresolved, the channel an unresolved {current_user_id} uses (a console.warn by default). The warning names the token and says there is no record in context. The value stays as written: never null, never dropped, and never reported as an unknown spelling or given a suggestion. A second record-context token in the spec breaks the compile through the same satisfies check the session tokens have.
  • @object-ui/react, useFilterScope(): this is the one seam. It returns the session scope plus recordId taken from the nearest RecordContextProvider, the provider whose row SchemaRenderer binds as record for visibleWhen. It reads no URL param and no page variable. FilterScopeProvider still carries session values only. Outside a record context the hook returns the provider's session object unchanged. Every data node that already resolves its filter through this hook now gets {record_id} too, with no per-surface code. That includes element:number, where both its properties.filter and its component-level dataSource.filter go through useResolvedFilter(scopedFilter.filter, useFilterScope()).
  • Holds: useResolvedFilter (@object-ui/react), useResolvedGridFilters (@object-ui/plugin-grid) and useResolvedFilterSegments (@object-ui/plugin-view) now include recordId in their key alongside the other scope members. Moving from one record to the next therefore resolves again and re-queries without a remount (AGENTS.md feat: add live playground for interactive schema demonstration #8: no key= bump).
  • Docs: content/docs/guide/data-source.md gets a new subsection with an element:number + dataSource.filter example, the refusal outside a record context, and the scope sentence (it scopes what is shown and is not access control). The packages/react/README.md section on useFilterScope / useResolvedFilter is updated to match. .changeset/7297-record-id-filter-token.md: core and react minor, plugin-grid and plugin-view patch.

Premises, measured on 81778b955 before building

  1. Partly falsified. The dispatch assumed 17.5.0 had made {record_id} a member of CONTEXT_TOKENS. It has not. The installed spec keeps CONTEXT_TOKENS as ['current_user_id', 'current_org_id'] and adds a separate RECORD_CONTEXT_TOKENS = ['record_id'] with isRecordContextToken. classifyFilterToken('{record_id}') returns kind record-context. CONTEXT_TOKEN_SUGGESTIONS gained four record_id near-misses. At the base, record_id had 0 non-test hits in packages/core/src, against 9 hits for current_user_id in filter-tokens.ts as the control. So the client resolver passed {record_id} through with no warning at all: it was neither a context token nor a near-miss key.
  2. Mounted record context = RecordContextProvider / useRecordContext() in @object-ui/react. RecordDetailView and Studio's PagePreview for type: 'record' drafts mount it. SchemaRenderer reads the same provider to bind record for visibleWhen.
  3. Falsified (stale reading). ElementNumberRenderer no longer passes props.filter straight to the adapter. Since objectui#10666 it resolves the scoped filter (its own, AND-combined with the binding's) through useResolvedFilter(…, useFilterScope()) before both aggregate and find. So elements.tsx needed no edit: the record id enters at the layer where the session tokens already resolve.
  4. Measured. For an unresolved session token, the resolver warns and leaves the token in place. The ObjectStack server then throws UnresolvedFilterTokenError (FILTER_TOKEN_UNRESOLVED, 400) naming it, and element:number shows that error text with the empty dash instead of a count. {record_id} outside a record context now follows the same path. The server already refuses it on every path (resolveFilterTokens in objectstack packages/core).

Where the change landed, compared with the claim's file surface

The claim named filter-tokens.ts, the record-context seam, their tests, the doc line and the changeset. The seam turned out to be packages/react/src/hooks/useFilterScope.ts, which is the only producer of recordId. useResolvedFilter.ts, ObjectGrid.tsx and plugin-view's ObjectView.tsx are the three holds that had to add recordId to their key so the value follows the record. elements.tsx is unchanged.

Tests

New pins (28 tests in 5 files):

  • packages/core/src/utils/__tests__/filter-tokens.recordContext-7297.test.ts: resolution in every filter shape and both spellings; two records give two values; resolution sits alongside the session tokens with neither reading the other; refusal with no member, null, undefined or empty recordId, where the value stays as written, one warning names "{record_id}" and "no record in context", and the warning is never "not a recognised token" or "did you mean"; the default console.warn; resolveFilterPlaceholders.
  • packages/react/src/hooks/__tests__/useFilterScope.recordContext-7297.test.tsx: the scope with and without a record provider, a numeric key narrowed to a string, and a provider with no record. useResolvedFilter follows the record change without a remount.
  • packages/components/src/renderers/basic/__tests__/elementNumber.recordIdFilterToken-7297.test.tsx: through the real SchemaRenderer, the count follows the record (3, then 5, with no remount). dataSource.filter takes the token. {current_user_id} still resolves to the viewer on a record page. Outside a record context the aggregate receives {record_id} as written and the warning names it. A server-shaped refusal (code: 'FILTER_TOKEN_UNRESOLVED', status: 400) renders its message and the dash, the same as the {current_user_id} control.
  • packages/plugin-grid/src/__tests__/ObjectGrid.recordIdFilterToken-7297.test.tsx and packages/plugin-view/src/__tests__/ObjectView.recordIdFilterToken-7297.test.tsx: each re-queries with the next record without a remount. The grid also covers the refusal outside a record context.

Verification (final head 0fb29c5fa; every command run from the worktree root, exit codes captured before any pipe)

  • Pins on the final head: pnpm exec vitest run on the 5 pin files gives Test Files 5 passed (5), Tests 28 passed (28), and os-verify-lock: VERDICT command-exit 0.
  • Build (dependency closure plus touched packages): pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-view...' --filter '@object-ui/components...' build gives VERDICT command-exit 0.
  • Type-check: pnpm --filter over core / react / plugin-grid / plugin-view / components type-check (tsc --noEmit && tsc -p tsconfig.test.json) reports type-check: Done for all five, VERDICT command-exit 0. tsc -p tsconfig.test.json --listFilesOnly confirms each package's test config includes its new pin file.
  • Package suites (source edited in these four):
    • vitest run packages/core/ packages/react/: 296 files, 5130 passed, 27 skipped.
    • vitest run packages/plugin-view/: 63 files, 599 passed.
    • packages/plugin-grid/, run in two halves by file list: 84 + 88 = 172 files, 731 + 853 tests passed. That is all 172 *.test.ts(x) files in the package; git ls-files finds no other test-file spelling there.
    • All with VERDICT command-exit 0.
  • Importers, a declared narrowing: 119 test files, 1214 tests passed, VERDICT command-exit 0.
    • Population: every test outside the four packages above that mounts RecordContextProvider, names useRecordContext, or reads useFilterScope / FilterScopeProvider, plus the element:number / record-picker / data-list tests. By package: app-shell 9, apps/console 2, components 29, plugin-dashboard 1, plugin-detail 75, plugin-form 1, plugin-list 2.
    • Why this is the whole affected population: useFilterScope() returns the provider's session object unchanged unless a RecordContextProvider with a non-null recordId is mounted above it. So the change is only observable under that provider.
    • The whole-package suites of components, plugin-detail, app-shell and the other importers are left to CI.
  • Lint: root-config eslint --format json on the 11 changed .ts/.tsx files: 11 files, 0 errors. The six source files have the same error/warning counts at base (git show 81778b955:PATH via --stdin) as at head. eslint.config.js extends tseslint.configs.recommended, not a type-checked config, and sets no parserOptions.project, so the diff cannot change the verdict on any untouched file. The repo-wide pnpm lint is CI's.
  • Other gates:
    • node scripts/check-changeset-presence.mjs exit 0 ("11 source file(s) of 5 released package(s) changed, and this change declares 1 changeset(s)").
    • pnpm check:control-bytes exit 0.
    • pnpm check:new-line-citations exit 0 (0 new).
    • pnpm check:doc-types exit 0.
    • check-changeset-no-major / check-changeset-fixed exit 0.
    • check:changeset-claims exit 0 (report-only).
    • check:pending-changeset-literals exit 0.
  • NOT MEASURED: pnpm check:doc-snippets. It exits 2 (PRECONDITION NOT MET) because the 34-package doc closure is not built here. The docs diff adds no ts/tsx fence (one json block plus prose), so the gate has nothing new of this change's to compile. CI's doc-snippet-types.yml builds the closure and runs it.

Ablations (one-time proof; each on the committed fix, through ablation-replace.mjs, anchor 1 to 0 and the blob hash changed, then restored to the HEAD blob with git diff HEAD empty)

  1. core resolver branch disabled (if (isRecordContextToken(token)) changed to if (false && …)): 20 of 28 pin tests fail across all 5 files. The 8 that stay green are the spec-tuple ratchet, the "a record id does not resolve {current_user_id}" case, the five useFilterScope composition cases, and the server-refusal rendering control.
  2. useFilterScope stops adding recordId: 9 fail (react, components, grid, view). The core file stays green.
  3. The recordId term removed from each hold:
    • useResolvedFilter: 2 fail (the react follow-the-record case and element:number two records, two counts).
    • ObjectGrid: 1 fail.
    • plugin-view ObjectView: 1 fail.

Acceptance notes (observations, not filed)

  • The three filter holds each keep their own list of scope members. A future FilterTokenScope member has to be added to all three; the ablations above show that a hold missing one goes stale silently. Carrier: none.
  • Several packages/types JSDoc lines that list the context tokens ({current_user_id}, {current_org_id}, the date macros) do not mention {record_id}. They are descriptive, so they were left alone. Carrier: none.
  • {record.id} / {record-id} near-misses: the client's whole-token pattern is [a-zA-Z0-9_]+, so these pass the client quietly. The spec's lint and the server refuse them. This was already recorded in filter-tokens.spec-derived-7265.test.ts at 17.5.0 and is not new here.

The session for this change is https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ (dispatched os-dev, seat domain:ui#1, claim comment 5908754368).


Generated by Claude Code

…d record, refused by name without one

@objectstack/spec 17.5.0 declares RECORD_CONTEXT_TOKENS = {record_id}, the id of
the record a type: 'record' page shows, resolved only by the page renderer.

- core: resolveContextTokens fills {record_id} from FilterTokenScope.recordId
  (new optional member). With none in scope it is refused by name through
  onUnresolved, in the voice of an unresolved session token, and left as
  written: never null, never dropped, never reported as an unknown spelling.
- react: useFilterScope() adds recordId from the nearest RecordContextProvider
  (the provider visibleWhen binds as record) and from nothing else; outside one
  it hands back the session scope unchanged. FilterScopeProvider stays
  session-only.
- react / plugin-grid / plugin-view: the three filter holds key on recordId
  like every other scope member, so moving to the next record re-resolves
  without a remount.

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
… sentence; changeset

- content/docs/guide/data-source.md: a subsection on scoping a filter to the
  record in view, with an element:number dataSource example, the refusal
  outside a record context, and that it scopes what is shown, not access.
- packages/react/README.md: useFilterScope / useResolvedFilter name the token,
  where its id comes from, and that the hold follows the record.
- .changeset/7297-record-id-filter-token.md: core + react minor,
  plugin-grid + plugin-view patch.

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
…7 pin

object-ui/no-unused-imports reported it; the JSX runtime needs no binding.

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 13 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 b55a34647 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/6726-find-envelope-records-arms.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

.changeset/7070-no-invented-gantt-date-fields.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.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-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.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/7912-schema-renderer-datasource-contract.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    Five as any reads of this context in @object-ui/fields are gone — they were redundant the moment the seam became honest — and LookupField's local re-declaration of the imported context as a Context of any, which laundered its dataSource read while looking typed, is gone with them. Both directions of the contract are pinned against the real compiler in SchemaRendererContext.dataSourceType.pin.test.ts, and the card's planted documentation probe (a bare string in packages/react/README.md's provider example) now fails pnpm check:doc-snippets, where it used to exit 0 with zero diagnostics.

.changeset/8284-content-channel-per-component.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    Migration. Nothing that renders today stops rendering: a document authoring the channel its renderer reads is unchanged, and a document authoring the other one rendered an empty element before and is now refused instead. The repo-wide census found five documents in this state — packages/react/README.md, content/docs/guide/expressions.md, two blocks in content/docs/guide/schema-rendering.md and packages/components/TESTING.md — every one of them a form or container authoring body; all five are corrected in this change. If your own metadata authors the refused channel on one of these twelve node types, the component was already drawing nothing there; rename the key to the one in the table.

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/9308-data-root-unbound-from-adapter.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    The Data Context passages of content/docs/guide/schema-rendering.md and packages/react/README.md teach the scope channel accordingly.

.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/object-view-unmirrored-keys-7779.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    What was measured. Every reading was taken on the object-view node renderer (packages/plugin-view/src/ObjectView.tsx, registered by plugin-view/src/index.tsx) with schema.objectName / schema.layout as the positive controls of the same schema.KEY query, so each zero is a reading; the repo-wide census of viewTabBar finds the key in no source file outside @object-ui/types (two doc tables listed it as authorable and are corrected here). The spec side was read through the installed pin (@objectstack/spec@17.2.0, ui entry, 117 exported object schemas walked; control keys objectName / columns / navigation / listViews hit): the three spec-modelled keys are optional slots on ListViewSchema and ObjectListViewSchema; the six local keys have no spec slot anywhere.

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 9419df198 (merge-base with origin/main): 13 file(s) changed outside .changeset/, read against 1804 pending declaration(s) that publish a body (2414 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3572.8 KB 3607.4 KB
Main entry chunk (gzip) 149.5 KB 350 KB
Entry file index-CAnMzduV.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
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.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
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) 559.46KB 134.18KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.51KB 63.39KB
fields (index.js) 261.19KB 66.27KB
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 (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.51KB 11.36KB
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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 240.43KB 63.18KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.30KB 63.23KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.48KB 22.77KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
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) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 19.82KB 7.15KB
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

Spec Main Shape Gate is red on this head (0fb29c5fa, job 109859478523), and the failure is not this PR's.

  • It compiles objectui against @objectstack/spec built from objectstack main 93e9e4263c38. The four diagnostics are in packages/app-shell/src/providers/writeWarningToast.ts:119 (TS2741, no computed entry in STRIPPED_LINE) and packages/data-objectstack/src/spec-symbol-batch6.test.ts:242 (TS2344); this PR touches neither file.
  • The cause is objectstack b2805465 (10:22Z), which adds computed to DroppedFieldsEvent['reason']. Every gate run compiled against objectstack main from then on fails the same way, merge groups included.
  • The repair is objectui#11206, claimed as the lane's p0 stop-the-bleed (5910146506). When it lands, this PR merges main and the gate re-runs; nothing in this diff changes for it.

domain:ui seat 1 · session_0122Knsowci76D2rBWReCzzZ · 2026-09-30T11:22Z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0fb29c5fad10f58c28178be279e51ac57a8cc298
Local-runs: none

Inputs, and nothing else: card #7297 (body and all 7 comments, the rulings 5819465578 and 5857193175, the unlock probe 5904090882, the claim 5908754368, the os-dev-report 5910030694); PR #11205 (body, 14-file list, diff at the head against merge-base 81778b955); the head's check-runs; the published spec source at objectstack 0f6dcac5e9 (context-tokens.zod.ts), which the head's lockfile resolves as @objectstack/spec@17.5.0. Files at a ref were read with git show; nothing was checked out, installed, built or run.

① Derived judgments

  1. Accept-set widening, @object-ui/core resolveContextTokens. {record_id} and ${record_id} (the whole-token pattern admits record_id) now resolve when scope.recordId is a non-empty string. At base the token fell through every branch in silence: not a context token, not a near-miss key, no warning. Branch order at head is isContextToken → isRecordContextToken → near-miss, and the record_id literal is ratcheted with satisfies over the spec's RecordContextToken, the same ratchet the two session tokens have. Right.
  2. Refusal shape. With recordId absent, undefined, null or '', the token goes through the same warn closure the session branch uses (scope.onUnresolved; default console.warn with the [object-ui] prefix; null silences), the message names "{record_id}" and says "no record in context", and return value leaves it as written. Never null, never dropped, never routed to the near-miss "not a recognised token / did you mean" voice, never rewritten to some other string. That is 5819465578 item 1 and 5857193175 item 3, and it is also what the spec's own docblock says the token must never become. The core pin covers all four empty-scope shapes, the ${} spelling, the default console.warn, and resolveFilterPlaceholders. Against an ObjectStack backend the leftover token is then refused by name (FILTER_TOKEN_UNRESOLVED), the same path an unresolved {current_user_id} takes; the warning text says so, and says what a backend with no resolver does instead. Right.
  3. Source of the value is the mounted record context and nothing else. The only in-tree writer of FilterTokenScope.recordId at the head is useFilterScope() (the three holds copy it; nothing else assigns it). It reads useRecordContext()?.recordId, the value RecordContextProvider publishes, which is the same context SchemaRenderer reads (const recordContext = useRecordContext(), recordContext?.data) to bind record for visibleWhen. The hook reads no router param and no usePageVariables; FilterScopeProvider still takes only currentUserId and currentOrgId; ObjectDataPage's URL-filter scope is session-only, so a {record_id} in a shared URL filter is refused, not filled from ?recordId=. That is 5819465578 item 2 and 5857193175 item 2. The member is a public optional field, so a host outside this tree could fill it from anything; the JSDoc on the member and on the hook carry the ⛔, which is the right place for it on a pure resolver. Right.
  4. Public-surface changes, per package. @object-ui/core: FilterTokenScope gains the optional recordId?: string | null (additive); the NEAR_MISS_SUGGESTIONS annotation is widened to the union the spec's CONTEXT_TOKEN_SUGGESTIONS already carries at 17.5.0 (the four record_id near-miss rows come from the spec bump already on main, not from this diff). No new named export: RecordContextToken is a type import, not re-exported, and RECORD_CONTEXT_TOKENS is not re-exported beside CONTEXT_TOKENS (an observation, not a defect; the changeset names only the member). @object-ui/react: useFilterScope() returns recordId inside a record context and the provider's own object, identity unchanged, outside one; index.ts, hooks/index.ts and context/index.ts are unchanged, so no new export. @object-ui/plugin-grid, @object-ui/plugin-view: internal hold keys only. Right, each.
  5. Follow-the-record without a remount. Exactly three member-wise holds exist at the head (useResolvedFilter, useResolvedGridFilters, useResolvedFilterSegments; three held.currentOrgId !== sites in the tree) and all three now compare recordId. The consumers that resolve inline (ObjectMetricWidget, DatasetWidget, ObjectChart's fetchData, useOpenRecordList) key on filterScope identity, which the hook's useMemo moves on [session, recordId], so they follow the record too. Right.
  6. element:number reaches the resolver on both card surfaces. elements.tsx is byte-identical to base (empty diff), and at base it already reads useFilterScope() and useResolvedFilter(scopedFilter.filter, filterScope), with that queryFilter feeding both adapter.aggregate and the find fallback (objectui#10666); scopedFilter AND-combines properties.filter with the binding's (objectui#10909). So properties.filter and dataSource.filter both get the token with no per-surface edit. The components pin drives it through the real SchemaRenderer: 3 then 5 with no remount, the dataSource.filter triple resolved, {current_user_id} still the viewer, the refusal left as written and named, and a server-shaped FILTER_TOKEN_UNRESOLVED rendered with the dash like the session-token control. Right.
  7. The two falsified PM assumptions, checked against the source. (a) The spec at 0f6dcac5e9 keeps CONTEXT_TOKENS as ['current_user_id', 'current_org_id'] and declares RECORD_CONTEXT_TOKENS = ['record_id'], RecordContextToken, isRecordContextToken, a record-context kind in classifyFilterToken, and deliberately keeps isKnownFilterToken server-shaped (no record token). The dispatch's CONTEXT_TOKENS premise was wrong; the diff implements against the sibling list, which is the contract as published. (b) elements.tsx needed no edit, per item 6. Both falsifications verified; the diff follows the measured state, not the order.
  8. Docs, the review face. content/docs/guide/data-source.md adds a subsection under the element:number binding prose: a dataSource.filter example on element:number; the id is the mounted record context, never a URL parameter or a page variable; refused by name, left as written rather than dropped or blanked, server FILTER_TOKEN_UNRESOLVED; and the scope sentence, "{record_id} scopes what a component shows; it is not access control, which stays with the server's row-level security". packages/react/README.md (useFilterScope / useResolvedFilter) carries the same facts, "It scopes what a component shows; it is not access control", FilterScopeProvider carries the session values only, and the hold key now names the record. That is spec(data): a record-scoped filter token {record_id} — resolved from the mounted record context, refused by name wherever there is none (spec half of objectui#7297) objectstack#20003 item 4 on this side. The new fence is json; "Doc Snippet Type Check", "Doc Fence Language Check", "Internal Docs Link Check" and "Doc Example Id Check" are success on the head. Right.

② Semver level

  • .changeset/7297-record-id-filter-token.md: @object-ui/core minor, @object-ui/react minor, @object-ui/plugin-grid patch, @object-ui/plugin-view patch; no major (the fixed-group rule; "Changeset Bump Policy", "Changeset Fixed Group Check" and "Changeset Declaration" are success). core and react widen an accept set and a public type, so minor is the floor for a yes declaration and is right; grid and view change an internal hold key only, so patch is right (the fixed group carries them to the same version regardless). @object-ui/components is touched by a test file only, so no changeset is owed for it under the presence rule. Matches what the diff publishes.
  • Prose: accurate on every claim checked against the diff: the member name, FilterScopeProvider unchanged, the three holds by surface name, the refusal semantics (onUnresolved, never null, never dropped, server refuses by name), the scope sentence, the spec citation. No model identifier, no cross-file line address.
  • The Clause-② declaration is yes in the claim (5908754368) and in the PR body. Right: a widening on both counts the arm distinguishes (a token value the client resolver now accepts; a new public type member) and no narrowing anywhere in the diff (the token's silent pass-through becomes a warned pass-through; the value sent is unchanged). No arm stated, none needed.

③ Boundary flags

Dev report: deviations 7, out_of_scope_findings 2, open_questions none.

  1. File surface (seam in useFilterScope.ts, three holds, elements.tsx untouched): answered by ① items 3, 5 and 6; the claim named the seam by function, and the diff lands it where the value is produced. Accepted.
  2. Importer suites as a declared 119-file narrowing: CI runs the whole set; see the check-runs below. Accepted on CI's reading.
  3. check:doc-snippets not measured locally: "Doc Snippet Type Check" is success on the head. Closed.
  4. plugin-grid run in two halves: a local-run shape, not a contract matter; the test shards are the verdict. Closed.
  5. Commit trailers per objectui AGENTS.md: not a contract matter. Closed.
  6. components left out of the changeset: right, ② above. Closed.
  7. Worktree removed after the PR opened: not applicable to the contract.
  • Out-of-scope (a), each hold keeps its own member list so a future FilterTokenScope member must be added to all three: verified true at the head, and all three carry recordId now. Carrier none is acceptable; the seat may file a hygiene card if it wants a pin on the three lists. Not blocking.
  • Out-of-scope (b), packages/types JSDoc lines that enumerate the filter tokens do not name {record_id}: descriptive only, no gate reads them; a follow-up doc card is the seat's call. Not blocking.
  • The {record.id} / {record-id} near-miss note in the PR body is pre-existing at 17.5.0 (recorded in filter-tokens.spec-derived-7265.test.ts), not this card's. Not blocking.

Check-runs on the head, read 2026-09-30T11:21:29Z: 42 runs. 31 success; 3 skipped (the coverage matrix and dependabot, which do not run on this event); 7 in_progress, not yet concluded and named here as such: Test shards 1, 3, 4, 5, 6 and 7 of 8, and Type Check. 1 failure: Spec Main Shape Gate. Its annotations attribute the break to packages/app-shell/src/providers/writeWarningToast.ts (TS2741, 'computed' missing from the stripped-line record) and packages/data-objectstack/src/spec-symbol-batch6.test.ts (TS2344), compiled against @objectstack/spec built from objectstack main@93e9e4263c38, not the installed 17.5.0. Neither file is among this PR's 14. The same gate is success on this PR's base 846cec0e (queue build of PR 11200, run 36703808273) and failure on the same two files on the unrelated queue build of PR 11202 (run 36706771637) against objectstack main@5cd403e3997d. That is drift on objectstack main (a new computed kind on a stripped-line vocabulary this diff does not touch), owed its own card, and not a verdict on this contract; the gate's own workflow header says it is requirable and not required. The seat reads CI convergence separately.

Implemented-by: claude/issue-7297-record-id-filter-token
Reviewed-by: session_0122Knsowci76D2rBWReCzzZ

VERDICT: PASS

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3572.9 KB 3607.4 KB
Main entry chunk (gzip) 149.6 KB 350 KB
Entry file index-DimLcTJo.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.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
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.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
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) 559.46KB 134.18KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.51KB 63.39KB
fields (index.js) 261.19KB 66.27KB
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 (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.51KB 11.36KB
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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 240.43KB 63.18KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.30KB 63.23KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.48KB 22.77KB
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.81KB 3.58KB
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) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
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) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
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.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
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.26KB
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) 19.82KB 7.15KB
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: 35fc216f2654fe3e4476c572262661bf1cb6d592
Local-runs: none

Delta record. This head is the GitHub update-branch merge of main into the head that record 5910180583 reviewed; nobody edited a file. Inputs, and nothing else: record 5910180583 (head 0fb29c5fad10f58c28178be279e51ac57a8cc298); origin/main at 9419df198f06856c3746738c99dd807fd3f7dd04 and the two heads, read after a fetch with git log, git merge-base, git diff, git show --cc and git patch-id --stable at refs; PR #11205 and its /files at this head; the objectui#11210 merge commit's 14-file stat; this head's check-runs. Nothing checked out, installed, built or run.

① Derived judgments

Carried over from record 5910180583 at head 0fb29c5fad10f58c28178be279e51ac57a8cc298, the net diff being identical (patch-id 72b6527012a2c438b69728b833e94b742e58146d at both heads). The three measurements that make the carry-over valid:

  1. Parents. 35fc216f26 is a merge commit with exactly two parents: first parent 0fb29c5fad10f58c28178be279e51ac57a8cc298, the reviewed head; second parent 9419df198f06856c3746738c99dd807fd3f7dd04, the tip of origin/main (the merge of objectui#11210). git rev-list --first-parent 0fb29c5f..35fc216f is the merge commit alone, so no other commit sits between the reviewed head and this one. git show --cc on the merge is empty: an automatic merge with no hand-resolved hunk.
  2. Net diff. merge-base(origin/main, 35fc216f) is 9419df198f; merge-base(origin/main, 0fb29c5f) is 81778b955575b61d56c7563ef854fa73b7745ed7. git diff --name-status over the two ranges lists the same 14 paths with the same A / M status each (the changeset, content/docs/guide/data-source.md, four core paths, two plugin-grid, two plugin-view, three react, one components test), and GitHub's /files on the PR at this head lists those same 14. git diff 9419df19 35fc216f | git patch-id --stable = 72b6527012a2c438b69728b833e94b742e58146d; git diff 81778b95 0fb29c5f | git patch-id --stable = 72b6527012a2c438b69728b833e94b742e58146d. Identical hunks.
  3. What main brought in. Nine merges between the two merge bases (objectui PRs test(types): a padded grouping field name is refused on every face that declares grouping (objectui#7347) #11191, feat(types,layout,app-shell): a navigation entry with no label renders its target's current label at render time (objectui#9868) #11186, fix(types): the six held public blocks refuse the content channels no renderer reads (objectui#10872 batch 5) #11193, feat(types,plugin-detail): declare the field-security triple on record:details / record:highlights / record:related_list and read it un-cast (objectui#8649) #11184, feat(types): the object-form zod mirror declares the members its TypeScript twin declared (objectui#6152, round 1) #11125, fix(app-shell): Studio resolves an app's own locale-map label in the designer locale instead of printing [object Object] (objectui#11181) #11194, fix(data-objectstack): the entry-form filter refuses an empty or non-string icontains comparand, reading the spec's own predicate (objectui#9048) #11192, fix(core,plugin-tree): the record-source ladder judges map / gantt / tree against their spec rows; the tree stops honouring an undeclared bare-array data (objectui#8348) #11200, fix(app-shell,i18n,data-objectstack): the computed strip reason is worded, and the table and the batch-6 pin compile against both the pinned spec and objectstack main (objectui#11206) #11210), 100 files. Intersection with this PR's 14 paths: none. objectui#11210's two files, packages/app-shell/src/providers/writeWarningToast.ts and packages/data-objectstack/src/spec-symbol-batch6.test.ts, are among the 100 and are not in this PR's file list.

So every judgment in ① of record 5910180583 (the {record_id} accept-set widening in resolveContextTokens, the refusal shape, the record-context-only source, the per-package public surface, the three holds, element:number on both card surfaces, the two falsified premises, the docs face) is a judgment on hunks this head carries byte for byte, merged onto a main that touches none of the same files. Unchanged.

② Semver level

Carried over from record 5910180583 at head 0fb29c5f: .changeset/7297-record-id-filter-token.md is byte-identical at the two heads (git diff 0fb29c5f 35fc216f -- .changeset/7297-record-id-filter-token.md is empty), the levels it declares (@object-ui/core minor, @object-ui/react minor, @object-ui/plugin-grid patch, @object-ui/plugin-view patch, no major) still match what the identical diff publishes, and the PR body's Clause-②: yes is unchanged. The changeset gates on this head (Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read, Changeset Overwrite Report) are success.

③ Boundary flags

  • The boundary flags of record 5910180583 carry over as recorded: the seven dev deviations and the two out-of-scope findings are dispositions on the diff, and the diff is the same; nothing the merge brought touches them.
  • What main brought in: no file of this PR (① item 3). Any cross-file effect of the 100 main files on these 14 is what this head's Type Check, Lint, Build & E2E and test shards measure, listed below.
  • objectui#11210's two files are not in this PR's file list. The Spec Main Shape Gate failure the earlier record attributed to those two files (objectstack-main drift, objectui#11206) is what this update-branch merge was made to pick up.
  • Check-runs on 35fc216f26, read 2026-09-30T13:06Z: 42 runs. 32 success: Action Ref Convention, Build & E2E, Build Docs, Bundle Analysis, Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Control Byte Scan, Doc Component Type Check, Doc Example Id Check, Doc Fence Language Check, Doc Snippet Type Check, Docs Route Eager Closure Check, Governed Surface Queue Guard, Inert vi.mock Specifier Check, Internal Docs Link Check, label, Line Citation Gate, Lint, Live E2E (informational), Pre-Install Import Graph Check, README Export Check, Shell Escape Residue Scan, Skill Eval Token Check, Skill Example Check, Skill Guide Path Check, Test (dist pins), Test (shard 6/8), Test (shard 7/8), Test (shard 8/8). 3 skipped: dependabot, the coverage-shard matrix job, Test (coverage), which do not run on this event. 0 failure. 7 in_progress, not yet concluded and named here as such: Spec Main Shape Gate, Test (shard 1/8), Test (shard 2/8), Test (shard 3/8), Test (shard 4/8), Test (shard 5/8), Type Check. Spec Main Shape Gate is still in_progress (job 109895536183 of run 36717971172), not yet concluded and named here as such. On the reviewed head it was the one failure, attributed to the two objectui#11210 files; this head is the first of this PR to carry those files at their objectui#11210 state. Its conclusion on this head is the one reading this record could not yet take; the seat reads CI convergence separately, and this record is a judgment of the diff, which the gate does not change.

Implemented-by: claude/issue-7297-record-id-filter-token
Reviewed-by: session_0122Knsowci76D2rBWReCzzZ

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 13:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit cfc9b6d Sep 30, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-7297-record-id-filter-token branch September 30, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants