Uh oh!
There was an error while loading. Please reload this page.
feat(types): close the dashboard widget type vocabulary and gate the plugin-dashboard catalog - #6003
Merged
yinlianghui merged 2 commits intoAug 24, 2026
Conversation
…-card `DashboardWidgetSchema.type` was `string` / `z.string()` — an unbounded hatch that accepted typos, retired chart families, and component types nothing registers. It is now closed: the spec's `ChartTypeSchema` families BY REFERENCE plus two named objectui extension sets, `DASHBOARD_WIDGET_TYPE_EXTENSIONS` (`list`, `custom`) and `DASHBOARD_COMPONENT_WIDGET_TYPES` (`metric-card`). `metric-card` lands in objectui's own CLOSED component enum, explicitly not the spec widget enum, per the maintainer ruling of 2026-08-14. Adds the standing catalog gate for the plugin-dashboard entries against objectui's own component schema, with a counter-probe that proves it bites. Part of #4600
`DashboardWidgetTypeSchema` is a bare vocabulary with no `.shape`, so it joins EXCLUSIONS beside the other enums rather than MIRRORS. Also rewords one docblock line: `zod-mirror-parity`'s spec-dependency scan attributes a trailing docblock to the PRECEDING export, so naming the local `Spec…` alias there recorded `DashboardWidgetLayoutSchema` as spec-derived, which is false. The prose now names the spec's own schema instead. Part of #4600
Contributor
✅ Console Performance Budget
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
Size Limits
|
This was referenced Aug 24, 2026
yinlianghui
marked this pull request as ready for review
August 24, 2026 12:04
Uh oh!
There was an error while loading. Please reload this page.
yinlianghui
deleted the
claude/issue-4600-schema-catalog-dashboard-gate
branch
August 24, 2026 12:16
This was referenced Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#4600
Implements the maintainer ruling of 2026-08-14 (objectstack#8593) — the
(d2)remainder of this card.(d1)landed earlier as #4615 and is not redone here; re-verified rather than assumed (see below).The card's premise, re-measured — and falsified
The card's headline was "9 of 9 REJECT", measured at
92250d648against@objectstack/spec'sDashboardSchema. The ruling says that is the wrong schema for these documents: an SDUI dashboard component node validates against objectui's own component schema, and the spec'sDashboardSchemagoverns stored metadata documents only.Re-measured on this tree with the schema the ruling names:
9 of 9 accepted. Classes A and B were artefacts of the wrong schema, exactly as the ruling says, so no identity keys were added and no envelope was stripped. C1 re-verified as already landed: the six
filtered-*entries carry the livedataset/dimensions/valuesandoptionsshapes, not the retired inline-analytics form.But acceptance alone would have been a gate that validates nothing
Measured on this tree before any change, the same
DashboardComponentSchema.safeParsealso accepted:{ type: 'zzz-not-a-widget-type', title: 'x' }typeat all{ type: 'metric', categoryField: 'stage', aggregate: 'sum' }DashboardWidgetSchema.typewasstringon the interface andz.string()in the Zod twin. So a gate asserting.successwould have passed by validating almost nothing — the outcome this card explicitly names as worse than no gate. Closing that hatch is what makes the gate real, and it is also literally what the ruling asks for:metric-cardjoins a CLOSED enum, not an open "extension allowed" hatch.What this PR does
1. Closes the widget
typevocabulary (packages/types), composed three ways, each by its own provenance:ChartTypeSchema.options— restating them would reproduce the "narrower than the contract it implements" bug this file already records forlabelanddefaultRange;DASHBOARD_WIDGET_TYPE_EXTENSIONS=list,custom— the pre-existing objectui-only families, until now carried only as the prose "widened toz.string()" and enforced nowhere;DASHBOARD_COMPONENT_WIDGET_TYPES=metric-card— objectui's own CLOSED component enum, per the ruling, explicitly not the spec widget enum.@objectstack/specis untouched; Clause-② staysno.TS (
DashboardWidgetTypeName) and Zod (DashboardWidgetTypeSchema) both close, so declared = enforced. The 2026-08-17 TypeScript-face comment was verified, not re-derived:DashboardComponentSchemais atcomplex.ts(now:800), there is still no per-family widget type, andMetricCardPropsis still not re-exported — nothing here re-exports it or invents aMetricCardSchema.2. Adds the standing gate —
examples/schema-catalog/test/plugin-dashboard-component-schema.test.ts. Three checks per entry: it parses underDashboardComponentSchema; every widget names a type in the closed vocabulary; no authored key is silently dropped. The third routes each widget to the schema that actually owns it — ametric-cardwidget slot holds an objectui component node, so its body is passthroughBaseSchema's (which is whyvalue/icon/trend/trendValue,plugin-dashboard's registryinputs, survive), while every other widget is the spec-derived widget schema's.Fenced to
plugin-dashboard, deliberately. #4616's 35 unregistered non-dashboard gallery entries would light up under a catalog-wide gate; that is a different card with a different decision behind it. Reported, not built.3. Counter-probe (the card's Zone 3 requirement). Seven mutants of a real entry, run through the same
auditEntrythe real entries run, each naming which check must catch it — plus an unmutated clone that must stay clean, so a bug making the audit report on everything cannot make the probes pass vacuously.Three real drifts the closure surfaced
Each is the same defect class as this card — a widget
typeno contract declares — and each was mechanical, with the correct form pinned by existing evidence. Named here rather than folded in silently:plugin-designer/src/DashboardEditor.tsxoffered agridwidget. It is not a spec family and not in@object-ui/types' ownDASHBOARD_WIDGET_TYPES(the exported listWidgetConfigPanelalready derives from) — it resolved throughComponentRegistryto the view grid and was refused at publish: designer saves what the server rejects. Removed; nothing pinned it (no test in that package referenced it, andPageDesigner's owngridpalette entry is a different surface, untouched).app-shell's widget inspector and the designer both wrote an unvalidatedstringfrom their select boxes. Both now resolve the DOM string against the list that rendered the options — no cast, no consumer-side tolerance: a value not in the palette writes nothing rather than storing a refusedtype.p1-spec-alignment.test.tspinnedtype: 'bar-chart'on a dataset-bound widget.bar-chartis aplugin-chartscomponent type; the spec's family isbar, andclassifyWidgetTypereturnspassthroughfor it, so the fixture pinned a shape that can never render what it describes. Spelling corrected; its ADR-0021 assertions are unaffected.Verification
All at
a54b8bca4(the pushed head).types,plugin-dashboard,plugin-designer,app-shell,sdui-parser,core,schema-catalog) — exit 0, 0 errors. This is also the cross-package reverse verification: theplugin-designerandapp-shellerrors appeared only after@object-ui/typeswas rebuilt, proving consumers read the new.d.tsand not a cached one.packages/types/src/__tests__+examples/schema-catalog/test: 65 files, 2240 passed. Plusplugin-dashboard+plugin-designer(88 files / 802) andapp-shell'smetadata-admin(195 files / 1982) green.linton all four touched packages: exit 0, 0 errors (warnings are pre-existing). A full-repoeslint . --no-inline-configrun (3623 files) was also completed in-cap; the single finding in a file this PR touches is atindex.ts:1190, ~900 lines from any hunk here, and is an artefact of--no-inline-configsuppressing that file's owneslint-disablepair.type: 'bar-chart'giveserror TS2322: Type '"bar-chart"' is not assignable to type 'DashboardWidgetTypeName | undefined'; restore is byte-identical (empty diffstat) and green again.object+categoryField+aggregateintofiltered-dashboard.jsonturns the gate red, naming all three keys on the real entry. Mutation confirmed on disk by grep count on the injected text (categoryField1,aggregate1) before the run, and absence confirmed after (0) with an empty diffstat; restore leg green, 21/21. The script carried atrap … EXIT INT TERMrestore throughout.DashboardWidgetTypeSchemaregistered inzod-mirror-parity'sEXCLUSIONS(a bare vocabulary with no.shape). One docblock line was reworded because that guard's spec-dependency scan attributes a trailing docblock to the preceding export, and naming the localSpec…alias there would have recordedDashboardWidgetLayoutSchemaas spec-derived — false.Out of scope, filed
#6002 —
DashboardWidgetSchemasilently DROPS every undeclared key (z.object()strip semantics), while keys the spec retired are refused by name with a tombstone. That asymmetry is why the pre-ADR-0021 keys validate clean today, and closing it (.strict()vs.passthrough()) is a published-contract decision needing a stored/designer population measurement first. This PR protects the corpus against it catalog-locally; it protects nothing else in the repo.#4616 (35 unregistered gallery entries) is noted above and left alone.
Generated by Claude Code