docs(spec): re-measure the lookup-picker reader claims against objectui and date them - #17004
Conversation
…ui 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c64a8d8e0f1eb09297393152e3d76dfc93d8ec1e && git checkout c64a8d8e0f1eb09297393152e3d76dfc93d8ec1e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8d4690b8f55d21da6b8530081ab76aa10ac524a7 af158289eee46ea7401d69b3fe1872f730b473c0 && git checkout -B drift-repro 8d4690b8f55d21da6b8530081ab76aa10ac524a7 && git merge --no-ff af158289eee46ea7401d69b3fe1872f730b473c0
node scripts/docs-audit/affected-docs.mjs --json 8d4690b8f55d21da6b8530081ab76aa10ac524a7
|
Fixes #15540
Three written-down claims in
packages/specsaid objectui reads asnake_casespelling it no longer reads, and oneevidencestring described a landed call in the future tense. All four are re-measured and rewritten to the measurement. No verdict, schema arm, accept set, default, bound or refusal moves — this is evidence prose and one docblock's factual claim about a foreign consumer.Base
fe2b7554a· headaf158289e· objectui measured ated971e8fc9e698f6fbe07355cfdbaef910037cb7.Clause-②: no
Every item is prose: two
notestrings and oneevidencestring in an evidenceledger, and one docblock above a declaration. No schema arm, bound, default or
refusal moves; no export is added, removed or renamed; no accept set widens or
narrows. 拉回已声明契约(把写下来的断言拉回可测量的事实)⇒ 常规档.
The objectui measurement — re-derived, not inherited
The dispatch carried a week-old six-key table from the
domain:uiseat with an explicit caveat that it may have moved. It had:dependsOnhas since lost its snake leg too, andallowCreate(unmeasured then) has one.Measured on objectui
ed971e8(main, 2026-09-08 23:30 UTC), over the three readers the docblock names —packages/fields/src/widgets/{LookupField.tsx,RecordPickerDialog.tsx,deriveLookupColumns.ts}:displayFieldLookupField.tsx:260descriptionFieldLookupField.tsx:261lookupColumnsLookupField.tsx:276lookupPageSizeLookupField.tsx:277lookupFiltersLookupField.tsx:278dependsOnLookupField.tsx:309allowCreateLookupField.tsx:271The three surviving snake legs, verbatim:
The four camel-only reads, verbatim:
dependsOn's retirement is stated in objectui's own source atLookupField.tsx:296-302, which is why the dispatch's table is now one row out of date:Firing controls on every zero
A grep returning 0 is not a reading until an adjacent term known present returns non-zero. Same command shape, same file set:
Scope note on the measurement radius
The docblock's claim is scoped to the three readers it names, and so is the table. Outside
packages/fields, other objectui consumers do still normalise snake spellings (app-shell/src/utils/resolveActionParams.ts,plugin-list/src/ListView.tsx,plugin-charts,plugin-form). The rewritten docblock therefore says what those three readers do, not what the whole objectui tree does — a blanket "objectui is camelCase-only" would have been a new false claim.Item A — the two
displayFieldnotes (packages/spec/liveness/field.json)Criterion: no
display_fieldtoken survives in either note, and each note names the reader it actually has.A1 —
/props/displayField. Before:After:
A2 —
/props/inlineColumns/children/displayField. Before:After:
A2's claim is checkable at
GridField.tsx:774,:870and:898, all three passingdisplayField: c.displayField, with the column type declaring it camelCase at:117.Criterion met, machine-checked:
Both entries keep
status: "live". Nothing is re-classified.Item B — the LOOKUP PICKER docblock (
packages/spec/src/data/field.zod.ts)Criterion: the per-key tolerance statement matches the fresh measurement key-for-key, including the keys where the snake leg IS still read.
Before (the tail of the docblock):
After — the blanket clause is dropped and replaced with a per-key paragraph:
(The rendered file spells that coalescing operator literally; it is transliterated here only so this body survives the comment sanitizer.)
The three keys on the exclusion list —
lookupColumns,lookupPageSize,dependsOn— are handled exactly as instructed: the two whose tolerance claim is true are named as still carrying a live snake leg and are not rewritten away;dependsOnis moved to the camel-only side because the measurement moved it, and the PR body states that as a change against the dispatch's own table rather than folding it in silently.Item C — the
valueDomainevidencetenseCriterion: the sentence's tense matches the landed state its own first clause describes.
Before:
…#isValueDomainMember (the ONE membership predicate the write path **will** call — shared with the settings door)After:
…#isValueDomainMember (the ONE membership predicate the write path **calls** — shared with the settings door)The same string's first clause already quotes the landed call site, and it is landed on this base:
Residual
will callin the ledger: 0. The evidence pointers themselves are unchanged, andcheck:livenessis green. ThevalueDomainnotewas not touched — that was corrected separately and is off this PR's scope.Changeset — both halves measured
A real
patchchangeset is present:.changeset/lookup-picker-reader-prose-remeasured.md.Half 1 — is the path in
@objectstack/spec'sfiles[]? Yes, via two distinct entries:Half 2 — is the changed text in what the package actually publishes? Measured with
npm pack --dry-run --jsonon this tree (@objectstack/spec@17.3.0, entryCount 2073):liveness/field.jsonsrc/data/field.zod.tsliveness/object.jsonsrc/data/object.zod.tssrc/index.tsscripts/liveness/check-liveness.mtsThe negative controls discriminate rather than merely being absent:
src/data/object.zod.tsships whilesrc/index.ts, its sibling in the same directory tree, does not — so thesrc/**/*.zod.tsglob is genuinely selective, not "everything undersrc". Bucket counts from the same tarball:liveness/38 entries,src/**/*.zod.ts206,src/non-.zod.ts1 (src/migrations/entries/README.md),scripts/0.And the changed text, not just the path, is in the published bytes:
The docblock additionally reaches
dist/ui/index.js,dist/ui/index.mjs,dist/browser/**as a preserved comment, so it publishes twice over. Firing control: adescribe()string from the same block (Rows per page in the record-picker dialog) resolves to the samedist/ui/index.js.⇒ Both halves say yes.
skip-changesetwould have been wrong.Governance and gates
Governed-surface verdict, run on the exact final file list:
Gate families derived from the real change set, not a hand-written list:
All 71 were run. 68 green on the first pass; 3 returned exit 3 (
PREREQUISITE NOT MET— explicitly NOT MEASURED, neither pass nor failure). Two of those were cleared by a targeted build and are now green:@objectstack/lint check:doc-formula-expressionsturbo build --filter=@objectstack/formula --filter=@objectstack/lintcheck:lean-entry-closureturbo build --filter=@objectstack/objectql(2 published conditions measured from a real load)check:dual-build-cjs-loadspnpm build, which does not fit this session's foreground ceiling. CI'sBuild Coresupplies it. This diff is a JSON data file plus a TSDoc comment and moves no package'sexports, entry points or dual-build emission shape.Named gates of interest, all exit 0:
check:liveness,check:docs,check:authorable-surface,check:api-surface,check:objectui-pin-citations,check:nul-bytes,check:published-files,check:closing-keyword-parity,check:empty-changeset,check:changeset-no-major,check:objectui-changeset,check:merge-driver,check:comment-mask-adoption.check:generated— regenerated nothing, as expected:Tests and typecheck, at head
af158289e. The split projects are addressed by their real spellings;test:repois implicated because four of its files read the liveness/field surface (scripts/liveness/evidence.test.ts,scripts/liveness/proof-registry.test.ts,scripts/file-description.test.ts,scripts/gen-sdui-manifest-collision.test.ts):Lint — the full repo scan, not a narrowing. It fit inside the foreground budget, so no scoping argument is needed:
Control characters.
pnpm check:nul-bytesexit 0, plus a self-scan of the two changed files withgrep -naPover the non-tab control class: 0 hits, with a firing control (a planted U+000B matched at exit 0, so the regex shape fires).验收备注
Two findings adjacent to this work, reported rather than folded in. Neither is filed as a card by this PR — the scoping call is the dispatching seat's.
packages/spec/liveness/field.jsoncarries two MORE notes of exactly this class, both measured false on objectuied971e8. They were not in this dispatch's enumerated scope and are not touched here./props/descriptionField— "…(reads descriptionField || description_field)." Measured:LookupField.tsx:261readsfieldMeta?.descriptionFieldonly; the snake leg went with the same objectui#7155 sweep. Zero snake reads in the reader radius, firing control as above./props/inlineColumns/children/idField— "objectui GridField.tsx lookup cells — LookupField id_field." Measured:GridField.tsxpassesidField: c.idFieldat all three call sites and the column type declaresidField?: string;LookupField.tsx:262readsfieldMeta?.idField || 'id'. objectui#7155 removedid_fieldfromLookupFieldMetadataalongside the other three.Why it matters more than usual: they now sit two and four entries away from freshly-dated notes in the same file, so the corrected notes lend credibility to the stale ones. Successor: whoever next holds this file's serial slot — this PR is that holder for today, and a follow-up on the same file is the cheapest place to take them.
dependsOn's snake leg is gone, which retires the dispatch's own table row. Not a defect in this repo; recorded because the dispatch asked for the measurement to be re-derived and it moved. The docblock now states it, dated.Noted, not filed, deliberately: the docblock is prose in the spec about what a consumer reads, which the
domain:uiseat flagged as a larger question than this card. Unchanged here; no successor named.Not done, on purpose
status:verdict moved. Every entry touched stayslive.verifiedAt/evidenceScopefield was added. Those are recognised ledger fields, but adding one would have made this diff more than prose and falsified the Clause-② ruling reproduced above. The dating lives inside the note text instead, which is what the seat's option-1-plus-dating actually asks for.objectuiwas read only. A detached read-only worktree atorigin/main; no edit, no branch, no PR there.🤖 Generated with Claude Code
Generated by Claude Code
Generated by Claude Code