Skip to content

fix(fields): the lookup cell names the referenced record from the fields the viewer may read (objectui#10501) - #10592

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10501-lookup-cell-read-gate
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10501-lookup-cell-read-gate

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10501
Clause-②: no

What changes

LookupCellRenderer names the record a lookup points at through the referenced object's schema: displayField, then nameField, then titleFormat, then the type-aware derivation. It did that on the row as served. On a backend that does not strip policy-denied keys, a titleFormat or nameField naming a denied field printed that field in every lookup cell.

  • resolveLookupRecordName now resolves from the row with the fields the loaded policy denies on the REFERENCED object removed. id and _id are kept. This is the objectui#10411 / objectui#10434 rule, so the ladder falls through exactly as it does for the row a stripping backend (FieldMasker) serves.
  • The policy is a required parameter of resolveLookupRecordName. So every caller gets the gated row, and the compiler refuses a new caller that leaves it out. The callers are the expanded record, the JSON-encoded reference, each multi-value chip and the overflow chip's title, and the fetch-on-demand path.
  • The gate is one module-private copy, withoutDeniedFields(record, policy, objectName), in packages/fields/src/index.tsx. LookupField.tsx's helpers are not in reach: they are module-private in a file the package entry re-exports whole (export * from './widgets/LookupField.js'). Reaching them would mean editing LookupField.tsx and adding a new internal module, and both are outside the claim's file surface. See the census below for the consolidation question.
  • No new public export. pnpm check:unreferenced-sources and check:phantom-deps are green. @object-ui/permissions was already a declared dependency, and it is already in the entry's static graph through LookupField.tsx.

Route change, measured: "perms in the effect's deps" would not relabel

The triage and the claim put perms in the resolving effect's deps. I measured that mechanism before building it, and it does not relabel. The fetch-on-demand hook cached the resolved NAME in a module-level map, and its effect returns early on a settled entry. A policy change re-runs the effect, the effect returns at once, and the stale name stays.

So the cache now holds the fetched record and schema, and the name is resolved on every render with the render's policy. That is the shape objectui#10487 (PR objectui#10557) gave the lookup editor's hydrated chip. A policy that loads or changes after the record arrived relabels the mounted cell with no second read. The effect only fetches, so it has no perms dependency to carry. The ablation M2b below is the evidence: I put the name back in the effect WITH policy in its deps, and the relabel pin went red.

The cache key still carries displayField (objectui#2926 ⑧). It is no longer needed now that the entry holds a record, but dropping it would change how many reads a screen makes, and this card does not make that change.

Reproduction (before the fix)

I wrote the new pin first and ran it on base 8740e86ce: 7 failed, 2 passed. The 2 that passed are the controls. Each denied-field row failed on the served value, for example:

AssertionError: expected '...' not to contain 'ada@example.com'
Received: "SPAN class="block max-w-full truncate" title="ada@example.com"...ada@example.com"

(The tag in the received line is spelled as a word so the text survives this page.)

The pin — packages/fields/src/__tests__/lookupCell.readGate-10501.test.tsx

The tests use the real PermissionProvider and a backend that does not strip. Each test uses its own referenced object, because the caches are module-level.

  • An expanded record whose titleFormat names a denied field prints the name, and the text is the same as the text a stripping backend's row gives.
  • An expanded record whose nameField is denied prints the name.
  • A bare id fetched on demand: the fetched record is named from its readable fields.
  • A multi-value cell: no chip and no overflow title prints a denied value.
  • A JSON-encoded reference prints the name.
  • Lit controls: a field the policy does not deny still prints, on the expanded path and the fetched path. With no provider (isLoaded false), the row is named as served.
  • A policy that arrives after mount relabels the SAME mounted label node (identity asserted), on the expanded path and the fetched path. The fetched path asserts findOne was called once: relabelling reads the policy again, not the backend.

Ablations: mutated on disk, restored by blob

Each leg ran on committed HEAD 4c1bad224. The mutation went through ablation-replace.mjs (anchor must hit, blob must change), was checked with an on-disk grep -c marker, and was restored from HEAD. The restore was proven by blob 9e540c8cffee == HEAD blob and an empty git diff HEAD. The test imports ../index (source), so no dist is involved.

leg mutation marker result
M1 gate removed (if (true or the rest) 1 7 failed / 2 passed: every denied-field and relabel row is red, both controls green
M2a name resolved inside the fetch effect, policy NOT in deps 1 1 failed / 8 passed: only "relabels … a bare id fetched on demand"
M2b the same, WITH policy in the effect deps (the triage's mechanism) 1 + 1 1 failed / 8 passed: same row, same stale title="ada@example.com"
restored none 0 9 passed

Verification

All the checks below ran at final HEAD a0885627b, from the repo root. The closure was built first with turbo run build --filter=@object-ui/fields^....

  • pnpm exec vitest run packages/fields/: 201 files passed, 1 skipped; 3315 tests passed, 7 skipped. The pin alone: 9/9.
  • pnpm --filter @object-ui/fields run type-check (tsc --noEmit && tsc -p tsconfig.test.json): exit 0. tsc -p tsconfig.test.json --listFiles includes the new test file (count 1).
  • eslint over the two touched files, with --no-inline-config --format json (2 files linted, 0 errors):
    • index.tsx: the per-rule counts are identical to base 8740e86ce: react-refresh/only-export-components 93, no-explicit-any 44, no-unused-vars 1. react-hooks/exhaustive-deps is 0 before and 0 after.
    • The new test file has 0 messages.
    • The config enables no type-aware linting (tseslint.configs.recommended, no parserOptions.project), so this diff cannot move the verdict on any untouched file.
  • These gates exit 0: check:control-bytes; check:new-line-citations (0 new); check-changeset-presence (1 changeset, @object-ui/fields patch); check-changeset-no-major; check-changeset-overwrite; check-type-check-coverage; check:phantom-deps; check:esm-specifiers (specifiers only); check-test-path-roots; check-vi-mock-override-shape; check:self-import; check:unreferenced-sources.
  • check:changeset-claims flags 4 pending changesets that name packages/fields/src/index.tsx: 6625, 6694, 6837 and 7166. I read each paragraph. None is falsified: this diff adds no field-meta read and does not touch decimals.
  • NOT MEASURED locally: the eager-closure budget (it needs built artifacts, so CI runs it). @object-ui/permissions was already in the entry's static graph through LookupField.tsx.

Census: every copy of "the row as the viewer may read it" (for the seat's decision)

The claim asks for this census and bars an export on this card. Five copies are on main after this PR, and a sixth is in open PR objectui#10570.

file symbol exported? keeps gate source shape
packages/fields/src/widgets/LookupField.tsx fieldReadGate + withoutDeniedFields(record, readable) module-private (the file is export *'d by the entry) id, _id, declared idField usePermissions() a gate closure plus a row filter (two functions)
packages/fields/src/widgets/RecordPickerDialog.tsx withoutDeniedFields(record, perms, objectName, idField) module-private (the file is export *'d by the entry) id, _id, idField usePermissions() row filter
packages/fields/src/index.tsx (this PR) withoutDeniedFields(record, policy, objectName) module-private id, _id usePermissions(), typed as the Pick of isLoaded/checkField row filter
packages/plugin-detail/src/withoutDeniedFields.ts withoutDeniedFields(record, perms, objectName) a module export, not a package export id, _id usePermissions(), the same Pick row filter
packages/app-shell/src/views/RecordDetailView.tsx withoutDeniedFields(record, perms, objectName) module-private id, _id usePermissions(), the same Pick row filter
packages/react/src/hooks/useRecordSearch.ts (open PR objectui#10570, not on main) readableRow(record, objectName, policy) module-private id, _id the caller passes a structural fieldReadPolicy (react does not depend on permissions) row filter, argument order differs

The six share one body: return the row unchanged unless the policy is loaded and an object is named. Keep id, _id and any extra identity key. Drop every key for which checkField(object, key, 'read') is false. Return the SAME object when nothing is withheld.

One pure export would replace all six. Its signature would be (record, policy, objectName, extraKeep?), where policy is structural and needs only isLoaded and checkField. The differences it has to absorb are the idField extra key in two fields copies, the closure split in LookupField, and the argument order in react.

The dependency facts that decide where the export lives:

  • @object-ui/permissions is a runtime dependency of app-shell, fields, plugin-calendar, plugin-dashboard, plugin-detail, plugin-form, plugin-gantt, plugin-grid, plugin-kanban, plugin-list, plugin-map, plugin-timeline, plugin-tree and plugin-view. apps/console lists it only as a devDependency.
  • @object-ui/react does not depend on it. @object-ui/permissions depends only on @object-ui/types, so adding it to react would not create a cycle.
  • @object-ui/core is already a dependency of fields, plugin-detail, app-shell and react. A structural-policy export there would need no new edge anywhere. An export from @object-ui/permissions would need react to add one edge.

A different shape is not covered by a row export: the column/field-LIST filters. These are PeoplePicker's $expand, subtitle and avatar filters, LookupField's readablePreviewColumns and expand list, and the keepReadableColumns family in RelatedList and the grids. They filter a list of names, not a row.

Acceptance notes

  • These notes are not filed and are not in scope.
  • UserCellRenderer names an embedded user from name / username / image off the row as served. It does not go through a schema or a policy. A policy that denies name on the user object would still print it. I did not probe this: there is no repro and no claim that such a policy exists. Terms a later search could use: UserCellRenderer denied name and user cell FLS.
  • The cache key keeps displayField (see above). Dropping it would save one read when two columns point at the same record with different display fields. That is a fetch-count change, not a correctness change.

Overlap

  • PR objectui#10568 edits the masked-cell region of the same file (MaskedCellRenderer / buildStandardCellRendererMap). This PR touches only the lookup resolver, useLookupName and LookupCellRenderer, so the hunks are disjoint.
  • The live claim objectui#10493 (ImageCellRenderer): this PR does not touch it.

This body was written by https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC (the domain:ui seat 1 dispatch).


Generated by Claude Code

…lds the viewer may read (objectui#10501)

`resolveLookupRecordName` resolved the referenced record's display name
(displayField -> nameField -> titleFormat -> derivation) on the row as
served, so on a backend that does not strip denied keys a `titleFormat` or
`nameField` naming a policy-denied field printed that value in every lookup
cell. It now resolves from the row with the fields the loaded policy denies
on the referenced object removed (`id`/`_id` kept) -- the rule the lookup
editor's option label (objectui#10411) and the record title
(objectui#10434) already apply. `policy` is a required parameter, so every
caller (expanded record, JSON-encoded reference, multi-value chips and the
overflow title, the fetch-on-demand path) gets the gated row.

The fetch-on-demand cache now holds the fetched record and schema instead
of a resolved name, and the name is resolved per render: a policy that
loads or changes after the record arrived relabels the mounted cell with
no second read.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
… instead of `any`

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 4 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/6625-retire-fieldmeta-decimals.md

  • names fields/src/index.tsx → packages/fields/src/index.tsx — edited by this change

    buildFieldMeta computed decimals: overrides.decimals ?? meta?.decimals ?? meta?.scale on every call and the value reached nothing. Re-measured on this branch's base (efdc6c62): zero .decimals member reads across @object-ui/fields, @object-ui/i18n, @object-ui/components, @object-ui/core and plugin-dashboard itself — the only non-comment occurrence was the write being removed here. The positive control in the same query shape fires: .scale member reads hit NumberField.tsx, GridField.tsx and fields/src/index.tsx. So the zero is a finding, not a broken query. The overrides.decimals ?? head of that chain had already lost its only feeder when objectui#6425's ruling removed the authored read from ObjectDataTable.enrich(); RecordDetailDrawer, the only other buildFieldMeta caller, passes no overrides at all. Both halves retire together, so the key leaves in one move.

.changeset/6694-dashboard-lookup-reference-meta.md

  • names packages/fields/src/index.tsx → packages/fields/src/index.tsx — edited by this change

    ⚠️ The copy set is three keys where ObjectGrid's RELATIONAL_META_KEYS is nine, and the difference is measured per key, not preferred. The grid's cells are EDITABLE, so its extra keys drive the inline picker's query (LookupField / UserField read id_field, description_field, lookup_filters, lookupFilters); these two widgets are read-only and their render path ends at a cell renderer. packages/fields/src/index.tsx reads exactly reference_to, reference and display_field off a cell's field prop; titleFormat is never read off a field meta at all (its readers take it off the object schema, which arrives here through useRefObjectSchema(reference_to)), and reference_to_field has zero member reads anywhere in the repo. Copying the other six would mint six members written on every call and read by nothing — precisely what objectui#6625 (decimals) and objectui#6597 (referenceTo) retired from this same file.

.changeset/6837-reference-to-arm-deletion.md

  • names fields/src/index.tsx → packages/fields/src/index.tsx — edited by this change

    Three readers were deliberately left alone. LookupCellRenderer (fields/src/index.tsx), LookupField and UserField read FieldMetadata — ObjectUI's OWN contract, whose LookupFieldMetadata declares reference_to and never declares reference. They are fed by the emitters above and by published example schemas (examples/schema-catalog/src/schemas/fields-lookup/*.json), so narrowing them would break in-repo producers, and plugin-grid's relationalMetaCopySet.derivation.test.ts re-derives its read set from exactly those three sources — where reference_to is recorded with verdict adapter-stamped. DetailViewFieldSchema is likewise untouched.

.changeset/7166-retire-inert-fieldmeta-copies.md

  • names packages/fields/src/index.tsx → packages/fields/src/index.tsx — edited by this change

    applyRelationalMeta writes the copy set onto the fieldMeta that generateColumns hands to ANGLE-BRACKETS(CellRenderer) as the field prop — six JSX passes across the three column-building paths, and nowhere else. For a relational column that resolves to LookupCellRenderer, which reads exactly reference_to, reference, display_field, displayField, reference_field and options; a user column resolves to UserCellRenderer, which destructures { value } and reads no field meta at all. Measured by receiver rather than by count: packages/fields/src/index.tsx, the file holding every cell renderer, contains zero occurrences of the three retired keys, against a control of 22 occurrences of the display_field / displayField / reference_to spellings the cell does read.

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.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with f5178a272 (merge-base with origin/main): 2 file(s) changed outside .changeset/, read against 1462 pending declaration(s) that publish a body (2044 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3047.2 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-BcVapjRg.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.68KB 6.20KB
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.18KB 10.59KB
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.41KB 1.23KB
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.32KB 130.64KB
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) 259.12KB 65.73KB
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.40KB 14.61KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.50KB 35.37KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.59KB 61.50KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 147.92KB 37.83KB
plugin-gantt (index.js) 169.62KB 41.91KB
plugin-grid (index.js) 215.37KB 58.92KB
plugin-kanban (index.js) 48.26KB 15.04KB
plugin-list (index.js) 114.43KB 28.26KB
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.64KB 8.94KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.31KB 21.78KB
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) 114.58KB 37.60KB
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) 0.20KB 0.18KB
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
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 10:47
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit a7df45f Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10501-lookup-cell-read-gate branch September 25, 2026 10:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant