Skip to content

fix(plugin-charts): the radar mark consults the series (objectui#8157) - #9864

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-8157-radar-series-presentation
Sep 18, 2026
Merged

os-justin merged 2 commits into
mainfrom
claude/issue-8157-radar-series-presentation

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #8157

The radar arm of AdvancedChartImpl.tsx rendered a bare fillOpacity={0.6} and read
nothing off the series, while every cartesian arm resolved its presentation through the
shared seriesStyle helper. Three things @objectstack/spec declares for every
ChartSeries were therefore inert on chartType: 'radar' at once — opacity and
dashArray (both read by normalizeSeries, then discarded) and the whole
variant: 'comparison' treatment, which is declared and documented with no family
restriction.

Anchors re-derived by content, because they had already moved twice

The card measured c14d3a090, triage re-measured 83d4fda16, the dispatch re-measured
592402ea3 — and on 592402ea3 the helper had moved once more. Everything below was
found by content, not by address:

thing found by reading on 592402ea3
the helper const seriesStyle = (s: any, kind: one definition
its call sites grep -c "seriesStyle(" 5, one per cartesian arm
the radar path the chartType === 'radar' branch 0 call sites
the literal fillOpacity={0.6} one, inside the radar mark

The control the dispatch called the sharp fact holds exactly as stated: five cartesian
call sites, zero on the radar path. One premise did move again and is reported below.

What changed

  • The radar arm calls seriesStyle(s, 'radar') and passes strokeOpacity and
    strokeDasharray through to the mark as well as fillOpacity. Radar is a stroked
    mark (stroke={color} sits right above the literal), so unlike a bar or a scatter
    this is a family where a dash actually paints.
  • seriesStyle gained 'radar' in its kind union and an areaLike binding. Radar
    takes the area family's muted defaults because it is the other mark this renderer
    both strokes and fills — no new numbers were invented for it.
  • content/docs/plugins/plugin-charts.mdx: three rows of the per-series options table
    said this renderer strokes line and area marks. That sentence is falsified by this
    change, so radar joins it there, and in the opacity and variant rows.

The fallback, which is the decision this card actually asks for

0.6 was a DEFAULT as much as a bug — a radar fill needs some opacity or an overlay is
unreadable — so the literal became the fallback rather than being deleted:

fillOpacity={pres.fillOpacity ?? 0.6}

⇒ a radar series declaring none of the three keys paints exactly what it painted
before
: fill-opacity 0.6, no stroke fade, no dash. That is the answer triage asked a
claimant to state, and it is pinned from both sides in the new test. Already-drawn radar
charts that author nothing are untouched; only a chart that authors one of the keys
changes, which is the repair.

No cartesian arm changed

areaLike is kind === 'area' || kind === 'radar', so for every cartesian kind it is
exactly kind === 'area' and every existing branch keeps its own value. Measured rather
than argued: ChartRenderer.seriesPresentation.test.tsx (the objectui#7698 pin, 13 cases
over bar / line / area / scatter / combo, both variants) is green on both legs of the
reverse verification below — with the helper edit and without it.

Pin — three reds, with the control green on both sides

packages/plugin-charts/src/ChartRenderer.radarSeriesPresentation-8157.test.tsx. It
asserts on the rendered polygon's attributes, never on the helper's return value: the
helper was not on this path at all, which was the entire defect, so a unit test of the
helper would have passed on the broken tree.

Reverse verification, run from the committed state (git checkout 592402ea3 -- ...
restores the pre-fix file; restore leg is git checkout HEAD -- ..., proved by the blob
hash returning to the HEAD blob and by an empty git diff HEAD). The mutation was proved
to have landed on disk before the run was read: seriesStyle(s, 'radar') count 1 to 0,
and the mutated blob hash differs from the HEAD blob hash.

case pre-fix / ablated with the fix
authored opacity reaches both channels RED — painted 0.6 / absent green — 0.25 / 0.25
authored dashArray dashes the outline RED — stroke-dasharray absent green — 2 6
variant: 'comparison' mutes the overlay RED — identical to the primary green — 0.2 / 0.6 / 4 4
authored keys override the comparison default RED green — 0.9 / 0.9 / 8 4
control: a series declaring none of the three green — 0.6, no fade, no dash green — unchanged
control: the primary beside a comparison overlay green green

Ablated run: Tests 4 failed | 15 passed (19) — the 4 reds, and 15 green made up of the
2 controls plus the whole objectui#7698 cartesian pin.

Verification

All readings below are from the final head 869c3160a (after origin/main was merged in).

command result
pnpm exec vitest run packages/plugin-charts/ Test Files 64 passed (64) · Tests 569 passed (569)
pnpm --filter @object-ui/plugin-charts type-check exit 0 (tsc --noEmit and tsconfig.test.json)
pnpm --filter @object-ui/plugin-charts lint exit 0 — 358 problems (0 errors, 358 warnings), all pre-existing
node scripts/check-changeset-presence.mjs exit 0 — declares .changeset/8157-radar-series-presentation.md
check:control-bytes · check:new-line-citations · check:changeset-claims exit 0
check:doc-fences · check:doc-types · check:test-path-roots exit 0
check:vi-mock-specifiers · check:vi-mock-inherit · check:vi-mock-override-shape exit 0
check:pending-changeset-literals exit 0
node scripts/check-governed-queue-guard.mjs --test (the 4 paths) NOT GOVERNED

The dependency closure was built first (pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-charts^...' build); before it, type-check reported only
Cannot find module '@object-ui/core' and friends, which is an unbuilt tree rather than a
finding.

Declared narrowing. check:doc-snippets and check:doc-examples are NOT MEASURED
here: both refuse to run until 34 packages are built, and building them locally for a
documentation check costs a long hold on the shared serialisation lock. The narrowing is
provable rather than hopeful — git diff -U0 on the .mdx shows three markdown table
rows changed and zero fence lines touched, so no TypeScript block in any document moved.
CI owns both gates.

Relationship to objectui#7698 and objectui#7546

  • objectui#7698 is the variant gate and the Bar / Scatter marks inside the helper's own
    coverage; its census classified this literal as "a literal constant", deliberately
    outside its measured surface. It is untouched here and stays as it is.
  • check:changeset-claims flagged one pending changeset that names this file —
    .changeset/7546-chart-series-keys-declared.md, whose body says the six keys "do real
    work in AdvancedChartImpl.tsx", with opacity / dashArray setting stroke and fill.
    Read as a paragraph: this change makes that sentence more true, not less — before
    it, the claim was silently overstated for one family. No correction is needed and none
    was made.

Acceptance notes

Noted while in this file, deliberately not filed and not fixed here:

  • packages/types/src/data-display.ts carries cross-file line-address citations into
    this package's AdvancedChartImpl.tsx and normalizeChartSchema.ts, and
    .changeset/7546-chart-series-keys-declared.md carries another into
    normalizeChartSchema.ts. The addresses are deliberately not reproduced here — one of
    them points into a span this diff shifts, which is the rot AGENTS.md [WIP] Update documentation for project #11 describes.
    That rule bans the form outright but rules the repair opportunistic, never a sweep,
    so it belongs to whoever next edits those files rather than to this PR, and no card is
    warranted by the rule's own wording. This change adds none:
    check:new-line-citations reports 0 new citation(s).
  • Re-grade probe from triage: a grep for authored radar metadata carrying opacity,
    dashArray or variant: 'comparison' finds none in this repo's docs, fixtures or
    examples, so the p3 grade stands.

Generated by Claude Code

The radar arm of `AdvancedChartImpl` rendered a bare `fillOpacity={0.6}`
and read nothing off the series, while every cartesian arm resolved its
presentation through the shared `seriesStyle` helper. Three things
`@objectstack/spec` declares for every `ChartSeries` were therefore inert
on `chartType: 'radar'` at once: `opacity` and `dashArray` (both read by
`normalizeSeries`, then discarded) and the whole `variant: 'comparison'`
treatment, which is declared and documented with no family restriction.

The control that made the absence a reading: `seriesStyle(` has five call
sites in this file, one per cartesian arm, and had zero on the radar path.

The radar arm now calls the helper like the cartesian arms do, and passes
`strokeOpacity` and `strokeDasharray` through to the mark as well — radar
is a stroked mark, so unlike a bar or a scatter this is a family where a
dash actually paints.

The default face does not move. `0.6` was a DEFAULT as much as a bug (a
radar fill needs some opacity or an overlay is unreadable), so it became
the fallback — `fillOpacity={pres.fillOpacity ?? 0.6}` — rather than
being deleted. A radar series declaring none of the three keys paints
exactly what it painted before: fill-opacity 0.6, no stroke fade, no
dash. That control is pinned on both sides in the new test.

No cartesian arm changed. The helper gained `'radar'` in its `kind` union
and an `areaLike` binding which, for every cartesian kind, is exactly
`kind === 'area'`; radar takes the area family's muted defaults because
it is the other mark this renderer both strokes and fills, so no new
numbers were invented for it.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpHzwH4wTy5sd7ibkD2yq
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/7546-chart-series-keys-declared.md

  • names AdvancedChartImpl.tsx → packages/plugin-charts/src/AdvancedChartImpl.tsx — edited by this change

    Every one of the six is read by normalizeSeries (@object-ui/plugin-charts, normalizeChartSchema.ts:242-255) and does real work in AdvancedChartImpl.tsx — label names the legend entry, variant === 'comparison' selects the muted overlay, opacity / dashArray set stroke and fill, stack becomes Recharts' stackId, yAxis binds the secondary axis. Any consumer of the parse output — objectui check / objectui validate via safeValidateSchema, a JSON schema derived from the mirror, or any pipeline that keeps parse()'s result — lost them outright.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 3a32a7cff (merge-base with origin/main): 3 file(s) changed outside .changeset/, read against 1160 pending declaration(s) that publish a body (1696 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3049.8 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-C-7wkwfX.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) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 545.76KB 130.74KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 215.98KB 59.97KB
fields (index.js) 249.23KB 62.88KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 131.44KB 34.65KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.37KB 65.90KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.81KB 34.17KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 212.61KB 57.90KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 112.74KB 27.70KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.05KB 21.01KB
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) 104.82KB 34.67KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 4.11KB 2.06KB
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.93KB 1.49KB
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 (strict-authoring-face.js) 14.04KB 5.36KB
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-justin
os-justin marked this pull request as ready for review September 18, 2026 12:18
@os-justin
os-justin added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 1fbf63f Sep 18, 2026
38 checks passed
@os-justin
os-justin deleted the claude/issue-8157-radar-series-presentation branch September 18, 2026 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants