Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
92c2a32
spec(types): DashboardWidgetSchema['type'] names the widget vocabular…
claude Oct 3, 2026
6e547f9
spec(plugin-dashboard): slot-entry callbacks read the slot's element …
claude Oct 3, 2026
b597df8
spec(app-shell,plugin-designer): read dashboard widgets[] entries by …
claude Oct 3, 2026
d746b88
docs(plugin-dashboard): read a widget key off widgets[] by narrowing …
claude Oct 3, 2026
bc1a344
test(plugin-dashboard): pin the object-chart producers against Object…
claude Oct 3, 2026
e602b4e
chore(changeset): objectui#11514 entries, and dated notes on four pen…
claude Oct 3, 2026
7b11ac1
spec(plugin-dashboard): the static chart branch carries the dispatche…
claude Oct 3, 2026
b92e80d
Merge main (8366accd1) into claude/issue-11514-dashboard-producer-types
claude Oct 3, 2026
a82acd8
fix(plugin-dashboard): a typeless widget draws as the spec's default …
claude Oct 3, 2026
332b423
fix(plugin-dashboard): the dataset path draws a typeless widget as th…
claude Oct 3, 2026
ef25db8
spec(plugin-dashboard): read an entry's legacy component envelope as …
claude Oct 3, 2026
c1481c3
docs(plugin-dashboard): a widget with no `type` draws as `metric`; an…
claude Oct 3, 2026
f1dd8b5
chore(changeset): the plugin-dashboard entry states the typeless and …
claude Oct 3, 2026
9b0ec49
Merge main (6903eafbc) into claude/issue-11514-dashboard-producer-types
claude Oct 3, 2026
16d2d8e
fix(plugin-dashboard): a typeless `component` envelope keeps its chro…
claude Oct 3, 2026
2fb1b92
docs(plugin-dashboard): a typeless `component` envelope is not given …
claude Oct 3, 2026
7939c9a
Merge main (9ed8d0f1) into claude/issue-11514-dashboard-producer-types
claude Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/11348-dashboard-widget-reads.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ literal is unchanged.
⚠️ **Dated note, 2026-10-01 — the component arm now declares `layout` — objectui#11070.** "the component arm has no spec row and declares none of them" above held when this change landed. Later in this same release, round 11 of objectui#11070 declared `layout` on the component arm, by reference to the spec's widget `layout`, because Save Layout writes it onto every `widgets[]` entry. `title` and `colorVariant` are still declared on the widget arm only. `.changeset/11070-dashboard-keys-round11.md` states what ships; the text above is kept as the reading of this change.

⚠️ **Dated note, 2026-10-02 — the component arm now declares `title` — objectui#11467.** "`title` and `colorVariant` are still declared on the widget arm only" in the note above held when that note was written. Later in this same release, objectui#11467 declared `metric-card`'s registered inputs on the component arm, `title` among them as the card's heading, typed as `MetricCard` reads it. So `title` is declared on both arms, and it reads as `string | I18nLabel` straight off a `widgets[]` entry. `colorVariant` is still declared on the widget arm only. `.changeset/11467-metric-card-arm-inputs.md` states what ships; the text above is kept as the reading of this change.

⚠️ **Dated note, 2026-10-03 — the read sites move to the slot's element type — objectui#11514.** At this change, the `layout` / `title` / `colorVariant` sites above read a `widgets[]` entry through `DashboardWidgetSchema`, and the component arm was assignable to it. Now `DashboardWidgetSchema['type']` names no component type, so the component arm is not assignable to it, and the sites in `DashboardGridLayout`, `DashboardRenderer`, `DashboardWithConfig` and the designer's `DashboardEditor` read an entry by the slot's element type, `DashboardComponentSchema['widgets'][number]`. `layout` and `title` read with their declared types off either arm; `colorVariant` is still declared on the widget arm only. `.changeset/11514-dashboard-slot-entry-types.md` states what ships. The rest of this entry is kept as the reading of this change.
2 changes: 2 additions & 0 deletions .changeset/11483-metric-card-needs-value.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ A `metric-card` in a dashboard's widget slot parses only with its `value` (objec
- **What does not move.** On the TypeScript face, `DashboardWidgetSchema['type']` still includes `'metric-card'`. That interface is also the read type of every `widgets[]` entry, and the component arm stays assignable to it. So a `metric-card` literal with no `value` still compiles directly in `widgets[]`, while the validator refuses it on both faces. A `metric-card` in a widget's legacy `component` envelope is judged as before: there, the tolerant face's `BaseSchema` fallback still accepts a card that fails the arm. Every `metric-card` in the schema catalog, the READMEs and the docs fences carries its `value`. The one docs example that did not, under "TypeScript Support" in `content/docs/plugins/plugin-dashboard.mdx`, is rewritten.

**Fix:** give the card its `value`. For a figure queried from a dataset, write a `metric` widget (`{ type: 'metric', dataset, values: [measure] }`), which the dashboard draws as the same dataset tile.

⚠️ **Dated note, 2026-10-03 — the TypeScript widget arm drops the component type too — objectui#11514.** At this change, "What does not move" above held: `DashboardWidgetSchema['type']` still included `'metric-card'`, the interface was the read type of every `widgets[]` entry, the component arm was assignable to it, and a `metric-card` literal with no `value` still compiled directly in `widgets[]`. Now `DashboardWidgetSchema['type']` is `DashboardWidgetTypeName` alone, `plugin-dashboard`, `plugin-designer` and `app-shell` read an entry by the slot's element type (`DashboardComponentSchema['widgets'][number]`), the component arm is no longer assignable to the widget arm, and `tsc` refuses a `metric-card` with no `value` as both validator faces do. `.changeset/11514-types-widget-arm-type.md` states what ships. The rest of this entry is kept as the reading of this change.
5 changes: 5 additions & 0 deletions .changeset/11514-app-shell-dashboard-preview-reorder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@object-ui/app-shell': patch
---

The metadata-admin dashboard preview's reorder handler takes `DashboardComponentSchema['widgets']`, the array `DashboardRenderer`'s `onWidgetsReorder` now hands back (objectui#11514). Type-only: the preview patches the same reordered array as before.
13 changes: 13 additions & 0 deletions .changeset/11514-dashboard-slot-entry-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@object-ui/plugin-dashboard': minor
---

`DashboardRenderer`'s `onWidgetsReorder` hands back the slot's own array type, `DashboardComponentSchema['widgets']`, instead of `DashboardWidgetSchema[]`; the dashboard's `object-chart` producers name `ObjectChartSchema`; and a widget with no `type`, or with a `type` that names nothing, draws what the spec says it is (objectui#11514). `minor`, per this repository's version alignment: a reorder handler typed `(widgets: DashboardWidgetSchema[]) => void` stops compiling, because the component arm of a `widgets[]` entry is no longer assignable to `DashboardWidgetSchema`. Type the handler's parameter as `DashboardComponentSchema['widgets']`.

- **A widget with no `type` is a `metric` widget.** `@objectstack/spec`'s `DashboardWidget.type` defaults to `metric`; both surfaces read that default from the spec and draw the widget exactly as the same widget with `type: 'metric'`: inline, bound to a dataset (where a typeless widget with a dimension drew a bar chart), and in `DashboardRenderer`'s mobile metric row. It used to reach the slot-component passthrough and draw the registry's red "Unknown component type" (OBJUI-001) panel.
- **A `type` that names no family** (and no component type) draws the labelled placeholder "「type」chart type is not supported yet", as a known family with no renderer does, instead of that red panel. Both validator faces already refuse such a widget at `type`.
- **The slot-component passthrough** serves the slot's component arm (`metric-card`) alone, unchanged for it.
- **Slot-entry reads.** `DashboardRenderer`, `DashboardGridLayout`, `DashboardWithConfig` and the retired-widget detector read a `widgets[]` entry by the slot's element type, `DashboardComponentSchema['widgets'][number]`, which is what lets `@object-ui/types` drop the component type from `DashboardWidgetSchema['type']`.
- **The `object-chart` producers.** A series dispatch carries the family as a literal union whose members are all families `ObjectChartSchema.chartType` declares, so the node both surfaces build for a `provider: 'object'` series widget satisfies `ObjectChartSchema` with no cast.
- **README.** "Reading a widget key off `widgets[]`" narrows an entry on `type` before reading a widget key, and a new section states what a widget with no `type`, or an unknown one, draws.
- **What does not move.** Every widget that names a known family draws as before: the dispatch routes the same families, and each `object-chart` node carries the same `chartType` it carried. A legacy `component` envelope with no `type` is not the spec's widget and is not given its default; it draws under its card heading as before, and a number or `true` in its `component` draws the same text, now forwarded through `toRenderableSchema`.
5 changes: 5 additions & 0 deletions .changeset/11514-designer-dashboard-entry-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@object-ui/plugin-designer': patch
---

The dashboard editor reads a `widgets[]` entry by the slot's element type, `DashboardComponentSchema['widgets'][number]`, in its widget card, its property panel, its preview and its measure probe (objectui#11514). `@object-ui/types` no longer lets the slot's component arm stand in for `DashboardWidgetSchema`, and these reads annotated entries with that interface. Type-only: nothing the editor renders, offers or writes changes.
12 changes: 12 additions & 0 deletions .changeset/11514-types-widget-arm-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@object-ui/types': minor
---

`DashboardWidgetSchema['type']` names the widget vocabulary only, `DashboardWidgetTypeName`: it drops `DashboardComponentWidgetType` (objectui#11514). This narrows the TypeScript face. `minor`, per this repository's version alignment; the narrowing is the breaking part.

**Clause-②: no (narrowing)** — nothing the TypeScript face refused is accepted now, and no validator face moves.

- **What changed.** The widget arm's `type` is the same set as its zod twin's, `DashboardWidgetTypeSchema`, which dropped the component type in objectui#11483. The TypeScript interface kept it then because it was also the read type of every `widgets[]` entry. `@object-ui/plugin-dashboard`, `@object-ui/plugin-designer` and `@object-ui/app-shell` now read an entry by the slot's element type, `DashboardComponentSchema['widgets'][number]`, so the widget arm no longer has to admit `metric-card`.
- **What now refuses that did not.** `tsc` refuses a `metric-card` with no `value` directly in `widgets[]`, as both validator faces already did: only the component arm, `DashboardWidgetSlotComponentSchema`, names `metric-card`, and its `value` is required. Assigning `type: 'metric-card'` to a `DashboardWidgetSchema` is a compile error. The component arm is no longer assignable to `DashboardWidgetSchema`, so a callback annotated `(w: DashboardWidgetSchema)` over `schema.widgets`, or a `DashboardWidgetSchema[]` annotation on it, stops compiling.
- **Fix.** Annotate an entry with `DashboardComponentSchema['widgets'][number]`. To read a widget key with its declared type, narrow the entry on `type` first: `metric-card` is the one component type the slot holds, and any other entry is the widget arm.
- **What does not move.** No zod schema, validator verdict, export name or runtime behaviour. `DASHBOARD_COMPONENT_WIDGET_TYPES` still lists `metric-card` as the component arm's `type`.
2 changes: 2 additions & 0 deletions .changeset/7952-dashboard-widgets-component-arm.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@
**What still refuses.** A widget that names a spec-family `type` and carries an undeclared key (`{ type: 'bar', bogus: 1 }`) is still a `tsc` error: the literal is discriminated by `type`, so the passthrough arm never applies to it. A `type` outside both vocabularies is refused as before. The one corner the TypeScript union cannot discriminate — a legacy `component` envelope with NO `type` plus an undeclared key — compiles on the TypeScript face and is refused by name at validation, as every `BaseSchema` slot already behaves.

**Consumers.** The new arm is assignable to `DashboardWidgetSchema`, so code that annotates a widget callback `(w: DashboardWidgetSchema)` keeps compiling unchanged. Code that reads a property off an unannotated element of `schema.widgets` now sees the union, and through `BaseSchema`'s index signature that read is `any` rather than the widget's declared type — annotate the parameter to keep the narrower type.

⚠️ **Dated note, 2026-10-03 — the component arm is no longer assignable to the widget arm — objectui#11514.** At this change, "**Consumers.**" above held: the new arm was assignable to `DashboardWidgetSchema`, so a widget callback annotated `(w: DashboardWidgetSchema)` kept compiling. Now `DashboardWidgetSchema['type']` names no component type, so the component arm is not assignable to it, and such a callback over `schema.widgets` is a compile error. Annotate an entry with the slot's element type, `DashboardComponentSchema['widgets'][number]`, and narrow it on `type` to read a widget key with its declared type. `.changeset/11514-types-widget-arm-type.md` states what ships. The rest of this entry is kept as the reading of this change.
2 changes: 2 additions & 0 deletions .changeset/dashboard-widget-type-closed-enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ It is now the CLOSED `DashboardWidgetTypeName` / `DashboardWidgetTypeSchema`: th
Three drifts the closure surfaced and this change fixes: the dashboard designer's palette offered `grid`, which is not a widget family in either contract and was refused at publish; the metadata-admin widget inspector and the designer both wrote an unvalidated `string` from their select boxes; and a `@object-ui/types` fixture pinned `bar-chart`, a `plugin-charts` component type, on a dataset-bound widget that could never render as one.

⚠️ **Dated note, 2026-10-02 — `metric-card` is no widget type — objectui#11483.** At this change, `DashboardWidgetTypeName` / `DashboardWidgetTypeSchema` were the spec's families plus two objectui sets, `DASHBOARD_WIDGET_TYPE_EXTENSIONS` and `DASHBOARD_COMPONENT_WIDGET_TYPES`; now they are the spec's families plus `DASHBOARD_WIDGET_TYPE_EXTENSIONS` only. A `metric-card` in the widget slot is read by the slot's component arm alone, which requires its `value`. `DASHBOARD_COMPONENT_WIDGET_TYPES` still lists `metric-card` as that arm's `type`, and `DashboardWidgetSchema['type']` on the TypeScript face still reads it. `.changeset/11483-metric-card-needs-value.md` states what ships. The rest of this entry is kept as the reading of this change.

⚠️ **Dated note, 2026-10-03 — the TypeScript widget arm no longer reads `metric-card` — objectui#11514.** At the 2026-10-02 note above, `DashboardWidgetSchema['type']` on the TypeScript face still read `metric-card`, because that interface was the read type of every `widgets[]` entry. Now it is `DashboardWidgetTypeName` alone: an entry is read by the slot's element type, and only the component arm names `metric-card`. `.changeset/11514-types-widget-arm-type.md` states what ships. The rest of this entry is kept as the reading of this change.
38 changes: 31 additions & 7 deletions content/docs/plugins/plugin-dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,26 @@ from widget `options`. In particular, on a dataset-bound gauge:
derived measure to the dataset (`derived: { op: 'ratio', … }`) and bind the
widget to it.

### A widget with no `type`, or a `type` that names no family

A widget that declares no `type` is a `metric` widget. `@objectstack/spec`'s
`DashboardWidget.type` defaults to `metric`, and both dashboard surfaces
(`DashboardRenderer` and `DashboardGridLayout`) read that default from the spec,
so the widget draws exactly as the same widget with `type: 'metric'` does,
inline or bound to a dataset (objectui#11514). objectui's validator accepts the
widget without a `type` and does not write the default in, so the surfaces are
where it resolves.

objectui's legacy `component` envelope (`{ id, component, layout }`) is not the
spec's widget, and the default is not applied to it: an envelope with no `type`
draws its `component` under its card heading, as it always did.

A `type` that names no widget family and no component type (a typo, or a family
the spec no longer has) is refused by both validator faces at `type`. A stored
one draws the labelled placeholder "「type」chart type is not supported yet", as
a known family with no renderer (`heatmap`) does, instead of the renderer's red
"Unknown component type" panel.

### How many measures a widget renders

A dataset-bound widget queries every measure in `values`. What it renders
Expand Down Expand Up @@ -537,18 +557,22 @@ component node placed directly in the slot (`DashboardWidgetSlotComponentSchema`
The component arm declares the card's registered inputs — the keys above. The
widget keys (`colorVariant`, `filter`, `dataset`, …) are declared on the widget
arm, which takes them from the spec's `DashboardWidget` row, and on no member of
the component arm. To read one off `widgets[]`, read it through
`DashboardWidgetSchema`. The component arm is assignable to that type, so the
annotation is checked rather than asserted, and the key gets its declared type
instead of the `any` the component arm's passthrough supplies:
the component arm. An entry's own type is the slot's element type,
`DashboardComponentSchema['widgets'][number]`. The component arm is not
assignable to `DashboardWidgetSchema`, whose `type` names no component type, so
narrow an entry on `type` before reading a widget key. `metric-card` is the one
component type the slot holds; any other entry is the widget arm, the compiler
checks the narrowing, and the key gets its declared type instead of the `any`
the component arm's passthrough supplies:

```ts
import type { DashboardComponentSchema, DashboardWidgetSchema } from '@object-ui/types';
import type { DashboardComponentSchema } from '@object-ui/types';

declare const dashboard: DashboardComponentSchema;

const widgets: DashboardWidgetSchema[] = dashboard.widgets;
const accents = widgets.map((w) => w.colorVariant ?? 'default');
const accents = dashboard.widgets.map((w) =>
w.type === 'metric-card' ? 'default' : (w.colorVariant ?? 'default'),
);
```

`title` and `layout` are the exceptions: both arms declare them. `title` is the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import * as React from 'react';
import { Loader2, Pencil, X, Check } from 'lucide-react';
import type { DashboardWidgetSchema } from '@object-ui/types';
import type { DashboardComponentSchema, DashboardWidgetSchema } from '@object-ui/types';
import { useAdapter } from '../../../providers/AdapterProvider.js';
import type { MetadataPreviewProps } from '../preview-registry.js';
import { PreviewShell, PreviewErrorBoundary, PreviewMessage } from './PreviewShell.js';
Expand Down Expand Up @@ -76,8 +76,10 @@ export function DashboardPreview({
[onSelectionChange, widgets, locale],
);

// Typed as `DashboardRenderer`'s `onWidgetsReorder` hands it: the slot's own
// array, each entry a widget or a component node (objectui#11514).
const handleReorder = React.useCallback(
(next: DashboardWidgetSchema[]) => {
(next: DashboardComponentSchema['widgets']) => {
if (!onPatch) return;
onPatch({ widgets: next });
},
Expand Down
37 changes: 30 additions & 7 deletions packages/plugin-dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,26 @@ the enum gets no accent and is not aliased to a nearby colour: it is invalid
metadata, rejected where it is authored and published rather than reinterpreted
here.

## A widget with no `type`, or a `type` that names no family

A widget that declares no `type` is a `metric` widget. `@objectstack/spec`'s
`DashboardWidget.type` defaults to `metric`, and both dashboard surfaces
(`DashboardRenderer` and `DashboardGridLayout`) read that default from the spec,
so the widget draws exactly as the same widget with `type: 'metric'` does,
inline or bound to a dataset (objectui#11514). objectui's validator accepts the
widget without a `type` and does not write the default in, so the surfaces are
where it resolves.

objectui's legacy `component` envelope (`{ id, component, layout }`) is not the
spec's widget, and the default is not applied to it: an envelope with no `type`
draws its `component` under its card heading, as it always did.

A `type` that names no widget family and no component type (a typo, or a family
the spec no longer has) is refused by both validator faces at `type`. A stored
one draws the labelled placeholder "「type」chart type is not supported yet", as
a known family with no renderer (`heatmap`) does, instead of the renderer's red
"Unknown component type" panel.

## How many measures a widget renders

A dataset-bound widget queries every measure in `values`. What it renders
Expand Down Expand Up @@ -670,18 +690,21 @@ The widget keys (`colorVariant`, `filter`, `dataset`, …) are declared on the
widget arm, `DashboardWidgetSchema`, which takes them from the spec's
`DashboardWidget` row. The component arm declares none of them. Read straight
off a `widgets[]` entry, such a key is typed `any`, supplied by the component
arm's passthrough. Read it through `DashboardWidgetSchema` instead, which is how
this package's own readers do it. The component arm is assignable to that type,
so the annotation is checked by the compiler, not asserted, and the key gets its
declared type:
arm's passthrough. This package's own readers take an entry by the slot's
element type, `DashboardComponentSchema['widgets'][number]`. The component arm
is not assignable to `DashboardWidgetSchema`, whose `type` names no component
type, so narrow an entry on `type` first: `metric-card` is the one component
type the slot holds, and any other entry is the widget arm. The compiler checks
the narrowing, not an annotation, and the key gets its declared type:

```typescript
import type { DashboardComponentSchema, DashboardWidgetSchema } from '@object-ui/types';
import type { DashboardComponentSchema } from '@object-ui/types';

declare const dashboard: DashboardComponentSchema;

const widgets: DashboardWidgetSchema[] = dashboard.widgets;
const accents = widgets.map((w) => w.colorVariant ?? 'default');
const accents = dashboard.widgets.map((w) =>
w.type === 'metric-card' ? 'default' : (w.colorVariant ?? 'default'),
);
```

`title` and `layout` are the two widget keys both arms declare. `title` is the
Expand Down
Loading
Loading