Uh oh!
There was an error while loading. Please reload this page.
fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284
Merged
Merged
Conversation
…th the object's name field An object that declares no list view gets its default grid columns synthesized from `highlightFields`, taken verbatim. But `highlightFields` is ADR-0085's "most important fields" role, not a column list, and its first consumer — the detail-page highlight strip — deliberately removes the title field because the page H1 above it already shows one. So correct metadata routinely omits the record name: `showcase_account` declares `nameField: "name"` and `highlightFields: ["status", "industry", "annual_revenue"]`, and its default grid rendered 14 rows with no name column and no way to tell them apart. A list has no H1, so the same declaration needs the opposite treatment. Not a new convention: `deriveLookupColumns` already leads picker columns with the display field and filters it out of the declared list. `@object-ui/core` gains `resolveNameField` (which field titles an object — the name-space twin of `getRecordDisplayName`, now sharing one spelling of the declared pointer so the two cannot drift) and `leadWithNameField` (move it to the front of a synthesized column list). All three synthesis faces use it: ObjectView, InterfaceListPage and ObjectGrid's own derivation. On the two capped faces the lead is applied before the 5/6-column slice. Author-declared column lists are untouched. Three cases decline to lead: no field def, `hidden: true`, and a derived pick landing on a system-managed column (#2702/#2777) — a declared `nameField` on a system field still leads. Fixes#7245 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
✅ 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
|
This was referenced Sep 2, 2026
hotlong
marked this pull request as ready for review
September 2, 2026 09:21
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#7245
The defect
An object that declares no list view gets its default grid columns synthesized from
highlightFields, taken verbatim. ButhighlightFieldsis ADR-0085's "most important fields" role, not a column list — and its first consumer, the detail-page highlight strip, deliberately removes the title field, because the page H1 directly above it already shows one.So metadata that is entirely correct routinely omits the record's name from
highlightFields, and the list faces had nothing to identify a row with. Confirmed against the running showcase backend rather than from the card:One correction to the card's wording, for the record:
showcase_accountdoes notdeclarenameFieldin its source (examples/app-showcase/src/data/objects/account.object.tshas no such key) — the platform stamps it onto the served metadata. The served reading the console actually consumes is exactly as reported, so the diagnosis is unaffected.A list has no H1 to lean on, so the same declaration needs the opposite treatment here. This is not a new convention:
deriveLookupColumnsin@object-ui/fieldsalready leads its record-picker columns with the display field and filters it out of the declared list. The list faces now agree with it.Column lists, before and after
showcase_account, default所有记录view:(furniture columns
#and 操作 unchanged). Measured as rendered DOM headers indefaultColumnsNameFieldLead-7245.test.tsx, which drives a realObjectGridagainst the served schema shape.Both halves are measured, not inferred. The before row is the card's reading. The after row was read out of the live console driven against the running showcase backend — this branch's source served by a Vite dev server on :5181 proxying to :3911, at
/apps/showcase_app/showcase_account, view所有记录:The 14 rows the card reports as indistinguishable now lead with the account name. The same column set is pinned headlessly in
defaultColumnsNameFieldLead-7245.test.tsx, which drives a realObjectGridagainst the served schema shape, so the fix does not depend on a live backend to stay pinned.Three faces, not one
The card's dispatch anticipated more than one synthesis point, and there are three. Each read
highlightFieldsverbatim:packages/app-shell/src/views/ObjectView.tsx—defaultListColumnsFromObjectpackages/app-shell/src/views/InterfaceListPage.tsx—defaultColumnsFromObjectpackages/plugin-grid/src/ObjectGrid.tsx—fieldsToShowFixed at the producer, not the consumer
@object-ui/coregains two exports on the existing ADR-0079 title ladder:resolveNameField(objectDef)— which field titles an object: the declarednameField, then its deprecateddisplayNameField/NAME_FIELD_KEYaliases, else the type-aware derivation. The name-space twin ofgetRecordDisplayName, which answers what that field says on one record. The??chain now has one spelling (declaredNameField), read by both, so the two cannot drift into naming different fields — the divergence ADR-0079 collapsed in the first place. No new alias is read; this is the existing ladder, extracted.leadWithNameField(objectDef, columns)— moves that field to the front of a synthesized column list.Core was chosen over the other two existing name-field spellings deliberately.
plugin-detail'sresolveTitleFieldruns a different ladder (primaryFieldfirst, a different fallback name set) and is not a dependency ofplugin-grid; core's is the one documented as the unified ADR-0079 resolver, and both app-shell and plugin-grid already depend on it.The name field is moved, not merely appended when missing — an author who lists it third still gets it first, because "the column that identifies the row" means first. On the two capped faces the lead is applied before the 5 / 6-column slice, so an object declaring its name field late no longer loses it off the end.
What is deliberately NOT changed
columns/fieldssaid what it wants; reordering it would be renderer-side second-guessing of metadata (AGENTS.md Commandment #0.1). Pinned as a control test.ObjectGrid's no-highlightFieldswalk. It takes every visible field with no cap, so the name field cannot fall off the end — it is already present, only its position could differ. The defect this card reports is unreachable from there. Stated in the code as a measured decision, and pinned as a second control.objectstackwould have hidden the defect rather than fixed it; the synthesis rule itself was wrong.Three cases decline to lead, each guarding an existing rule: a name field the object carries no field def for (never fabricate a column), one marked
hidden: true(the author said don't show it), and a derived pick that lands on a system-managed column —deriveTitleFieldfilters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameFieldon a system field still leads:sys_migrationreally does point atid, and an explicit designation is not a heuristic misfire.Verification
Union re-run on the final merged head 34b307b.
Reverse verification (ablation). The three call sites were reverted to
origin/mainwith the core helper and every test kept, so the faces fail on assertions rather than on a missing import. Predicted direction: red. Measured: 10 failed / 46 passed — every failure inside a#7245block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameFieldoccurrences 4/4/3 → 0/0/0, and each file's blob hash equal to itsorigin/mainblob); the restore was proven by observation afterwards, not by an exit code —git diff HEADempty,git statusclean, and all three blob hashes byte-identical to the pre-ablation reading. No rebuild leg was needed and none is claimed: the root vitest config aliases@object-ui/coretopackages/core/src(vitest.config.mts:277), so nothing resolves throughdistand there is no stale-artifact surface for the ablation to hide behind.hostFetchedDefaultColumns-6677and the just-landedListView.speculativeFls-7216)Test Files 14 passed (14)/Tests 207 passed (207)packages/core/+packages/plugin-grid/full suitesTest Files 1 failed | 217 passed (218)/Tests 1 failed | 3139 passed (3140)— see the flake note belowtype-check(core, app-shell, plugin-grid)Done; each echoedtsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked toocheck-changeset-presence.mjs✅ 8 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)check-changeset-no-major.mjs✅ No changeset declares a major bump.The one red, and why it is not this change.
plugin-grid/src/__tests__/rowRecordCrudVerdict.test.tsx > paginates under the server's capfailed withError: Test timed out in 15000msat 16471ms — a timeout, not an assertion, and the signature AGENTS.md documents for unbounded module loading charged to a bounded window under a saturated parallel run. Re-run alone:Test Files 1 passed (1)/Tests 15 passed (15). The file covers batched CRUD verdicts and touches no part of column synthesis.Declared narrowing 1 — verification ran UNLOCKED
The official wording from
scripts/pm/os-verify-lock.sh, pasted as it asks:Declared narrowing — verification ran UNLOCKED.
scripts/pm/os-verify-lock.shcould not take the shared verify lock on this host: no usableflock. The shared verify lock is declared Linux-only (flockis util-linux, and a stock macOS does not ship it), so the command below was run directly, without the lock — a declared narrowing, not a silent one. No serialization guarantee held for this run, nor for any sibling agent in this container while it ran.This applies to every command in the table above.
Declared narrowing 2 — the app-shell suite was scoped, not run whole
pnpm exec vitest run packages/app-shell/was killed by the container's ~10-minute foreground cap (exit 143) before producing any verdict. It was replaced by the 10-file blast radius — every app-shell test that referencesdefaultListColumnsFromObject,defaultColumnsFromObject,getRecordDisplayNameorderiveTitleField, plus theObjectView.*/InterfaceListPage.*suites:Test Files 10 passed (10)/Tests 110 passed (110). The rest ofapp-shellis left to CI, which runs the farm regardless. This is a scoped run, not a claim of full-package coverage.On the lint scope
Three pieces of evidence, so the scoping is a measurement rather than an omission:
--format jsonoutput, 0 errors (4154 pre-existing warnings, allwarn-level rules such as@typescript-eslint/no-explicit-any; the repo has no formatting gate).eslint.config.jsdeclares noparserOptions.projectand noprojectService, so type-aware linting is not enabled: every file's verdict depends only on its own source plus the shared config. This diff changes no config file, so it cannot move the verdict of any file it did not touch.Overlap with in-flight work
Re-checked after merging
origin/main. PR #7261 (plugin-listspeculative$selectFLS gating) landed as ac257b3 while this was in progress; its file surface ispackages/plugin-list/src/ListView.tsxplus its own test — zero overlap with the four files here, and it gates which fields are fetched, not which columns are synthesized. Its suite is included in the union above and is green on this head.Generated by Claude Code