This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit f61dab1
feat(types,core,fields,app-shell,plugins)!: reference is the only relational-target spelling objectui writes or reads; reference_to retires (objectui#11070, round 4) (#11264)
Refs #11070
Clause-②: yes (narrowing). `LookupFieldMetadata`,
`MasterDetailFieldMetadata`, and `DetailViewField` retire `reference_to`
and declare `reference`, and the ingestion pass no longer stamps
`reference_to` onto served defs, under the startup no-gradualism rule.
It is priced in the changeset.
Round 4 of objectui#11070, the `reference_to` round: the seat's answer A
to Q1 (`5915992755`), under the maintainer ruling on objectui#6837
(`5475017957`, 2026-08-31, verbatim:
「objectui不是前端的项目吗?后端的元数据只要对,前端按协议执行就行了呀」, with the earlier question 「按
spec 拒绝面直接删不是更合理吗」). Claim `5916640555`.
`reference` is now the only spelling ObjectUI writes or reads for a
relational field's target object. The one fold that remains is the
ruling's choke point: `normalizeFieldReferenceKeys` folds a foreign
`reference_to` / `referenceTo` onto `reference` when `reference` is
absent, keeps its dev warning, and never drops a key.
## What changed
- **Types (`@object-ui/types`).** `LookupFieldMetadata` and
`MasterDetailFieldMetadata` declare `reference` typed by reference to
`@objectstack/spec`'s `FieldSchema.reference`. `DetailViewField` and its
zod mirror `DetailViewFieldSchema` move from `reference_to` to
`reference` in the same commit, so the two faces keep equal key sets.
The `form.ts` comments and the form-field mirror's comment now say that
the lookup and user widgets read `reference` only.
`strict-face-read-keys-11070.test.ts` moves `reference_to` from the
pending list to the retired list, and gains a type-level pin: both field
metadata types carry the spec member, and none of the three contracts
has a `reference_to` key.
- **Ingestion (`@object-ui/core`, `reference-keys.ts`).** The pass folds
onto `reference` and no longer stamps `reference_to`. The header prose
that described a stamp of "BOTH snake_case keys" now describes the fold,
and it records why the stamp protected nothing once both ends moved. The
leave-arm rule stands.
- **Readers read `reference` only:** `LookupField`, `UserField`,
`LookupCellRenderer` and `UserCellRenderer` (only those two reads in
`fields/src/index.tsx`), `InlineFieldInput`'s reference fallback,
`schemaDefaults`' `current_user` seed, `sectionFields`' view override,
and `MetadataProvider`'s inline-subform parent.
- **Emitters write `reference`:** `paramToField`, `RecordDetailView`,
`FlowReferenceField` (its one target line), `RecordDetailPanel`,
`RecordMetaFooter`, `RelatedList`, `fieldEnrichment`,
`buildDefaultPageSchema`, `sectionFields`, `bulkParamToField` and
`ObjectGallery`.
- **Copy sets and ledgers.** `plugin-grid`'s `RELATIONAL_META_READ_SET`
loses its `reference_to` row and the `adapter-stamped` verdict that row
alone held. `plugin-dashboard`'s `CELL_RELATIONAL_META_KEYS` and
`FieldMeta` drop `reference_to`. `resolveActionParams`' unread
`RuntimeField.reference_to` member goes. `FieldDefaultsSchemaLike` pins
three members.
- **Docs and fixtures.** `lookup.mdx` (typed fences, plaintext fences,
prose, plus one paragraph on the retirement), `record-edit-modes.md`,
`plugin-form.mdx`'s field-slot row and `plugin-form`'s README. The tests
that fed `reference_to` to a widget, a cell or a served-def stub now
feed `reference`. The pins whose job is the fold, or a refusal, keep
`reference_to` as their input and assert the fold to `reference` with no
stamp back, or the refusal.
- **One changeset**, `.changeset/11070-reference-to-round4.md`, minor
with the break stated (AGENTS.md §9: never `major`). Fifteen pending
changesets that this round made false get an appended dated note, in the
append-only shape: `6837-reference-to-arm-deletion`,
`6837-gantt-tree-referenceto-arms`,
`6837-recorddrawer-invented-target-arms`,
`6694-dashboard-lookup-reference-meta`,
`6597-retire-fieldmeta-referenceto`,
`7166-retire-inert-fieldmeta-copies`,
`paramtofield-reference-rule-derives-from-core-5312`,
`6711-retire-reference-to-field`, `6874-retire-titleformat`,
`7155-converge-lookup-dialect-camelcase`,
`6528-resolve-reference-to-census`,
`7324-plugin-form-nameable-parameter-types`,
`11070-field-metadata-spec-spellings`, `10535-person-name-read-gate` and
`6875-grid-relational-meta-derive`.
### Pins that flipped, and why
These pins asserted the stamp. Each now asserts that no stamp happens,
and that the fold still does:
- `reference-keys.test.ts`: a `reference` def gains no `reference_to`;
`referenceTo` folds to `reference` only; idempotence; the map and array
containers.
- `reference-keys.legacyWarning-6837.test.ts`, case 5: "the STAMP is
unchanged" becomes "the FOLD still runs". The `referenceTo` case asserts
no `reference_to`; the `reference_to` case keeps the input key, because
the pass never drops one.
- `MetadataProvider.itemReferenceKeys-7650.test.tsx`: the first pin
asserted the stamp and now asserts its absence. The rest feed a legacy
spelling, because a `reference`-only def comes back unchanged and cannot
show that the pass ran.
- `getObjectSchema.test.ts` (data-objectstack).
- The emit-side pins that read `reference_to` off an emitted bag:
`referenceArms-6837.divergent.test.ts` (plugin-detail),
`RecordDetailDrawer.referenceArms-6837.test.tsx`,
`HeaderHighlight.editable.test.tsx`, `relationalMetaCopySet-6711` /
`-6874`, `lookupRelationalMeta-6694`, `sectionFields*`,
`paramToField.test.ts`, `resolveActionParams.test.ts`,
`bulkParamToField.test.ts`.
- New refusal pins: the lookup cell, the user cell and `LookupField`
each read no target from a `reference_to`-only def.
Pins that assert something else were not "fixed green". Three copy-set
floors, which required more than two keys in `RELATIONAL_META_KEYS`, now
require more than one, because the set is now `reference` and
`displayField`, and each floor now also names `reference`.
### Beyond the claim's file surface, stated
- `ObjectDataTable.tsx` gains
`ObjectDataTableRetiredReferenceToSnakeTombstone`, and both
ObjectDataTable test files gain a directive and a counter-control for
it. Retiring `FieldMeta.reference_to` removed the key from the derived
refusal bands' pool, and `recordFields.tsx`'s own `FieldMeta` docblock
requires that a retired member be re-refused by hand at that seam. This
is the third tombstone, beside `decimals` and `referenceTo`.
- Comment-only corrections to statements this round made false:
`auditHistoryDisplay.ts`, `deriveRelatedLists.ts`,
`useDatasetFields.ts`, `object-fields-io.ts`,
`components/custom/field.tsx`, core `ActionRunner.ts` /
`chart-series.ts` / `expand-fields.ts`, `data-objectstack/src/index.ts`,
`RecordPickerDialog.tsx`, `lookupColumnDisplay.tsx`,
`HeaderHighlight.tsx` and `ObjectGantt.tsx`, plus the stamp prose in the
gantt / tree / drawer 6837 pins.
- The named tail is folded. PR #11243 landed, so the three
`ObjectGrid.tsx` relational-metadata comments now name `reference` and
`displayField` (seat message, 18:53Z). `ListView.tsx`'s two lines were
left as they are, because both are true after this round: one says
`FieldSchema` refuses `reference_to`, the other that a list column
declares neither spelling. PR #11233 landed, so the `form.zod.ts`
comment, the `FormPage.tsx` comment, the `plugin-form.mdx` row and the
`FormPage.sharedFieldResolver-10179` pin were folded as well (seat
message, 19:54Z; the comment only in `form.zod.ts`, with no new
declaration). `FormPage.sharedWidgets-10179`'s lookup fixture moved
first, because that test went red on the change.
## Census, before and after
Base: merge base `615346d` for the round's own diff. Code lines are
non-comment lines in `packages/*/src`, `apps/console/src` and
`examples/*/src`, excluding tests. The typed half comes from a
TypeScript LanguageService `findReferences` over 5568 source files, with
workspace packages resolved to source.
| kind | before | after |
| --- | --: | --: |
| emitter sites writing `reference_to` onto a def or widget `field` | 11
| 0 |
| ingestion stamp of `reference_to` | 1 | 0 |
| copy sets carrying `reference_to` (grid ledger row, dashboard cell
keys) | 2 | 0 |
| reader lines reading `reference_to` (widgets, cells, inline editor,
seeding, section override, subform parent) | 10 | 0 |
| the ingestion fold's read of the legacy key (the ruling's choke point)
| 1 | 1 |
| declared `reference_to` members (field-types ×2, views, views.zod,
RuntimeField, FieldMeta, schemaDefaults ×2) | 8 | 0 |
| `reference_to?: never` refusal (tombstone) | 0 | 1 |
| typed references to a `reference_to` member: production writes / test
writes | 2 / 7 | no member left to reference |
The eleven emitters are the PM census's eleven. The type-checker census
found two of them typed (`FlowReferenceField`, `RelatedList`); the other
nine write through `any` or `Record` bags. It found no typed reader:
every reader was an untyped read.
## Runtime probe — the user-visible proof
A scratch vitest file, never committed, ran against the source at merge
base `615346d` and at head (`1972d01`, re-run at `338b19d` with
identical readings). `ObjectForm` in create mode opens its lookup
picker; `ObjectGrid` renders a lookup column with a primitive id; the
referenced record is `{ id: 'a1', name: 'Acme Corp' }`.
| case | base | head |
| --- | --- | --- |
| (a) served def with `reference` only, through `ObjectStackAdapter` |
picker queries `probe_account`; cell shows `Acme Corp`; served keys
`label, reference, reference_to, type` | picker queries `probe_account`;
cell shows `Acme Corp`; served keys `label, reference, type` |
| (a) the same def from a BYO `DataSource` | works | works |
| (b) action param `referenceTo` through `paramToField` into
`LookupField` | field carries `reference_to`; picker queries
`probe_account` | field carries `reference`; picker queries
`probe_account` |
| (c1) `reference_to`-only def served through `ObjectStackAdapter` |
folded (`reference` added); dev warning fires; picker and cell work |
the same: folded, warned, picker and cell work |
| (c2) `reference_to`-only def from a BYO `DataSource` | picker queries
`probe_account`; cell shows `Acme Corp` | **no `find` call at all; the
cell shows the raw id `a1`** |
(c2) is the break, and the changeset's BREAKING paragraph names that
host.
## Hypotheses
- **H1 holds.** Read at objectstack `origin/main` `165c1d49e`. The
example apps write `reference_to` zero times, against a control of 30
`Field.lookup` / `Field.masterDetail` calls in 19 files; both helpers
write `reference`. `FieldSchema` refuses `reference_to` by name
(installed 17.5.0: `unrecognized_keys`, "Did you mean `reference_to` →
`reference`?"), and so does the spec's form-field schema. The
`field-reference-to-alias` conversion
(`packages/spec/src/conversions/registry.ts`) rewrites stored rows on
rehydration and in `os migrate meta`. The only other hits are refusals
(the SQL and Mongo doors, verify, lint), a reader of three spellings in
`plugin-security`, and a comment in `plugin-approvals` that describes
this repo's widget key (noted below).
- **H2 holds.** Every production write is one of the eleven emitters,
the ingestion stamp, or one of the two copy sets the brief listed as
readers. No twelfth writer turned up, typed or untyped.
- **H3 holds.** On the ObjectStack path the cells receive folded defs,
as probe cases (a) and (c1) show. `RELATIONAL_META_KEYS` is derived to
`reference, displayField`, and the grid's derivation gate re-extracts
the cell's read set with `reference` in it.
- **H4:** **false as posed.** The `reference_to` pair was not in M3's
class (ii) at this base. Round 1 (PR #11115) moved both `fields-lookup`
fixtures to `reference`, and round 3 reported class (ii) without it.
Measured with `scripts/measure-strict-authoring-face.mjs --json` at
merge base `af9e957` and at `338b19d`: every refusal figure is identical
(2224 nodes, 105 strict-refused, 66 strict-only, 39 red today, 599
documents, 98 refused whole-tree), and `reference_to` occurs in zero
refusals at both. The only differences are the renderer-source
word-mention counts, which the comment edits moved, and the error text
of one authored module that fails to load under either build state. So
this round moves M3 by zero in every class. The absolute class (ii)
figure at this base is NOT MEASURED: round 3's shipped-face port of PR
#11069's `findUndeclaredKeys` was a scratch script and was never
committed.
## Checks
Heavy runs went through `os-verify-lock` (slot `issue-11070`). Exit
codes were captured before any pipe.
- **Build closure** (`check-doc-snippet-types --build-filter`, turbo
`--concurrency=2`): `35 successful, 35 total`, before and after the
first merge.
- **type-check** of 21 packages, the ones this diff touches plus the
console and the schema catalog (the type-checker census found no other
typed reader): `Tasks: 56 successful, 56 total`, before and after the
first merge. After the second merge, `@object-ui/types` was rebuilt and
type-checked again, exit 0.
- **Tests, full affected set** (the 21 packages' paths, 3536 files),
before the first merge: `4 failed | 3527 passed | 5 skipped (3536)`. The
four were pins that asserted the old shape: three copy-set floors of
more than two keys, and the console shared-widget lookup fixture. They
were fixed in `c684de4` and re-run: `Test Files 4 passed (4)`, `Tests 30
passed (30)`.
- **Tests after the first merge** (`338b19d`), narrowed to the files
both sides touched plus this round's relational surface (types, fields
tests and widgets, the Flow inspectors, console components, plugin-grid,
plugin-detail and plugin-dashboard tests, plugin-form, core
reference-keys, the adapter, and app-shell providers and utils): `Test
Files 1137 passed | 3 skipped (1140)`, `Tests 16884 passed | 31 skipped
(16915)`. After the second merge (`16b43a7`, no file overlap):
`packages/types/` `295 passed (295)`, `7375 passed (7375)`, including
`zod-mirror-parity.test.ts`, whose totals did not move.
- **Doc gates** at `338b19d`: `check:doc-snippets` `696 of 696 block(s)
judged, 0 failed`; `check:doc-examples`, `check:doc-types`,
`check:spec-symbols`, `check:doc-example-readers` and
`check:doc-example-ids` all exit 0.
- **At `16b43a7`**, all exit 0: `check:doc-fences`, `docs:check-links`,
`check:control-bytes`, `check:pending-changeset-literals`,
`check-changeset-presence` (`174 source file(s) of 20 released
package(s) changed, and this change declares 1 changeset(s)`),
`changeset:check` (no major), `check:new-line-citations` (`0 new
citation(s)`), `check:test-path-roots`, `check:changeset-claims`
(report-only; every named changeset was read), and
`check-governed-queue-guard --test` over the 194 paths (`NOT GOVERNED`).
- **Lint, a measured narrowing.** The population is `eslint.config.js`'s
`**/*.{ts,tsx}` files entry; there are no `parserOptions.project` or
`projectService`, so type-aware linting is off and this diff cannot move
an untouched file's verdict. The 174 changed `.ts` / `.tsx` files were
linted at `16b43a7` and at merge base `54a7830`: 0 errors at both, 1909
warnings at both, and no file is worse than base.
- **Ablations**, each run on a committed, clean tree through
objectstack's `ablation-replace.mjs`. In every case the anchor hit once,
the blob moved, the blob was restored equal to HEAD, and `git diff HEAD`
was empty afterwards.
- A1: `lookup.mdx`'s fence writes `reference_to`. `check:doc-snippets`
exited 1 with `696 of 696 block(s) judged, 1 failed`, TS2561
"'reference_to' does not exist in type 'LookupFieldMetadata'. Did you
mean to write 'reference'?", which proves the gate read the rebuilt
`.d.ts`.
- A2: the fold stamps `reference_to` again. 6 red / 83 green, against 5
predicted: the three core stamp pins, the adapter pin and the provider
pin, plus the empty-target pin, because an empty `reference` also gained
a stamp.
- A3: `LookupCellRenderer` reads `reference || reference_to` again. 2
red / 14 green, as predicted: the new cell refusal pin, and the grid
derivation gate's unclassified-spelling check.
- **Type-checker census** at `338b19d`: the seven contracts carry no
`reference_to` member. The control run of the same instrument on
`reference` finds 7 declarations, the same two typed production writes
that moved, 1 production read, 3 test reads and 10 test writes.
- **Diff size:** 194 files, +1022 / −647 against merge base `54a7830`.
That is under the 5000-line human-merge threshold: 62 non-test files (16
of them changesets) and 132 test files.
NOT MEASURED:
- The repository-wide lint and the test shards outside the sets above.
CI runs those.
- Tests after the second merge outside `packages/types/`. Its three
incoming commits share no file with this branch, and none contains
`reference_to`.
- A browser dogfood against a live objectstack app. The probe used
`ObjectForm` / `ObjectGrid` with a stub or adapter-wrapped `DataSource`.
- The absolute M3 class (ii) figure (see H4).
- CI convergence. It was not waited on, per the dispatch contract.
## Acceptance notes
- `docs/adr/0059-action-params-shared-field-widgets.md`'s decision
section still records `referenceTo` → `reference_to` as `paramToField`'s
mapping. It was left as written: it is the accepted decision's record
(2026-07-19), and the ruling kept the historical record. Editing
`docs/adr/**` would also make this PR governed. The seat may want a
dated amendment line in a docs-only PR.
- `ROADMAP.md`'s two hits are completed items and changelog history, not
current behaviour, so they were not changed.
- `.changeset/audit-field-def-reference-narrow.md` says the choke point
"stamps both snake_case spellings". It has empty front matter, so
nothing publishes it, and no note was appended.
- objectstack `plugin-approvals`' `sys-approval-request.object.ts` has a
comment saying the console resolves a lookup config as `reference_to:
sys_user`. That spelling is now `reference`. It is a comment in the
sister repo, not a producer; noted, not filed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f4ed238 commit f61dab1
194 files changed
Lines changed: 1022 additions & 647 deletions
File tree
- .changeset
- apps/console/src/components
- content/docs
- fields
- guide
- plugins
- packages
- app-shell/src
- providers
- __tests__
- utils
- __tests__
- views
- metadata-admin
- inspectors
- previews
- components/src
- __tests__
- custom
- renderers/form/__tests__
- core/src
- actions
- utils
- __tests__
- data-objectstack/src
- fields/src
- __tests__
- widgets
- plugin-calendar/src/__tests__
- plugin-dashboard/src
- __tests__
- plugin-detail/src
- __tests__
- renderers/__tests__
- synth
- plugin-form
- src
- __tests__
- plugin-gantt/src
- plugin-grid/src
- __tests__
- components
- plugin-kanban/src/__tests__
- plugin-list/src
- __tests__
- plugin-map/src
- plugin-timeline/src
- plugin-tree/src
- plugin-view/src/__tests__
- react/src
- __tests__
- hooks/__tests__
- types/src
- __tests__
- zod
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| 62 | + | |
| 63 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
| 59 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
| 42 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| 54 | + | |
| 55 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
| 122 | + | |
| 123 | + | |
0 commit comments