Repository navigation
feat(plugin-dashboard): a widget's component envelope draws a metric-card and nothing else (objectui#12254) - #12265
Conversation
…ic-card` and nothing else (objectui#12254) Both dashboard surfaces now draw the legacy `component` envelope by the rule the authoring faces have stated since objectui#12163: a `metric-card` node draws as before, and any other node (another type, no type, a primitive) draws the existing retired-format placeholder and sends no query. One predicate decides it for both, `isRetiredEnvelopeNode`, read from `DASHBOARD_COMPONENT_WIDGET_TYPES`; the `object-metric` case objectui#11466 retired is one case of it. A read-only census of stored use found no producer of a non-`metric-card` envelope node outside tests and docs. A `custom` widget with no `component` on `DashboardRenderer` drew a prompt to set `component` to a UIComponent schema, which this made false; it now draws the labelled placeholder `DashboardGridLayout` already drew. Claude-Session: https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd Co-authored-by: Claude <noreply@anthropic.com>
…the placeholder being a text node too (objectui#12254) Claude-Session: https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: PR #12265 (card #12254), head Check-runs on the head: 30 completed ① Derived judgmentsThe published surface of
No derived judgment is wrong. Nothing widens. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #12254
Clause-②: no (narrowing)
Both dashboard surfaces now draw a widget's legacy
componentenvelope by the rule the authoring faces have stated since objectui#12163: ametric-cardnode draws as before, and any other node draws the existing retired-format placeholder and sends no query. This executes the follow-on that ruling C on objectui#12163 named (6121298298, taken with letter A,6125043394), as triage6125692023directs: a read-only census first, then, on a count of 0, the retirement.Census of stored use (read-only)
The census reads metadata as stored, and source. Each store is read structurally: a TypeScript-AST and JSON/YAML walker visits every
widgetsarray element that carriescomponent, everycomponent: { type … }object literal anywhere, and everytype: 'custom'. A control count ofwidgetsarrays reached is given for each store, so a 0 is a reading and not a walk that found nothing.metric-cardenvelope nodes outside tests and docsexamples/,apps/, templates, fixturesorigin/main0cd322d7, 7655 json/yaml/js/ts files (the 87 unparsed are JSONCtsconfigfiles)widgetsarrays in 125 files reached, the 9examples/schema-catalogdashboards among them. Every envelope hit is a test inpackages/typesorpackages/plugin-dashboard.examples/, templates, seedsorigin/maine49aaec7, 9494 fileswidgetsarrays in 76 files, among them theapp-crm,app-showcase(3) andapp-tododashboards andplatform-objects'system_overview.0f84c9c7, 570 files, nothing executedfunnelandmetricwidgets with nocomponent.git clone --depth 1answered "could not read Username for 'https://github.com': terminal prompts disabled"; the REST read answered 403 "GitHub access to this repository is not enabled for this session".0cd322d7:plugin-designer'sDashboardEditor; Studio'sDashboardDefaultInspector,DashboardWidgetInspectorandDashboardPreviewcomponent, and no type picker offerscustom. They writeid,title,type,layout,colorVariant,dataset,dimensions,valuesandfilterBindings; an update spreads{ ...w, ...updates }, which carries a stored key through and never authors one.origin/maine49aaec7componentkey at all:saveMetaItemvalidates against@objectstack/spec'sDashboardSchema, whose widget is astrictObjectthat refusescomponentby name (QUARANTINED_WIDGET_KEYS, pinned by the spec'sdashboard.test.ts"quarantine branch").findfor*.db/*.sqlite*outsidenode_modulesAn inference, not a read: a cloud package loaded through
defineStackor saved throughsaveMetaItemis parsed by the same strict spec widget, which refuses acomponentkey whatever node it holds. That bounds the unread store; it does not count it.What changes
isRetiredEnvelopeNode(legacyRetiredWidget.ts), the one predicate both surfaces read, now answers true for every envelope node but ametric-card: a node whosetypeis not a member ofDASHBOARD_COMPONENT_WIDGET_TYPES(the closed list the zod faces route on), a node with notype, a string or a number. It used to nameobject-metricalone (objectui#11466), which is now one case of the rule. An empty envelope still holds no node and draws by its family.DashboardRendererandDashboardGridLayout: the envelope branch hands the renderertoDashboardNodeType(node), only ever ametric-cardnow. ThetoRenderableSchemabridge for a primitive is gone from both, since a primitive in the envelope now draws the placeholder.DashboardRendererdrew acustomwidget with nocomponentas a prompt to "setcomponentto a UIComponent schema". A UIComponent other than ametric-cardnow draws the retired placeholder, so the prompt sent authors to a dead end. That widget now drawsunsupportedWidgetSchema('custom'), whichDashboardGridLayoutalready drew for it. No placeholder, prop, export, spec key or i18n key is added; one bespoke placeholder is removed.object-chartandobject-data-table, which no longer draw there. Thecustomdispatch family's doc says what it draws now.content/docs/plugins/plugin-dashboard.mdxand the package README state the draw rule where they stated that the surfaces still drew any stored node.@object-ui/plugin-dashboardminor, BREAKING stated in prose with FROM and TO examples.@object-ui/typesand the zod faces are not touched.Pins
New:
packages/plugin-dashboard/src/__tests__/envelopeDrawRetired-12254.test.tsx, run on both surfaces:customwidget'stext,object-chart(with the filter bar set),object-data-table,card, a node with notype, the namespacedplugin-dashboard:metric-cardnode key, a string, a number andtrue. Each drawsLEGACY_RETIRED_WIDGET_SCHEMAby identity, puts nothing of the node on screen and sends nofind,aggregateorqueryDataset. Each is also refused atcomponentby the zod face, so the draw and authoring agree.object-chart, a number, ortrue) keeps its card heading around the placeholder.customwidget with nocomponentdrawsunsupportedWidgetSchema('custom').metric-cardenvelope (typeless, and in acustomwidget) draws its figure under the namespaced node key, exactly{ ...card, type: 'plugin-dashboard:metric-card' }, and the zod face accepts it.object-metricenvelope pin objectui#11466 wrote (inlineObjectMetricRetired-11525.test.tsx), and both surfaces'legacyRetiredsuites.Fixture triage, by what each fixture pinned:
inlineObjectMetricRetired-11525.test.tsx: its last block pinned that the filter bar still scoped an envelope'sobject-chartandobject-data-table, which is the branch this change removes. The block is replaced by the new file's cases, and a dated note explains the change.dashboard-producer-node-types-11514.test.tsx: section 3 pinned thetoRenderableSchemabridge for a primitive in the envelope, which this change removes. It is replaced the same way.nodeSlotDeclaration-11170.test.tsx: it used atextnode only as a probe of thewidgets[].componentnode slot. It is re-spelled to ametric-cardcarrying the mark. The slot declaration stays true, because the renderer still hands the slot's node toSchemaRenderer.Reverse verification
Both legs ran through
ablation-replace.mjs(WRAP mode) from committeddd076421. The expected direction was stated before each run, in the new file's header.object-metriconly):Tests 24 failed | 38 passed (62). Every retired-draw case and every heading case is red on both surfaces. The four CONTROLs, bothcustom-no-component cases and the whole 11525 file are green. The anchor went from 1 hit to 0, and the blob fromfd792f2dtoe3670325. On restore, the blob is againfd792f2d, equal to HEAD, andgit diff HEADis empty.customprompt planted back:Tests 1 failed | 29 passed (30). The red one is the renderer'scustom-no-component case; the grid never had that branch. The blob went from3a7628c5to74db5575, and on restore it equals HEAD withgit diff HEADempty.The tests import the subject by relative source path, so no
dist/is involved.Verification at
dd076421pnpm exec vitest run --maxWorkers=2 packages/plugin-dashboard/:Test Files 175 passed (175),Tests 1749 passed | 6 skipped (1755).pnpm --filter @object-ui/plugin-dashboard type-check(tsc --noEmit && tsc -p tsconfig.test.json): exit 0. The test program lists all four touched test files.pnpm --filter @object-ui/plugin-dashboard lint(eslint .): exit 0, 0 errors.check:doc-snippets(788 of 788 blocks judged, 0 failed),check:doc-examples,check:readme-exports,check:doc-fences,check:doc-json,docs:check-links,check:doc-types,check:doc-example-idsandcheck:doc-example-readers.check:control-bytes,check:new-line-citations(0 new),check:vi-mock-inherit,check:vi-mock-specifiers,check:vi-mock-override-shape,check:test-path-roots,check:changeset-claims,check:pending-changeset-literals,check:unreferenced-sources,check:phantom-deps,check:unused-deps,check:component-surface-parity,check:handler-key-reads,check-changeset-no-major.mjsandcheck-changeset-presence.mjs.check-governed-queue-guard.mjs --testover the diff: NOT GOVERNED.check:eager-closure. Reason: it readsapps/console/dist/eager-closure.json, a console build this run did not make. The diff adds no import that is not already in the package's closure.A contract-tier review is owed before the queue: this narrows what the published renderer draws, as the claim records.
Acceptance notes
customstays in objectui's widget vocabulary (DASHBOARD_WIDGET_TYPE_EXTENSIONS). After this change acustomwidget draws ametric-cardenvelope or a placeholder, nothing else. Whether thecustomtype itself retires is the D question's other half, and it was not measured here. Carrier: none.unsupportedWidgetSchema's wording, "chart type is not supported yet", now also coverscustom, which is not a chart family. That is existing wording; this change does not touch it. Carrier: none.Generated by Claude Code