You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding(types): DashboardComponentSchema.widgets is a two-arm union in zod and a one-arm array in TypeScript — the ruled-legal metric-card component node parses green and tsc refuses it, and the parity ledger files the key as SCHEMA-NODE #7952
Observation-class finding, measured while implementing objectui#7035 (PR #7951). Filed unassigned, not claiming. Grading and domain:* are the triage seat's.
The claim
DashboardComponentSchema.widgets is sorted into zod-mirror-parity.test.ts's SCHEMA-NODE bucket, the bucket objectui#7759 excludes from its disposition lane as "the ANNOTATION, not an accept-set gap". Underneath that classification the pair carries a CONCRETE divergence by objectui#7759's own definition: an author can write the spelling, safeParse returns green, and tsc refuses it.
The divergence is not a key. It is a whole union arm.
face
declaration
Zod mirror
packages/types/src/zod/complex.zod.ts:887 — widgets: z.array(z.union([DashboardWidgetSlotComponentSchema, DashboardWidgetSchema])). The first arm (:749) is BaseSchema.extend({ type: z.enum(DASHBOARD_COMPONENT_WIDGET_TYPES) }) — a passthrough component node.
TypeScript
packages/types/src/complex.ts:1733 — widgets: DashboardWidgetSchema[]. One arm. No component-node form.
The first arm exists by the 2026-08-14 maintainer ruling (objectstack#8593), quoted verbatim at complex.ts:1539 and again at complex.zod.ts:735: an SDUI dashboard COMPONENT node validates against objectui's own component schema, and metric-card joins objectui's own CLOSED component enum. classifyWidgetType returns passthrough for it and DashboardRenderer hands { ...widget } to SchemaRenderer. So the arm is deliberate, live, and documented — on the Zod side only.
Measured on a2e10cf96, both faces
Runtime — ACCEPT.DashboardComponentSchema.safeParse on the shape packages/plugin-dashboard/README.md teaches at :48, :178 and :275:
The same document rendered through the shipped DashboardRenderer paints title, value, trend, trendValue and description. Both halves of the ruling hold.
TypeScript — REFUSED. The same literal, annotated and compiled --strict against the built dist/:
error TS2561: Object literal may only specify known properties, but 'value' does not
exist in type 'DashboardWidgetSchema'. Did you mean to write 'values'?
Five occurrences across the README's three legal blocks. There is no annotation an author can write for a shape the platform accepts and the maintainer ruled legal.
Why the ledger reads it as SCHEMA-NODE
WiderThanDeclared's entry (zod-mirror-parity.test.ts:1787) is a MIXED note over four keys, and its clause for this one is three words:
widgets is SCHEMA-NODE.
That is true of the SECOND arm — DashboardWidgetSchema.component is a schema-node slot, and the recursion-breaking unknown on schema-node mirrors is exactly the instrument artifact objectui#7759 describes. It is not true of the FIRST arm, which is a concrete BaseSchema.extend({ type: z.enum(...) }) with no schema-node recursion in it at all. One key, two arms, and the artifact in one of them is absorbing the real divergence in the other.
⇒ The interesting half of this card is not the dashboard. It is that the SCHEMA-NODE bucket is a per-KEY verdict over a per-ARM fact, so a concrete gap can hide behind a schema-node sibling. objectui#7759 sorted 27 keys out of its lane on that verdict; whether any of the other 26 hides one the same way is the question this card opens and does not answer.
What NOT to do
⛔ Do not widen DashboardWidgetSchema with value / icon / trend / trendValue. Both declarations say so in terms, for the same reason (complex.ts:1553, complex.zod.ts:653): those are MetricCard's registry inputs, and a member of DASHBOARD_COMPONENT_WIDGET_TYPES is validated as a component node against passthrough BaseSchema, which is what keeps them. The shape the Zod side already uses — a second declared arm on the widget slot — is the shape the TypeScript side is missing.
Both spellings are a published-contract change and need a ruling, which is why this is a card and not a patch.
Observation-class finding, measured while implementing objectui#7035 (PR #7951). Filed unassigned, not claiming. Grading and
domain:*are the triage seat's.The claim
DashboardComponentSchema.widgetsis sorted intozod-mirror-parity.test.ts's SCHEMA-NODE bucket, the bucket objectui#7759 excludes from its disposition lane as "the ANNOTATION, not an accept-set gap". Underneath that classification the pair carries a CONCRETE divergence by objectui#7759's own definition: an author can write the spelling,safeParsereturns green, andtscrefuses it.The divergence is not a key. It is a whole union arm.
packages/types/src/zod/complex.zod.ts:887—widgets: z.array(z.union([DashboardWidgetSlotComponentSchema, DashboardWidgetSchema])). The first arm (:749) isBaseSchema.extend({ type: z.enum(DASHBOARD_COMPONENT_WIDGET_TYPES) })— a passthrough component node.packages/types/src/complex.ts:1733—widgets: DashboardWidgetSchema[]. One arm. No component-node form.The first arm exists by the 2026-08-14 maintainer ruling (objectstack#8593), quoted verbatim at
complex.ts:1539and again atcomplex.zod.ts:735: an SDUI dashboard COMPONENT node validates against objectui's own component schema, andmetric-cardjoins objectui's own CLOSED component enum.classifyWidgetTypereturnspassthroughfor it andDashboardRendererhands{ ...widget }toSchemaRenderer. So the arm is deliberate, live, and documented — on the Zod side only.Measured on
a2e10cf96, both facesRuntime — ACCEPT.
DashboardComponentSchema.safeParseon the shapepackages/plugin-dashboard/README.mdteaches at:48,:178and:275:The same document rendered through the shipped
DashboardRendererpaints title, value, trend, trendValue and description. Both halves of the ruling hold.TypeScript — REFUSED. The same literal, annotated and compiled
--strictagainst the builtdist/:Five occurrences across the README's three legal blocks. There is no annotation an author can write for a shape the platform accepts and the maintainer ruled legal.
Why the ledger reads it as SCHEMA-NODE
WiderThanDeclared's entry (zod-mirror-parity.test.ts:1787) is a MIXED note over four keys, and its clause for this one is three words:That is true of the SECOND arm —
DashboardWidgetSchema.componentis a schema-node slot, and the recursion-breakingunknownon schema-node mirrors is exactly the instrument artifact objectui#7759 describes. It is not true of the FIRST arm, which is a concreteBaseSchema.extend({ type: z.enum(...) })with no schema-node recursion in it at all. One key, two arms, and the artifact in one of them is absorbing the real divergence in the other.⇒ The interesting half of this card is not the dashboard. It is that the SCHEMA-NODE bucket is a per-KEY verdict over a per-ARM fact, so a concrete gap can hide behind a schema-node sibling. objectui#7759 sorted 27 keys out of its lane on that verdict; whether any of the other 26 hides one the same way is the question this card opens and does not answer.
What NOT to do
⛔ Do not widen
DashboardWidgetSchemawithvalue/icon/trend/trendValue. Both declarations say so in terms, for the same reason (complex.ts:1553,complex.zod.ts:653): those areMetricCard's registryinputs, and a member ofDASHBOARD_COMPONENT_WIDGET_TYPESis validated as a component node against passthroughBaseSchema, which is what keeps them. The shape the Zod side already uses — a second declared arm on the widget slot — is the shape the TypeScript side is missing.Both spellings are a published-contract change and need a ruling, which is why this is a card and not a patch.
Refs
type: 'card'widget #7951 — the docs card this was measured under. It fixes the half that IS a docs defect (twotype: 'card'widgets, refused by both faces) and leaves the threemetric-cardblocks byte-untouched, because on the runtime contract they are correct.WiderThanDeclareddisposition lane, and the classification this card questions.Generated by Claude Code