Skip to content

refactor(plugin-dashboard): retire FieldMeta.referenceTo, withdraw the README promise (#6597) - #6695

Merged
os-sales merged 3 commits into
mainfrom
claude/issue-6597-referenceto-enforce-or-remove
Aug 28, 2026
Merged

refactor(plugin-dashboard): retire FieldMeta.referenceTo, withdraw the README promise (#6597)#6695
os-sales merged 3 commits into
mainfrom
claude/issue-6597-referenceto-enforce-or-remove

Conversation

@os-sales

@os-salesos-sales commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes#6597

The measurement (measure-then-act card)

The card's rule: withdraw is the lean default when no demand is measured; enforce only
on a measured authoring story.
This PR measured, and lands withdraw.

The two referenceTo populations, kept separate (per the card's own trap warning)

referenceTo survives legitimately elsewhere as an internal TS property name that is
not this card's subject:

  • DatasetRelationship.referenceTo — a resolver's output, not an authoring input.
    Not touched by this PR.
  • plugin-dashboard's own FieldMeta.referenceTothis card's subject: the README's
    documented author-facing column override.

A bare grep for the identifier conflates these two populations. This PR only ever
inspected the second.

Does a real authoring story exist for a column-level reference override?

No, measured on four independent axes:

  1. Zero readers, override or not.LookupCellRenderer (@object-ui/fields) resolves
    its lookup target from field.reference_to / field.reference — never
    field.referenceTo — and computeLookupExpand (ObjectDataTable.tsx) builds
    $expand from the OBJECT SCHEMA's field types, never from an authored column key. This
    was already pinned by ObjectDataTable.overrideSource-6425.test.tsx's
    referenceTo-vs-options positive control: options (a live override) separates two
    equal-valued columns; referenceTo does not.
  2. RecordDetailDrawer, the only other buildFieldMeta caller, doesn't even offer a
    column-level override
    — it calls buildFieldMeta with no overrides at all. Only
    ObjectDataTable ever passed one through, and it reached nothing per (1).
  3. No precedent in the sibling producer.ObjectGrid's own relational-meta
    pass-through (applyRelationalMeta, packages/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. If a
    column-level reference override were a real pattern anywhere in this codebase, this is
    where it would show up first. It doesn't.
  4. No doc, example, or fixture anywhere in the repo shows a table column pinning a
    lookup's target away from what its schema field already says (checked
    content/docs/**, examples/**, and every JSON/TS occurrence of referenceTo outside
    this package's own internals).

⇒ Under the maintainer's standing startup-stage rule (2026-08-27: no measured demand
retires immediately, no transition window), this selects withdraw.

Both keyof FieldMeta seam bands (the card's fence, in full)

ObjectDataTable derives two refusal bands from keyof FieldMeta, and both had to move:

enrich() no longer reads authored.referenceTo into buildFieldMeta's overrides at
all, and FieldMeta.referenceTo / BuildFieldMetaParams.overrides.referenceTo are gone
from recordFields.tsx.

Ablation — prediction vs observation

Leg 1 (mutate): removing the tombstone intersection should make two @ts-expect-error
directives unused (TS2578).

Replaced & ObjectDataTableRetiredReferenceToTombstone with the old HELD shape
(& { referenceTo?: unknown }) at both of its two intersection sites in
ObjectDataTable.tsx. Anchored count before: grep -c ObjectDataTableRetiredReferenceToTombstone = 7;
after mutation, grep -n "ABLATION: tombstone removed" matched both edited sites and
grep -c "& ObjectDataTableRetiredReferenceToTombstone;" = 0, confirming the mutation
landed on disk.
Observed:tsc -p tsconfig.test.json exited 2
ObjectDataTable.emitBoundary-6373.test.tsx(370,5): error TS2578: Unused '@ts-expect-error' directive.
and ObjectDataTable.overrideSource-6425.test.tsx(303,5): error TS2578: Unused '@ts-expect-error' directive.
— exactly the two new counter-control pins this PR adds. Matches prediction.

Leg 2 (restore): the tree should return to byte-identical HEAD.
Restore was git checkout HEAD -- PATH (never bare checkout --, which would read from
the polluted index) inside a trap ... EXIT INT TERM, REPO_ROOT resolved via
git rev-parse --show-toplevel before mutating.
Observed:git diff HEAD --quiet exited 0 (clean); re-running tsc -p tsconfig.test.json afterward exited 0 with no output. Matches prediction.

Behaviour is unchanged

  • The pre-existing referenceTo reaches NOTHING on this path runtime assertion
    (ObjectDataTable.overrideSource-6425.test.tsx) passes unchanged — an authored
    referenceTo still renders byte-identical to its absence, before this PR and after.
  • Two new counter-control tests (mirroring the ones finding(plugin-dashboard): FieldMeta.decimals is written from the schema def but read by nothing #6625 added for decimals) prove the
    tombstone, not the derived band or a lingering hold, is what refuses the key at each
    seam band.
  • packages/plugin-dashboard/src/__tests__/ObjectDataTable.emitBoundary-6373.test.tsx's
    schema fixture (owner: { type: 'lookup', ..., referenceTo: 'user' }) is retired-spelling
    fixture rot from before this card — updated to the spec-canonical reference: 'user'
    (fixture triage: pure respelling, the field's type: 'lookup' is what actually drives
    the render being tested, nothing in this package's pipeline ever consumed the key either
    spelling).

README

Removed referenceTo from the "Author overrides always win" line
(packages/plugin-dashboard/README.md) and added one sentence stating the actual, measured
behaviour: a lookup column's related-object target always comes from the schema field def,
with no column-level override.

Scope note (why this isn't a published-type removal)

FieldMeta, AuthoredColumnOverrides, EnrichedColumn and ObjectDataTableColumnHolds
are absent from dist/index.d.tsplugin-dashboard's barrel (src/index.tsx)
re-exports only the ObjectDataTable component, and the package's exports map publishes
only ".". No downstream consumer can name any of these types. This is a package-internal
contract change plus a README correction, not a removal from a published type surface.
Changeset: minor (this repo's version-alignment rule reserves major for following
@objectstack across a major — see AGENTS.md 版本号策略).

Out-of-scope finding filed separately

While tracing LookupCellRenderer's actual read path, found that ObjectDataTable /
RecordDetailDrawer never feed reference_to / reference onto FieldMetaat all
so useRefObjectSchema-driven display-name resolution and ReferencedRecordLink's
drill-through links silently never engage for any lookup cell in either widget, regardless
of this card's ruling (the gap predates it — referenceTo was never the snake_case
spelling either). ObjectGrid's applyRelationalMeta is the working sibling pattern.
This is a behaviour change, not a retirement, and outside this card's stop-and-report
line ("enforcing must not widen behaviour beyond the documented promise") — filed as
#6694, unassigned, finding label, no pm:queue (an observation
for triage to size, not a ready-to-dispatch fix).

Gate table (HEAD 3c5d330a8, merged onto origin/main @ 84b4fb69d)

GateCommandResult
type-check (src + tests)pnpm --filter '@object-ui/plugin-dashboard' run type-check✅ exit 0, no output
vitest, full packagepnpm exec vitest run packages/plugin-dashboard/Test Files 83 passed (83) / Tests 781 passed (781)
vitest, touched files (post-merge, on HEAD)pnpm exec vitest run .../ObjectDataTable.overrideSource-6425.test.tsx .../ObjectDataTable.emitBoundary-6373.test.tsxTest Files 2 passed (2) / Tests 24 passed (24)
check:control-bytesnode scripts/check-control-bytes.mjs✅ OK, 5546 tracked text files scanned
eslint, touched files onlypnpm exec eslint --format json (4 touched files)✅ 0 errors; 59 warnings — identical to the origin/main baseline for the same 4 files (measured via eslint --stdin), so 0 new warnings introduced
check:doc-fencesnode scripts/check-doc-fence-languages.mjs✅ clean
check:readme-exportsnode scripts/check-readme-exports.mjsNOT MEASURED — population collapsed (packagesRead: found 13, floor is 25) because most sibling packages' dist/ aren't built in this worktree (only plugin-dashboard's own dependency closure was). Unrelated to this diff: plugin-dashboard's own README raised zero findings, and CI's full build covers this gate meaningfully.
pnpm changeset presencenode scripts/check-changeset-presence.mjs✅ 1 changeset declared for the 4 changed source files
changeset no-majornode scripts/check-changeset-no-major.mjs✅ no major bump declared

Type-aware eslint is not enabled in this repo (eslint.config.js's languageOptions sets
no parserOptions.project), so a file-scoped eslint run is a complete judgement, not a
narrowed one.


Generated by Claude Code

WIP checkpoint before the ablation proving the new tombstone is load-bearing.
…lumn-holds interface
`ObjectDataTableColumnHolds` lost its only member (`referenceTo`, retired by the
previous commit) and eslint's `@typescript-eslint/no-empty-object-type` flags the
resulting `{}`. Kept as an `interface` (a documented extension point for a future
per-key ruling) rather than switched to `type = object`, with a scoped disable and
the reasoning inline. Also adds the changeset for the referenceTo retirement.
Claude-Session: https://claude.ai/code/session_8ca04858-ea8e-5b85-9182-de59aa49e00c
Co-authored-by: Claude <noreply@anthropic.com>
@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation plugin tests labels Aug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 49 chunks)3231.6 KB3266.6 KB
Main entry chunk (gzip)157.2 KB350 KB
Entry fileindex-DNHpRuTN.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)11.89KB4.50KB
app-shell (runtime-config.js)20.61KB7.35KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)509.24KB115.61KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)173.10KB47.96KB
fields (index.js)239.05KB60.06KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.85KB12.89KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)190.33KB45.10KB
plugin-dashboard (index.js)133.26KB34.43KB
plugin-designer (index.js)212.80KB43.15KB
plugin-detail (index.js)245.29KB62.39KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.01KB32.23KB
plugin-gantt (index.js)165.16KB40.33KB
plugin-grid (index.js)201.51KB54.54KB
plugin-kanban (index.js)53.11KB14.62KB
plugin-list (index.js)113.01KB27.57KB
plugin-map (index.js)20.09KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)26.44KB7.59KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)85.87KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)65.97KB21.98KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)2.44KB1.21KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sales
os-sales marked this pull request as ready for review August 28, 2026 16:08
@os-sales
os-sales added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit 8a6ecacAug 28, 2026
30 checks passed
@os-sales
os-sales deleted the claude/issue-6597-referenceto-enforce-or-remove branch August 28, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationplugintests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

referenceTo column override: promised in the README, kept by nothing — make the promise real or withdraw it (enforce-or-remove, from #6425)

2 participants

@os-sales@claude