Skip to content

fix(dashboard): honour a widget's declared chartConfig on the inline chart relays - #9202

Merged
os-tesla merged 3 commits into
mainfrom
claude/issue-4044-dashboard-chartconfig-forwarding
Sep 11, 2026
Merged

fix(dashboard): honour a widget's declared chartConfig on the inline chart relays#9202
os-tesla merged 3 commits into
mainfrom
claude/issue-4044-dashboard-chartconfig-forwarding

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Part of #4044

Continues the branch a previous seat pushed before it was killed mid-flight (commit dd461a0d3, which left no report). Nothing in that commit was inherited on trust: every claim it made is re-measured below on this tree, and one of them turned out to be wrong in a way that mattered.

Dispatched on the ruling in objectui#4044 comments 5617466747 and 5617614527, not on the card body's three-route menu, which the ruling supersedes. Clause-2: no.


What lands

DashboardWidget.chartConfig is declared as the spec's full ChartConfigSchema — 14 authorable keys, measured off @objectstack/spec@17.4.0 at runtime — on every dashboard widget. The two INLINE relays, DashboardRenderer and DashboardGridLayout (they compose the chart node for a widget bound to inline rows or to a provider: 'object' aggregate, neither of which is an ADR-0021 dataset), mentioned chartConfig zero times. Both now lower it through chartConfigPresentation — the same @object-ui/core whitelist the dataset path already uses, imported rather than forked.

Two commits:

  1. dd461a0d3 — the forwarding itself, plus a seam pin, a DOM pin and a showLegend drawn pin.
  2. 139997756 — the evidence that commit was missing: a dashboard-surface drawn pin for the six plot-internal keys, the aria measurement, and three comments corrected because they now read false.

A. Per-key measurement — the card's premise is stale in BOTH directions

The card (filed 2026-08-04, migrated 2026-08-10) says only showLegend lands on the dashboard path. Measured at the merge-base with one command, every zero carrying a lit control in the same sweep:

6a8e2a31 DashboardRenderer.tsx    chartConfig=0  chartConfigPresentation=0  [control] widget=173  lines=1299
6a8e2a31 DashboardGridLayout.tsx  chartConfig=0  chartConfigPresentation=0  [control] widget=88   lines=626
6a8e2a31 DatasetWidget.tsx        chartConfig=7  chartConfigPresentation=4  [control] widget=85   lines=1658

So the premise is wrong twice over. The dataset path is no longer a one-key path — objectstack#7016 landed the per-key forwarding there on 2026-08-09, five days after the card was written, and DatasetWidget.tsx is byte-identical before and after this PR. The relays were worse than the card says: not "only showLegend", but nothing at all, showLegend included.

Emitted keys measured functionally, by calling chartConfigPresentation with each declared key alone:

key declared dataset path before relays before relays now read by the renderer (measured)
title yes forwarded no yes ChartFrame heading
subtitle yes forwarded no yes ChartFrame subheading
description yes forwarded no yes role="img" + aria-label on the chart container
colors (array) yes forwarded no yes positional palette, off the sector fills
colors (record) yes forwarded no yes, as categoryColors per-category fill wins over the palette
height yes forwarded no yes inline height on the chart container
showLegend yes forwarded (#3135) no yes legend wrapper present/absent
showDataLabels yes forwarded no yes Recharts LabelList texts
annotations yes forwarded no yes ReferenceLine / ReferenceArea
interaction yes forwarded no yes Brush, and the tooltip wrapper
type yes refused no refused n/a — see B
xAxis / yAxis / series yes refused no refused not ours — objectstack#17385 owns the precedence question, and the whitelist emits none of the three, so this change cannot pre-empt it
aria yes refused no refused nothing — see B

Nine keys land on the relays (ten counting the categoryColors arm). Eight of the nine on the ruling's DO-NOW list are delivered; the ninth is aria.


B. aria and type — the CALL measured, not the name

aria is refused, and here is the measurement behind the refusal

aria is on the ruling's DO-NOW list, so "nothing reads it" is measured rather than asserted, in both spellings it could travel in:

  • nested, as the spec object { ariaLabel, ariaDescribedBy, role } on the chart node;
  • flattened, onto the node's own ariaLabel / ariaDescribedBy / role — members BaseSchema already declares and SchemaRenderer.resolveAriaProps already converts into aria-label / aria-describedby / role. That route needs no new declaration anywhere, which is exactly why it had to be measured rather than dismissed.

Rendering the real chain with each spelling on the chart node produced, for both:

anyAriaLabel: []   anyRoleFigure: 0   anyAriaDescribedBy: 0   chart role: null

and the lit control in the same probe — description, travelling the same whitelist onto the same node — produced:

anyAriaLabel: [["DIV","Invoice value by status"]]   chart role: "img"

Then the same thing from the other end: two ablation legs that forward aria on purpose from both relays, nested and flattened. In both runs the only red was the seam refusal assertion; the DOM assertion "an authored chartConfig.aria reaches no attribute on this surface" stayed green. Forwarding it changes nothing on screen.

Cause: ChartRenderer destructures { schema, onChartClick } and drops every other prop; AdvancedChartImpl declares no aria prop; normalizeChartSchema names neither. The chart's one accessible name comes from description.

So delivering aria needs a READER inside @object-ui/plugin-charts — a new member on a published face — and it also has to answer to the accessible name description already sets on the same element. The claim comment on this card leaves exactly this door open, and this is it: reported, not guessed, and not implemented. Forwarding it without a reader would have moved declared-but-not-delivered one layer down, which is the disease this card exists to cure.

type is refused, and that is measurable too

Not on the ruling's DO-NOW list. On this path the chart family travels as chartType, set from the widget's own type through classifyWidgetType; the node's type is the component-registry dispatch key (chart / object-chart). Both ablations confirm the refusal pin can fail: emitting chartConfig.type as chartType, and emitting it under its own name type, each redden "emits no chart family from chartConfig.type — the widget type still picks it" and nothing else.


CONTROL — the react tier's ObjectChart flat-prop path

Named by the ruling, measured as blob identity against the merge-base rather than by reading the diff:

IDENTICAL  packages/plugin-charts/src/ObjectChart.tsx         32b7ef61c
IDENTICAL  packages/plugin-charts/src/ChartRenderer.tsx       13e49a047
IDENTICAL  packages/plugin-charts/src/AdvancedChartImpl.tsx   ae52b1d01
IDENTICAL  packages/plugin-charts/src/normalizeChartSchema.ts 3217874418
IDENTICAL  packages/plugin-charts/src/ChartContainerImpl.tsx  117306bae
IDENTICAL  packages/plugin-charts/src/index.tsx               12a42f173
IDENTICAL  packages/plugin-charts/src/types.ts                be0839f0d
IDENTICAL  packages/core/src/utils/chart-presentation.ts      48a0d0eef
IDENTICAL  packages/react/src/SchemaRenderer.tsx              67a602afe

Every file on that path is byte-identical. @object-ui/core's whitelist is untouched too, so the dataset path and the report renderer are unchanged by construction. Behavioural half: packages/plugin-charts/ and packages/plugin-dashboard/ run green in full (below).


The pin shape, and the thing the first commit got wrong

The ruling asks for a rendering assertion on the dashboard surface for every forwarded key, never a "the prop was passed" assertion. The first commit left the six plot-internal keys (colors, categoryColors, showDataLabels, annotations, interaction, showLegend) with drawn evidence only in plugin-charts/src/ChartRenderer.dashboardChartConfig.test.tsx, which hand-builds its schema. Measured: delete the forwarding from both relays and every test in that file still passes. It pins the chart block, never this seam.

DashboardChart.chartConfigMarks-4044.test.tsx closes that: widget metadata in, drawn Recharts marks out, both relays, no renderer stub anywhere in the chain.

Its header comment claimed the marks could only be pinned inside plugin-charts, because a recharts module mock is needed and recharts resolves there alone. The premise is still true — re-measured, require.resolve('recharts') from packages/plugin-dashboard is MODULE_NOT_FOUND — but the conclusion was wrong. ResponsiveContainer seeds its size from getBoundingClientRect() on its own element, read synchronously inside its resize effect, consulting ResizeObserver only for later changes; the repo's happy-dom polyfill for that observer is a no-op, which is why nothing painted. Sizing that one element is enough, and needs no module mock.

Scoped to that element, never blanket — also measured, because the blanket form looks like it works: with every element answering 480x320, Recharts' own axis-label measurement reads 480x320 too, the x-axis claims the whole box, and the plot renders with a clip rect of height="0" and no marks while .recharts-surface is present. A file that waited on the surface and then asserted "no data labels" would have passed for that reason.


Ablation matrix

Every leg mutates both relays at the same anchor, runs the three dashboard pin files, then restores with git checkout HEAD -- path and proves the restore by blob hash and an empty git diff HEAD. Each mutation is proven on disk before the run (anchor count 1 before, 0 after; marker count 1 after).

mutation seam assertion reddened rendering assertion reddened
whole forwarding removed 24 22 (46 total across the three files; 0 in plugin-charts)
drop title forwards the chart titles and the accessibility description draws chartConfig.title / .subtitle above the plot
drop subtitle same same
drop description same announces chartConfig.description as the chart graphic accessible name
drop height forwards an explicit plot height and drops a non-positive one applies chartConfig.height over the relay height class
drop colors lets an array colors override the relay default palette paints the marks from an array colors palette
drop categoryColors lowers a record colors as the per-category map, not as the palette paints per-category colours from a record colors map
drop showLegend forwards showLegend and showDataLabels in both directions draws no legend when showLegend is false, and draws one otherwise
drop showDataLabels same prints each point value on the mark when showDataLabels is on
drop annotations forwards annotations and the interaction toggles draws a reference line / a reference area
drop interaction same adds the range selector; removes the hover tooltip
ADD aria (nested) ignores chartConfig.aria none — the finding, not a hole
ADD aria (flattened) ignores chartConfig.aria none — same
ADD type (as chartType) emits no chart family from chartConfig.type n/a, refused key
ADD type (as type) emits no chart family from chartConfig.type n/a, refused key

No forwarded key has an assertion that survives its own deletion. The control arms (undeclared / off) deliberately stay green in every leg — that is what makes the positive arms readable.


Verification

Everything below ran on the final head 9adbdfaeb (this branch merged with origin/main at 1f4e02995; synced by merge, never rebase).

  • pnpm exec vitest run packages/plugin-dashboard/ packages/plugin-charts/ under the shared verify lock: 174 files, 1623 tests, all passed, lock verdict line VERDICT command-exit 0.
  • pnpm turbo run type-check --filter=@object-ui/plugin-dashboard --filter=@object-ui/plugin-charts (tsc --noEmit plus tsc -p tsconfig.test.json, whose include names src/**/*.test.tsx, so the new pin is type-checked): 15 tasks successful, VERDICT command-exit 0.
  • eslint . in each of the two packages: exit 0, 0 errors (470 and 368 pre-existing no-explicit-any warnings, unchanged in kind).
  • Gates derived by hand from this repo's package.json and .github/workflows/ (this repo has no scripts/pm/dispatch-gates.mjs), each read off its own printed verdict line, never a bare exit variable: check-changeset-presence (6 source files of 2 released packages changed, 1 changeset declared), check-changeset-claims, check-control-bytes (7404 files), check-test-path-roots, check-new-cross-file-line-citations (0 new citations), check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, markdown-test-inputs.mjs --audit (47 candidates, all adjudicated — the new pin reads no markdown, so ADJUDICATED needs no entry, and the gate's own audit is what says so), check-governed-queue-guard --test on the changed paths (NOT GOVERNED).
  • Repo-wide gates not run locally: CI owns the farm.

Acceptance notes

  • The dataset path has the same evidence gap this PR closes for the relays, and it is deliberately left alone. DatasetWidget.chartConfig.dom.test.tsx pins only title / subtitle / description / height; its five plot-internal keys, forwarded since objectstack#7016, still have no dashboard-surface drawn assertion. The technique in the new marks file transfers verbatim. Out of this card's scope (the ruling's DO-NOW is the not-yet-forwarded keys, and the relays are what was unforwarded), so: noted, not filed.
  • The changeset said "Three keys are deliberately still not forwarded" and then listed five. Corrected to five, with the aria measurement written into it.
  • There is no package: plugin-dashboard label in this repository, so the package labels here are what exists.

Not enqueued, no auto-merge, stays in draft — the dispatching seat arms it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ


Generated by Claude Code

…chart relays

`DashboardWidget.chartConfig` is declared as the spec's full
`ChartConfigSchema` on every dashboard widget, but only the ADR-0021
dataset path (`DatasetWidget`) ever read it. `DashboardRenderer` and
`DashboardGridLayout` — which compose the chart node for a widget bound
to inline rows or to a `provider: 'object'` aggregate — mentioned
`chartConfig` zero times, so an author who wrote `chartConfig.title` /
`.colors` / `.height` on such a widget parsed clean and got nothing.

Both relays now lower the authored config through the same
`chartConfigPresentation` whitelist `DatasetWidget` uses, so one chart
config means the same thing on every dashboard surface. Widgets that
declare no `chartConfig` compose exactly what they composed before.

Still refused, each for a measured reason: `type` (the widget's own type
picks the family on this path); `xAxis` / `yAxis` / `series` (whether an
authored axis beats the dataset derivation is an open protocol question,
objectstack-ai/objectstack#17385); and `aria` (nothing on this path
reads it — the chart's one accessible name comes from `description`).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
…face itself

The forwarding commit before this one left every plot-internal key
(`colors` and its `categoryColors` arm, `showDataLabels`, `annotations`,
`interaction`, `showLegend`) without a dashboard-surface rendering
assertion. Its drawn evidence lived in plugin-charts'
`ChartRenderer.dashboardChartConfig.test.tsx`, which hand-builds its own
schema — measured: delete the forwarding from BOTH relays and every test
in that file still passes. The ruling on objectui#4044 asks for a
rendering assertion on the dashboard face for each forwarded key, so
those six had none.

`DashboardChart.chartConfigMarks-4044.test.tsx` adds them: widget
metadata in, drawn Recharts marks out, both relays, no renderer stub in
the chain. The plot gets a box without a `recharts` module mock (which
this package cannot install — `require.resolve('recharts')` from
`packages/plugin-dashboard` is MODULE_NOT_FOUND): `ResponsiveContainer`
seeds its size from `getBoundingClientRect()` on its own element, read
synchronously in its resize effect, so stubbing that ONE element is
enough. Scoped rather than blanket, and that is measured too — a blanket
stub also answers Recharts' axis-label measurement, the x-axis then
claims the whole box, and the plot renders with `height="0"` and no
marks while `.recharts-surface` is present.

Ablation, per key, mutating both relays and running the three dashboard
files: `title`, `subtitle`, `description`, `height`, `colors`,
`categoryColors`, `showLegend`, `showDataLabels`, `annotations`,
`interaction` each redden a seam assertion AND a rendering assertion;
deleting the whole forwarding reddens 46 assertions across the three,
and none in plugin-charts.

`aria` stays refused, now with the measurement rather than an assertion:
forwarding it on purpose — nested, and again flattened onto the node's
own `ariaLabel`/`ariaDescribedBy`/`role`, which `BaseSchema` declares and
`SchemaRenderer` already converts to DOM attributes — changed no
attribute on screen in either run, because `ChartRenderer` destructures
`{ schema, onChartClick }` and drops the rest. Delivering it needs a
reader inside `@object-ui/plugin-charts`, which is a separate decision.

Also corrects three comments that now read false: the DOM file's claim
that the marks could only be pinned inside plugin-charts, the
plugin-charts header's claim to cover three seams, and the changeset's
"three keys" count (it lists five).

Part of objectui#4044

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3112.0 KB 3134.8 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-DTZwxZJP.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) 500.84KB 114.81KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 209.38KB 57.92KB
fields (index.js) 247.41KB 62.43KB
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.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
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.84KB 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) 4.39KB 1.66KB
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.03KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.27KB 34.62KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 251.95KB 65.34KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.94KB 41.04KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.10KB 14.33KB
plugin-list (index.js) 112.58KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.54KB 3.31KB
plugin-view (index.js) 84.42KB 20.80KB
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) 94.03KB 31.02KB
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) 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.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.27KB 5.47KB
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

Copy link
Copy Markdown
Collaborator Author

Contract review — PASS. Recorded now so the arm is a pure CI gate.

minor on @object-ui/plugin-dashboard is the right bump, and the changeset argues it in the only terms that settle the question:

a dashboard whose stored metadata ALREADY carries chartConfig on an inline-bound chart widget renders differently after this change … Widgets that declare no chartConfig compose exactly what they composed before.

That is the distinction that decides minor vs patch, and it is worth contrasting with the call I made one PR over. On #9200 I accepted patch because the affected configuration was already broken — an empty rail on a 400, under a link that mangled its own filter. Here the affected configuration worked; it merely ignored keys the author had written, and existing stored metadata now renders differently with nobody editing it. ⇒ minor, and the no-chartConfig path being byte-identical is what keeps it from being wider than that.

The five refused keys each carry a measured reason rather than a silence, which is the whole point of the card:

  • type — the widget's own type already picks the family on this path.
  • xAxis / yAxis / series — an open protocol question, filed for the spec seat as objectstack#17385. ⇒ Part of, ⛔ not a closing keyword, correctly.
  • aria — ⭐ refused on a measurement, taken in both spellings it could travel in, not on a judgement call. Forwarded nested and again flattened onto the node's own ariaLabel/ariaDescribedBy/role; neither moved a single attribute, because ChartRenderer drops every prop but schema and onChartClick. Delivering it needs a reader inside @object-ui/plugin-charts — a new member on a published face, which Clause-② no forbids without a ruling. That decision is now posted on the card (comment 5639000150) as owed item feat: add live playground for interactive schema demonstration #8.

Verified independently before this verdict

  • Card premise wrong in both directions, measured at the merge base with lit controls in the same sweep: chartConfig 0 in each relay (control widget 173 and 88) — so the card's "only showLegend lands" was optimistic, nothing landed there; and chartConfigPresentation already in DatasetWidget.tsx — so it was pessimistic about the dataset path, which has lowered the whole whitelist since objectstack#7016.
  • React-tier control by blob identity, not by reading a diff: ObjectChart.tsx, core/src/utils/chart-presentation.ts and DatasetWidget.tsx are byte-identical to the merge base. Because the shared whitelist is untouched, the dataset path is unchanged by construction rather than by assertion.
  • --pair 9202 → both carriers agree, and the checker adds that the diff carries no widening tell (⚠️ a tell is not a proof, and its absence is not one either — but it corroborates the no).

⇒ On CI reaching pending: 0, failing: 0 I clear needs:contract-review from both carriers in one stroke, flip ready and arm SQUASH. ⛔ Nothing to re-review at that point.


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 11, 2026 19:00
@os-tesla
os-tesla added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit 91af675 Sep 11, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-4044-dashboard-chartconfig-forwarding branch September 11, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants