Repository navigation
fix(fields)!: the record picker's display column labels a record the way the lookup dropdown does (objectui#10486) - #10590
Conversation
…way the lookup dropdown does
The browse-all picker's display column rendered a bare titleFormat string
through its own single-brace renderer, above the referenced object's nameField
and above the lookup's declared displayField, and never read nameField when
no template was declared. The dropdown option label already goes through
getRecordDisplayName (the lookup branch of ruling C1 on objectui#9436), so
one record read two ways.
RecordPickerDialog's titleFormat prop is replaced by objectSchema, and
LookupField passes the referenced object's schema and its declared
displayField. The display column now renders
getRecordDisplayName(objectSchema, row, { titleField }) on the row the
dropdown labels from, with the dropdown's floor exclusion. The private
template renderer and its EMPTY_SLOT sentinel are gone.
The two pins that passed titleFormat to the dialog move to objectSchema
with unchanged assertions; new pins read both surfaces off one mounted
LookupField.
Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-authored-by: Claude <noreply@anthropic.com>
…cker column before objectui#10486 Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…ribe the picker column as objectui#10486 leaves it
Prose only; both front matters are unchanged. The objectui#10343 upgrade
paragraph's last sentence said the browse-all picker's display column still
showed the template; it now says objectui#10486 moved that column onto the
dropdown's resolver. The objectui#6874 sentence naming the single
titleFormat= pass now names the objectSchema={refObjectSchema} pass, and
keeps its conclusion. This PR's own changeset stops calling them stale.
Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Reviewed against merge-base ① Derived judgmentsBehaviour changes the diff introduces
Asked and missing / done and not asked
Existing pins the diff edits (no pin is deleted)
New pins
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #10486
Clause-②: yes
The browse-all record picker's display column now labels a record exactly as the lookup dropdown's option label and the read cell do: through
@object-ui/core'sgetRecordDisplayName, with the same inputs and the same row.RecordPickerDialog's publishedtitleFormatprop is replaced byobjectSchema(breaking; the changeset isminorunder this repo's version policy).Authority
Ruling C1 on objectui#9436, verbatim (comment 5657441402, ratified in 5814246926):
The lookup branch of that ruling (objectui#10343, landed as PR objectui#10479) put the dropdown's option label on the order: the field's own
displayField, then the object'snameField, then the deprecatedtitleFormat. The triage on this card (comment 5828316879), verbatim: 「route the column through the samegetRecordDisplayNamecall as the dropdown and the read cell; ⛔ not a third copy of the ladder.」What changed
lookupColumnDisplay.tsx:LookupColumnRenderContextcarriesobjectSchemaandtitleFieldin place oftitleFormat. Step 1 ofrenderLookupColumnValuerendersgetRecordDisplayName(objectSchema, row, { titleField })for the display column whenever a schema is given, not only when a template is declared. It usesrecordToOption's floor exclusion: anUntitledorRecord #idanswer is not a title, so the cell falls through to the column's own renderer. The private single-brace template renderer and itsEMPTY_SLOTsentinel are deleted (0 readers left;git grepforEMPTY_SLOTandrefTitleFormatunderpackages/exits 1, and the controlgit grep -c renderLookupColumnValuehits).RecordPickerDialog.tsx: thetitleFormatprop is removed andobjectSchemais added.displayFieldis now the DECLARED display field; the display column is keyed ondisplayField ?? 'name', and only the declared value reaches the resolver astitleField.renderCellContenthands step 1 the same row as before (objectui#10223 / objectui#10373): relations collapsed bytoPredicateRecord, fields the loaded policy denies removed bywithoutDeniedFields. That is the row the dropdown'srecordToOptionlabels from.LookupField.tsx(the one widened surface, in two spots only): therefTitleFormatmemo is deleted, and theRecordPickerDialogJSX passesdisplayField={declaredDisplayField}andobjectSchema={refObjectSchema}.titleFormat=straight to the dialog now passobjectSchema=with unchanged assertions. They also drop their explicitdisplayField="name": their rows carry anamevalue, so a declarednamewould rank above the template, which is what the migrated case means by "no declared display field". The pins areLookupField.candidateExpand-10223'picker: same title cells, same task cells, same records handed to onSelectRecords' andRecordPickerDialog.displayFls-10373(its twotitleFormatcases).LookupField.pickerColumnOrder-10486.test.tsx: each reads both surfaces off one mounted LookupField.Before and after (the new pins)
Fixture: fields
contract_no/name/account/short_code; rows c1 = HT-001 / Acme / Globex / AC and c2 = HT-002 / Initech / Umbrella / IN.nameField: contract_no+titleFormat: '{account} / {contract_no}'(agreement pin)nameFielddisplayField: short_codenameFieldonly, rows carrynamenameFieldonly, nonamefieldBreaking change
RecordPickerDialogis re-exported from the@object-ui/fieldsentry, so its props are published.titleFormatis gone, with no dual spelling and no fallback built from it: passobjectSchema, the referenced object's schema, instead. PassdisplayFieldonly when the lookup declares one. The column also moves fornameField-only objects. Callers re-checked at8740e86: onlyLookupFieldpassedtitleFormat(the only non-testtitleFormat=hit).AccessExplainPanel,AssignedUsersSectionandRelatedListrender the dialog without it and pass noobjectSchema, so their display columns render exactly as before.PeoplePickerimports onlylookupFiltersToRecord, andLocationFieldonly names the dialog in a comment.Verification (head
3e3ad60)pnpm exec vitest run --maxWorkers=2 packages/fields/(through the verify lock): Test Files 201 passed, 1 skipped (202); Tests 3315 passed, 7 skipped; exit 0.pnpm turbo run type-check --filter @object-ui/fields --concurrency=2: Tasks 12 successful, 12 total;@object-ui/fields:type-checkcache miss, executed; exit 0.tsc -p tsconfig.test.json --listFilesincludes the three touched test files.ESLint.isPathIgnoredis false for 6 of 6. Count:--format jsongives 6 results. Invariance:eslint.config.jssets noparserOptions(no type-aware linting), and noeslint-rules/rule reads another file.check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:new-line-citations(0 new citations),check:changeset-claims(report-only),check:pending-changeset-literals,check:phantom-deps,check-changeset-presence.mjs(6 source files of 1 released package, 1 changeset),check-changeset-no-major,check-changeset-fixedandcheck-changeset-overwrite.20b031b). The three implementation files were swapped to their8740e86blobs, and the swap was proven by the disk blob equalling the base blob for each file, plus marker counts:getRecordDisplayName0 andEMPTY_SLOT5 inlookupColumnDisplay.tsx. The new pin file plus the two migrated pins then ran: exit 1, 8 failed and 23 passed (31). Red: the five LookupField-mounted cases above other than the lit control, the unit control 'a record with a title renders it in the display column only', the objectui#10223 picker pin, and the objectui#10373 lit-control case. The restore (git checkout HEAD --, under a trap) was proven by the disk blob equalling the HEAD blob for all three files and an emptygit diff HEAD. The same files at the fixed head: exit 0, 39 passed (five files). Predicted before the run: 9 red. Observed: 8 (see Acceptance notes).Acceptance notes
titleFormatnaming a denied field does not render it in the display column' stayed green under the reverse verification. Its expected cells equal what the column renders with no template at all. Its lit twin in the same file went red, so the pair still bites; the weakness predates this PR.titleFormat={refTitleFormat}sentence. This PR's changeset names both. A prose correction of those two files is outside the claimed surface and is left to the seat.withoutDeniedFieldsdoc comment inLookupField.tsxstill says the picker applies the rule to "its display column'stitleFormat". It now applies it to the display column's title. That comment sits outside the two fenced spots; noted, not edited.Untitled/Record #id) is spelled out besiderecordToOption's and the read cell's. It is a floor check, not the ladder. Core exports no floor predicate, andrecord-title.tsis fenced.displayFieldnaming a relation, against a backend that does not honour$expand, read[org1, org2]in both the dropdown and the picker, at base and at this head, so no change was measured. The expanding-backend case is NOT MEASURED.origin/mainat8740e86. Six commits have landed onmainsince, and none touchespackages/fieldsor the two changesets named above (git diff --statis empty).Generated by Claude Code