Skip to content

docs(plugin-dashboard): README's chart example taught a rejected type: 'card' widget - #7951

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-7035-dashboard-readme-rejected-widget-shapes
Sep 6, 2026
Merged

docs(plugin-dashboard): README's chart example taught a rejected type: 'card' widget#7951
baozhoutao merged 1 commit into
mainfrom
claude/issue-7035-dashboard-readme-rejected-widget-shapes

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #7035

The README's "Dashboard with Charts" example put two widgets spelled type: 'card' with a
nested body into widgets[]. Measured, not read off the type: @object-ui/types/zod's
DashboardComponentSchema.safeParse returns invalid_union at widgets[1] and widgets[2],
so a reader who copied the block got a rejection. 'card' is not a member of
DashboardWidgetTypeName, the vocabulary #4600 closed.

Both are now the chart-family widgets the renderer actually dispatches — line and pie,
with inline rows under options.data and options.xField / options.yField — plus one
paragraph stating the rule the example now follows.

The card's population is smaller than the measurement predicted — and the other half is a fork, not a fix

Comment 5523309430 widened this card from one fence to four blocks that put a
component-shaped node straight into widgets[]
, leaving open which side was wrong.
Measured on a2e10cf96, the answer splits, and only one half is a docs defect:

README block shipped Zod contract shipped renderer verdict
:48 quick start — type: 'metric-card' ACCEPT, every authored key preserved renders title / value / trend / trendValue docs correct
:178 Basic Dashboard — three metric-card ACCEPT renders docs correct
:217 Dashboard with Charts — two type: 'card' REJECT invalid_union ×2 card chrome renders; its body names line-chart, which nothing registers, so the registry paints its OBJUI-001 panel this PR
:275 Data Sources — type: 'metric-card' ACCEPT renders docs correct

metric-card in a widget slot is not an accident of tolerance: DASHBOARD_COMPONENT_WIDGET_TYPES
(packages/types/src/complex.ts:1568) admits it by the 2026-08-14 maintainer ruling
(objectstack#8593), the Zod widget slot is a union whose first arm is exactly that component node
(packages/types/src/zod/complex.zod.ts:887 over :749), and classifyWidgetType returns
passthrough for it on purpose. So those three blocks are left byte-untouched.

What the same measurement did surface is a divergence this PR deliberately does not touch,
because it lives in packages/types and is a published-contract decision:
DashboardComponentSchema.widgets is declared DashboardWidgetSchema[]
(complex.ts:1733) with no component-node arm, so annotating the legal metric-card examples
yields TS2561: 'value' does not exist in type 'DashboardWidgetSchema' — the Zod mirror accepts
a whole arm the TypeScript declaration refuses. That is filed as #7952 and is not addressed
here
— it is a packages/types decision, and #7952 also records why the parity ledger files
that key as SCHEMA-NODE and so sorts it out of #7759's disposition lane.

What was measured

Everything below ran on 70ea5b79b.

Before. The batch-10 diagnostics reproduced against the built dist/, --strict:

error TS2561: Object literal may only specify known properties, but 'value' does not exist
              in type 'DashboardWidgetSchema'. Did you mean to write 'values'?      (x5)
error TS2322: Type '"card"' is not assignable to type 'DashboardWidgetTypeName | undefined'. (x2)

Renderer. Each shape rendered through the shipped DashboardRenderer with
@object-ui/plugin-charts registered: the type: 'card' widget paints Unknown component type: line-chart … (OBJUI-001); the rewrite paints two charts and no OBJUI-001; a mis-spelled
type stays red. (Scratch harness, not committed.)

After. Every dashboard block extracted from the file — not retyped — parses:

:48 Automatic Registration => ACCEPT      :217 Dashboard with Charts        => ACCEPT
:129 Dashboard            => ACCEPT      :257 Responsive Dashboard         => ACCEPT
:178 Basic Dashboard      => ACCEPT      :275 Integration with Data Sources => ACCEPT
6 dashboard block(s); 0 rejected.

Ablation. Reverting the block to the type: 'card' spelling turns the new test red on
exactly that block, naming both widgets — mutation proven on disk (type: 'card' count 0→2,
type: 'line', count 1→0, blob hash changed), restore proven byte-identical to the HEAD blob:

× :217 Dashboard with Charts validates against the shipped DashboardComponentSchema
AssertionError: README.md:217 ("Dashboard with Charts") is refused:
  invalid_union at widgets1: Invalid input; invalid_union at widgets2: Invalid input
Tests  1 failed | 6 passed (7)

Why a test file

No gate asked this question, each for a stated reason: check:doc-snippets compiles fenced
ts and these are untyped const schema = { … } literals (its header names schema-key
validity as the question it does not answer); check:doc-types walks content/docs/** and the
root README, so package READMEs are outside it (#7896, whose live-instance list this README's
unregistered line-chart joins); check:readme-exports judges import bindings. The new test is
this document only, and follows plugin-gantt/src/readme-navigation-example.test.ts — the same
pattern one package over.

Gates

Gate Verdict line
pnpm check:doc-snippets Every covered documentation snippet compiles against the built types. (467/467 judged, 0 failed)
pnpm check:doc-fences ✅ check:doc-fences — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript…
pnpm check:doc-types ✅ Every documented component type is registered.
pnpm check:readme-exports ✅ check-readme-exports: OK (43 tracked README(s)…)
pnpm check:control-bytes ✅ check-control-bytes: OK (scanned 6410 tracked text file(s); skipped 85 binary).
node scripts/check-changeset-presence.mjs ✅ … declares 1 changeset(s) … Every one of them has an EMPTY frontmatter — declared as releasing nothing
node scripts/check-changeset-no-major.mjs ✅ No changeset declares a major bump.
node scripts/check-governed-queue-guard.mjs --test THE-3-PATHS ✅ NOT GOVERNED — 3 path(s) checked against 5 governed surface(s); none matched.
vitest run packages/plugin-dashboard/ Test Files 94 passed (94) · Tests 846 passed (846)
vitest run examples/schema-catalog/test/plugin-dashboard-component-schema.test.ts Test Files 1 passed (1) · Tests 21 passed (21) — the standing widget-slot contract gate
pnpm --filter @object-ui/plugin-dashboard type-check exit 0; the new test is in the program (tsc -p tsconfig.test.json --listFiles names it)
pnpm --filter @object-ui/plugin-dashboard lint ✖ 426 problems (0 errors, 426 warnings) — all pre-existing no-explicit-any

The repo-wide pnpm lint and the rest of the farm are CI's.


Generated by Claude Code

…e: 'card'` widget (objectui#7035)

The "Dashboard with Charts" example put two widgets spelled `type: 'card'` with a
nested `body` into `widgets[]`. `'card'` is not a member of `DashboardWidgetTypeName`
— the vocabulary objectui#4600 closed — so `@object-ui/types/zod`'s
`DashboardComponentSchema.safeParse` refuses the whole document. Measured on the
shipped contract, not read off the type: the block returned `invalid_union` at
`widgets[1]` and `widgets[2]`. Rendered unvalidated, the first one's `body` names
`line-chart`, which nothing registers, so it paints the registry's OBJUI-001 panel.

Both are now the chart-family widgets the renderer actually dispatches — `line` and
`pie`, with their inline rows under `options.data` and `options.xField` /
`options.yField` — plus one paragraph stating the rule the example now follows.

The new test parses every dashboard example on the page through the shipped schema.
No gate asked that question before: `check:doc-snippets` compiles fenced blocks and
these are untyped literals, `check:doc-types` covers `content/docs/**` and the root
README only, and `check:readme-exports` judges imports. Verified by reverting the
block: the test goes red on exactly those two widgets.

The three `type: 'metric-card'` examples are deliberately untouched — the 2026-08-14
maintainer ruling (objectstack#8593) admits that component node into the widget slot,
and both the Zod union and the renderer accept it as written.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uAaxiwgYDybsTNV9xwa1M
@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) 3189.5 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-C35DcPnR.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) 510.60KB 116.20KB
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.98KB 10.98KB
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.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.37KB
plugin-dashboard (index.js) 132.88KB 34.69KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.56KB 27.70KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.40KB 8.76KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
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

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

Labels

documentation Improvements or additions to documentation plugin tests

Projects

None yet

2 participants