Skip to content

ObjectMap reads schema.data in one place again — normalize the array shorthand at the getDataConfig boundary - #5501

Merged
qq9340100 merged 3 commits into
mainfrom
claude/issue-5305-objectmap-schema-data-dep
Aug 21, 2026
Merged

ObjectMap reads schema.data in one place again — normalize the array shorthand at the getDataConfig boundary#5501
qq9340100 merged 3 commits into
mainfrom
claude/issue-5305-objectmap-schema-data-dep

Conversation

@qq9340100

@qq9340100qq9340100 commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes#5305

ObjectMap's fetch effect carried a second short-circuit beside the props.data one #5003 / PR #5297 handled: it read schema.data directly and tested whether that value was itself an array. eslint reported it as missing dependency: schema.data — the last react-hooks/exhaustive-deps warning on that effect.

The card framed this as a binary: delete the branch if no producer can reach it, or thread the dependency if one can. The measurement selected neither, and this PR explains why before it explains what changed.

What the measurement found

1. The dependency was never actually missing.getDataConfig(schema) already returns schema.data verbatim; the result is memoized on JSON.stringify(rawDataConfig) into dataConfig, and dataConfigis one of the effect's declared dependencies. Authored rows therefore reached the effect before this change. The direct read was a duplicate of an already-threaded value — so adding schema.data to the dependency array would have silenced a warning without changing any behaviour.

2. The array shape is not an ObjectMap accident. It is a deliberate, commented convention in six sibling blocks — ObjectGrid's own getDataConfig ("Check if data is an array (shorthand format)"), ListView ("Also support schema.data as a plain array (shorthand for value provider)"), ObjectTree, ObjectChart, ObjectDataTable, calendar-view-renderer.

3. There is a landed ruling on exactly this question.#5090 raised the same declared-vs-runtime split for object-grid and landed as PR #5108, which corrected the declaration to a ViewData object ("inline rows go under items here rather than in a bare array") and left the runtime array normalization in placeObjectGrid.tsx:393 still carries it on main. The settled position is: not authorable, still normalized at runtime.

4. Deleting was measured, not assumed. With the array handling ablated, an array-shorthand map does not render an empty map — it renders Error: DataSource required for object/api providers. object-map would have become the one block in the family that answers the shorthand with an error box.

The change

The array handling moved into getDataConfig, where ObjectGrid already pins the same normalization. The effect now reads only dataConfig, which is already a dependency, so the warning is gone with no directive and no redundant dependency.

One behavioural consequence, and it is the point: an array under data now yields provider: 'value', so hasInlineData is true and the sibling effect no longer calls dataSource.getObjectSchema() for it. That request's only read site is buildExpandFields() inside the object-provider fetch branch, which an inline schema never reaches — the call was pure waste. The shorthand now behaves exactly like the declared { provider: 'value', items } form it is shorthand for.

packages/types/** was read-only for this card and is untouched — the declared shape stays data?: ViewData.

Producer-side census, with its control probes

Every zero below was counter-probed; a zero without a passing control is not a reading.

searched (X)control (Y)Y hit?X hit?
bare data: / "data": array literal across packages, apps, examples, contentstaticData: / "staticData": array literal, identical scope and pattern shapeyes, 15 (incl. all 3 plugin-map catalog JSON, plugin-map.mdx, a plugin-map test)414 hits, none in an object-map schema
object-map anywhere in the objectstack spec repoobject-grid, same scopeyes, 50
array-shaped schema.data reaching ObjectMap from any in-repo producerArray.isArray(schema.data) consumers, same scopeyes, 12 across 6 blocks0 producers

Per-producer results:

  • ListView's map branch forwards no data key at all (the gantt branch immediately above it does — ...(schema.data ? { data: schema.data } : {}) — so the omission is visible, not assumed). ListView instead passes its rows as the dataprop, {...(ganttOwnsData ? {} : { data })}, which is the dataProp path fix(plugin-map): thread props.data and seed clustering zoom from the applied camera #5297 already handles.
  • The 3 examples/schema-catalog map schemas author staticData, not data.
  • content/docs/plugins/plugin-map.mdx has no bare data: key anywhere; its Schema API documents staticData as an array of any and data as a ViewData config.
  • The console block-binding probe deliberately leaves data unset, and says so in a comment.
  • plugin-map's own 11 test files author data: { provider: 'value', items } throughout.
  • The objectstack spec repo has no object-map fixture; its two object-map hits are English prose in packages/rest meaning "object-shaped map".

So: no in-repo producer sends an array-shaped schema.data to ObjectMap, and none of the spec, the types, or this package's docs offer it. The shape is nonetheless normalized rather than dropped, because six sibling blocks accept it, PR #5108 kept the equivalent normalization for object-grid, and an author or generator that learned the shorthand from object-grid writes it for object-map next.

Contract position

ViewData resolves to @objectstack/spec's ViewDataSchema — a z.discriminatedUnion('provider', […]) over object variants, whose value member additionally declares aliases: { data: 'items', rows: 'items', records: 'items' }. A bare array under data is off-contract twice over, and this PR does not make it authorable: it neither widens a type nor adds an input declaration. It only keeps the renderer's existing tolerance in one place instead of two.

Verification

Gate union at 0fe2fc76b, the final commit:

  • pnpm --filter @object-ui/plugin-map type-checkTYPECHECK_EXIT=0 (pnpm echoed tsc --noEmit && tsc -p tsconfig.test.json, so this was a real run, not a zero-match no-op)
  • npx vitest run --maxWorkers=2 packages/plugin-map/src, from the repo root → VITEST_EXIT=0, Test Files 12 passed (12), Tests 70 passed (70)
  • eslint on the changed source surface → 0 errors; schema.data dep warning present: false. ObjectMap.tsx alone goes 16 problems → 15, react-hooks/exhaustive-deps 3 → 2. The two that remain are both on the pre-existing useMemo at :569 (JSON.stringify in its dependency array), a different hook that the card scoped out.

Ablation. The array normalization was removed, the mutation confirmed on disk by grep count (const authored: unknown 1 → 0, items: authored 1 → 0, restored to 1/1 afterwards by an EXIT/INT/TERM trap), and the suite re-run: 3 failed | 2 passed. The three array-shorthand tests went red and the two controls — the declared { provider: 'value', items } form and the data-prop precedence test — stayed green. No rebuild was needed for this ablation: the tests import ./ObjectMap as a relative source specifier, so nothing resolves through dist/.

Not addressed here

object-map's registry inputs declare 2 of the 12 keys ObjectMapSchema says the renderer reads — the #4648 gap, never applied to plugin-map. That is the designer/declaration surface rather than this fetch effect, so it is recorded separately in #5500 and is out of scope: #5500 remains open.


Generated by Claude Code

…Config boundary
WIP — tests to follow. Removes the fetch effect's direct `schema.data` read
(react-hooks/exhaustive-deps 609:6) by normalizing the array shorthand into the
declared `value` provider in `getDataConfig`, where ObjectGrid already pins the
same shape.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zHsbJoTkTZeJQ5DLbRXrE
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zHsbJoTkTZeJQ5DLbRXrE
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3781.2 KB3867.2 KB
Main entry chunk (gzip)151.2 KB350 KB
Entry fileindex-Dv83zOsj.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 (index.js)10.04KB3.72KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.34KB7.05KB
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)6.35KB2.43KB
auth (index.js)2.77KB1.22KB
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.02KB0.89KB
auth (useIsWorkspaceAdmin.js)3.04KB1.45KB
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)506.94KB113.63KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.34KB
fields (index.js)237.21KB59.50KB
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.22KB3.08KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)30.51KB7.57KB
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.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
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.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)128.51KB32.94KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)242.15KB60.89KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)124.40KB30.26KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)200.75KB54.24KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.64KB27.13KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.52KB20.67KB
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)36.10KB12.26KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
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.08KB1.53KB
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

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@qq9340100@claude