From 107b5e05181acd8a71c473f0b269f5069c757039 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 14:01:42 +0000 Subject: [PATCH 1/3] docs(spec): say only what holds before the picklist reference is resolved The picklist describe, the FieldSchema.picklist docblock, the served-shape docblock and the picklist-plus-options refusal said, in the present tense, that the server resolves the reference and serves the options. Nothing does that yet, so those sentences are deleted, or restated as what is true now. No behavior or assertion changes. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude --- packages/spec/src/data/field.zod.ts | 10 +++------- packages/spec/src/data/picklist.zod.ts | 5 +---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/spec/src/data/field.zod.ts b/packages/spec/src/data/field.zod.ts index f2003089a31..ff1f6499e26 100644 --- a/packages/spec/src/data/field.zod.ts +++ b/packages/spec/src/data/field.zod.ts @@ -1351,17 +1351,13 @@ export const FieldSchema = lazySchema(() => { * the option types (`select`, `radio`, `multiselect`, `checkboxes`, * `tags`), the types whose value is an option code. * - * The reference is resolved on the SERVER: the field a client reads from - * the object read exits carries the resolved `options` next to this key - * (`PicklistServedFieldSchema`), so renderers, the record validator - * and filter pickers read `options` exactly as they do for an inline list. + * `PicklistServedFieldSchema` declares the served form. * Option labels translate under `picklists..options.`, which * every referencing field inherits. */ picklist: SnakeCaseIdentifierSchema.optional().describe( 'Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. ' - + 'Option types only (select, radio, multiselect, checkboxes, tags). The server resolves the ' - + 'reference: the field clients read carries the resolved `options`.', + + 'Option types only (select, radio, multiselect, checkboxes, tags).', ), /** @@ -2151,7 +2147,7 @@ export const FieldSchema = lazySchema(() => { path: ['options'], message: '`picklist` and `options` cannot both be declared — a field takes its options from exactly ' + - "one source. Keep `picklist: ''` and delete `options`: the shared list supplies them " + + "one source. Keep `picklist: ''` and delete `options`: the picklist holds them " + '(to offer a new value, add it to the picklist, or through `picklistExtensions` when another ' + 'package owns it). Or delete `picklist` to keep an inline list of this field\'s own.', }); diff --git a/packages/spec/src/data/picklist.zod.ts b/packages/spec/src/data/picklist.zod.ts index 8cd2e97508a..56447b6a03a 100644 --- a/packages/spec/src/data/picklist.zod.ts +++ b/packages/spec/src/data/picklist.zod.ts @@ -144,10 +144,7 @@ export type PicklistExtensionParsed = z.infer; * A field authors `picklist: ''` instead of `options`. What a client * receives for that field carries both: `options`, RESOLVED — the picklist's * options together with the options its `picklistExtensions` add — and - * `picklist`, still naming the list they came from. Every consumer that reads - * `field.options` today (renderers, the record validator, filter pickers, - * import coercion) therefore reads the key it always has, and nothing a - * client does changes for a picklist-bound field. + * `picklist`, still naming the list they came from. * * This is a SERVED shape, never an authoring one: `FieldSchema` refuses * `picklist` together with `options`, so a served field sent back through an From c9eca3df299370339e3c1168caa927c6b3c55c70 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 14:01:42 +0000 Subject: [PATCH 2/3] chore(spec): regenerate the reference pages that copy the picklist describe Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude --- content/docs/references/data/field.mdx | 2 +- content/docs/references/data/object.mdx | 4 ++-- content/docs/references/system/migration.mdx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/content/docs/references/data/field.mdx b/content/docs/references/data/field.mdx index 9f3a3af0d9a..d104f41f55b 100644 --- a/content/docs/references/data/field.mdx +++ b/content/docs/references/data/field.mdx @@ -75,7 +75,7 @@ const result = CurrencyConfigSchema.parse(data); | **accept** | `string[]` | optional | Permitted upload types for media fields, as MIME types or extensions (e.g. ["image/*", ".pdf"]). Offered to the file picker AND enforced on write. | | **maxSize** | `integer` | optional | Maximum permitted file size in BYTES for media fields. Enforced on write against the stored file size, not just checked in the browser. | | **options** | `{ label: string; value: string; description?: string; color?: string; … }[]` | optional | Static options for select/multiselect | -| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). The server resolves the reference: the field clients read carries the resolved `options`. | +| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). | | **reference** | `string` | optional | 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. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target. | | **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. | | **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted | diff --git a/content/docs/references/data/object.mdx b/content/docs/references/data/object.mdx index d9927849adc..44be57e6d4a 100644 --- a/content/docs/references/data/object.mdx +++ b/content/docs/references/data/object.mdx @@ -238,7 +238,7 @@ const result = ApiMethod.parse(data); | **accept** | `string[]` | optional | Permitted upload types for media fields, as MIME types or extensions (e.g. ["image/*", ".pdf"]). Offered to the file picker AND enforced on write. | | **maxSize** | `integer` | optional | Maximum permitted file size in BYTES for media fields. Enforced on write against the stored file size, not just checked in the browser. | | **options** | `{ label: string; value: string; description?: string; color?: string; … }[]` | optional | Static options for select/multiselect | -| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). The server resolves the reference: the field clients read carries the resolved `options`. | +| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). | | **reference** | `string` | optional | 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. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target. | | **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. | | **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted | @@ -571,7 +571,7 @@ const result = ApiMethod.parse(data); | **accept** | `string[]` | optional | Permitted upload types for media fields, as MIME types or extensions (e.g. ["image/*", ".pdf"]). Offered to the file picker AND enforced on write. | | **maxSize** | `integer` | optional | Maximum permitted file size in BYTES for media fields. Enforced on write against the stored file size, not just checked in the browser. | | **options** | `{ label: string; value: string; description?: string; color?: string; … }[]` | optional | Static options for select/multiselect | -| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). The server resolves the reference: the field clients read carries the resolved `options`. | +| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). | | **reference** | `string` | optional | 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. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target. | | **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. | | **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted | diff --git a/content/docs/references/system/migration.mdx b/content/docs/references/system/migration.mdx index a7b7e3a90b1..bb2c7757a81 100644 --- a/content/docs/references/system/migration.mdx +++ b/content/docs/references/system/migration.mdx @@ -76,7 +76,7 @@ Add a new field to an existing object | **accept** | `string[]` | optional | Permitted upload types for media fields, as MIME types or extensions (e.g. ["image/*", ".pdf"]). Offered to the file picker AND enforced on write. | | **maxSize** | `integer` | optional | Maximum permitted file size in BYTES for media fields. Enforced on write against the stored file size, not just checked in the browser. | | **options** | `{ label: string; value: string; description?: string; color?: string; … }[]` | optional | Static options for select/multiselect | -| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). The server resolves the reference: the field clients read carries the resolved `options`. | +| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). | | **reference** | `string` | optional | 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. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target. | | **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. | | **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted | @@ -497,7 +497,7 @@ Add a new field to an existing object | **accept** | `string[]` | optional | Permitted upload types for media fields, as MIME types or extensions (e.g. ["image/*", ".pdf"]). Offered to the file picker AND enforced on write. | | **maxSize** | `integer` | optional | Maximum permitted file size in BYTES for media fields. Enforced on write against the stored file size, not just checked in the browser. | | **options** | `{ label: string; value: string; description?: string; color?: string; … }[]` | optional | Static options for select/multiselect | -| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). The server resolves the reference: the field clients read carries the resolved `options`. | +| **picklist** | `string` | optional | Name of a shared `picklist` whose options this field offers — instead of `options`, never with it. Option types only (select, radio, multiselect, checkboxes, tags). | | **reference** | `string` | optional | 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. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target. | | **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. | | **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted | From 3ccc350fab32cb4c728e664071f63b559abdd4fd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 14:34:56 +0000 Subject: [PATCH 3/3] chore(changeset): spec patch for the picklist wording Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude --- .changeset/19518-picklist-wording.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/19518-picklist-wording.md diff --git a/.changeset/19518-picklist-wording.md b/.changeset/19518-picklist-wording.md new file mode 100644 index 00000000000..00eab7a60d4 --- /dev/null +++ b/.changeset/19518-picklist-wording.md @@ -0,0 +1,7 @@ +--- +'@objectstack/spec': patch +--- + +docs(spec): the `picklist` field key's description, two doc comments and the refusal of `picklist` with `options` no longer say that the reference is resolved and its options served (#19518) + +Clause-②: no