Skip to content

fix(plugin-map): read map config from the declared map input only, not schema.filter - #4331

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-4034-objectmap-filter-config
Aug 11, 2026
Merged

fix(plugin-map): read map config from the declared map input only, not schema.filter#4331
yinlianghui merged 1 commit into
mainfrom
claude/issue-4034-objectmap-filter-config

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#4034. Source thread: objectstack-ai/objectstack#7138 (triage promotion of record; its anchors re-verified below at current main).

getMapConfig probed every filter for a map key and, on a hit, used it as the map's configuration — schema.filter.map, plus a schema.filter.map.style half in the style chain. That shape predates the { name: 'map', type: 'object' } input both registrations declare, and it gave filter two meanings inside one block: the query filter at $filter: schema.filter, and a configuration slot.

Both reads are deleted. The block consumes only what it declares.

Dispatch guard — grep first, 0 live usages

Run before deleting anything, per the triage ruling. Patterns, across this repo (including examples/ and the schema-catalog fixtures) and the sibling objectstack checkout:

patternscopehits
filter\s*:\s*\{[^{}]{0,400}\bmap\s*: (multiline)objectui + objectstack0
"filter"\s*:\s*\{[^{}]{0,400}"map"\s*: (JSON)objectui + objectstack0
filter.map / filter?.mapobjectui3, all unrelated — two Array.prototype.map calls, one comment
filter: block with nested map: (YAML)objectui + objectstack0

All three object-map catalog fixtures (examples/schema-catalog/src/schemas/plugin-map/) author the canonical map. The docs (content/docs/plugins/plugin-map.mdx, package README) document map only and never mentioned the legacy shape, so nothing in them became false. The two objectstack hits for the string "object-map" are the phrase used to mean a JS object-as-map (fields shape) in packages/rest export tests, not this block.

The premise is confirmed, and it is larger than the card recorded

Both anchors were live at b7da03dd7: the style half at ObjectMap.tsx:145, the config probe at :163-164.

The card and triage both describe this as observation-class — "no user hits it today; it needs an undocumented legacy shape". That is true of the filter.map stash, but the probe has a second, live effect that neither had measured:

'map' in schema.filter

in walks the prototype chain, and the ordinary filter is an array. Every array inherits Array.prototype.map, so the probe matched on any array-shaped filter and assigned the inherited function as the MapConfig. The spread of a function is {}, and the declared schema.map sat unreachable in the else branch. Measured:

'map' in [['rating','=','hot']] -> true
config -> function map() { [native code] }
{...config} -> {}

So a map authored with bothmap and filter — two declared inputs, no legacy shape required — lost latitudeField / longitudeField / titleField, failed extractCoordinates on every record, and rendered zero markers behind a "records with missing or invalid coordinates excluded from the map" banner. The only console output was [ObjectMap] Invalid map configuration: from the Zod parse of a function.

This also corrects the mechanism the card predicted for the objectstack#7121 binding path. The card expected the merged filter to carry no map key and the map to fall back to the default config. Measured, the merged node is an array:

["and",[["owner","=","me"]],[["rating","=","hot"]]]

so the probe hitArray.prototype.map there too — the result was not the default config but an empty one. Same outcome for the user (no markers), different cause; the test pins the real shape.

What changed

  • Deleted the config probe (:163-164) and the filter.map.style half (:145).
  • Kept intact, as the card requires: the declared schema.map config, the top-level locationField / latitudeField branch, and the style / mapStyle top-level reads.
  • filter keeps exactly one meaning in the file: $filter: schema.filter is untouched, and the filter is not rewritten — a field genuinely named map still filters on it.
  • No silent removal: a schema still carrying the legacy stash gets a dev-mode warning naming the shape and pointing at schema.map.

The warning follows the existing warn-once diagnostic idiom of app-shell/src/views/metadata-admin/predicate.ts (#4049) rather than a new logging dialect: defensive globalThis.process?.env?.NODE_ENV dev gate (the form plugin-dashboard uses, since process may not exist in a pure browser bundle), a [ObjectMap] prefix matching this file's existing warn, and a warn-once memo — getMapConfig runs on every render, and a flooding warning is a muted one. It is deliberately narrow: own properties only, so an inherited map method never trips it, and object-valued only, so filter: { map: 'x' } reads as a filter on a field named map.

Production behavior is unchanged beyond the configuration no longer being read.

Tests

New packages/plugin-map/src/ObjectMap.filterConfig.test.tsx, written red-first with the direction predicted before each run.

Reverse verification by restoring ObjectMap.tsx to origin/main with the new tests kept (commit-then-revert, never git stash) — prediction was "the 7 behavioral tests go red, the 6 controls stay green", and that is exactly what happened, identical to the pre-implementation baseline:

groupbeforeafter
legacy stash ignored, falls to the default configredgreen
the default config is genuinely live (not "no config")redgreen
dev warning names the shape and points at schema.mapredgreen
filter.map.style half no longer readredgreen
control: declared schema.map applies, reads map.style, no warninggreengreen
control: top-level latitudeField branchgreengreen
control: $filter receives array / object / map-keyed filters verbatimgreengreen
array filter alongside map renders markers (the live defect)redgreen
no warning from mere array-ness (no own map key)redgreen
merged and binding node keeps the config, no false-positive warningredgreen

The merged-node test pins the mechanism so it cannot come back: the node has no ownmap key, yet 'map' in it is true.

Commands:

pnpm exec vitest run packages/plugin-map/ -> 5 files, 24 tests passed
pnpm --filter '@object-ui/plugin-map' type-check -> clean (tsc --noEmit && tsc -p tsconfig.test.json)
pnpm --filter '@object-ui/plugin-map' lint -> 0 errors (75 pre-existing warnings, unchanged)
node scripts/check-changeset-presence.mjs -> 1 changeset declared
node scripts/check-changeset-no-major.mjs -> no major bump

Downstream consumer sweep — only apps/console and apps/site depend on @object-ui/plugin-map; console's public-contract and public-block-binding-reach suites pass (27 tests). Dependency closure was built before type-checking.

Changeset: patch for @object-ui/plugin-map.


Generated by Claude Code

…not `schema.filter` (#4034)
`getMapConfig` probed every filter for a `map` key and used the hit as the
MapConfig (`schema.filter.map`, plus a `schema.filter.map.style` half in the
style chain) — a shape predating the `{ name: 'map', type: 'object' }` input
both registrations declare, and a second meaning for `filter` inside a block
that also sends it to the query as `$filter`.
The probe was `'map' in schema.filter`, and `in` walks the prototype chain.
The ordinary filter is an array, every array inherits `Array.prototype.map`,
so the probe matched and handed the component a function as its config; the
spread of a function is `{}`, and the declared `schema.map` sat unreachable in
the `else` branch. A map authored with both `map` and `filter` — two declared
inputs, no legacy shape needed — therefore lost its field mapping and rendered
zero markers. Same for the `and` node objectstack#7121's dataSource binding
merges, which is likewise an array.
Both legacy reads are deleted. The declared `map` config, the top-level
`locationField`/`latitudeField` branch and the `style`/`mapStyle` reads are
untouched, and `filter` reaches the query verbatim.
A schema still carrying the legacy stash gets a dev-mode warning naming the
shape and pointing at `schema.map`, following the warn-once diagnostic idiom
of `metadata-admin/predicate.ts` (#4049): own-property and object-valued only,
so an inherited `map` method or a field genuinely named `map` never trips it.
Source thread: objectstack-ai/objectstack#7138.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
@vercel

vercelBot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 11, 2026 3:23pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)29.5 KB350 KB
Entry fileindex-DWcWpAnZ.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.56KB3.59KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.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)489.10KB108.43KB
core (index.js)3.04KB1.15KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)150.04KB39.79KB
fields (index.js)228.45KB56.62KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)16.38KB5.47KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)4.52KB1.96KB
layout (index.js)38.98KB10.85KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.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.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.23KB12.45KB
plugin-charts (index.js)62.18KB17.67KB
plugin-chatbot (index.js)180.33KB42.79KB
plugin-dashboard (index.js)121.58KB31.58KB
plugin-designer (index.js)210.91KB42.67KB
plugin-detail (index.js)238.95KB59.76KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)114.58KB27.68KB
plugin-gantt (index.js)164.14KB39.98KB
plugin-grid (index.js)188.00KB49.94KB
plugin-kanban (index.js)48.60KB13.41KB
plugin-list (index.js)109.93KB26.65KB
plugin-map (index.js)18.05KB5.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.60KB10.58KB
plugin-timeline (index.js)26.21KB7.52KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)23.71KB7.96KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.23KB0.66KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
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-retry.js)4.32KB2.02KB
types (index.js)3.05KB1.52KB
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 (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
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 11, 2026 15:36
@yinlianghui
yinlianghui added this pull request to the merge queueAug 11, 2026
Merged via the queue into main with commit b388d0eAug 11, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4034-objectmap-filter-config branch August 11, 2026 15:36
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 把 schema.filter 当地图配置的容器读(getMapConfig 读 filter.map / filter.map.style):filter 是过滤器,不是配置槽

2 participants

@yinlianghui@claude