From 0361c3440247269b9b178b192ae33e83ac2c8983 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 01:06:43 +0000 Subject: [PATCH 1/2] docs(spec): re-measure the lookup-picker reader claims against objectui and date them packages/spec/liveness/field.json carried two displayField notes crediting objectui with reading a snake_case spelling that upstream deleted from LookupFieldMetadata, and field.zod.ts's LOOKUP PICKER docblock told authors that all seven camelCase keys below it are read with their snake_case aliases too. Both claims are re-derived against objectui ed971e8 and rewritten to the measurement: three of the seven keys keep a live snake leg (lookupColumns, lookupPageSize, allowCreate), four are camelCase-only (displayField, descriptionField, lookupFilters, dependsOn). Also corrects the valueDomain evidence string's tense: it described the write-path membership call in the future while its own first clause already quotes the landed call site. Each rewritten claim names the objectui commit it is dated to, so a later reader can see how old the evidence is. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --- packages/spec/liveness/field.json | 6 +++--- packages/spec/src/data/field.zod.ts | 21 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index 65dbcab6fc..a9642c5687 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -224,7 +224,7 @@ "valueDomain": { "status": "live", "verifiedAt": "2026-09-04", - "evidence": "packages/objectql/src/validation/record-validator.ts#validateOne (the write-path seam, beside `maxLength`'s: `if (def.valueDomain !== undefined && VALUE_DOMAIN_FIELD_TYPES.has(t) && !isValueDomainMember(def.valueDomain, s)) return fail('value_domain', { valueDomain: def.valueDomain }, ...)` \u2014 a non-member WRITTEN to a `text` field declaring a domain is refused with the ADR-0114 code `value_domain` and `constraint.valueDomain`); packages/spec/src/data/field.zod.ts#VALUE_DOMAIN_FIELD_TYPES (the parse-time applicability door: the key is accepted on `text` only and refused with a located `custom` issue at [valueDomain] on every other type — the same superRefine mechanism `maxLength` / `minLength` use); packages/spec/src/shared/value-domain.zod.ts#isValueDomainMember (the ONE membership predicate the write path will call — shared with the settings door)", + "evidence": "packages/objectql/src/validation/record-validator.ts#validateOne (the write-path seam, beside `maxLength`'s: `if (def.valueDomain !== undefined && VALUE_DOMAIN_FIELD_TYPES.has(t) && !isValueDomainMember(def.valueDomain, s)) return fail('value_domain', { valueDomain: def.valueDomain }, ...)` \u2014 a non-member WRITTEN to a `text` field declaring a domain is refused with the ADR-0114 code `value_domain` and `constraint.valueDomain`); packages/spec/src/data/field.zod.ts#VALUE_DOMAIN_FIELD_TYPES (the parse-time applicability door: the key is accepted on `text` only and refused with a located `custom` issue at [valueDomain] on every other type — the same superRefine mechanism `maxLength` / `minLength` use); packages/spec/src/shared/value-domain.zod.ts#isValueDomainMember (the ONE membership predicate the write path calls — shared with the settings door)", "note": "The write path enforces it since 2026-09-04 (#15161, the engine half of the maintainer ruling 2026-09-02 option A on #14168; the spec half declared the slot, the closed vocabulary, the shared predicate, the ADR-0114 catalog member and its four-locale templates). WRITTEN VALUE ONLY, the `min`/`max`/`maxLength` transition-gate class: a stored value outside a domain declared later is never re-read and survives unrelated edits, and an absent/empty value is the field's `required` handling, not this check \u2014 both pinned in packages/objectql/src/validation/record-validator.value-domain.test.ts, together with the per-domain matrix (iso_3166_alpha2 admits CH and refuses ZZ; iana_time_zone admits UTC and refuses Mars/Olympus; iso_4217_currency admits CHF and refuses chf). The applicability door is one constant read by both seams \u2014 the schema refuses the key outside VALUE_DOMAIN_FIELD_TYPES at parse and the validator judges exactly that set, so the two cannot drift into two opinions (the #11875 discipline; the subset relation to BOUNDED_STRING_FIELD_TYPES, which the enforcement branch rides on, is pinned in the same file). The settings door (`service-settings/value-domains.ts`) has re-pointed onto that same predicate (#15434, the services half of the same ruling): its second copy of all three definitions is deleted and `firstRejectedDomainMember` asks `isValueDomainMember`, so a value Settings admits is the value a field admits and vice versa; what is left on that side is the door's own business (which declarations it agrees to enforce, how a multi-value carrier is walked, the fragments the env-override log line needs), and a re-added local table reddens packages/services/service-settings/src/value-domains.shared-predicate.pin.test.ts." }, "rows": { @@ -236,7 +236,7 @@ }, "displayField": { "status": "live", - "note": "objectui LookupField/RecordPickerDialog — candidate label field in the record picker (reads displayField || display_field)." + "note": "objectui LookupField/RecordPickerDialog — candidate label field in the record picker. Read camelCase-only: packages/fields/src/widgets/LookupField.tsx does `fieldMeta?.displayField || fieldMeta?.reference_field || 'name'` and hands the result to RecordPickerDialog as its `displayField` prop. The snake_case twin this note used to name was deleted from LookupFieldMetadata by objectui#7155 (maintainer ruling A′ of 2026-09-03 — no deprecation window, no dual read), so the camelCase spelling this schema declares is the only authored one the picker reaches for; `reference_field` is objectui's own third leg of that chain, not a spec key (objectui#6875 grades it `no-producer`). Claim dated to objectui ed971e8, re-measured 2026-09-09 — a reading of a pinned foreign tree that nothing in this repo re-checks." }, "descriptionField": { "status": "live", @@ -333,7 +333,7 @@ }, "displayField": { "status": "live", - "note": "objectui GridField.tsx lookup cells — LookupField display_field." + "note": "objectui GridField.tsx lookup cells — the column's camelCase `displayField` is handed straight to LookupField as `displayField` at all three lookup-cell call sites (packages/fields/src/widgets/GridField.tsx: the read-only table cell, the list form-factor cell and the editable cell; the column type declares `displayField?: string`). The snake_case twin this note used to name was deleted from LookupFieldMetadata by objectui#7155. Claim dated to objectui ed971e8, re-measured 2026-09-09 — a reading of a pinned foreign tree that nothing in this repo re-checks." }, "idField": { "status": "live", diff --git a/packages/spec/src/data/field.zod.ts b/packages/spec/src/data/field.zod.ts index 818caeaea8..a05e28e318 100644 --- a/packages/spec/src/data/field.zod.ts +++ b/packages/spec/src/data/field.zod.ts @@ -1347,8 +1347,25 @@ export const FieldSchema = lazySchema(() => { * list of children); these configure the CHILD-side picker that chooses the * parent. All optional: the renderer auto-derives a sensible multi-column * result from the referenced object's schema when omitted (objectui - * packages/fields: LookupField / RecordPickerDialog / deriveLookupColumns, - * which read both these camelCase keys and their snake_case aliases). + * packages/fields: LookupField / RecordPickerDialog / deriveLookupColumns). + * + * SNAKE_CASE TOLERANCE — measured on the consumer, not assumed. Those + * readers accept a snake_case alias for three of the seven keys declared + * below, and for three only: `lookupColumns`, `lookupPageSize` and + * `allowCreate` are each read as ` ?? ` in LookupField.tsx. + * The other four — `displayField`, `descriptionField`, `lookupFilters` + * and `dependsOn` — are read camelCase-only, so a snake_case spelling of + * any of those four arrives at the picker as nothing at all. Three of them + * lost their twin in objectui#7155's LookupFieldMetadata alias sweep (no + * deprecation window, no dual read); `dependsOn` lost its own later, + * retired under ADR-0049 enforce-or-remove in objectui#7357. The three + * aliases that survive are objectui's own back-compat, never a spelling + * this schema declares — the camelCase key is the authored one in every + * case. + * + * Claim dated to objectui ed971e8, re-measured 2026-09-09. It is a reading + * of a pinned foreign tree and no gate here re-checks it: when the console + * pin moves, this paragraph is what goes stale. */ displayField: z.string().optional().describe("Field shown as each candidate's label in the picker/popover (defaults to the referenced object's name/title)."), descriptionField: z.string().optional().describe('Secondary field shown under the label in the quick-select popover.'), From af158289eee46ea7401d69b3fe1872f730b473c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 01:46:58 +0000 Subject: [PATCH 2/2] chore(changeset): patch for the re-measured lookup-picker reader prose Both changed paths are inside @objectstack/spec's published files[] and both were measured present in the real tarball (npm pack --dry-run), so the corrected text ships. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --- .changeset/lookup-picker-reader-prose-remeasured.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/lookup-picker-reader-prose-remeasured.md diff --git a/.changeset/lookup-picker-reader-prose-remeasured.md b/.changeset/lookup-picker-reader-prose-remeasured.md new file mode 100644 index 0000000000..f8f6252451 --- /dev/null +++ b/.changeset/lookup-picker-reader-prose-remeasured.md @@ -0,0 +1,13 @@ +--- +"@objectstack/spec": patch +--- + +The lookup-picker "who reads this" claims in `packages/spec` are re-measured against objectui and dated to the commit they were measured on. No schema, accept set, default or refusal moves — this is evidence prose, and every verdict it sits under is unchanged. + +Three claims had gone false, all in the same direction: they credited objectui's picker with reading a `snake_case` alias that objectui no longer reads. A stale *tolerance* claim fails in the dangerous direction — it tells an author a spelling is accepted downstream when it is not, so a value that will silently arrive as nothing looks supported by the spec's own prose. + +- **`liveness/field.json`, both `displayField` notes.** `/props/displayField` claimed the record picker "reads displayField || display_field"; `/props/inlineColumns/children/displayField` named the `snake_case` spelling flatly as *the* key the grid's lookup cells pass. objectui deleted that twin from `LookupFieldMetadata` with no deprecation window and no dual read. Both notes now name the read chain they actually have — `LookupField.tsx`'s `fieldMeta?.displayField || fieldMeta?.reference_field || 'name'`, and `GridField.tsx` handing the column's camelCase `displayField` straight through at all three lookup-cell call sites. Both entries stay `status: "live"`: `displayField` is live, and more exclusively so than the notes claimed. +- **`src/data/field.zod.ts`, the LOOKUP PICKER (forward) docblock.** It told authors that objectui's `LookupField` / `RecordPickerDialog` / `deriveLookupColumns` read "both these camelCase keys and their snake_case aliases" — a blanket claim over all seven keys declared beneath it. Measured, it holds for three: `lookupColumns`, `lookupPageSize` and `allowCreate` are each read as ` ?? `. The other four — `displayField`, `descriptionField`, `lookupFilters` and `dependsOn` — are read camelCase-only. The docblock now states that per key, keeps saying the truth for the three aliases that survive, and records that those three are objectui's own back-compat rather than a spelling this schema declares. +- **`liveness/field.json`, the `valueDomain` `evidence` string.** It described the shared membership predicate as one "the write path **will** call" while its own first clause already quotes the landed call site that calls it. Tense corrected; the pointer is unchanged. + +Each rewritten claim now names the objectui commit it is dated to, so a later reader can tell how old the evidence is instead of assuming it is current. That dating is prose by design: a gate over a pinned foreign tree would go stale at every pin bump and need its own anti-vacuity self-test, which is a worse trade than a dated sentence.