Repository navigation
fix(plugin-list): ListView's columns-only fallback stops reading undeclared relational keys off a list column - #10544
Conversation
…onal key off a list column Before the object definition loads, ListView builds its filter and sort candidates from the declared columns. That branch read reference_to / reference, display_field / reference_field and id_field off each column; ListColumnSchema declares none of them in either casing. The reads are removed per the ruling: a relational target comes from the object definition once it loads. Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC Co-authored-by: Claude <noreply@anthropic.com>
The plain-column control and the definition-supplies-the-target control now stand as their own cases, so an ablation that restores the column reads turns only the pin red. Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC 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
|
Fixes #7531
Clause-②: no
What changed
Until the object definition loads,
ListViewbuilds its filter and sort candidates from the view's declaredcolumns(the!objectDef?.fieldsbranch of thecandidateFieldsmemo). That branch readf.reference_to || f.reference,f.display_field || f.reference_fieldandf.id_fieldoff each column, andListColumnSchemadeclares none of them in either casing. The card's ruling (comment 5809501335, letter remove) takes the three reads out. The fallback candidate now carries only what a column declares. The relational target arrives with the object definition through theobjectDef.fieldsbranch, which this PR leaves untouched.packages/plugin-list/src/ListView.tsx: removes the three properties. The objectui#7642 census comment above them becomes one line that points at this card. Nothing else in the file changes.packages/plugin-list/src/__tests__/ListView.columnsOnlyRelationalReads-7531.test.tsx: the pin and its two lit controls..changeset/7531-listview-column-undeclared-reads.md:patchon@object-ui/plugin-list.Stop condition: the producer census (run before deleting)
Method. The census was an AST walk. The file list and the file contents both came from the same git ref (
git ls-tree -randgit cat-file --batch), never from a worktree:6881e9e47(this branch's base).bc80e1626(itsorigin/mainwhen this ran).The walk parsed these kinds of file:
.ts .tsx .js .jsx .mjs .cjs .mts .cts, using the TypeScript compiler API;.jsonand.yaml, parsed whole;json/yaml/ts/jsblocks inside.md/.mdx.What counted as a column object. An object literal that is an element of an array bound to any of:
columnsproperty;columnsJSX attribute;columns = [...]assignment;cols/columns, or typed with aColumntype.The census looked for eight spellings on those objects:
reference_to,reference,display_field,reference_field,id_field,referenceTo,displayFieldandidField. It also listed two wider shapes:fieldplus one of the eight, wherever it sits. This catches columns built by amap.fieldplus a spread. This catches keys that could arrive at runtime.Controls.
fieldhit 460 column objects in objectui and 435 in objectstack.widthhit 27 in objectui and 41 in objectstack. The walk found 956 column objects in objectui and 515 in objectstack.columnsattribute, acolsvariable, JSON, YAML, a Markdown json block and a Markdown ts block. The walk hit 7 of 7.Results
bc80e16266881e9e47apps/console/src/dev/DevLookup.tsx,docs/adr/0001-master-detail-subform.md(therecord:line_itemsexample) andpackages/fields/src/widgets/GridField.test.tsxare line-item grid columns (GridField/LineItemsField). That is a different contract: its columns are shaped like field defs, andGridFieldreadsc.reference,c.displayFieldandc.idFieldoff them on purpose. They are not list columns.packages/plugin-grid/src/__tests__/ObjectGrid.linkCellAnchor.test.tsxis a grid-view column carryingreference_to: 'crm_account'. That makes it the one list-column hit. It is classified as not a producer, for three reasons:ObjectGriddirectly, soListView's fallback never sees it.ObjectGridcode reads a relational key off a column. The lookup cell takes its target from the object definition's field, throughapplyRelationalMeta(fieldMeta, objectDefField).None of the wider shapes found a list-column producer. The
fieldplus relational key matches were form section fields, action and bulk-action params, a form field schema and an engine log record. The spread matches (including the 8 spread column literals in objectui) carry none of the eight spellings.Not measured:
objectstack-ai/cloud. That repo cannot be read from this container. It was not censused, and this PR makes no claim about it.Downstream readers of the three keys (measured)
sortFieldsmemo reads onlyvalue,labelandtypefrom each candidate. It never read the three keys, so sorting is unaffected.FilterBuilderdraws its remoteLookupValuePickeronly when the field is lookup-like, has no static option domain, and has areferenceTo(ortype === "user"). The picker then readsdisplayField(default"name") andidField(default"id").ListColumnSchemaparses cannot carry any of the five read spellings (the pin's first case asserts this against the installed spec). So all three values were alreadyundefinedfor such a column. The plain-column control stays green with the reads put back (see the ablation below). Once the definition loads, theobjectDef.fieldsbranch suppliesreferenceTo. The third case asserts this, and it passes with and without the reads.Pin, and the ablation that proves it can fail
The pin holds
getObjectSchemapending on purpose, so the columns-only branch is the only one that runs. It captures the candidate listListViewhands toFilterBuilder, using avi.mockthat inherits the real module and overrides onlyFilterBuilder. The four cases:unrecognized_keys). Control: the bare column parses.account_id/Account/lookup, thenname/Name/text), with noreferenceTo.toStrictEquals the plain one, withreferenceTo,displayFieldandidFieldall undefined.reference: 'crm_account', the candidate showsreferenceTo: 'crm_account'. This proves the capture sees the key when one exists.Ablation, on HEAD
086e71456. The three reads were put back withnode ../objectstack/scripts/ablation-replace.mjsin wrap mode, holding the verify lock:ListView.tsxblob03798ad2b3be→39feb0e2facd; count of the restored read line in the file = 1.Tests 1 failed | 3 passed (4). Only the pin went red:03798ad2b3beequals the HEAD blob, andgit diff HEADon the file is empty.../ListView(source), so the mutated file is what ran. Nodist/is in that path, so no rebuild was needed for either leg.Verification (HEAD
086e71456)pnpm exec vitest run packages/plugin-list/from the repo root:Test Files 93 passed (93),Tests 1098 passed (1098).pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-list^...' build(12 packages built;Scope: 13 of 47), thenpnpm --filter @object-ui/plugin-list type-check(tsc --noEmit && tsc -p tsconfig.test.json): exit 0.tsc -p tsconfig.test.json --listFilesincludes the new test.pnpm check:control-bytes: OK.pnpm check:new-line-citations: 0 new citations.node scripts/check-changeset-presence.mjs: ✅, one changeset declared.pnpm check:changeset-claims: 7 pending changesets nameListView.tsx. All were read. They cover gantt, kanban,userActionsand sort-comment changes, none describes the candidate fallback's relational reads, and all stay true.check-test-path-roots, changeset fixed / no-major / overwrite,check:spec-symbols,check:unreferenced-sources,check:phantom-deps,check-type-check-coverageandcheck-lint-coverage.ListView.tsx: 0 errors and 183 warnings, with per-rule counts identical to the base file (checked through--stdinat the same path).eslint.config.jshas no type-aware linting (noparserOptions.projectorprojectService), so this diff cannot change the verdict on any file it did not touch. The fullpnpm lintis left to CI.Acceptance notes
objectDef.fieldsbranch's comment still says "The SIBLING branch above (theschema.columnsfallback) is NOT narrowed … That is a different question and is filed, not answered here." After this PR the sibling reads none of those keys. The dispatch fenced every other line ofListView.tsx, so that comment is untouched. Thedomain:uiseat's planned same-file docblock edits (after objectui#10534 lands) are the natural place to cut it.ObjectGrid.linkCellAnchor.test.tsx, the case "a lookup column pointing at ANOTHER object stays plain" passes no matter what the column carries. The column'sreference_tois read by nothing, and the fixture has no object definition. This is a test-quality note, and it has no carrier.objectDef.fieldsbranch readsdisplay_field/reference_field/id_field, but not the spec'sdisplayField. A probe showed that a definition field carrying onlydisplayFieldgives a candidate with nodisplayField.f.options, whichListColumnSchemarefuses.Session:
https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC(thedomain:uiseat's dispatch of objectui#7531).Generated by Claude Code