Skip to content

Commit 9d7419b

Browse files
spec(types,plugin-dashboard,app-shell,plugin-designer): widgets[] entries read by the slot's element type, a typeless widget draws as metric and an unknown type the placeholder, the object-chart producers name ObjectChartSchema (objectui#11514) (#11524)
Fixes #11514 Clause-②: no (narrowing) objectui#11514 is rescoped by the seat's answers (comment 5964120765) to three things, all in this PR: objectui#11483's closure (`DashboardWidgetSchema['type']` drops the component type, and every reader of a `widgets[]` entry reads it by the slot's element type), both `object-chart` producers named `ObjectChartSchema` with no cast, and Q2 A (what a widget with no `type`, or with a `type` that names nothing, draws). The `object-metric` producer moved to objectui#11525 and is untouched here. The class-a finding this PR's first report raised (a rule-list `filter` with a structured `groupBy` draws an error) is filed as objectui#11526. ## What changed — before and after | | before (`fc7db059f`) | after | |---|---|---| | `DashboardWidgetSchema['type']` (TS face) | `DashboardWidgetTypeName \| DashboardComponentWidgetType` | `DashboardWidgetTypeName`, the zod twin's set | | `{ type: 'metric-card', title }` directly in `widgets[]` | compiles (the MEASURED LIMIT pin); both zod faces refuse | `tsc` refuses it too; zod faces unchanged | | read type of a `widgets[]` entry in `DashboardRenderer`, `DashboardGridLayout`, `DashboardWithConfig`, `isLegacyRetiredWidget` and the designer's `DashboardEditor` | `DashboardWidgetSchema` | `DashboardComponentSchema['widgets'][number]` | | `DashboardRendererProps['onWidgetsReorder']` parameter, and app-shell `DashboardPreview`'s handler | `DashboardWidgetSchema[]` | `DashboardComponentSchema['widgets']` | | `WidgetDispatch['chartType']` | `string` | `SeriesChartFamily`: the 12 series families, each a member of `ObjectChartSchema['chartType']` | | both `object-chart` producers | untyped literal, `chartType` typed `string` | `satisfies ObjectChartSchema`, no cast | | a widget with no `type`, both surfaces | the slot-component passthrough: the red "Unknown component type" (OBJUI-001) panel; dataset-bound with a dimension: a bar chart | draws exactly what the same widget with `type: 'metric'` draws: inline, dataset-bound, and in `DashboardRenderer`'s mobile metric row | | a `type` that names no family and no component type, both surfaces | the passthrough: the OBJUI-001 panel | the labelled placeholder "「type」chart type is not supported yet", as `heatmap` draws | | the slot-component passthrough | every entry no family matched | the component arm (`metric-card`) alone, drawn as before | | the legacy `component` envelope | its node, handed to `SchemaRenderer` bare | the same node; a typeless envelope keeps its card heading (the spec default is not applied to it); a number or `true` in it goes through `toRenderableSchema`, which draws the same text | What renders differently is exactly the two cases Q2 A lifts: a typeless widget and an unknown-type widget. Every widget that names a known family draws as before, and so does every legacy envelope (control below). ## Q2 A: the ruling and how it lands 「协议为基准」: `@objectstack/spec` 17.5.0's `DashboardWidgetSchema.type` defaults to `metric`. objectui's validator accepts a typeless widget and does not write the default in, so the surfaces are where it resolves. - `specDefaultWidgetType()` reads the default from the spec (`DashboardWidgetSchema.shape.type.parse(undefined)`), not a literal; a row pins it to `metric` at the installed spec. - `resolveWidgetType(entry)` is the authored `type`, or that default when the entry names none. Every read of an entry's `type` on both surfaces goes through it: the dispatch, the metric span and self-contained chrome, the mobile metric row. `DatasetWidget` takes the same default. - The legacy `component` envelope (`{ id, component, layout }`, objectui's own format, no member on the spec's widget) is not given the default: `resolveWidgetType` keeps `undefined` for an entry whose `component` is truthy, the same test both surfaces use to take the envelope branch. - `isSlotComponentEntry(entry)` gates the passthrough to the component arm; anything else that reaches it draws `unsupportedWidgetSchema`, the placeholder an unsupported family already drew, now declared once for both surfaces. - `entryComponent(entry)` reads the envelope's `component` typed as the widget arm declares it. A bare `entry.component` read through `BaseSchema`'s index signature and made both `getComponentSchema`s return `any`, which is what had let an earlier cast removal look clean. ## The `object-metric` producer Not touched, and no type added for it: it moved to objectui#11525. The premise measurement (branch B: the broadcast's `$and` carries the widget's MongoDB-style `filter`, and the bar's rule list makes the spec-shape aggregate branch draw an error) is in this card's first `os-dev-report` (comment 5964044891). It stays red under V1, as expected. ## V1 census Probe: PR #11512's branch (`ff7f748e`) merged with this branch at `2fb1b92b` through `git merge-tree` in a throwaway worktree (the one known conflict, `zod-mirror-parity.test.ts`'s comment opener, restored by hand). Nothing was committed or pushed from it; the worktree and its ref are removed. - **Cast sites:** 8 lines, the card's 4 sites in plugin-dashboard's src and test programs: `DashboardGridLayout.tsx` 648 and 701, `DashboardRenderer.tsx` 1077 and 1118, each in both programs. 0 new lines. The types test program: 1 line, `node-recursion-point-8344`'s drift ratchet, objectui#11515's (unchanged from the first report). - **What holds them, per branch** (a probe-only annotation typing both `getComponentSchema`s as the renderer's prop, `DeclaredNode | string | null | undefined`): the `object-metric` branch (`DashboardGridLayout` 381, 383; `DashboardRenderer` 876, 878, 879), objectui#11525; `plugin-dashboard:metric` (`DASHBOARD_NODE_TYPES.metric` typed `string`; 396 and 894), objectui#11466's registry entry; `DashboardGridLayout`'s static `pivot` (472), objectui#11466. The passthrough and the unknown-type branch: 0. The envelope branch: 0. Before the `toRenderableSchema` bridge it was 2 (`string | number | true | DeclaredNode`). - The two `SchemaRenderer` casts stay: the branches they cover are objectui#11525's and objectui#11466's. - **Not re-probed:** the merge of `main` `9ed8d0f1` (objectui#11517) that followed. NOT MEASURED, because rebuilding the probe means a fresh install and closure build on a shared box. What was checked instead: none of objectui#11517's 15 files is among V1's own 74 changed files; `git merge-tree` of the new head with `ff7f748e` shows only the same one conflict; and `main`'s type-check at the new head is green. ## Pins, controls, reverse verification, ablation - `widget-type-default-and-unknown-11514.test.tsx` (12 tests). On both surfaces: a typeless widget draws byte-for-byte what `type: 'metric'` draws, inline and dataset-bound (the control is the explicit `metric`), and in the mobile layout (non-vacuity: its 2-column metric row is drawn). An unknown `type` (`gauge2`) draws the placeholder and no OBJUI-001 panel. Controls: `heatmap` draws the same placeholder; a `metric-card` still draws through the component arm. Plus one row: the spec default is `metric`. - **Reverse verification:** the three surface files at `b92e80dc` (before Q2 A): 7 failed, 5 passed (the 5 are the controls and the spec-default row). Restored from `HEAD`, blob-matched. - `dashboard-producer-node-types-11514.test.tsx` (32 tests): round one's 28, plus a number and `true` in a legacy `component` envelope, on both surfaces, drawn under the widget's heading. - **Envelope control:** those 4 rows against the surfaces at `b92e80dc` pass (4 passed, 28 skipped), so the envelope draws as it did. **Ablation:** the envelope exemption removed from `resolveWidgetType` (`ablation-replace.mjs`, anchor 1 to 0, blob `7f60e1c6` to `da48a7f9`): all 4 rows fail. Restored: blob equal to `HEAD`, `git diff HEAD` empty. - Round one's: the `SERIES_CHART_FAMILIES` ablation (`'gauge'` planted: 2 `tsc` errors at both `satisfies ObjectChartSchema` sites, 2 vitest rows red), the `DashboardPreview` reverse verification (TS2322 at its `onWidgetsReorder` prop), and the objectui#11483 `@ts-expect-error` row. ## Readers of the narrowed type Needed an edit: plugin-dashboard's `DashboardRenderer`, `DashboardGridLayout`, `DashboardWithConfig`, `isLegacyRetiredWidget`, `DatasetWidget` (the default `type`), and two test readers; app-shell's `DashboardPreview` (type-only); plugin-designer's `DashboardEditor` and three test readers (its build went red on 3 lines once the component type left the widget arm); the types pins for objectui#11483 and objectui#7952 and `zod-mirror-parity`'s `KnownDrift` row; the plugin page and the plugin-dashboard README, which taught `const widgets: DashboardWidgetSchema[] = dashboard.widgets` and now narrow on `type` and say what a typeless or unknown-type widget draws. Needed nothing: core's `buildWidgetScopedFilter`, `useWidgetSubCaption`, app-shell's `DashboardWidgetInspector`, and `apps/*`. Render suites outside the package that mount a dashboard (app-shell's `DashboardView` and previews, `StudioDesignSurface`, the widget DOM-leak sweep, the schema-catalog galleries, the console's drill-down and chart-stub suites) pass unchanged. ## Changeset sweep - **This PR's entries:** `11514-types-widget-arm-type` (`@object-ui/types` minor), `11514-dashboard-slot-entry-types` (`@object-ui/plugin-dashboard` minor; now also states the typeless and unknown-type renders, the passthrough serving the component arm, and the envelope exemption), `11514-designer-dashboard-entry-type` and `11514-app-shell-dashboard-preview-reorder` (patch, type-only). - **Dated notes (2026-10-03), from round one:** `11483-metric-card-needs-value`, `7952-dashboard-widgets-component-arm`, `11348-dashboard-widget-reads`, `dashboard-widget-type-closed-enum`. - **Swept again for Q2 A, no new note needed:** `dashboard-widget-type-closed-enum` (its OBJUI-001 sentence describes the hatch before that change, still true of it), `8760-unfulfilled-chart-stubs` (its OBJUI-001 sentence is about a document node, not a `widgets[]` entry), `7952-dashboard-widgets-component-arm` ("What still refuses" is about validation), `10859-dashboard-node-keys` and `11483-metric-card-needs-value` (unchanged), and objectui#11517's three entries (drill-down only). ## Acceptance notes - **Filed by the seat:** the class-a finding (an authored `object-metric` with a rule-list `properties.filter` and a structured `aggregate.groupBy` draws `UnloweredAggregateWhereError` instead of its number), as objectui#11526. - **File surface beyond the claim's list:** plugin-designer's `DashboardEditor` and its three tests, `DatasetWidget`, the two docs pages, the objectui#7952 pin, the two plugin-dashboard test readers, `isLegacyRetiredWidget`, and the static `chart` producer's `chartType`. Each is the same retype or the Q2 A ruling, and is named above. - **Merges of `main`:** `b92e80dc` (`8366accd1`), `9b0ec494` (`6903eafbc`, objectui#11484's docs) and `7939c9a0` (`9ed8d0f1`, objectui#11517). All three merged cleanly. - The title drops "part 1": the card is rescoped to what this PR holds. ## Tests and gates, at `7939c9a0` - `type-check` for `@object-ui/types`, `@object-ui/plugin-dashboard`, `@object-ui/plugin-designer` and `@object-ui/app-shell` (each script echoed, after rebuilding the designer and app-shell closures): exit 0. - vitest, `pnpm exec vitest run packages/plugin-dashboard/`: 164 files passed; 1591 tests passed, 6 skipped. Readers: the types pins and objectui#11517's drill-down suites, the schema-catalog, console and script suites (24 files, 1136 tests), and the designer and app-shell dashboard suites (212 files, 2140 tests): all passed. - `check:doc-snippets`: 777 of 777 blocks judged, 0 failed. `check:doc-examples`, `check:doc-types`, `check:doc-fences`, `check:doc-example-ids`, `check:doc-example-readers`, `check:readme-exports`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:new-line-citations` (0 new), `check:control-bytes`, `check:handler-key-reads`, `check:spec-symbols`, `check:test-path-roots`, `check:component-surface-parity`, `check:phantom-deps`, `check:esm-specifiers`, `check:installed-pin-claims`, `check-changeset-no-major`, `check-changeset-presence`: exit 0. Governed-surface guard over the 29 paths: NOT GOVERNED. - `check:eager-closure` and `check:sdui-registration-pins`: NOT MEASURED, reason: both read a built console bundle (exit 2, precondition). The one new runtime import, `@objectstack/spec/ui` in `widgetDispatch.ts`, is a module app-shell already imports eagerly (`UnifiedSidebar`, `MetadataProvider`), and no registration input moved. - eslint, narrowed to the 19 changed TS files (`eslint.config.js` lints `**/*.{ts,tsx}` with no parser project, so it is not type-aware and this diff cannot move an untouched file's verdict): 0 errors, 66 warnings. The 17 files that exist at the merge base: 58 warnings there, 58 now, file for file; the new producer pin carries 8 `no-explicit-any`, the recorder and fixture casts. Repo-wide lint is CI's. --- _Generated by [Claude Code](https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 06634af commit 9d7419b

29 files changed

Lines changed: 806 additions & 228 deletions

‎.changeset/11348-dashboard-widget-reads.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ literal is unchanged.
3030
⚠️ **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.
3131

3232
⚠️ **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.
33+
34+
⚠️ **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.

‎.changeset/11483-metric-card-needs-value.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ A `metric-card` in a dashboard's widget slot parses only with its `value` (objec
1212
- **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.
1313

1414
**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.
15+
16+
⚠️ **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.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
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.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/plugin-dashboard': minor
3+
---
4+
5+
`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']`.
6+
7+
- **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.
8+
- **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`.
9+
- **The slot-component passthrough** serves the slot's component arm (`metric-card`) alone, unchanged for it.
10+
- **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']`.
11+
- **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.
12+
- **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.
13+
- **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`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@object-ui/plugin-designer': patch
3+
---
4+
5+
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.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/types': minor
3+
---
4+
5+
`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.
6+
7+
**Clause-②: no (narrowing)** — nothing the TypeScript face refused is accepted now, and no validator face moves.
8+
9+
- **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`.
10+
- **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.
11+
- **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.
12+
- **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`.

‎.changeset/7952-dashboard-widgets-component-arm.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@
1111
**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.
1212

1313
**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.
14+
15+
⚠️ **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.

‎.changeset/dashboard-widget-type-closed-enum.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ It is now the CLOSED `DashboardWidgetTypeName` / `DashboardWidgetTypeSchema`: th
1313
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.
1414

1515
⚠️ **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.
16+
17+
⚠️ **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.

‎content/docs/plugins/plugin-dashboard.mdx‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,26 @@ from widget `options`. In particular, on a dataset-bound gauge:
390390
derived measure to the dataset (`derived: { op: 'ratio', … }`) and bind the
391391
widget to it.
392392

393+
### A widget with no `type`, or a `type` that names no family
394+
395+
A widget that declares no `type` is a `metric` widget. `@objectstack/spec`'s
396+
`DashboardWidget.type` defaults to `metric`, and both dashboard surfaces
397+
(`DashboardRenderer` and `DashboardGridLayout`) read that default from the spec,
398+
so the widget draws exactly as the same widget with `type: 'metric'` does,
399+
inline or bound to a dataset (objectui#11514). objectui's validator accepts the
400+
widget without a `type` and does not write the default in, so the surfaces are
401+
where it resolves.
402+
403+
objectui's legacy `component` envelope (`{ id, component, layout }`) is not the
404+
spec's widget, and the default is not applied to it: an envelope with no `type`
405+
draws its `component` under its card heading, as it always did.
406+
407+
A `type` that names no widget family and no component type (a typo, or a family
408+
the spec no longer has) is refused by both validator faces at `type`. A stored
409+
one draws the labelled placeholder "「type」chart type is not supported yet", as
410+
a known family with no renderer (`heatmap`) does, instead of the renderer's red
411+
"Unknown component type" panel.
412+
393413
### How many measures a widget renders
394414

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

545568
```ts
546-
import type { DashboardComponentSchema, DashboardWidgetSchema } from '@object-ui/types';
569+
import type { DashboardComponentSchema } from '@object-ui/types';
547570

548571
declare const dashboard: DashboardComponentSchema;
549572

550-
const widgets: DashboardWidgetSchema[] = dashboard.widgets;
551-
const accents = widgets.map((w) => w.colorVariant ?? 'default');
573+
const accents = dashboard.widgets.map((w) =>
574+
w.type === 'metric-card' ? 'default' : (w.colorVariant ?? 'default'),
575+
);
552576
```
553577

554578
`title` and `layout` are the exceptions: both arms declare them. `title` is the

‎packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

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

79+
// Typed as `DashboardRenderer`'s `onWidgetsReorder` hands it: the slot's own
80+
// array, each entry a widget or a component node (objectui#11514).
7981
const handleReorder = React.useCallback(
80-
(next: DashboardWidgetSchema[]) => {
82+
(next: DashboardComponentSchema['widgets']) => {
8183
if (!onPatch) return;
8284
onPatch({ widgets: next });
8385
},

0 commit comments

Comments
 (0)