Skip to content

refactor(plugin-grid,core): the bulk dialog's data-source rule derives from core, not a fourth private copy - #5314

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-4815-data-source-widget-types-convergence
Aug 19, 2026
Merged

refactor(plugin-grid,core): the bulk dialog's data-source rule derives from core, not a fourth private copy#5314
os-support-ai merged 1 commit into
mainfrom
claude/issue-4815-data-source-widget-types-convergence

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes#4815

packages/plugin-grid/src/components/bulkParamToField.ts held its own DATA_SOURCE_WIDGET_TYPES — the fourth hand-maintained answer to one question ("which widget has to query records, so it must be handed a DataSource and a reference target"), and the only one whose member set matched none of the other three. Nothing anywhere could detect the drift. Same shape as the convergences #4770 and #4790 each landed on another surface.

Verified on current origin/main (de4e29a) rather than taken from the card: the owner cell is already gone (#4814 / PR #4915USER_WIDGET_TYPES reads new Set(['user']) with a tombstone above it), so the remaining ask was exactly the convergence.

What changed

One definition. The reference half is no longer restated here: widgetNeedsDataSource() reads EXPANDABLE_FIELD_TYPES from @object-ui/core — the same object the $expand builder, the predicate-record projection and the object form's needsDataSourceWiring already read. All three consumers of the rule now go through that one predicate:

consumerwhat it decides
isLookupishParamlabel prefetch + option source in BulkActionDialog
fieldNeedsDataSourcethe dataSource prop threaded into the widget (BulkActionDialog.tsx:600)
bulkParamToFieldthe reference_to / display_field branch of the field shape

packages/core/src/utils/expand-fields.ts gains the fourth consumer in its "one family, N consumers" TSDoc, so the shared table names every surface that reads it.

No reachable behaviour change — which is why the changeset is patch, and it is the roll-back clause taken seriously rather than assumed:

  • tree is the one cell where the two tables differed, and it can never be a widget key on this path: it is absent from the fields widget map and mapFieldTypeToFormType sends it to field:lookup, so a tree param reaches the rule as lookup. Counter-probed (0 hits for tree in fieldWidgetMap while lookup / master_detail / user give 3 from the same extraction) and pinned by a test, so if someone ever registers a real tree widget this flips loudly instead of silently.
  • The divergence in the other direction is deliberately preserved. The object form also wires object-ref / filter-condition / recipient-picker — widget hints no object schema can declare and no bulk param produces. Absorbing them would change which widgets receive a DataSource here: a behaviour change, not a convergence. finding(plugin-grid): DATA_SOURCE_WIDGET_TYPES 是「哪些 widget 要 DataSource」的第四份私有副本 —— 与表单规则不同集、零 gate #4815's own analysis is the spec for that cell, and a test pins it shut.

The pin is an identity pin

expect(...).toEqual([...]) on the members would have passed against exactly the defect this PR removes — four copies that happen to agree today — so it would have been worse than no test. The pin instead spies on the has of the Set object core exports and asserts this module called that object.

Proved to discriminate, not assumed. Reverse verification ran two legs, each predicted before running and matched exactly:

legpredictedobserved
A — revert the source, keep the teststhe 2 identity assertions red, 13 greenTests 2 failed | 13 passedexpected [] to include 'lookup'
B — substitute a member-identical copy (new Set([...EXPANDABLE_FIELD_TYPES]), members equal by construction)the same 2 redTests 2 failed | 13 passed — same two

Leg B is the one that matters: every membership-shaped assertion in the file stayed green against that copy, which is the empirical version of "a value-equality assertion reports a convergence that isn't there".

Stated rather than counted: the other 3 new assertions are green on both legs by design. They pin the normalization contract (every core member is closed under resolveBulkParamWidgetType) and the two deliberate boundaries above — they guard a future widening, not this convergence.

Rebuild question, per leg: no build artifact sits between any edit and the runner. The canonical invocation (pnpm exec vitest run ... from the repo root, which is what CI shards) loads the root vitest.config.mts, whose resolve.alias maps @object-ui/core to packages/core/src; the test imports bulkParamToField by a relative specifier. Proved directly rather than read off the config: with packages/core/dist moved aside entirely the run is still 15/15 green. Both red legs above are therefore self-proving — they could not have gone red unless the edit reached the runner. The pin also holds under the other resolution regime (a package-cwd run, where @object-ui/core resolves through package exports to dist): 15/15.

Verification

All at 455a274f6, after the final commit:

  • pnpm exec vitest run over the 8 affected files (bulk param + dialog params/record/multiple/fanout, core expand-fields + unmaterialized-fields, the form's data-source wiring): 8 files / 80 tests passed
  • pnpm --filter @object-ui/plugin-grid --filter @object-ui/core type-check: both Done (script name echoed, so it is not a zero-match silent pass)
  • eslint on the three changed files: 0 errors (5 pre-existing no-explicit-any warnings on untouched lines)
  • check:control-bytes, check:phantom-deps, check:self-import, check:esm-specifiers: pass; plus a direct control-byte scan of the changed files

Out of scope, filed not fixed

Generated by Claude Code


Generated by Claude Code

…s from core, not a fourth copy (#4815)
`bulkParamToField` held a private `DATA_SOURCE_WIDGET_TYPES` — the fourth
hand-maintained answer to "which widget must be handed a DataSource", and the
only one matching none of the other three. It now reads
`@object-ui/core`'s `EXPANDABLE_FIELD_TYPES` through one predicate, feeding all
three consumers of the rule.
No reachable behaviour change: `tree` (the one cell the two tables differed on)
can never be a widget key here — it is absent from the fields widget map and
resolves to `lookup` — and the form-only widget-hint pickers are deliberately
NOT absorbed, since pulling them in would change which widgets get a DataSource.
Pinned by identity, not membership: a spy on the `has` of core's exported Set,
so a member-identical private copy fails the test.
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)25.3 KB350 KB
Entry fileindex-lfbDrWmx.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.83KB3.70KB
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)29.33KB7.05KB
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)40.21KB10.79KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.71KB1.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.88KB
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)506.26KB113.31KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.34KB
fields (index.js)237.07KB59.46KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.42KB1.39KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)29.43KB7.15KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
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)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.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
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)127.92KB32.80KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)241.46KB60.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)124.19KB30.20KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)198.22KB53.28KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.66KB27.13KB
plugin-map (index.js)20.08KB6.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.33KB20.61KB
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.44KB0.22KB
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

@os-support-ai
os-support-ai marked this pull request as ready for review August 19, 2026 13:20
@os-support-ai
os-support-ai added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit 2533ec5Aug 19, 2026
22 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-4815-data-source-widget-types-convergence branch August 19, 2026 13:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-support-ai@claude