Skip to content

Field designer offers a referenceTo control for lookup fields — a key FieldSchema rejects by name, so saving a lookup field hard-blocks the object #6041

Description

@yinlianghui-tw

Surfaced by the key-level parity gate built for #5761 (scripts/check-designer-field-key-parity.mjs). Filed rather than fixed: #5761's ruling is that the gate surfaces instances and each surfaced key gets its own card, because the three prior instances (#4644indexed, #4687distance_metric, #4676placeholder) took three different correct resolutions.

The key

referenceTo is not in FieldSchema's accept set. Measured against the installed @objectstack/spec 17.2.0:

FieldSchema.safeParse({ type:'lookup', label:'Account', referenceTo:'account' })
=> success = false
=> [{ code: 'unrecognized_keys', keys: ['referenceTo'] }]

The spec's spelling is reference:

reference — Target object name (snake_case) for lookup/master_detail fields. Required for relationship types. Used by $expand to resolve foreign key IDs into full objects.

Why this is live, not latent

Unlike sortOrder (declared but never populated), referenceTo has a live form control and two live writers:

  • packages/plugin-designer/src/FieldDesigner.tsx:311 — the field editor renders an input for it, visibleWhen: "record.type == 'lookup'".
  • packages/plugin-designer/src/FieldDesigner.tsx:221,242 — both the create and the update path write referenceTo: data.referenceTo ? String(data.referenceTo) : undefined.
  • packages/plugin-designer/src/MetadataFieldsPage.tsxfromDesignerField writes referenceTo: designed.referenceTo onto the ServerFieldSchema that is PUT.
  • packages/app-shell/src/services/MetadataService.ts:104toFieldPayload writes referenceTo: field.referenceTo onto FieldMetadataPayload, which saveFields PUTs as fields.map(toFieldPayload).

So an author who creates a lookup field in the designer and fills in the referenced object writes a key the API refuses. This is the #4644 shape exactly: PUT /api/v1/meta/object/:name returns a hard 422 INVALID_METADATA, which then blocks every subsequent save of that object until the key is cleared — and the author has no way to tell from the designer UI which key did it.

Also note the read direction is broken symmetrically: toDesignerField reads raw.referenceTo from the server payload, but a spec-parsed server sends reference, so an existing lookup field loads into the designer with an empty reference box.

Why this is not a mechanical rename in this card

reference is the obvious candidate, but choosing it is an adjudication:

Both directions (read and write), both designers, and the i18n label key appDesigner.fieldDesigner.referenceTo are in scope for whoever takes it.

Verification

pnpm check:designer-field-key-parity reports it. The key is recorded in that gate's KNOWN_UNPARSEABLE_KEYS ledger pointing here; removing it from the payload shapes without removing the ledger entry is also red, so the ledger cannot go stale.

Refs: #5761 (the gate) · #4644 · #4687 · #4676

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions