Skip to content

fix(plugin-map): resolve marker titles through getRecordDisplayName - #5975

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-5953-objectmap-display-name
Aug 24, 2026
Merged

fix(plugin-map): resolve marker titles through getRecordDisplayName#5975
yinlianghui merged 1 commit into
mainfrom
claude/issue-5953-objectmap-display-name

Conversation

@yinlianghui

@yinlianghuiyinlianghui commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Fixes#5953

ObjectMap was the only one of the four view renderers outside ADR-0079's unified
record display-name resolver. getMapConfig filled an absent title binding with the
string literal 'name', and the marker transform then did a bare record[titleField]
read — so for every object whose display field is not literally name, each marker
popup titled itself undefined. This converges it onto @object-ui/core#getRecordDisplayName,
the same call ObjectKanban.tsx:301, ObjectCalendar.tsx:356 and ObjectGantt.tsx:600
already make.

The change

At the marker read site:

consttitle=getRecordDisplayName(objectSchema,record,{titleField: mapConfig.titleField,fallback: 'Marker',});

The declared binding is passed through as the resolver's explicit titleField
option rather than dropped, so an authored map.titleField still wins outright — the
resolver checks that option first. No authoring surface changes and no new map config
keys: this is a bug fix, not a widening.

Three supporting edits:

  1. getMapConfig, flat branchtitleField: schema.titleField || 'name' loses the
    || 'name'. A forged binding is not merely useless, it is harmful: it outranks the
    object's own declared nameField at step 0 of the resolver.
  2. getMapConfig, default branch — the same literal, titleField: 'name', removed.
    Bounded in-place fix, declared: the issue names only the flat-branch literal, but
    this is the same literal in the sibling branch of the same function, the same defect
    class, and the correct shape is pinned by the same ADR-0079 evidence. Leaving it would
    have kept the bug alive in a narrower form — a stray name key on a record beating a
    declared nameField. The surrounding coordinate guesses (latitude/longitude/
    location) stay: nothing but this component can read a location out of an
    unconfigured record, whereas a title is precisely what the resolver reads better.
  3. objectSchema joins the marker memo's dependency array. It lands from an async
    fetch after first paint, and the titles are now resolved from it; omitting it would
    have left first-painted markers titled from a null object definition permanently.

The pinned test — what its subject actually is

The card flagged that ObjectMap.listViewMapConfigReach.test.tsx "currently pins the
undefined/placeholder behaviour". Measured, it does not. Every one of its six arms
declares titleField: 'title' and asserts a positive title ('Install rooftop unit',
'Bldg A'). The undefined / 'Marker' mention is prose in a comment at line 138,
narrating history — there is no assertion on it anywhere in the file, and a repo-wide
grep for a 'Marker' string assertion returns only that comment and the source line.

So the pin's subject is unambiguously reach — that a declared map block arrives at
getMapConfig and drives the read — and it observes the bug not at all, incidentally or
otherwise. It exercises the one path that was never broken: an authored binding. Nothing
in it is deleted, loosened or touched, and it stays green unchanged.

Placeholders: two competed, one line splits them

Once the resolver arrives it brings its own record-number floor — Record # followed by
the record's id, e.g. Record #77 — alongside this component's 'Marker'. They are
split by whether the record has an id, because that is exactly the split in how much
either can say:

  • record has an id → the record-number floor.'Marker' was always weak here — every
    pin on a map is a marker, so the word separates none of them, while the id names
    exactly one record.
  • record has no id at all → 'Marker'. Passed as the resolver's declared fallback
    option, displacing its generic 'Untitled', which is the worse word for this surface.

fallback being a declared parameter of getRecordDisplayName, resolving this needed
no change to @object-ui/core — the shared surface the card fenced off.

Tests

New: packages/plugin-map/src/ObjectMap.markerTitle.test.tsx, 7 arms — the flat form
with no titleField (the card's literal symptom), the declared block with no
titleField, the authored-titleField-still-wins invariant, a titleFormat template,
inline value data where no object definition is ever fetched, and the two placeholder
arms.

Reverse-verification. Reverting only ObjectMap.tsx to origin/main and re-running
the file: 6 failed | 1 passed — the direction predicted before the run. The one
survivor is the authored-titleField arm, which is correct: it pins an invariant that
was never broken, not the defect. The mutation was confirmed on disk by anchored greps
in both directions before the run (deleted text restored: 1 hit; injected text gone: 0
hits; the || 'name' literal back: 1 hit), and the restore was trapped on EXIT INT TERM.
No rebuild is involved on this leg: the root vitest.config.mts aliases every
@object-ui/* specifier to that package's src/, so the run resolves source directly
and no dist/ can go stale between legs.

Gates below all ran on 9b93dea with a clean tree.

gateverdict line
vitest run packages/plugin-map/Test Files 14 passed (14) / Tests 83 passed (83)
pnpm --filter @object-ui/plugin-map type-checkexit 0 (tsc --noEmit && tsc -p tsconfig.test.json)
pnpm --filter @object-ui/plugin-map lint✖ 110 problems (0 errors, 110 warnings)
check-control-bytes✅ OK (scanned 4952 tracked text file(s); skipped 85 binary)
check-changeset-presence✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
check-changeset-fixed / check-changeset-no-majorexit 0 / ✅ No changeset declares a major bump
flatten-producer consumers (4 files, plugin-view + plugin-list + app-shell)Test Files 4 passed (4) / Tests 39 passed (39)

Lint was run over the whole plugin-map package — the exact unit CI's lint job
covers — not over my three files. The 110 warnings are all pre-existing
no-explicit-any / no-unused-vars across the package's 21 files; the new test file
contributes 7 of them, in the same vi.mock idiom every sibling file in the package uses
(they run 5–12 each), and .github/workflows/lint.yml sets no --max-warnings.
Other packages' lint jobs were left to CI, and that narrowing excludes nothing: the file
population came from eslint's own resolution rather than a hand-written list, the count
of 21 was read from --format json, and eslint.config.js enables no type-aware
linting
(no project / projectService in languageOptions), so no rule's verdict on
an untouched file in another package can move as a result of this diff.

The repo-wide pnpm check farm is CI's run, not re-derived here.

Note: an earlier revision of this body spelled the record-number floor with an angle-bracket
placeholder; GitHub's body sanitizer ate the bracketed fragment on write. Respelled above.


Generated by Claude Code

ObjectMap was the only view renderer outside ADR-0079's unified record
display-name resolver. getMapConfig filled an absent title binding with the
string literal 'name' and the marker transform did a bare record[titleField]
read, so every object whose display field is not literally 'name' titled every
marker popup undefined.
Both literals are removed and the read site calls getRecordDisplayName, passing
the declared binding as its explicit titleField option so an authored
map.titleField still wins. objectSchema joins the marker memo's deps, since it
arrives from an async fetch after first paint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSoz9uGhaaSgiq3hshtN7L
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3232.3 KB3990.2 KB
Main entry chunk (gzip)153.6 KB350 KB
Entry fileindex-D4Qp-lc4.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)10.13KB3.77KB
app-shell (runtime-config.js)13.57KB4.78KB
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)504.75KB114.32KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)165.30KB45.79KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
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)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
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)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.35KB34.44KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)244.00KB61.86KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.15KB39.88KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.86KB27.22KB
plugin-map (index.js)20.10KB6.64KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.57KB20.74KB
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)3.77KB1.33KB
react (SchemaRenderer.js)52.40KB17.45KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
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)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
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)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.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 (index.js)3.88KB1.85KB
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

@yinlianghui
yinlianghui marked this pull request as ready for review August 24, 2026 10:31
@yinlianghui
yinlianghui added this pull request to the merge queueAug 24, 2026
Merged via the queue into main with commit 2aac61fAug 24, 2026
23 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-5953-objectmap-display-name branch August 24, 2026 10:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ObjectMap is the only view renderer that does not resolve marker titles through getRecordDisplayName — it reads a hard-coded 'name' key instead

2 participants

@yinlianghui@os-litant