fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield
Sep 2, 2026
Merged

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field#7284
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield

Conversation

@hotlong

@hotlonghotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#7245

The defect

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 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:

GET /api/v1/meta/object/showcase_account
nameField = "name"
highlightFields = ["status", "industry", "annual_revenue"]
listViews / list_views / list → all ABSENT
GET /api/v1/meta/view
35 views total, 0 of them bound to showcase_account

One correction to the card's wording, for the record: showcase_account does notdeclarenameField in its source (examples/app-showcase/src/data/objects/account.object.ts has 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: deriveLookupColumns in @object-ui/fields already 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:

data columns
before生命周期, 行业, 年收入
afterAccount Name, 生命周期, 行业, 年收入

(furniture columns # and 操作 unchanged). Measured as rendered DOM headers in defaultColumnsNameFieldLead-7245.test.tsx, which drives a real ObjectGrid against 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 所有记录:

thead th → ["", "#", "客户名称", "生命周期", "行业", "年收入", "操作"]
tbody tr → 14
row 1 → ["", "1", "Vandelay Industries", "Active", "Finance", "6,700,000"]

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 real ObjectGrid against 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 highlightFields verbatim:

facefilecap
console object view (the reported one)packages/app-shell/src/views/ObjectView.tsxdefaultListColumnsFromObject5
interface pagespackages/app-shell/src/views/InterfaceListPage.tsxdefaultColumnsFromObject6
the grid's own derivationpackages/plugin-grid/src/ObjectGrid.tsxfieldsToShownone

Fixed at the producer, not the consumer

@object-ui/core gains two exports on the existing ADR-0079 title ladder:

  • resolveNameField(objectDef)which field titles an object: the declared nameField, then its deprecated displayNameField / NAME_FIELD_KEY aliases, else the type-aware derivation. The name-space twin of getRecordDisplayName, 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's resolveTitleField runs a different ladder (primaryField first, a different fallback name set) and is not a dependency of plugin-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

  • Author-declared column lists. A view or grid that declares columns / fields said 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-highlightFields walk. 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.
  • The showcase app. Adding a list view over in objectstack would 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 — deriveTitleField filters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameField on a system field still leads: sys_migration really does point at id, 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/main with 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 #7245 block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameField occurrences 4/4/3 → 0/0/0, and each file's blob hash equal to its origin/main blob); the restore was proven by observation afterwards, not by an exit code — git diff HEAD empty, git status clean, 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/core to packages/core/src (vitest.config.mts:277), so nothing resolves through dist and there is no stale-artifact surface for the ablation to hide behind.

gateresult
targeted vitest, 14 files (both new suites, both edited suites, the blast radius, plus hostFetchedDefaultColumns-6677 and the just-landed ListView.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 below
type-check (core, app-shell, plugin-grid)all three Done; each echoed tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
eslint over the three packages0 errors across 1397 files processed
check-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 cap failed with Error: Test timed out in 15000ms at 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.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is 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 references defaultListColumnsFromObject, defaultColumnsFromObject, getRecordDisplayName or deriveTitleField, plus the ObjectView.* / InterfaceListPage.* suites: Test Files 10 passed (10) / Tests 110 passed (110). The rest of app-shell is 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:

  1. Population — read from eslint's own resolution of the three package directories under the root flat config, not from a hand-listed file set.
  2. Count1397 files, read from --format json output, 0 errors (4154 pre-existing warnings, all warn-level rules such as @typescript-eslint/no-explicit-any; the repo has no formatting gate).
  3. Invarianceeslint.config.js declares no parserOptions.project and no projectService, 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-list speculative $select FLS gating) landed as ac257b3 while this was in progress; its file surface is packages/plugin-list/src/ListView.tsx plus 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

hotlongand others added 2 commits September 2, 2026 10:34
…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.3 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-D-6lW3N_.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.76KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@hotlong
hotlong marked this pull request as ready for review September 2, 2026 09:21
@hotlong
hotlong added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 19f1639Sep 2, 2026
32 checks passed
@hotlong
hotlong deleted the claude/issue-7245-default-view-namefield branch September 2, 2026 09:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default list view synthesized from highlightFields omits the nameField — the showcase Account list shows 14 rows with no name column

1 participant

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield
Sep 2, 2026
Merged

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field#7284
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield

Conversation

@hotlong

@hotlonghotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#7245

The defect

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 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:

GET /api/v1/meta/object/showcase_account
nameField = "name"
highlightFields = ["status", "industry", "annual_revenue"]
listViews / list_views / list → all ABSENT
GET /api/v1/meta/view
35 views total, 0 of them bound to showcase_account

One correction to the card's wording, for the record: showcase_account does notdeclarenameField in its source (examples/app-showcase/src/data/objects/account.object.ts has 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: deriveLookupColumns in @object-ui/fields already 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:

data columns
before生命周期, 行业, 年收入
afterAccount Name, 生命周期, 行业, 年收入

(furniture columns # and 操作 unchanged). Measured as rendered DOM headers in defaultColumnsNameFieldLead-7245.test.tsx, which drives a real ObjectGrid against 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 所有记录:

thead th → ["", "#", "客户名称", "生命周期", "行业", "年收入", "操作"]
tbody tr → 14
row 1 → ["", "1", "Vandelay Industries", "Active", "Finance", "6,700,000"]

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 real ObjectGrid against 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 highlightFields verbatim:

facefilecap
console object view (the reported one)packages/app-shell/src/views/ObjectView.tsxdefaultListColumnsFromObject5
interface pagespackages/app-shell/src/views/InterfaceListPage.tsxdefaultColumnsFromObject6
the grid's own derivationpackages/plugin-grid/src/ObjectGrid.tsxfieldsToShownone

Fixed at the producer, not the consumer

@object-ui/core gains two exports on the existing ADR-0079 title ladder:

  • resolveNameField(objectDef)which field titles an object: the declared nameField, then its deprecated displayNameField / NAME_FIELD_KEY aliases, else the type-aware derivation. The name-space twin of getRecordDisplayName, 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's resolveTitleField runs a different ladder (primaryField first, a different fallback name set) and is not a dependency of plugin-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

  • Author-declared column lists. A view or grid that declares columns / fields said 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-highlightFields walk. 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.
  • The showcase app. Adding a list view over in objectstack would 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 — deriveTitleField filters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameField on a system field still leads: sys_migration really does point at id, 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/main with 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 #7245 block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameField occurrences 4/4/3 → 0/0/0, and each file's blob hash equal to its origin/main blob); the restore was proven by observation afterwards, not by an exit code — git diff HEAD empty, git status clean, 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/core to packages/core/src (vitest.config.mts:277), so nothing resolves through dist and there is no stale-artifact surface for the ablation to hide behind.

gateresult
targeted vitest, 14 files (both new suites, both edited suites, the blast radius, plus hostFetchedDefaultColumns-6677 and the just-landed ListView.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 below
type-check (core, app-shell, plugin-grid)all three Done; each echoed tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
eslint over the three packages0 errors across 1397 files processed
check-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 cap failed with Error: Test timed out in 15000ms at 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.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is 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 references defaultListColumnsFromObject, defaultColumnsFromObject, getRecordDisplayName or deriveTitleField, plus the ObjectView.* / InterfaceListPage.* suites: Test Files 10 passed (10) / Tests 110 passed (110). The rest of app-shell is 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:

  1. Population — read from eslint's own resolution of the three package directories under the root flat config, not from a hand-listed file set.
  2. Count1397 files, read from --format json output, 0 errors (4154 pre-existing warnings, all warn-level rules such as @typescript-eslint/no-explicit-any; the repo has no formatting gate).
  3. Invarianceeslint.config.js declares no parserOptions.project and no projectService, 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-list speculative $select FLS gating) landed as ac257b3 while this was in progress; its file surface is packages/plugin-list/src/ListView.tsx plus 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

hotlongand others added 2 commits September 2, 2026 10:34
…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.3 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-D-6lW3N_.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.76KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@hotlong
hotlong marked this pull request as ready for review September 2, 2026 09:21
@hotlong
hotlong added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 19f1639Sep 2, 2026
32 checks passed
@hotlong
hotlong deleted the claude/issue-7245-default-view-namefield branch September 2, 2026 09:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default list view synthesized from highlightFields omits the nameField — the showcase Account list shows 14 rows with no name column

1 participant

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield
Sep 2, 2026
Merged

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field#7284
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield

Conversation

@hotlong

@hotlonghotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#7245

The defect

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 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:

GET /api/v1/meta/object/showcase_account
nameField = "name"
highlightFields = ["status", "industry", "annual_revenue"]
listViews / list_views / list → all ABSENT
GET /api/v1/meta/view
35 views total, 0 of them bound to showcase_account

One correction to the card's wording, for the record: showcase_account does notdeclarenameField in its source (examples/app-showcase/src/data/objects/account.object.ts has 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: deriveLookupColumns in @object-ui/fields already 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:

data columns
before生命周期, 行业, 年收入
afterAccount Name, 生命周期, 行业, 年收入

(furniture columns # and 操作 unchanged). Measured as rendered DOM headers in defaultColumnsNameFieldLead-7245.test.tsx, which drives a real ObjectGrid against 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 所有记录:

thead th → ["", "#", "客户名称", "生命周期", "行业", "年收入", "操作"]
tbody tr → 14
row 1 → ["", "1", "Vandelay Industries", "Active", "Finance", "6,700,000"]

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 real ObjectGrid against 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 highlightFields verbatim:

facefilecap
console object view (the reported one)packages/app-shell/src/views/ObjectView.tsxdefaultListColumnsFromObject5
interface pagespackages/app-shell/src/views/InterfaceListPage.tsxdefaultColumnsFromObject6
the grid's own derivationpackages/plugin-grid/src/ObjectGrid.tsxfieldsToShownone

Fixed at the producer, not the consumer

@object-ui/core gains two exports on the existing ADR-0079 title ladder:

  • resolveNameField(objectDef)which field titles an object: the declared nameField, then its deprecated displayNameField / NAME_FIELD_KEY aliases, else the type-aware derivation. The name-space twin of getRecordDisplayName, 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's resolveTitleField runs a different ladder (primaryField first, a different fallback name set) and is not a dependency of plugin-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

  • Author-declared column lists. A view or grid that declares columns / fields said 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-highlightFields walk. 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.
  • The showcase app. Adding a list view over in objectstack would 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 — deriveTitleField filters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameField on a system field still leads: sys_migration really does point at id, 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/main with 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 #7245 block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameField occurrences 4/4/3 → 0/0/0, and each file's blob hash equal to its origin/main blob); the restore was proven by observation afterwards, not by an exit code — git diff HEAD empty, git status clean, 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/core to packages/core/src (vitest.config.mts:277), so nothing resolves through dist and there is no stale-artifact surface for the ablation to hide behind.

gateresult
targeted vitest, 14 files (both new suites, both edited suites, the blast radius, plus hostFetchedDefaultColumns-6677 and the just-landed ListView.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 below
type-check (core, app-shell, plugin-grid)all three Done; each echoed tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
eslint over the three packages0 errors across 1397 files processed
check-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 cap failed with Error: Test timed out in 15000ms at 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.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is 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 references defaultListColumnsFromObject, defaultColumnsFromObject, getRecordDisplayName or deriveTitleField, plus the ObjectView.* / InterfaceListPage.* suites: Test Files 10 passed (10) / Tests 110 passed (110). The rest of app-shell is 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:

  1. Population — read from eslint's own resolution of the three package directories under the root flat config, not from a hand-listed file set.
  2. Count1397 files, read from --format json output, 0 errors (4154 pre-existing warnings, all warn-level rules such as @typescript-eslint/no-explicit-any; the repo has no formatting gate).
  3. Invarianceeslint.config.js declares no parserOptions.project and no projectService, 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-list speculative $select FLS gating) landed as ac257b3 while this was in progress; its file surface is packages/plugin-list/src/ListView.tsx plus 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

hotlongand others added 2 commits September 2, 2026 10:34
…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.3 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-D-6lW3N_.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.76KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@hotlong
hotlong marked this pull request as ready for review September 2, 2026 09:21
@hotlong
hotlong added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 19f1639Sep 2, 2026
32 checks passed
@hotlong
hotlong deleted the claude/issue-7245-default-view-namefield branch September 2, 2026 09:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default list view synthesized from highlightFields omits the nameField — the showcase Account list shows 14 rows with no name column

1 participant

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield
Sep 2, 2026
Merged

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field#7284
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield

Conversation

@hotlong

@hotlonghotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#7245

The defect

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 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:

GET /api/v1/meta/object/showcase_account
nameField = "name"
highlightFields = ["status", "industry", "annual_revenue"]
listViews / list_views / list → all ABSENT
GET /api/v1/meta/view
35 views total, 0 of them bound to showcase_account

One correction to the card's wording, for the record: showcase_account does notdeclarenameField in its source (examples/app-showcase/src/data/objects/account.object.ts has 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: deriveLookupColumns in @object-ui/fields already 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:

data columns
before生命周期, 行业, 年收入
afterAccount Name, 生命周期, 行业, 年收入

(furniture columns # and 操作 unchanged). Measured as rendered DOM headers in defaultColumnsNameFieldLead-7245.test.tsx, which drives a real ObjectGrid against 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 所有记录:

thead th → ["", "#", "客户名称", "生命周期", "行业", "年收入", "操作"]
tbody tr → 14
row 1 → ["", "1", "Vandelay Industries", "Active", "Finance", "6,700,000"]

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 real ObjectGrid against 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 highlightFields verbatim:

facefilecap
console object view (the reported one)packages/app-shell/src/views/ObjectView.tsxdefaultListColumnsFromObject5
interface pagespackages/app-shell/src/views/InterfaceListPage.tsxdefaultColumnsFromObject6
the grid's own derivationpackages/plugin-grid/src/ObjectGrid.tsxfieldsToShownone

Fixed at the producer, not the consumer

@object-ui/core gains two exports on the existing ADR-0079 title ladder:

  • resolveNameField(objectDef)which field titles an object: the declared nameField, then its deprecated displayNameField / NAME_FIELD_KEY aliases, else the type-aware derivation. The name-space twin of getRecordDisplayName, 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's resolveTitleField runs a different ladder (primaryField first, a different fallback name set) and is not a dependency of plugin-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

  • Author-declared column lists. A view or grid that declares columns / fields said 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-highlightFields walk. 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.
  • The showcase app. Adding a list view over in objectstack would 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 — deriveTitleField filters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameField on a system field still leads: sys_migration really does point at id, 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/main with 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 #7245 block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameField occurrences 4/4/3 → 0/0/0, and each file's blob hash equal to its origin/main blob); the restore was proven by observation afterwards, not by an exit code — git diff HEAD empty, git status clean, 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/core to packages/core/src (vitest.config.mts:277), so nothing resolves through dist and there is no stale-artifact surface for the ablation to hide behind.

gateresult
targeted vitest, 14 files (both new suites, both edited suites, the blast radius, plus hostFetchedDefaultColumns-6677 and the just-landed ListView.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 below
type-check (core, app-shell, plugin-grid)all three Done; each echoed tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
eslint over the three packages0 errors across 1397 files processed
check-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 cap failed with Error: Test timed out in 15000ms at 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.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is 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 references defaultListColumnsFromObject, defaultColumnsFromObject, getRecordDisplayName or deriveTitleField, plus the ObjectView.* / InterfaceListPage.* suites: Test Files 10 passed (10) / Tests 110 passed (110). The rest of app-shell is 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:

  1. Population — read from eslint's own resolution of the three package directories under the root flat config, not from a hand-listed file set.
  2. Count1397 files, read from --format json output, 0 errors (4154 pre-existing warnings, all warn-level rules such as @typescript-eslint/no-explicit-any; the repo has no formatting gate).
  3. Invarianceeslint.config.js declares no parserOptions.project and no projectService, 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-list speculative $select FLS gating) landed as ac257b3 while this was in progress; its file surface is packages/plugin-list/src/ListView.tsx plus 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

hotlongand others added 2 commits September 2, 2026 10:34
…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.3 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-D-6lW3N_.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.76KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@hotlong
hotlong marked this pull request as ready for review September 2, 2026 09:21
@hotlong
hotlong added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 19f1639Sep 2, 2026
32 checks passed
@hotlong
hotlong deleted the claude/issue-7245-default-view-namefield branch September 2, 2026 09:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default list view synthesized from highlightFields omits the nameField — the showcase Account list shows 14 rows with no name column

1 participant

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield
Sep 2, 2026
Merged

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field#7284
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield

Conversation

@hotlong

@hotlonghotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#7245

The defect

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 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:

GET /api/v1/meta/object/showcase_account
nameField = "name"
highlightFields = ["status", "industry", "annual_revenue"]
listViews / list_views / list → all ABSENT
GET /api/v1/meta/view
35 views total, 0 of them bound to showcase_account

One correction to the card's wording, for the record: showcase_account does notdeclarenameField in its source (examples/app-showcase/src/data/objects/account.object.ts has 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: deriveLookupColumns in @object-ui/fields already 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:

data columns
before生命周期, 行业, 年收入
afterAccount Name, 生命周期, 行业, 年收入

(furniture columns # and 操作 unchanged). Measured as rendered DOM headers in defaultColumnsNameFieldLead-7245.test.tsx, which drives a real ObjectGrid against 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 所有记录:

thead th → ["", "#", "客户名称", "生命周期", "行业", "年收入", "操作"]
tbody tr → 14
row 1 → ["", "1", "Vandelay Industries", "Active", "Finance", "6,700,000"]

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 real ObjectGrid against 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 highlightFields verbatim:

facefilecap
console object view (the reported one)packages/app-shell/src/views/ObjectView.tsxdefaultListColumnsFromObject5
interface pagespackages/app-shell/src/views/InterfaceListPage.tsxdefaultColumnsFromObject6
the grid's own derivationpackages/plugin-grid/src/ObjectGrid.tsxfieldsToShownone

Fixed at the producer, not the consumer

@object-ui/core gains two exports on the existing ADR-0079 title ladder:

  • resolveNameField(objectDef)which field titles an object: the declared nameField, then its deprecated displayNameField / NAME_FIELD_KEY aliases, else the type-aware derivation. The name-space twin of getRecordDisplayName, 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's resolveTitleField runs a different ladder (primaryField first, a different fallback name set) and is not a dependency of plugin-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

  • Author-declared column lists. A view or grid that declares columns / fields said 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-highlightFields walk. 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.
  • The showcase app. Adding a list view over in objectstack would 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 — deriveTitleField filters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameField on a system field still leads: sys_migration really does point at id, 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/main with 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 #7245 block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameField occurrences 4/4/3 → 0/0/0, and each file's blob hash equal to its origin/main blob); the restore was proven by observation afterwards, not by an exit code — git diff HEAD empty, git status clean, 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/core to packages/core/src (vitest.config.mts:277), so nothing resolves through dist and there is no stale-artifact surface for the ablation to hide behind.

gateresult
targeted vitest, 14 files (both new suites, both edited suites, the blast radius, plus hostFetchedDefaultColumns-6677 and the just-landed ListView.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 below
type-check (core, app-shell, plugin-grid)all three Done; each echoed tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
eslint over the three packages0 errors across 1397 files processed
check-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 cap failed with Error: Test timed out in 15000ms at 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.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is 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 references defaultListColumnsFromObject, defaultColumnsFromObject, getRecordDisplayName or deriveTitleField, plus the ObjectView.* / InterfaceListPage.* suites: Test Files 10 passed (10) / Tests 110 passed (110). The rest of app-shell is 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:

  1. Population — read from eslint's own resolution of the three package directories under the root flat config, not from a hand-listed file set.
  2. Count1397 files, read from --format json output, 0 errors (4154 pre-existing warnings, all warn-level rules such as @typescript-eslint/no-explicit-any; the repo has no formatting gate).
  3. Invarianceeslint.config.js declares no parserOptions.project and no projectService, 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-list speculative $select FLS gating) landed as ac257b3 while this was in progress; its file surface is packages/plugin-list/src/ListView.tsx plus 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

hotlongand others added 2 commits September 2, 2026 10:34
…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.3 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-D-6lW3N_.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.76KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@hotlong
hotlong marked this pull request as ready for review September 2, 2026 09:21
@hotlong
hotlong added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 19f1639Sep 2, 2026
32 checks passed
@hotlong
hotlong deleted the claude/issue-7245-default-view-namefield branch September 2, 2026 09:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default list view synthesized from highlightFields omits the nameField — the showcase Account list shows 14 rows with no name column

1 participant

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield
Sep 2, 2026
Merged

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field#7284
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield

Conversation

@hotlong

@hotlonghotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#7245

The defect

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 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:

GET /api/v1/meta/object/showcase_account
nameField = "name"
highlightFields = ["status", "industry", "annual_revenue"]
listViews / list_views / list → all ABSENT
GET /api/v1/meta/view
35 views total, 0 of them bound to showcase_account

One correction to the card's wording, for the record: showcase_account does notdeclarenameField in its source (examples/app-showcase/src/data/objects/account.object.ts has 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: deriveLookupColumns in @object-ui/fields already 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:

data columns
before生命周期, 行业, 年收入
afterAccount Name, 生命周期, 行业, 年收入

(furniture columns # and 操作 unchanged). Measured as rendered DOM headers in defaultColumnsNameFieldLead-7245.test.tsx, which drives a real ObjectGrid against 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 所有记录:

thead th → ["", "#", "客户名称", "生命周期", "行业", "年收入", "操作"]
tbody tr → 14
row 1 → ["", "1", "Vandelay Industries", "Active", "Finance", "6,700,000"]

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 real ObjectGrid against 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 highlightFields verbatim:

facefilecap
console object view (the reported one)packages/app-shell/src/views/ObjectView.tsxdefaultListColumnsFromObject5
interface pagespackages/app-shell/src/views/InterfaceListPage.tsxdefaultColumnsFromObject6
the grid's own derivationpackages/plugin-grid/src/ObjectGrid.tsxfieldsToShownone

Fixed at the producer, not the consumer

@object-ui/core gains two exports on the existing ADR-0079 title ladder:

  • resolveNameField(objectDef)which field titles an object: the declared nameField, then its deprecated displayNameField / NAME_FIELD_KEY aliases, else the type-aware derivation. The name-space twin of getRecordDisplayName, 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's resolveTitleField runs a different ladder (primaryField first, a different fallback name set) and is not a dependency of plugin-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

  • Author-declared column lists. A view or grid that declares columns / fields said 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-highlightFields walk. 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.
  • The showcase app. Adding a list view over in objectstack would 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 — deriveTitleField filters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameField on a system field still leads: sys_migration really does point at id, 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/main with 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 #7245 block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameField occurrences 4/4/3 → 0/0/0, and each file's blob hash equal to its origin/main blob); the restore was proven by observation afterwards, not by an exit code — git diff HEAD empty, git status clean, 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/core to packages/core/src (vitest.config.mts:277), so nothing resolves through dist and there is no stale-artifact surface for the ablation to hide behind.

gateresult
targeted vitest, 14 files (both new suites, both edited suites, the blast radius, plus hostFetchedDefaultColumns-6677 and the just-landed ListView.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 below
type-check (core, app-shell, plugin-grid)all three Done; each echoed tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
eslint over the three packages0 errors across 1397 files processed
check-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 cap failed with Error: Test timed out in 15000ms at 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.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is 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 references defaultListColumnsFromObject, defaultColumnsFromObject, getRecordDisplayName or deriveTitleField, plus the ObjectView.* / InterfaceListPage.* suites: Test Files 10 passed (10) / Tests 110 passed (110). The rest of app-shell is 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:

  1. Population — read from eslint's own resolution of the three package directories under the root flat config, not from a hand-listed file set.
  2. Count1397 files, read from --format json output, 0 errors (4154 pre-existing warnings, all warn-level rules such as @typescript-eslint/no-explicit-any; the repo has no formatting gate).
  3. Invarianceeslint.config.js declares no parserOptions.project and no projectService, 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-list speculative $select FLS gating) landed as ac257b3 while this was in progress; its file surface is packages/plugin-list/src/ListView.tsx plus 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

hotlongand others added 2 commits September 2, 2026 10:34
…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.3 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-D-6lW3N_.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.76KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@hotlong
hotlong marked this pull request as ready for review September 2, 2026 09:21
@hotlong
hotlong added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 19f1639Sep 2, 2026
32 checks passed
@hotlong
hotlong deleted the claude/issue-7245-default-view-namefield branch September 2, 2026 09:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default list view synthesized from highlightFields omits the nameField — the showcase Account list shows 14 rows with no name column

1 participant

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield
Sep 2, 2026
Merged

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field#7284
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield

Conversation

@hotlong

@hotlonghotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#7245

The defect

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 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:

GET /api/v1/meta/object/showcase_account
nameField = "name"
highlightFields = ["status", "industry", "annual_revenue"]
listViews / list_views / list → all ABSENT
GET /api/v1/meta/view
35 views total, 0 of them bound to showcase_account

One correction to the card's wording, for the record: showcase_account does notdeclarenameField in its source (examples/app-showcase/src/data/objects/account.object.ts has 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: deriveLookupColumns in @object-ui/fields already 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:

data columns
before生命周期, 行业, 年收入
afterAccount Name, 生命周期, 行业, 年收入

(furniture columns # and 操作 unchanged). Measured as rendered DOM headers in defaultColumnsNameFieldLead-7245.test.tsx, which drives a real ObjectGrid against 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 所有记录:

thead th → ["", "#", "客户名称", "生命周期", "行业", "年收入", "操作"]
tbody tr → 14
row 1 → ["", "1", "Vandelay Industries", "Active", "Finance", "6,700,000"]

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 real ObjectGrid against 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 highlightFields verbatim:

facefilecap
console object view (the reported one)packages/app-shell/src/views/ObjectView.tsxdefaultListColumnsFromObject5
interface pagespackages/app-shell/src/views/InterfaceListPage.tsxdefaultColumnsFromObject6
the grid's own derivationpackages/plugin-grid/src/ObjectGrid.tsxfieldsToShownone

Fixed at the producer, not the consumer

@object-ui/core gains two exports on the existing ADR-0079 title ladder:

  • resolveNameField(objectDef)which field titles an object: the declared nameField, then its deprecated displayNameField / NAME_FIELD_KEY aliases, else the type-aware derivation. The name-space twin of getRecordDisplayName, 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's resolveTitleField runs a different ladder (primaryField first, a different fallback name set) and is not a dependency of plugin-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

  • Author-declared column lists. A view or grid that declares columns / fields said 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-highlightFields walk. 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.
  • The showcase app. Adding a list view over in objectstack would 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 — deriveTitleField filters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameField on a system field still leads: sys_migration really does point at id, 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/main with 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 #7245 block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameField occurrences 4/4/3 → 0/0/0, and each file's blob hash equal to its origin/main blob); the restore was proven by observation afterwards, not by an exit code — git diff HEAD empty, git status clean, 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/core to packages/core/src (vitest.config.mts:277), so nothing resolves through dist and there is no stale-artifact surface for the ablation to hide behind.

gateresult
targeted vitest, 14 files (both new suites, both edited suites, the blast radius, plus hostFetchedDefaultColumns-6677 and the just-landed ListView.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 below
type-check (core, app-shell, plugin-grid)all three Done; each echoed tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
eslint over the three packages0 errors across 1397 files processed
check-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 cap failed with Error: Test timed out in 15000ms at 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.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is 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 references defaultListColumnsFromObject, defaultColumnsFromObject, getRecordDisplayName or deriveTitleField, plus the ObjectView.* / InterfaceListPage.* suites: Test Files 10 passed (10) / Tests 110 passed (110). The rest of app-shell is 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:

  1. Population — read from eslint's own resolution of the three package directories under the root flat config, not from a hand-listed file set.
  2. Count1397 files, read from --format json output, 0 errors (4154 pre-existing warnings, all warn-level rules such as @typescript-eslint/no-explicit-any; the repo has no formatting gate).
  3. Invarianceeslint.config.js declares no parserOptions.project and no projectService, 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-list speculative $select FLS gating) landed as ac257b3 while this was in progress; its file surface is packages/plugin-list/src/ListView.tsx plus 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

hotlongand others added 2 commits September 2, 2026 10:34
…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.3 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-D-6lW3N_.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.76KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@hotlong
hotlong marked this pull request as ready for review September 2, 2026 09:21
@hotlong
hotlong added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 19f1639Sep 2, 2026
32 checks passed
@hotlong
hotlong deleted the claude/issue-7245-default-view-namefield branch September 2, 2026 09:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default list view synthesized from highlightFields omits the nameField — the showcase Account list shows 14 rows with no name column

1 participant

@hotlong
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field - #7284

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield
Sep 2, 2026
Merged

fix(core,app-shell,plugin-grid): lead the synthesized default list view with the object's name field#7284
hotlong merged 2 commits into
mainfrom
claude/issue-7245-default-view-namefield

Conversation

@hotlong

@hotlonghotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#7245

The defect

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 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:

GET /api/v1/meta/object/showcase_account
nameField = "name"
highlightFields = ["status", "industry", "annual_revenue"]
listViews / list_views / list → all ABSENT
GET /api/v1/meta/view
35 views total, 0 of them bound to showcase_account

One correction to the card's wording, for the record: showcase_account does notdeclarenameField in its source (examples/app-showcase/src/data/objects/account.object.ts has 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: deriveLookupColumns in @object-ui/fields already 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:

data columns
before生命周期, 行业, 年收入
afterAccount Name, 生命周期, 行业, 年收入

(furniture columns # and 操作 unchanged). Measured as rendered DOM headers in defaultColumnsNameFieldLead-7245.test.tsx, which drives a real ObjectGrid against 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 所有记录:

thead th → ["", "#", "客户名称", "生命周期", "行业", "年收入", "操作"]
tbody tr → 14
row 1 → ["", "1", "Vandelay Industries", "Active", "Finance", "6,700,000"]

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 real ObjectGrid against 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 highlightFields verbatim:

facefilecap
console object view (the reported one)packages/app-shell/src/views/ObjectView.tsxdefaultListColumnsFromObject5
interface pagespackages/app-shell/src/views/InterfaceListPage.tsxdefaultColumnsFromObject6
the grid's own derivationpackages/plugin-grid/src/ObjectGrid.tsxfieldsToShownone

Fixed at the producer, not the consumer

@object-ui/core gains two exports on the existing ADR-0079 title ladder:

  • resolveNameField(objectDef)which field titles an object: the declared nameField, then its deprecated displayNameField / NAME_FIELD_KEY aliases, else the type-aware derivation. The name-space twin of getRecordDisplayName, 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's resolveTitleField runs a different ladder (primaryField first, a different fallback name set) and is not a dependency of plugin-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

  • Author-declared column lists. A view or grid that declares columns / fields said 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-highlightFields walk. 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.
  • The showcase app. Adding a list view over in objectstack would 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 — deriveTitleField filters by type only, and leading a default list with a raw id is the objectui#2702 / #2777 regression. A declarednameField on a system field still leads: sys_migration really does point at id, 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/main with 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 #7245 block, every pre-existing assertion in those same files still green. The mutation was proven on disk before measuring (leadWithNameField occurrences 4/4/3 → 0/0/0, and each file's blob hash equal to its origin/main blob); the restore was proven by observation afterwards, not by an exit code — git diff HEAD empty, git status clean, 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/core to packages/core/src (vitest.config.mts:277), so nothing resolves through dist and there is no stale-artifact surface for the ablation to hide behind.

gateresult
targeted vitest, 14 files (both new suites, both edited suites, the blast radius, plus hostFetchedDefaultColumns-6677 and the just-landed ListView.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 below
type-check (core, app-shell, plugin-grid)all three Done; each echoed tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
eslint over the three packages0 errors across 1397 files processed
check-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 cap failed with Error: Test timed out in 15000ms at 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.sh could not take the shared verify lock on this host: no usable flock. The shared verify lock is declared Linux-only (flock is 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 references defaultListColumnsFromObject, defaultColumnsFromObject, getRecordDisplayName or deriveTitleField, plus the ObjectView.* / InterfaceListPage.* suites: Test Files 10 passed (10) / Tests 110 passed (110). The rest of app-shell is 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:

  1. Population — read from eslint's own resolution of the three package directories under the root flat config, not from a hand-listed file set.
  2. Count1397 files, read from --format json output, 0 errors (4154 pre-existing warnings, all warn-level rules such as @typescript-eslint/no-explicit-any; the repo has no formatting gate).
  3. Invarianceeslint.config.js declares no parserOptions.project and no projectService, 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-list speculative $select FLS gating) landed as ac257b3 while this was in progress; its file surface is packages/plugin-list/src/ListView.tsx plus 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

hotlongand others added 2 commits September 2, 2026 10:34
…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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3160.3 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-D-6lW3N_.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)514.46KB117.29KB
core (index.js)5.55KB2.23KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.77KB40.76KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@hotlong
hotlong marked this pull request as ready for review September 2, 2026 09:21
@hotlong
hotlong added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 19f1639Sep 2, 2026
32 checks passed
@hotlong
hotlong deleted the claude/issue-7245-default-view-namefield branch September 2, 2026 09:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default list view synthesized from highlightFields omits the nameField — the showcase Account list shows 14 rows with no name column

1 participant

@hotlong