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
Conversation
…y only; a valueless metric-card is refused on the TS face The widget arm's `type` drops DashboardComponentWidgetType, so the TS face matches the Zod twin's widget vocabulary: a `metric-card` is read by the slot's component arm alone and its required `value` governs. The MEASURED LIMIT pin becomes a compile-time refusal, the KnownDrift row loses `type`, and the 7952 pin records that the component arm is no longer assignable to the widget arm. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…type; the object-chart producer names ObjectChartSchema DashboardRenderer, DashboardGridLayout, DashboardWithConfig and isLegacyRetiredWidget read a widgets[] entry by DashboardComponentSchema['widgets'][number], and onWidgetsReorder hands back the slot's own array type. A series dispatch carries SeriesChartFamily, so both object-chart producers build a node that satisfies ObjectChartSchema with no cast. Runtime is unchanged: the families set and the dispatch are the same values, and chartType is the dispatch's resolved family on both branches. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…the slot's element type DashboardPreview's reorder handler takes DashboardComponentSchema['widgets'], the array DashboardRenderer's onWidgetsReorder now hands back. The designer's DashboardEditor reads an entry (WidgetCard, WidgetPropertyPanel, its preview and the measure probe) by the same element type, because the component arm is no longer assignable to the widget arm. Type-only; nothing renders differently. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…the entry on type The README and the plugin page taught reading entries through DashboardWidgetSchema because the component arm was assignable to it. It no longer is, so both blocks narrow an entry on `type` (`metric-card` is the one component type the slot holds) and read the widget key off the widget arm, which the compiler checks. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…ChartSchema on both faces A type row holds SeriesChartFamily inside ObjectChartSchema['chartType'] and the slot-entry and reorder types to the slot's own; a runtime row renders every series family through both surfaces and has the Zod ObjectChartSchema judge the node each one hands the renderer. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…ding entries the narrowing makes false New: @object-ui/types minor (the widget arm's type), @object-ui/plugin-dashboard minor (onWidgetsReorder's parameter type), @object-ui/plugin-designer and @object-ui/app-shell patch (type-only reads). Dated, append-only notes on 11483-metric-card-needs-value, 7952-dashboard-widgets-component-arm, 11348-dashboard-widget-reads and dashboard-widget-type-closed-enum. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…d family as its chartType Inside the series branch the dispatch's chartType is set, so the value is the one the removed resolvedWidgetType held; it now types as SeriesChartFamily, as DashboardGridLayout's twin already did, instead of the slot entry's widened type. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ 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
|
Brings objectui#11506 (the drill drawer's runtimeFilter, PR #11516) onto the branch; no file overlaps this branch's diff. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC 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
|
…`metric`; an unknown type draws the labelled unsupported placeholder (objectui#11514, Q2 A) Both dashboard surfaces resolve an absent widget `type` to the spec's default, read from @objectstack/spec's DashboardWidget.type rather than restated, for the dispatch, the metric span and chrome, and the mobile metric row. The slot-component passthrough serves the component arm alone; any other entry that reaches it names no family and draws the labelled placeholder an unsupported family draws, shared now as unsupportedWidgetSchema, instead of the registry's OBJUI-001 panel. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…e spec's `metric` too; pin both Q2 A renders on both surfaces (objectui#11514) DatasetWidget resolves an absent widget `type` to the spec's default, so a typeless dataset-bound widget with a dimension draws the metric tile instead of a bar chart. The new pin holds, on both surfaces, that a typeless widget draws byte-for-byte what `type: 'metric'` draws (inline, dataset-bound and on the renderer's mobile layout), that an unknown `type` draws the labelled placeholder a known unsupported family draws, and that a metric-card still passes through the component arm. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC 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
|
✅ 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
|
…the widget arm declares it, so the producers' return is no longer any On the slot entry union, a bare entry.component reads the component arm's index signature and is `any`, which made both surfaces' getComponentSchema return `any`: the SchemaRenderer casts were casts of `any`, and dropping them would have compiled for the wrong reason, under V1 too. entryComponent reads the same value, typed DashboardWidgetSchema['component']. The cast comment in DashboardGridLayout now names what the casts still narrow. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
… unknown `type` draws the labelled placeholder Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…unknown-type renders (objectui#11514, Q2 A) Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
Brings objectui#11484 (the plugin page's charts example, PR #11523); it merges cleanly beside this branch's sections of the same page. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC 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
|
…me; its primitives bridge through toRenderableSchema (objectui#11514) The spec's default widget `type` (`metric`) is a statement about the spec's widget. objectui's legacy `component` envelope has no member there, draws its node rather than a family, and so is not given the default: a typeless envelope keeps `undefined` from `resolveWidgetType` and draws with its card chrome, as before. The envelope's `component` is `SchemaNode` (number and boolean members the renderer's prop does not declare); both surfaces now forward it through `toRenderableSchema`, which draws the same text the renderer drew for them bare. Pinned per surface: a number and `true` draw their text under the widget heading. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC Co-authored-by: Claude <noreply@anthropic.com>
…the spec's default `type` (objectui#11514) The README, the docs page and the plugin-dashboard changeset state that the legacy envelope keeps its card heading when it names no `type`, and that a number or `true` in its `component` draws the same text through `toRenderableSchema`. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC 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
|
Brings in objectui#11517 (PR #11527): DrillDownConfig.report's name arm retired on all three faces. No conflict. Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC 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
|
metric and an unknown type the placeholder, the object-chart producers name ObjectChartSchema (objectui#11514)
Contract reviewServed-tier: Inputs, as read. Card objectui#11514: its body and its five comments ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
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 awidgets[]entry reads it by the slot's element type), bothobject-chartproducers namedObjectChartSchemawith no cast, and Q2 A (what a widget with notype, or with atypethat names nothing, draws). Theobject-metricproducer moved to objectui#11525 and is untouched here. The class-a finding this PR's first report raised (a rule-listfilterwith a structuredgroupBydraws an error) is filed as objectui#11526.What changed — before and after
fc7db059f)DashboardWidgetSchema['type'](TS face)DashboardWidgetTypeName | DashboardComponentWidgetTypeDashboardWidgetTypeName, the zod twin's set{ type: 'metric-card', title }directly inwidgets[]tscrefuses it too; zod faces unchangedwidgets[]entry inDashboardRenderer,DashboardGridLayout,DashboardWithConfig,isLegacyRetiredWidgetand the designer'sDashboardEditorDashboardWidgetSchemaDashboardComponentSchema['widgets'][number]DashboardRendererProps['onWidgetsReorder']parameter, and app-shellDashboardPreview's handlerDashboardWidgetSchema[]DashboardComponentSchema['widgets']WidgetDispatch['chartType']stringSeriesChartFamily: the 12 series families, each a member ofObjectChartSchema['chartType']object-chartproducerschartTypetypedstringsatisfies ObjectChartSchema, no casttype, both surfacestype: 'metric'draws: inline, dataset-bound, and inDashboardRenderer's mobile metric rowtypethat names no family and no component type, both surfacesheatmapdrawsmetric-card) alone, drawn as beforecomponentenvelopeSchemaRendererbaretruein it goes throughtoRenderableSchema, which draws the same textWhat 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/spec17.5.0'sDashboardWidgetSchema.typedefaults tometric. 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 tometricat the installed spec.resolveWidgetType(entry)is the authoredtype, or that default when the entry names none. Every read of an entry'stypeon both surfaces goes through it: the dispatch, the metric span and self-contained chrome, the mobile metric row.DatasetWidgettakes the same default.componentenvelope ({ id, component, layout }, objectui's own format, no member on the spec's widget) is not given the default:resolveWidgetTypekeepsundefinedfor an entry whosecomponentis 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 drawsunsupportedWidgetSchema, the placeholder an unsupported family already drew, now declared once for both surfaces.entryComponent(entry)reads the envelope'scomponenttyped as the widget arm declares it. A bareentry.componentread throughBaseSchema's index signature and made bothgetComponentSchemas returnany, which is what had let an earlier cast removal look clean.The
object-metricproducerNot touched, and no type added for it: it moved to objectui#11525. The premise measurement (branch B: the broadcast's
$andcarries the widget's MongoDB-stylefilter, and the bar's rule list makes the spec-shape aggregate branch draw an error) is in this card's firstos-dev-report(comment 5964044891). It stays red under V1, as expected.V1 census
Probe: PR #11512's branch (
ff7f748e) merged with this branch at2fb1b92bthroughgit merge-treein 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.DashboardGridLayout.tsx648 and 701,DashboardRenderer.tsx1077 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).getComponentSchemas as the renderer's prop,DeclaredNode | string | null | undefined): theobject-metricbranch (DashboardGridLayout381, 383;DashboardRenderer876, 878, 879), objectui#11525;plugin-dashboard:metric(DASHBOARD_NODE_TYPES.metrictypedstring; 396 and 894), objectui#11466's registry entry;DashboardGridLayout's staticpivot(472), objectui#11466. The passthrough and the unknown-type branch: 0. The envelope branch: 0. Before thetoRenderableSchemabridge it was 2 (string | number | true | DeclaredNode).SchemaRenderercasts stay: the branches they cover are objectui#11525's and objectui#11466's.main9ed8d0f1(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-treeof the new head withff7f748eshows only the same one conflict; andmain'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 whattype: 'metric'draws, inline and dataset-bound (the control is the explicitmetric), and in the mobile layout (non-vacuity: its 2-column metric row is drawn). An unknowntype(gauge2) draws the placeholder and no OBJUI-001 panel. Controls:heatmapdraws the same placeholder; ametric-cardstill draws through the component arm. Plus one row: the spec default ismetric.b92e80dc(before Q2 A): 7 failed, 5 passed (the 5 are the controls and the spec-default row). Restored fromHEAD, blob-matched.dashboard-producer-node-types-11514.test.tsx(32 tests): round one's 28, plus a number andtruein a legacycomponentenvelope, on both surfaces, drawn under the widget's heading.b92e80dcpass (4 passed, 28 skipped), so the envelope draws as it did. Ablation: the envelope exemption removed fromresolveWidgetType(ablation-replace.mjs, anchor 1 to 0, blob7f60e1c6toda48a7f9): all 4 rows fail. Restored: blob equal toHEAD,git diff HEADempty.SERIES_CHART_FAMILIESablation ('gauge'planted: 2tscerrors at bothsatisfies ObjectChartSchemasites, 2 vitest rows red), theDashboardPreviewreverse verification (TS2322 at itsonWidgetsReorderprop), and the objectui#11483@ts-expect-errorrow.Readers of the narrowed type
Needed an edit: plugin-dashboard's
DashboardRenderer,DashboardGridLayout,DashboardWithConfig,isLegacyRetiredWidget,DatasetWidget(the defaulttype), and two test readers; app-shell'sDashboardPreview(type-only); plugin-designer'sDashboardEditorand 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 andzod-mirror-parity'sKnownDriftrow; the plugin page and the plugin-dashboard README, which taughtconst widgets: DashboardWidgetSchema[] = dashboard.widgetsand now narrow ontypeand say what a typeless or unknown-type widget draws.Needed nothing: core's
buildWidgetScopedFilter,useWidgetSubCaption, app-shell'sDashboardWidgetInspector, andapps/*. Render suites outside the package that mount a dashboard (app-shell'sDashboardViewand previews,StudioDesignSurface, the widget DOM-leak sweep, the schema-catalog galleries, the console's drill-down and chart-stub suites) pass unchanged.Changeset sweep
11514-types-widget-arm-type(@object-ui/typesminor),11514-dashboard-slot-entry-types(@object-ui/plugin-dashboardminor; now also states the typeless and unknown-type renders, the passthrough serving the component arm, and the envelope exemption),11514-designer-dashboard-entry-typeand11514-app-shell-dashboard-preview-reorder(patch, type-only).11483-metric-card-needs-value,7952-dashboard-widgets-component-arm,11348-dashboard-widget-reads,dashboard-widget-type-closed-enum.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 awidgets[]entry),7952-dashboard-widgets-component-arm("What still refuses" is about validation),10859-dashboard-node-keysand11483-metric-card-needs-value(unchanged), and objectui#11517's three entries (drill-down only).Acceptance notes
object-metricwith a rule-listproperties.filterand a structuredaggregate.groupBydrawsUnloweredAggregateWhereErrorinstead of its number), as objectui#11526.DashboardEditorand its three tests,DatasetWidget, the two docs pages, the objectui#7952 pin, the two plugin-dashboard test readers,isLegacyRetiredWidget, and the staticchartproducer'schartType. Each is the same retype or the Q2 A ruling, and is named above.main:b92e80dc(8366accd1),9b0ec494(6903eafbc, objectui#11484's docs) and7939c9a0(9ed8d0f1, objectui#11517). All three merged cleanly.Tests and gates, at
7939c9a0type-checkfor@object-ui/types,@object-ui/plugin-dashboard,@object-ui/plugin-designerand@object-ui/app-shell(each script echoed, after rebuilding the designer and app-shell closures): exit 0.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-closureandcheck:sdui-registration-pins: NOT MEASURED, reason: both read a built console bundle (exit 2, precondition). The one new runtime import,@objectstack/spec/uiinwidgetDispatch.ts, is a module app-shell already imports eagerly (UnifiedSidebar,MetadataProvider), and no registration input moved.eslint.config.jslints**/*.{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 8no-explicit-any, the recorder and fixture casts. Repo-wide lint is CI's.Generated by Claude Code