Skip to content

fix(plugin-charts): pie / donut / radar / scatter render as the family they name - #8034

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-7401-chart-family-from-type
Sep 6, 2026
Merged

fix(plugin-charts): pie / donut / radar / scatter render as the family they name#8034
os-steve merged 2 commits into
mainfrom
claude/issue-7401-chart-family-from-type

Conversation

@os-steve

@os-steve os-steve commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7401

Implements the ruling recorded on the card (5557016463, director seat, on maintainer authorisation): route CChartRenderer derives the chart family from the schema's own type, and the inert defaultProps go with it. ⛔ A/B are not re-opened and the A radius is not re-measured.

What was wrong

pie-chart, donut-chart, radar-chart and scatter-chart each declared their family as defaultProps: { chartType: … } on the registration, and nothing on the SDUI path has ever read a registration's defaultProps. So ChartRenderer resolved no family and AdvancedChartImpl fell to its 'bar' default: four documented component types were one family, on valid data, with no data-chart-error that could fire.

The five ruled items

1. The family is derived from the schema's own type. ✅ At the resolution point the ruling names — ChartRenderer.tsx:163, chartType: schema.chartType ?? spec.chartType — which is unchanged. The derivation lands in the spec.chartType half, inside normalizeChartSchema, which already carries a type limb (chartTypespecType → a bare spec family name) and is documented as the package's ONE translation point. Two reasons it belongs there rather than inline in ChartRenderer:

  • normalizeChartSchema is exported from the package entry precisely so a consumer can ask what AdvancedChartImpl is actually handed. A derivation that lived only in ChartRenderer would make that exported function answer undefined for a schema the runtime draws as a pie — a published function lying about the runtime.
  • Precedence falls out instead of being re-implemented: schema.chartType still wins at both layers, so plugin-charts:chart with chartType: 'scatter' (the card's control row) is byte-unchanged, and type: 'pie-chart' + chartType: 'line' draws a line.

Both spellings resolve — bare (pie-chart) and namespaced (plugin-charts:pie-chart); the card measured both drawing a bar.

⚠️ Not a -chart suffix rule. A suffix rule accepts the whole cross product of RENDERABLE and would answer funnel for funnel-chart, a keyword nothing registers — an unreachable answer today that becomes load-bearing the day someone registers it meaning something else. CHART_TYPE_KEYWORD_FAMILIES names exactly the keywords that exist and is pinned against the live registry in both directions, so adding a registration without a family entry (or an entry without a registration) is what goes red.

2. The five inert defaultProps: { chartType: … } are removed.index.tsx lines 144 / 155 / 166 / 177 / 188 (chart:bar, pie-chart, donut-chart, radar-chart, scatter-chart), re-measured on f5c8b8e and unchanged from the dispatch's reading.

One mechanism, stated: bar-chart keeps its wrapper. It is registered to ChartBarRenderer, a different component that renders through ChartImpl and never calls normalizeChartSchema at all — so a family entry for it would be inert in exactly the way this card removed. It is deliberately absent from the table, and the docblock at both ends says so. chart:bar is the one that moved onto the derivation: it renders through ChartRenderer, so it is a table entry and now declares bar rather than inheriting it from a default.

The two surviving defaultProps objects in this file (bar-chart, plugin-charts:chart) are sample-data seeds, not family declarations, and are not among the ruled five. They remain unread on the SDUI path — part of ruling item 5's 32-site census, not this card.

3. examples/chart-examples.ts draws a pie. ✅ Confirmed by rendering the example object itself, not by argument — pieChartExample, donutChartExample and radarChartExample are imported into the new pin and rendered through SchemaRenderer. No edit to the examples file was needed; it was correct all along and the renderer was not.

4. The app-shell DOM-leak sweep went red, and the red was correct. ✅ See its own section below.

5. Not A. ✅ No change to how the SDUI path treats registration defaultProps generally. That mechanism is still unread repo-wide.

Ruling item 4 — the expected red, and what it actually was

packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx went red on exactly one target, first run after the fix:

× plugin-charts:scatter-chart  (10026ms)
  plugin-charts:scatter-chart: readiness selector `[data-slot="chart"]` never matched.
  Body was: data-chart-error="scatter-multi-series"
  "A scatter plots one measure. Keep exactly one series: sales, revenue"

That is not a DOM leak. It is PR #7400's refusal firing for the first time on a target that had been passing as a bar chart — the very evidence the card was opened on ("PR #7400's refusal should have reddened this two-series entry and did not"). The sweep's four chart targets were never sweeping pie / donut / radar / scatter; they were sweeping one bar chart four times.

Leaks exposed: zero. With the four renderers rendering as themselves for the first time, pie-chart, donut-chart and radar-chart pass the attribute scan unchanged, and scatter-chart passes once it is handed a schema scatter can draw. plugin-charts stays at 9 targets / 0 leaking / 0 attributes — the reading in that file's table is still true, and is now true about four more renderers than it was. No leak cards to file, and none folded in.

⛔ Nothing was reverted, skipped, quarantined or .skipped. What changed is the scatter target's fixture, on two counts, and both of them add coverage rather than remove it:

  • one series — scatter binds one measure (series[0].dataKey is the YAxis key) and refuses a second by name. Two series here would have swept the refusal's markup, leaving the scatter renderer exactly as unswept as it has been.
  • a numeric x — scatter's category axis is a measure, so name: 'Jan' has no position and every row is unplaceable (no-plottable-points) — the other refusal, and the same blindness one step over.

Both refusals render attribute-clean markup, which is precisely the phantom-clean pass that file's own objectui#5630 section warns about. The comment on SCATTER_DATA records all of this so the next editor does not "simplify" it back onto the shared constants.

The #7396 / #7400 interaction — confirmed, not folded in

scatter-chart now genuinely reaches the scatter arm, so it meets both siblings. ⛔ Neither is folded in.

Mechanism assumptions the dispatch asked to be measured

  1. Is ChartRenderer.tsx:163 still the right resolution point? Yes, and schema.type is in scope there. The derivation is nonetheless placed one layer down for the two reasons in item 1 above; line 163 is untouched and still the point where the family is resolved.
  2. Do both spellings need handling? Yes, confirmed — both are pinned, bare and namespaced.
  3. Does removing the five defaultProps break a reader? No, and WidgetRegistry.ts:189 specifically does not reach them. Traced: it is a write, not a read — it copies a widget manifest's defaultProps INTO ComponentRegistry.register(...). Nothing flows the other way. The tree's only other defaultProps reader is PageDesigner.tsx:173, paletteItem?.defaultProps, which reads DEFAULT_PALETTE — a designer-local literal in the same file that has no defaultProps on any entry and no chart-type entries at all. No gate script or doc reads them either.

Tests

Ablation (reverse verification), run from the committed implementation:

  • mutation — delete the ?? familyFromComponentType(rawType) limb. Proven on disk before running: deleted-text count 1 → 0, injected-text count 1, and git hash-object differing from the HEAD blob. The mutation reaches the code under test through vitest's source alias (vitest.config.mts:421 maps @object-ui/plugin-charts to src), so there is no dist staleness leg.
  • direction: RED — 12 of 17 cases fail. The pin can fail.
  • restoregit checkout HEAD -- ..., proven by an empty git diff HEAD and a blob hash byte-identical to HEAD, not by an exit code.
run result
the new pin, packages/plugin-charts/src/__tests__/chart-family-from-type-7401.test.tsx 17 passed
packages/plugin-charts/ (the whole package) 47 files, 433 passed
the sweep, before the fixture correction 1 failed / 201 passed — the expected red, quoted above
the sweep, after 202 passed
packages/app-shell/ + packages/plugin-report/ + packages/plugin-dashboard/ + examples/schema-catalog/ 770 files, 9188 passed, 1 skipped
pnpm --filter @object-ui/plugin-charts type-check / @object-ui/app-shell type-check 0 / 0
pnpm --filter @object-ui/plugin-charts lint / @object-ui/app-shell lint 0 / 0

plugin-report is in that list because ReportRenderer.test.tsx:44 authors chart: { type: 'pie-chart' } — the other in-repo consumer of these keywords besides the examples file. examples/schema-catalog carries no FAMILY-chart fixture (checked); its two chart fixtures are type: "chart" with an explicit chartType, which the derivation cannot reach.

Gates, derived by hand from the changed paths — ⚠️ scripts/pm/dispatch-gates.mjs does not exist in this repo, it is objectstack-only. Exit codes read before any pipe:

check:control-bytes 0   check:changeset-presence 0   check:changeset-fixed 0
check:changeset-no-major 0   check:changeset-overwrite 0   check:self-import 0
check:phantom-deps 0   check:unreferenced-sources 0   check:side-effects-array 0
check:element-data-source-declaration 0   check:vi-mock-specifiers 0
check:vi-mock-inherit 0   check:spec-symbols 0   check:handler-key-reads 0
check:entry-guard 0   check:doc-types 0   check:i18n-keys 0
check:published-tsconfig-exclude 0   check:lint-coverage 0
check:type-check-coverage 0   check:doc-example-readers 0

check:sdui-registration-pins is left to CI: it weighs apps/console/dist/assets and needs a console build. It reads the registration KEY set, which this PR does not move — all five ComponentRegistry.register calls keep their literal keys and only their defaultProps member is removed.

node scripts/check-governed-queue-guard.mjs --test on the five changed paths: NOT GOVERNED. This PR nonetheless stays a draft — it is dispatched work and the maintainer merges it.

One observation, reported rather than swallowed

On the first full packages/plugin-charts/ run, ChartRenderer.catalogRender-6939.test.tsx > advanced-line-chart renders identically to BASE failed with nothing drew — stuck on the Suspense skeleton at the default 1s waitFor, in a run whose own summary reports 279s of import time across 47 files. The same file passes 5/5 in isolation, the whole package re-ran 47/47 files, 433/433 tests green, and both of that file's fixtures are type: "chart" with an explicit chartType, which the derivation provably cannot reach. Recorded as a load-induced timeout in that pin, not as a result of this change; ⛔ not fixed here and not folded in.

Scope

Clause-②: no — no packages/types or @objectstack/spec file is touched; this is registration mechanics inside plugin-charts. Siblings #7681 #7682 #7690 #7698 #7396 are not folded in. Changeset: @object-ui/plugin-charts patch, naming the four types. ⚠️ The skip-changeset label is inert in this repo and is not applied.


Generated by Claude Code

os-steve and others added 2 commits September 6, 2026 11:21
`pie-chart`, `donut-chart`, `radar-chart` and `scatter-chart` all rendered
as BAR charts. Each registration declared its family as
`defaultProps: { chartType: … }`, and nothing on the SDUI path has ever read
a registration's `defaultProps` — `SchemaRenderer` does not, and the tree's
one consumer (`WidgetRegistry`) writes manifest defaults INTO the registry
rather than reading these back. So `ChartRenderer` resolved no family and
`AdvancedChartImpl` fell to its `'bar'` default: valid data, a confidently
wrong picture, no refusal that could fire.

`normalizeChartSchema` — the package's single translation point, and the
producer of the `spec.chartType` that `ChartRenderer` already resolves
through — now maps the registered chart-type keywords to their families,
both the bare and the namespaced spelling. An explicit `chartType` keeps
precedence. The five inert `defaultProps: { chartType: … }` are removed
with it.

The app-shell DOM-leak sweep swept those four renderers for the first time
as a result; its scatter target now carries a schema scatter can draw.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnpvbdoRisQdRAczkLwnf5
`SchemaRendererProvider` requires one; every schema in the pin carries its
rows inline, so the stub answers with nothing — a chart that started querying
instead of plotting its own `data` shows up as an empty plot, not a pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnpvbdoRisQdRAczkLwnf5
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3186.0 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-DXbrvHg-.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 497.06KB 113.79KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 47.35KB 13.21KB
plugin-charts (index.js) 70.43KB 19.71KB
plugin-chatbot (index.js) 193.53KB 46.05KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.59KB 63.48KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.56KB 56.63KB
plugin-kanban (index.js) 52.30KB 14.49KB
plugin-list (index.js) 113.24KB 27.66KB
plugin-map (index.js) 20.35KB 6.77KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 29.95KB 8.67KB
plugin-tree (index.js) 9.16KB 3.18KB
plugin-view (index.js) 84.33KB 20.75KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@os-steve
os-steve marked this pull request as ready for review September 6, 2026 12:21
@os-steve
os-steve added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit ed4a2f1 Sep 6, 2026
33 of 34 checks passed
@os-steve
os-steve deleted the claude/issue-7401-chart-family-from-type branch September 6, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant