From 450df6cb503b17c766200eb512a59935106f0337 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 08:28:54 +0000 Subject: [PATCH 1/2] fix(spec): name `reference`, not the rejected `referenceTo` alias, in the public-picker prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `FormFieldPublicPickerSchema.object` told authors the picker target resolves from `referenceTo` on the parent object's field definition. `referenceTo` is not a key `FieldSchema` accepts — it is a rejected alias listed only so `strictUnknownKeyError` can offer a rename hint, so an author who followed the sentence had their whole object metadata refused at parse. The `.describe()` half is the sharper one: it is published into the generated JSON Schema and the reference docs, so it reached authors (and AI authors reading the generated schema as ground truth) who never open this file. Purely textual. Nothing about the runtime resolution changes, and this takes no position on the REST route's legacy-spelling chain — the sentence is wrong under either outcome, because a conformant authored field carries `reference` in both worlds. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- packages/spec/src/ui/view.zod.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 59b9d67419..747f1132f9 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -1907,12 +1907,17 @@ export const FormFieldPublicPickerSchema = lazySchema(() => strictObject({ ), /** * Referenced-object override. Omitted, the route resolves the target from - * the field definition on the parent object (`referenceTo`); set it only - * when that resolution is wrong for this form. + * the field definition on the parent object (`reference`); set it only when + * that resolution is wrong for this form. + * + * `reference` is the key `FieldSchema` accepts — `referenceTo` is only a + * rejected alias it lists so a failed parse can offer a rename hint, so an + * author following the old spelling of this sentence had their whole object + * metadata refused at parse. */ object: z.string().optional().describe( - 'Referenced-object override for the picker search; omitted → resolved from the field ' - + 'definition (`referenceTo`).', + 'Referenced-object override for the picker search; omitted → resolved from the `reference` ' + + 'key on the field definition.', ), }).describe('Public-lookup opt-in: enables GET /forms/:slug/lookup/:field for this field on an anonymous public form (without it the route answers 403 LOOKUP_NOT_PUBLIC).')); From 361bd52295bd8bedd2b784b91d26d65b957d3ff4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 08:58:06 +0000 Subject: [PATCH 2/2] docs(spec): regenerate the ui/view reference for the corrected picker prose, add changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:generated` proved exactly one artifact stale after the describe change: `content/docs/references/ui/view.mdx`. Regenerated with the repo's own `pnpm --filter @objectstack/spec gen:docs`, never by hand. The false sentence rendered TWICE on that page — the picker schema is emitted both nested under `FormField` and standalone as `FormFieldPublicPicker` — which is the published half of the defect the card names. Changeset is a `@objectstack/spec` patch, matching in-repo precedent for describe-string corrections that regenerate a references page. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --- .../public-picker-reference-key-prose.md | 32 +++++++++++++++++++ content/docs/references/ui/view.mdx | 4 +-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 .changeset/public-picker-reference-key-prose.md diff --git a/.changeset/public-picker-reference-key-prose.md b/.changeset/public-picker-reference-key-prose.md new file mode 100644 index 0000000000..c5eba669ce --- /dev/null +++ b/.changeset/public-picker-reference-key-prose.md @@ -0,0 +1,32 @@ +--- +"@objectstack/spec": patch +--- + +fix(spec): `FormFieldPublicPickerSchema.object` now names `reference` — the key `FieldSchema` actually accepts — instead of the rejected `referenceTo` alias (#13138) + +Both sentences on the `object` key told authors the picker target resolves from +`referenceTo` on the parent object's field definition. `referenceTo` is not a +key `FieldSchema` accepts: it is a **rejected alias**, listed in the field +schema's alias map only so `strictUnknownKeyError` can offer a rename hint when +a parse fails on it. An author who followed the sentence and wrote `referenceTo` +on the parent object's field got their whole object metadata refused at parse — +a failure, not a degraded render. + +Measured against the built `packages/spec/dist/data/index.mjs` with a three-level +control (a two-level one cannot separate "rejected" from "not measured"): + +- `reference: 'sys_user'` (positive control) parses, `parsed.reference` is `'sys_user'` +- `zzz_not_a_key` (negative control) is refused `unrecognized_keys`, with no rename hint +- `referenceTo` is refused `unrecognized_keys`, **with** the hint ``Did you mean `referenceTo` → `reference`?`` + +The `.describe()` half is the load-bearing one: it is published, flowing into the +generated JSON Schema and `content/docs/references/ui/view.mdx`, so the wrong key +name reached authors who never open this file — and AI authors reading the +generated schema as ground truth. The reference docs are regenerated with the +repo's own `gen:docs` in the same change. + +Prose only: no schema shape, no accept/reject movement, no new or removed keys — +every previously-valid input still parses byte-identically. This takes no +position on whether the REST route's legacy-spelling fallback chain survives: +the sentence was wrong under either outcome, because a conformant authored field +carries `reference` in both worlds, and the route already reads `reference` first. diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index 4e0c8b2343..74d3e853dc 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -235,7 +235,7 @@ Form-view select option — the object-field option shape minus the per-option ` | **displayFields** | `string[]` | optional | Fields projected into each picker result (with `id`); the visitor's search matches `contains` on the first entry. At most 5 (the route projects no more); omitted → ['name']. | | **maxResults** | `integer` | optional | Maximum rows a lookup returns (default 20, hard ceiling 50 — the route clamps; anonymous visitors cannot paginate past it). | | **filter** | `{ field: string; operator?: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Static pre-filter rows ANDed ahead of the visitor's search (e.g. only active records are searchable). Same `{ field, operator, value }` dialect as list-view filters. | -| **object** | `string` | optional | Referenced-object override for the picker search; omitted → resolved from the field definition (`referenceTo`). | +| **object** | `string` | optional | Referenced-object override for the picker search; omitted → resolved from the `reference` key on the field definition. | ### Nested Shape: `FormField.keyField` @@ -262,7 +262,7 @@ Public-lookup opt-in: enables GET /forms/:slug/lookup/:field for this field on a | **displayFields** | `string[]` | optional | Fields projected into each picker result (with `id`); the visitor's search matches `contains` on the first entry. At most 5 (the route projects no more); omitted → ['name']. | | **maxResults** | `integer` | optional | Maximum rows a lookup returns (default 20, hard ceiling 50 — the route clamps; anonymous visitors cannot paginate past it). | | **filter** | `{ field: string; operator: Enum<'equals' \| 'not_equals' \| 'contains' \| 'not_contains' \| 'icontains' \| …>; value?: string \| number \| boolean \| null \| (string \| number)[] }[]` | optional | Static pre-filter rows ANDed ahead of the visitor's search (e.g. only active records are searchable). Same `{ field, operator, value }` dialect as list-view filters. | -| **object** | `string` | optional | Referenced-object override for the picker search; omitted → resolved from the field definition (`referenceTo`). | +| **object** | `string` | optional | Referenced-object override for the picker search; omitted → resolved from the `reference` key on the field definition. | ### Nested Shape: `FormFieldPublicPicker.filter[number]`