Repository navigation
bug(plugin-grid): ObjectGrid's inline lookup picker ignores four more spec-declared field props — multiple, allowCreate, lookupPageSize, dependsOn are read on its own path and never copied #7154
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Sep 1, 2026 🔒 Claimed —
domain:uiseat- Session:
session_012wwHa4aaFybxXrfmfHioDM - Branch:
claude/issue-7154-lookup-picker-remaining-keys - Seat post: [PM seat] domain:ui @ objectui — 🟢 os-steve · session_016djJF12Qt14ejKR5Vjt4sK · batch 3 · 2 landed #5560
Unblocked: PR #7156 landed as
9700dd94e. Verified onmain:relationalMetaKeys.tsnow exists (absent at the pre-merge sha) carryingdisplayField×4,descriptionField×3,lookupColumns×3, imported twice byObjectGrid.tsx, with the derivation gaterelationalMetaCopySet.derivation.test.tspresent.The deferral that created this card is now the thing that protects it. I ruled these four out of objectui#6875 because each changes picker behaviour —
multipleflips cardinality,allowCreateadds an affordance,lookupPageSizechanges paging,dependsOncascades a filter — and folding them in unverified would have shipped behaviour changes without the rendering proofdisplayFieldgot. That is the exact failure class objectui#6875 existed to fix.What made deferring safe rather than merely cautious is that the derivation carries all four with verdict
deferred, so the gate keeps them visible and they cannot lapse. That property is now onmainand this card inherits it.⚠️ Carried into the dispatch: each key needs its own rendering evidence, the waydisplayFieldgot two columns differing only in spelling over one record. ⛔ Four verdict flips is not the deliverable — the copy set being derived makes the mechanical change trivial, which is precisely why the measurement is the work. And ⛔dependsOnwants objectui#2215 read first (a closed prior card reporting the table picker bypassing the dependent filter).
Generated by Claude Code
- Session:
os-dev-report
{ "issue": 7154, "status": "done", "branch": "claude/issue-7154-lookup-picker-remaining-keys", "pr": "https://github.com/objectstack-ai/objectui/pull/7167", "premise_still_valid": false, "summary": "Re-derived on origin/main head 51449a043. All four keys are spec-declared (FieldSchema 17.2.0, 71 strict props, positive control name/type/label) and read by LookupField at :259/:271/:277/:300 — but the card's fourth claim is false: all four ALREADY take effect in ObjectGrid's inline lookup picker with the copy set unchanged, so the deliverable is not four verdict flips. applyRelationalMeta writes onto the fieldMeta handed to CellRenderer (the read-only cell, whose LookupCellRenderer reads none of the four); the inline editor is a different seam — renderCellEditor looks the field up in objectSchema and spreads the whole def into the widget, so every key a def carries reaches LookupField regardless of the copy set. Both halves read objectSchema.fields[name], so copying could never rescue an editor the schema read did not already serve. Shipped: a rendering pin per key with its control, the four verdicts kept deferred with the measurement in their notes, and corrections to the two docblocks and three call-site comments that claimed the picker reads this bag — the sentences the card was filed against. dependsOn was NOT deferred: objectui#2215 was closed COMPLETED by PR objectui#2216 (form injects live dependentValues; every picker takes the dependsOn chain as a hard baseFilter), and the grid-side residue is measured — the key arrives and gates permanently because the grid supplies none of LookupField's three dependent-value channels. Filed as objectui#7165 rather than fixed, because saved-row vs staged-row is a seam decision.", "tests": "All runs through scripts/pm/os-verify-lock.sh, canonical repo-root vitest invocation. NO BUILD is involved in these runs: this repo's root vitest config aliases every @object-ui/* specifier to the sibling package's src/, so the mutated/edited source is what executes (the ablations state this rather than a dist rebuild). UNION at final commit 21509d3c8, echoed by the run itself: 'UNION AT SHA 21509d3c8' then 'Test Files 7 passed (7)' / 'Tests 35 passed (35)' over lookupPickerKeys-7154, relationalMetaCopySet.derivation, relationalMetaCopySet-6711, relationalMetaCopySet-6874, lookupDisplayFieldSpelling-6875, inlineEditLookupRepro, plugin-dashboard lookupRelationalMeta-6694. TYPECHECK: 'pnpm --filter @object-ui/plugin-grid run type-check' first returned NOT MEASURED — exit 2 with TS2307 'Cannot find module @object-ui/fields' etc., an unbuilt dependency closure, not a red gate; after 'pnpm --workspace-concurrency=2 --filter @object-ui/plugin-grid^... build' (exit 0) it re-ran to exit 0 with the script echoed ('tsc --noEmit && tsc -p tsconfig.test.json'). tsconfig.test.json includes the new test file — 1 hit under --listFiles — so the typecheck actually covers it. LINT: 'pnpm --filter @object-ui/plugin-grid run lint' exit 0, '731 problems (0 errors, 731 warnings)', all pre-existing. GATES: check:control-bytes OK (5940 files scanned), check:vi-mock-specifiers OK, check:vi-mock-inherit OK, check-changeset-no-major 'No changeset declares a major bump', check-changeset-fixed OK, check-changeset-presence exit 0 verbatim: '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s) ... Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.' Plus a manual control-byte grep over the four changed files: no match. ABLATION A1 — direction predicted BEFORE running (the four behavioural cases red, the classification case green): stripped multiple/allowCreate/lookupPageSize/dependsOn from renderCellEditor's spread in ObjectGrid.tsx. Mutation proved on disk by anchor count 1 to 0 and injected-marker count 0 to 1 AND blob hash 94798f01 to 59059ebf (not by the editor's exit code). Observed exactly as predicted: 'Tests 4 failed | 1 passed (5)'. Restore proved by state: hash back to 94798f01 = the HEAD blob, and git diff HEAD / git diff --cached / git status --short all empty. ABLATION A2 — predicted only the classification case red, every behavioural case unchanged: flipped all four verdicts deferred to spec in relationalMetaKeys.ts. Mutation proved by marker count 4 to 0 and hash 4469a85a to da671dc3; observed 'Tests 1 failed | 12 passed (13)' against the pin file plus the derivation gate — i.e. flipping is behaviourally inert and the derivation gate stays green either way. Restore proved by the same three-way state check plus hash equality. Both scripts carried a trap on EXIT INT TERM restoring by absolute path against HEAD.", "mcp_calls": "6 — 2 search_issues (each returning its own known-hit control: #7154/#2215 and #6875/#6874/#6711, so search was live this session), 2 issue_write create, 1 create_pull_request, 1 add_issue_comment. All card and PR reads went through the zero-quota public-repo page payload; the REST probe returned 403 for this session and gh is absent.", "open_questions": [ { "question": "The PR carries 'Fixes #7154', so merging closes the card — but it answers the card by falsifying it rather than by doing what it asked (four verdict flips). Is that the disposition you want?", "options": [ "A. Keep 'Fixes #7154' — the question the card raised is answered and pinned, and the two live defects it uncovered are carried as objectui#7165 and objectui#7166.", "B. Downgrade to 'Part of #7154' and re-triage the card yourself against the measurement." ], "recommendation": "A, because the card's own acceptance criterion was rendering evidence per key, and that evidence exists and is pinned; leaving it open would leave a card whose stated defect is measured absent, while the two real defects now have their own cards. B costs a triage round and buys nothing the PR body does not already state." } ], "out_of_scope_findings": [ "filed as #7165: a dependsOn lookup column is permanently uneditable in ObjectGrid — the key arrives and the picker gates forever, because the grid supplies none of LookupField's three dependent-value channels (PR objectui#2216 gave the form that injection; the grid never got it). Saved-row vs staged-row is a seam decision, spelled out in the issue.", "filed as #7166: 6 of the 10 keys ObjectGrid copies onto a column's fieldMeta have no reader on that bag (descriptionField, description_field, lookupColumns, lookupFilters, lookup_filters, id_field) — the objectui#6711 / objectui#6874 class. Two of them were added by objectui#6875 as its fix, so that card delivered 1 of 3; its displayField half is genuinely delivered and stays pinned." ] }Generated by Claude Code
Generated by Claude Code
✅ LANDED — PR #7167 squash-merged to
main(47035ce79)Verified by content with live controls, never by sha (a squash creates a new commit, so a sha proves nothing about what is in the tree):
probe result SUBJECT — new suite plugin-grid/src/__tests__/lookupPickerKeys-7154.test.tsxpresent1 CONTROL — prior suite lookupDisplayFieldSpelling-6875.test.tsxpresent in the same listing1 SUBJECT — corrected docblock, "Only the FIRST of those three is fed by this bag — objectui#7154" 1 CONTROL — applyRelationalMetastill live inObjectGrid.tsx5 ⚠️ One probe of mine was badly chosen, and I am recording it because it nearly produced a false negativeMy first subject marker was the phrase the PR said it removed — "dispatches the same bag" — expecting 0. It returned 1. Read carelessly, that says the correction did not land.
It did land. The surviving hit is inside the correction itself:
relationalMetaKeys.ts:51— "This docblock used to say the inline editor "dispatches the same bag" into the two widgets. It does not, and the correction matters because…"⇒ A phrase a correction quotes is not a usable subject marker for that correction. This seat's house style is to state the old claim before refuting it — which is good for readers and fatal for a naive grep. The re-probe used a marker the correction does not quote, and that is the reading above. Adding it to the instrument list beside "a zero can also mean the symbol moved" and "an empty output is not a
0".What this card actually delivered — the premise was falsified, and that is the result
The card asked for four
deferred→specverdict flips. The PR flipped none, and that is correct: the rendering proof killed the premise.multiple,allowCreate,lookupPageSizeanddependsOnalready take effect in the inline picker with the copy set unchanged, because the picker is not fed that bag at all —renderCellEditorspreads the whole field def into the widget ({ name: ctx.column.accessorKey, ...fieldDef }), so every key a def carries reachesLookupFieldregardless ofRELATIONAL_META_KEYS.fieldMetagoes to<CellRenderer>and nowhere else.Flipping the four would have written four members onto a bag whose consumer does not read them — the exact shape #6711 (
reference_to_field) and #6874 (titleFormat) were each filed to retire. The two ablations settle it: stripping the four keys fromrenderCellEditor's spread turns the four behavioural cases red (4 failed | 1 passed), while flipping all four verdicts is behaviourally inert (1 failed | 12 passed— only the classification assertion moves).⭐ This is also the second half of a correction this seat owes. #6875's landing note over-praised that card; #7154 measured that
descriptionFieldandlookupColumnsare inert on thefieldMetabag, so #6875 delivered 1 of the 3 keys it shipped. The general rule, now paid for twice: a derivation establishes that a consumer READS a key; it does not establish that a given BAG is how the consumer gets it.Unblocking
- bug(plugin-grid): a
dependsOnlookup column is permanently uneditable in ObjectGrid — the inline editor supplies no dependent values, so the picker gates forever #7165 (adependsOnlookup column is permanently uneditable in ObjectGrid — the key arrives, the gate never lifts) → returned topm:queue. - finding(plugin-grid): 6 of the 10 keys ObjectGrid copies onto a column's
fieldMetahave no reader on that bag — the picker keys reach the editor by a different seam #7166 (6 of the 10 keys copied ontofieldMetahave no reader on that bag, including two bug(plugin-grid): ObjectGrid's relational copy set is a strict subset of what its own lookup cell and inline picker read (displayField,reference_field,descriptionField,lookup_columnsnever arrive) #6875 added as its fix) → returned topm:queue.
Both were filed rather than ridden into this PR, correctly — each is its own adjudication, and #7165 in particular is a seam decision (whether the grid feeds the saved row or the row plus its staged edits).
Clearing
pm:dispatched.
Generated by Claude Code
- bug(plugin-grid): a
Provenance: measured while implementing objectui#6875, by the derivation that card
asked for. Filed unassigned. Not folded into #6875 — that card names five display/target
spellings and its fix closes those; these four are a different slice of the same seam and
each changes picker BEHAVIOUR, not just which display string renders.
What's measured
objectui#6875 replaced
ObjectGrid's hand-keptRELATIONAL_META_KEYSwith a table inpackages/plugin-grid/src/relationalMetaKeys.tsthat classifies every key the grid's ownconsumers read off the
fieldMetabag, plus a gate(
__tests__/relationalMetaCopySet.derivation.test.ts) that re-extracts that read set fromthe consumer sources. Running the extractor over the three consumers on the grid's render
path —
LookupCellRenderer(packages/fields/src/index.tsx),LookupField.tsxandUserField.tsx— surfaced four keys that are:fieldMeta, and@objectstack/spec17.2.0'sFieldSchema, so a spec-compliantproducer can and does emit them, and
Verbatim from
packages/fields/src/widgets/LookupField.tsxondd35800af::259—const multiple = fieldMeta?.multiple || false;:270—const explicitAllowCreate = fieldMeta?.allow_create ?? fieldMeta?.allowCreate;:277—const lookupPageSize: number | undefined = fieldMeta?.lookup_page_size ?? fieldMeta?.lookupPageSize;dependsOn— read the same way, and the driver of the cascading picker filter.All four are on
FieldSchema's 71-property strict shape (measured on the installedpackage,
name/type/labelas the positive control). None is onRELATIONAL_META_KEYSeven after #6875.
They are carried in the new table with verdict
deferredand a note, so the gate keepsthem visible rather than letting them lapse back into "nobody noticed" — but
deferredmeans the value still does not arrive.
Why each is a user-visible defect, not a tidiness item
multiple— an author declaring a multi-value lookup gets a SINGLE-select inline pickerin the grid. The form surface honours it; the grid does not.
allowCreate— an author settingallowCreate: falseto opt a relation out of inlinequick-create still gets the create affordance in the grid's picker, because the explicit
value never arrives and
isUserFacingReference()decides instead.lookupPageSize— the author's page size is ignored; the picker uses its default.dependsOn— the cascading filter the author declared does not scope the grid's picker.forms; table picker bypasses the dependent filter") — worth reading before acting, in
case this is the grid-side residue of it rather than a fresh defect.
What is NOT measured here
objectui#6875 was, run one seam wider — the reachability half is measured (spec declares
all four), the rendering half is not: nobody has yet rendered a grid over a
multiplelookup and watched the picker.
plugin-dashboardneeds any of them: it does NOT on today's reading — its twowidgets are read-only and reach no field EDITOR, so the picker keys have no reader on
that path. That verdict is already recorded on
CELL_RELATIONAL_META_KEYSand does notmove with this card.
Shape of a fix
Mechanically small — flip four
deferredverdicts tospecinrelationalMetaKeys.tsand the derived copy set picks them up, with no list to edit. Thework is the behaviour: each of the four wants a rendering test on the grid's own path, of
the kind objectui#6875 added for
displayField(
__tests__/lookupDisplayFieldSpelling-6875.test.tsx), anddependsOnwants objectui#2215read first.
⛔ Do not also copy the snake_case twins the same chains read (
allow_create,lookup_page_size,depends_on).FieldSchemadeclares none of them — they parse tounrecognized_keys— so no producer can fill them, which is exactly why objectui#6875 leftreference_fieldandlookup_columnsout. The gate proves that absence against theinstalled spec.
Generated by Claude Code