Skip to content

docs(schema-reference): dashboard widgets teach layout and type + options, not colSpan / rowSpan / body - #10452

Merged
os-elon-musk merged 2 commits into
mainfrom
claude/issue-8290-dashboard-reference-widget-keys
Sep 25, 2026
Merged

os-elon-musk merged 2 commits into
mainfrom
claude/issue-8290-dashboard-reference-widget-keys

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #8290
Clause-②: no — documentation only: it stops a published page from teaching keys (colSpan, rowSpan, body) that the strict DashboardWidgetSchema refuses by name. No schema, export or accept set moves.

What changed

content/docs/api/schema-reference.md, the DashboardComponentSchema section only (JSON example, widgets row, prose under the table), plus one empty-frontmatter changeset.

  • Example. Each widget sizes itself with layout: { x, y, w, h } instead of colSpan / rowSpan, and holds its content as a family in type with settings under options (metric, list), or as a registered component node in component (the chart widget) instead of body.
  • widgets row. The type column names both arms of the slot as landed by objectui#7952 (component arm first, as on both faces): (DashboardWidgetSlotComponentSchema | DashboardWidgetSchema)[]. The description names layout, type + options, component, and the component-node arm.
  • Prose. Says what layout is (all four numbers or none), that colSpan / rowSpan / body are refused by name (objectui#6002), what each family reads from options, and that a metric widget's caption is options.description.

The seconds half of the card was already discharged at base cc4366d: the row reads refreshIntervalSeconds ("Auto-refresh interval in seconds", objectui#7783), the example writes "refreshIntervalSeconds": 30, and both renderers compute schema.refreshIntervalSeconds * 1000 (DashboardRenderer, DashboardGridLayout). Not redone.

Parse evidence (scratch harness, source zod faces, not committed)

The harness takes the first json fence under ### DashboardComponentSchema in the file it is given and parses it.

Control, base cc4366d copy of the page:

DashboardComponentSchema.safeParse(example) success=false
  node issue code=invalid_union path=["widgets",0]  msg=Invalid input
  node issue code=invalid_union path=["widgets",1]  msg=Invalid input
  node issue code=invalid_union path=["widgets",2]  msg=Invalid input
  widget id=revenue DashboardWidgetSchema success=false unrecognized_keys keys=["colSpan","rowSpan","body"] path=[]
  widget id=chart DashboardWidgetSchema success=false unrecognized_keys keys=["colSpan","rowSpan","body"] path=[]
  widget id=tasks DashboardWidgetSchema success=false unrecognized_keys keys=["colSpan","rowSpan","body"] path=[]

This branch, head 5eca3f2:

DashboardComponentSchema.safeParse(example) success=true
  widget id=revenue DashboardWidgetSchema success=true
  widget id=chart DashboardWidgetSchema success=true
  widget id=tasks DashboardWidgetSchema success=true
chart widget component -> ChartSchema.safeParse success=true keys=["type","data","chartType","series","xAxisKey"]

Prose probes: area / bar / line / pie / metric / list / table / metric-card are all members of DashboardWidgetTypeSchema, and a widget with layout: { w: 2, h: 1 } is refused invalid_type at layout.x and layout.y.

TypeScript face: I pasted the example into a scratch file as const doc: DashboardComponentSchema = … (imported from packages/types/src/complex) and ran tsc --strict. The new example produces 0 diagnostics in that file. The base example produces 3 (TS2322, one per body). Both runs also show the same 2 environment-only diagnostics in complex.ts, "Cannot find namespace 'React'", which come from --ignoreConfig.

Gates (local, head 5eca3f2)

  • check-changeset-presence: exit 0. It prints "✅ No source or published contract of a released package changed in this range, so no changeset is owed." The added changeset is the empty-frontmatter declaration.
  • check:doc-types: exit 0, "✅ Every documented component type is registered."
  • check-prompt-component-keys: exit 0.
  • check:doc-fences --self-test and check:doc-fences: both exit 0.
  • check:doc-example-ids: exit 0, "✅ 414 real reference(s) all resolve in the catalog registry."
  • check:new-line-citations: exit 0, "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0".
  • check:control-bytes: exit 0.
  • check-doc-links: exit 0, "Links are valid across 17 scan roots."
  • check-changeset-no-major: exit 0.
  • check-changeset-overwrite: exit 0.
  • check:pending-changeset-literals: exit 0.
  • check:changeset-claims (report-only): exit 0. 14 pending changesets name this file, and none of them mentions the dashboard section.
  • check-doc-expression-carriage (report-only): exit 0. Its one site on this page is in the ActionSchema section, which already existed and is outside this diff.
  • Tests that read this page, run under the verify lock: pnpm exec vitest run on 8 files (4 packages/types pins that read schema-reference.md, the zod mirror parity suite, and check-doc-component-types / check-doc-links / component-node-vocabulary-7434 under scripts/__tests__). Result: "Test Files 8 passed (8) · Tests 557 passed (557)", lock verdict "VERDICT command-exit 0".
  • NOT MEASURED: check:doc-snippets / check:doc-examples. The gate exits 2 because the packages it reads are unbuilt. doc-snippets compiles only ts/tsx fences, and this diff changes one json fence and prose. doc-examples reads JSDoc in packages/*/src, and this diff touches no source. Build Docs, lychee external links and the docs route closure are left to CI.

Deviation: the chart widget uses the component slot

My first commit (13b194c) wrote the chart as "type": "area" + options. check:doc-types rejected it: unregistered-doc-type ... type 'area' (json). No spec chart family is a registered component key. A shorthand chart family inside a docs code block therefore needs a DOC_TYPE_EXEMPTIONS entry in scripts/check-doc-component-types.mjs, which is outside this claim's file surface. The chart widget now puts its chart node in component. That form is legal on this node and needs no exemption. It also means the example shows both content forms the row names. The prose teaches the chart families' options keys. If the seat prefers the shorthand chart in the example, the follow-up is one exemption entry of the same kind the guide/dashboard-filters.md bar / line entries already are.

Acceptance notes

  • My hunks are near the DashboardComponentSchema heading. Draft PR objectui#8941 edits this file near line 684, so the two do not overlap.
  • Zero label writes. objectui path labels come from labeler.yml.

Generated by Claude Code

…ions, not colSpan / rowSpan / body

The DashboardComponentSchema section taught three widget keys the strict
DashboardWidgetSchema refuses by name. The example now sizes each widget with
layout { x, y, w, h } and gives it content as a family in `type` with its
settings under `options`; the widgets row names both arms of the slot and the
prose under the table says what each family reads from `options`.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
… component slot

A spec chart family named in a docs code block needs a doc-types exemption
entry, which lives outside this change's file surface; the chart widget now
holds its `chart` node in `component`, so the example shows both content forms
the widgets row names and the prose teaches the chart families' options.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 14 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/3917-retire-action-condition-branch.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • ActionCondition is removed from @object-ui/types (and from the barrel export). - ActionSchema.condition is retyped to the predicate the runtime actually honours: boolean | string | { dialect?: string; source: string } — the same three arms ActionRunner's own ActionDef.condition carries, and the same vocabulary visible and disabled use. - ActionConditionSchema is removed from @object-ui/types/zod (and from the zod barrel); the condition key now validates against that predicate union. - The two teaching sites (content/docs/core/enhanced-actions.mdx Conditional Execution, content/docs/api/schema-reference.md ActionSchema table) are rewritten to the live vocabulary: condition is a gate; a branch is expressed as separate actions with mutually exclusive conditions.

.changeset/6896-retire-chart-inline-data.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ One correction to the record the ruling rests on. The ruling states zero authorship of a populated series[].data outside tests across packages/ / apps/ / examples/. The re-measurement finds one such site inside those roots — packages/types/examples/data-display-examples.json (2 series) — plus four outside them, in documentation: content/docs/api/schema-reference.md (3) and content/docs/core/report-schema.mdx (1).

.changeset/6939-kanban-column-cards.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Breaking, deliberately. KanbanColumn declared its card list as items in complex.ts and in the zod mirror complex.zod.ts. Every board reads cards. Measured on origin/main 78a3cc238: KanbanImpl.tsx reads .cards on 12 lines, KanbanEnhanced.tsx on 8, and bucketCardsIntoColumns twice more as col.cards || []; .items had zero read sites in either board (a same-shaped .title control on the same two files returns 8 and 3, so those zeros are readings and not a mis-shaped probe). Both catalog entries, the plugin docs and content/docs/api/schema-reference.md all author cards.

.changeset/6951-tree-view-data-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What was measured, on this branch's base. TreeViewSchema declared two spellings for its one inline-nodes slot — nodes (read second) and data (read third: boundData || schema.nodes || schema.data || [] at renderers/data-display/tree-view.tsx:105), both declared by objectui#6150. data had been REQUIRED until objectui#6939 / PR fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) #7533 made it optional, so this retirement starts from a declared-and-optional member on both faces. The in-repo corpus at the retirement: seven tree-view nodes under examples/schema-catalog and packages/types/examples plus one content/docs fence — six on nodes, two on data (packages/types/examples/data-display-examples.json and content/docs/api/schema-reference.md), both rewritten; no package source authored either spelling.

.changeset/7664-kanban-arm-plugin-dialect.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Migration. Author boards in the plugin dialect — objectName + groupBy for an object-bound board, or columns[].cards[] with badges for a static one. Replace DeclarativeKanbanSchema imports with KanbanSchema (from @object-ui/types, or the Zod KanbanSchema from @object-ui/types/zod; @object-ui/plugin-kanban re-exports the same KanbanSchema type). Delete draggable (drag-and-drop is always on) and column color (style a lane through className). content/docs/api/schema-reference.md's kanban section now documents this dialect.

.changeset/7997-detail-view-related-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Documentation. packages/plugin-detail/README.md and content/docs/api/schema-reference.md stop teaching the retired array and gain a migration block each.

.changeset/8268-testid-emitted-as-data-testid.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    The other direction — retiring the promise — was considered and declined. It is what objectui#7088 did for BaseSchema.hidden, but that key had a working behaviour to describe and zero named consumers, and the ruling's decline turned on exactly that. This promise already has carriers outside the type declaration: content/docs/api/schema-reference.md states it as a table row and authors testId in that page's own base-schema example, @object-ui/cli's OBJECTUI_STRUCTURAL_KEYS identifies a file as an ObjectUI schema node by this key, ObjectGridSlotKey / ObjectFormSlotKey pin it, SchemaBuilder.testId() writes it, and ADR-0054 C4 — shipped — reads "the renderer emits data-testid … derived from metadata".

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/8801-object-kanban-allow-collapse-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/9187-record-highlights-layout-two-values.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ The census behind this narrowing covers this repository only, and it found no in-repo authoring to migrate: every in-tree layout: 'grid' belongs to a different component (detail-view in content/docs/api/schema-reference.md and phase2-schemas.test.ts, ai-recommendations in packages/plugin-ai/README.md), and the one in-repo consumer of this interface that writes a layout (p1-spec-alignment.test.ts) writes 'horizontal'. So no document in this repository stops type-checking. A TypeScript consumer outside this repo that wrote grid is not observable from here and gets a compile error (TS2322) naming the key — which is why the FROM/TO is spelled out above.

.changeset/calendar-view-schema-converge.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Runtime renderer behaviour is unchanged. @object-ui/plugin-calendar's README and content/docs/api/schema-reference.md are repaired to the converged surface in the same change, so no copy of the old contradiction survives.

.changeset/issue-5373-retire-crud-schema.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    crud had four declaration faces and no registered renderer, for the whole life of the key: the TS interface (packages/types/src/crud.ts), the zod mirror (packages/types/src/zod/crud.zod.ts), a dedicated branch in validateSchema that affirmatively PASSED it, and CRUDBuilder in @object-ui/core. A node spelling it painted the OBJUI-001 "Unknown component type" panel, and content/docs/api/schema-reference.md published it as reference material — so a reader (or an AI author) who copied the page got a red panel.

  • names api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Authoring crud is now REFUSED BY NAME rather than passed or silently ignored. validateSchema returns an error with code: 'RETIRED_TYPE' on schema.type — at any depth, since it is what validateChildren recurses with — so assertValidSchema throws and isValidSchema answers false. The message names the migration: object-grid for the record table with its toolbar, filters, pagination and row/batch actions, object-form for the create/edit form, and detail for the record view. api/schema-reference.md is rewritten around those shapes.

.changeset/object-view-unmirrored-keys-7779.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    listViews stays unmirrored, on the ruling's own fallback clause. The declaration's value is the local NamedListView — 64 declared top-level members (⚠️ re-taken at objectui#8980, which added the seventeen the protocol declares on this surface to the 47 this entry first measured), of which the renderer reads 21 off a named view. data is one of the 21 now: it used to reach the renderer through an as any cast on the named-view config in packages/plugin-view/src/ObjectView.tsx and be declared nowhere, and the objectui#8980 ruling declared it by name — objectui#7928's open half, answered. The spec's ViewSchema.listViews is a record of the STRICT ObjectListViewSchema, which requires columns and refuses options, ObjectQL tuple filters and default — that is, it refuses the named views this package's own README and content/docs/api/schema-reference.md teach ({ label: 'All Users' } fails at columns; filter: [["owner", "=", "..."]] fails at filter.0). Mirroring the spec value would lose documented behaviour; mirroring the local value would enforce 43 unread members (64 declared, minus the 21 that are both declared and read) into the contract — the very thing ruling B refused for the six local keys. The key therefore stays in the parity ledger with that measurement, pinned, until the maintainer decides its value type. It is not papered over with z.any().

.changeset/table-renderer-declared-column-contract-5350.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What makes this site different from its three siblings is that the alias was not merely tolerated, it was published. content/docs/api/schema-reference.md §TableSchema shipped a copyable { "name": "id", "label": "#" } example and a property row reading "Column definitions with name, label, …", while packages/types declared the opposite pair. Docs and type disagreed about one type they both call TableColumn, each internally consistent. Retiring the alias without correcting the page would have turned a documented, working example into a silently broken one, so both halves land together: the page now authors accessorKey/header. The same row also advertised a render property that TableColumn has never declared — the renderer's hook is cell — and that claim is dropped rather than re-spelled.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with ff14e29b5 (merge-base with origin/main): 1 file(s) changed outside .changeset/, read against 1383 pending declaration(s) that publish a body (1960 pending in total). · run

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5eca3f2efe85d956f33b48efc3399f56fc3c99ee

① Derived judgments

Surface. git diff $(git merge-base refs/review/main refs/review/pr-10452) refs/review/pr-10452 --stat: 2 files, +21/−16. Merge-base cc4366d2. .changeset/8290-dashboard-reference-widget-keys.md (+4, empty frontmatter) and content/docs/api/schema-reference.md (+17/−16). Every page hunk sits inside the ### DashboardComponentSchema section: the JSON fence (head lines 968–1018), the widgets table row (1024), and the two prose paragraphs (1027, 1029). No packages/**, scripts/** or other source touched. Branch carries 2 commits (13b194c, 5eca3f2), head trailer carries Claude-Session: …session_014mXUNuFomfj24w7s1pZzhN. Clause-②: no is accurate — prose plus one JSON fence, nothing else.

Parse evidence (my own harness, not the dev's). Scratch project under scratchpad/pr-10452/harness/: git archive refs/review/pr-10452 packages/types/src, pnpm install of the package's two runtime deps (@objectstack/spec 17.4.0, zod 4.6.5), tsx parse.ts importing packages/types/src/zod/complex.zod.ts and data-display.zod.ts from source. Takes the first json fence under the heading.

  • HEAD example: DashboardComponentSchema.safeParse success=true; each of revenue/chart/tasks parses under DashboardWidgetSchema (success=true); the chart widget's component node parses under ChartSchema (success=true, keys type,data,chartType,series,xAxisKey).
  • BASE control (merge-base copy of the page): success=false, 3 issues, each unrecognized_keys at widgets[0..2] with keys ["colSpan","rowSpan","body"]; per-widget DashboardWidgetSchema refuses all three widgets naming the same three keys. (Under zod 4.6.5 the union surfaces the strict arm's issue directly rather than the invalid_union wrapper the PR body recorded; same refusal, different aggregation.)

Table row widgets, sentence by sentence, at head.

  • Type column (DashboardWidgetSlotComponentSchema | DashboardWidgetSchema)[]: zod widgets: z.array(z.union([DashboardWidgetSlotComponentSchema, DashboardWidgetSchema])) (complex.zod.ts:1231); TS widgets: Array<DashboardWidgetSlotComponentSchema | DashboardWidgetSchema> (complex.ts, DashboardComponentSchema). Component arm first on both faces. True. **Required.**: not .optional() on zod, non-optional member on TS. True.
  • id, title, description are widget keys: zod shape keys measured ["title","description",…,"layout","options",…,"id","type","component"] (23 keys). True.
  • layout: { x, y, w, h }: spec's layout inner object keys x,y,w,h, catchall never; TS DashboardWidgetLayout four required numbers. True.
  • content is type + options or component: DashboardRenderer.tsx getComponentSchema: if (widget.component) return widget.component; else classifyWidgetType(widget.type) over options. True.
  • "full key set is the spec's DashboardWidget plus objectui's own": specFieldsExcept(SpecDashboardWidgetSchema.shape, ['id','type']).extend({id, type, component}).strict(). True.
  • Component node: DASHBOARD_COMPONENT_WIDGET_TYPES = ['metric-card'] (complex.ts:2019); harness probe {type:'metric-card', value:42, icon, trend} in the slot → success=true, keys kept ["type","value","icon","trend"]; the same object under DashboardWidgetSchema alone → refused value,icon,trend. So "its other keys are that component's own props" is true, and the routing is the slot's, as the row says.
  • refreshIntervalSeconds row (unchanged): both renderers multiply by 1000 (DashboardRenderer.tsx:465, DashboardGridLayout.tsx:188). True.

Prose paragraph 1.

  • w/h are spans: dashboard applies gridColumn: span w / gridRow: span h (DashboardRenderer.tsx:930-932), no explicit x/y placement. x/y are the position on the editable dashboard-grid: DashboardGridLayout.tsx:113-116 reads layout.x/y/w/h into react-grid-layout and merges them back on Save (:77). True. (Nuance, not a contradiction: dashboard also reads layout.x + w to infer columns when the schema omits columns, :247; the example sets columns: 4.)
  • "layout takes all four numbers or is left out": probes {w,h} → invalid_type at layout.x, layout.y; {} → four invalid_type; no layout → success=true; an extra key static → unrecognized_keys on the box. True on both faces.
  • "colSpan, rowSpan and body are not widget keys … refuses all three by name": probe → unrecognized_keys keys=["colSpan","rowSpan","body"]. .strict() at complex.zod.ts DashboardWidgetSchema with the objectui#6002 docblock. True.

Prose paragraph 2.

  • metric shows options.value: value: options.value ?? rows[0]?.[valueField] ?? '—' in the metric family branch. True.
  • list/table show options.data rows: TABLE_LIKE_TYPES = {'table','list'}; widgetData = widget.data || options.data; static path returns { type: 'data-table', data: staticRows, columns: … }. data is not a widget key (strict), so options.data is the only authorable path. True.
  • chart families area/bar/line/pie read options.data/xField/yField: all four in SERIES_CHART_TYPES; xAxisKey = options.xField || 'name', yField = options.yField || 'value', returns { type: 'chart', data: dataItems, xAxisKey, series: [{ dataKey: yField }] }. True. All eight prose-named families are DashboardWidgetTypeSchema members (harness: 23 members; statistic, the base example's inner type, is not).
  • component node keys are ChartSchema's: harness ChartSchema.safeParse success=true. True.
  • metric caption is options.description: widgetSubCaption.ts:18 "the AUTHORED value, widget.options.description"; renderer passes description: subCaption. widget.description is the card-header subtitle: Card path renders CardTitle then <p>{resolvedDescription}</p> from tWidgetDescription(widget) = widget.description. An inline metric does not draw it: isSelfContained = widget.type === 'metric' && !datasetBound renders SchemaRenderer without the Card wrapper and never forwards widget.description. True, with the right qualifier (dataset-bound metrics and kpi/gauge/bullet do get the header).
  • Example's options.trend: { value: 12, direction: "up" } matches MetricWidget's trend?: { value: number; label?; direction?: 'up'|'down'|'neutral' } (MetricWidget.tsx:242-246), so the example teaches a shape the renderer reads.

Rest of the page and the README. grep -n -i -E "colSpan|rowSpan|dashboard|metric-card|DashboardWidget" schema-reference.head.md outside 964–1031: 0 colSpan/rowSpan hits; the only other dashboard authoring is the CRM page example (1310–1315), two metric-card slot nodes — the component-node arm the row now documents. The page's body rows (81, 96, 139, 201) describe BaseSchema's retired child-list spelling (objectui#6771); DashboardWidgetSchema is not built on BaseSchema and refuses body as an unrecognized key, which is exactly what the prose attributes to #6002 strictness — no contradiction. packages/plugin-dashboard/README.md at head: 0 colSpan/rowSpan; line 283–287 teaches the same options.data + xField/yField and "no nested body slot"; its type table (496–497) names the two arms the same way. Consistent.

② Semver level

Empty frontmatter is right. content/ has no package.json (git ls-tree empty); .changeset/config.json fixed group (40 packages) contains nothing under content/; ignore is @object-ui/example-*, @object-ui/site, @object-ui/test-support. scripts/check-changeset-presence.mjs header guards <pkg>/src/** of fixed-group packages plus index.html / files-published entries minus *.md, so a content/docs/**-only change owes no changeset, and AGENTS.md:167 names the empty-frontmatter file as the legitimate first-class "no release" declaration. CI on head: Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read all success. The changeset body and the new prose carry no cross-file path:line citation (AGENTS.md #11; Line Citation Gate success).

③ Boundary flags

  • Keeping component for the chart widget makes no sentence false. Both faces accept it (component: BaseSchema.optional(); TS component?: SchemaNode), the renderer honours it first, and the row and prose name it explicitly ("as the chart widget above does"). Advisory, non-blocking: both faces' docblocks call component "legacy format — objectui-only, no spec counterpart" with migration to the shorthand "deferred", and the page does not say so; a reader authoring stored metadata against the spec's DashboardWidget is not warned that the chart widget copied from this example uses an objectui-only key. One clause would close it; the seat's option B later would too.
  • Why the shorthand type: 'area' failed the gate and metric/list did not: check:doc-types judges type literals against the node-type registry; metric (plugin-dashboard/src/index.tsx:140) and list (components/src/renderers/data-display/list.tsx:14) are registered node types, area is not. The two shorthand literals pass by spelling coincidence between the widget-family and node-type vocabularies (the gate's own kanban exemption reason describes the same coincidence). Green today; if either registration were ever retired this page would need a DOC_TYPE_EXEMPTIONS row of the guide/dashboard-filters.md bar/line kind (check-doc-component-types.mjs:834-839). Non-blocking.
  • NOT MEASURED items are covered by CI. doc-snippet-types.yml runs both node scripts/check-doc-snippet-types.mjs and node scripts/check-doc-example-types.mjs in the one job named Doc Snippet Type Check; that check-run is success on the head.
  • Check-runs on head: /commits/5eca3f2…/check-runs?per_page=100 → 41 runs, 38 success, 3 skipped (Test (coverage), dependabot, the coverage-shard matrix template), 0 failure, 0 in_progress. Doc Component Type Check, Build Docs, Internal Docs Link Check, Docs Route Eager Closure Check, Doc Fence Language Check, Doc Example Id Check all success.
  • Nuance, non-blocking: the zod-face DashboardWidgetSlotComponentSchema is a deliberately non-exported const; the name in the type column is the exported TS interface (complex.ts:2197), matching the README's usage. A reader importing that name from @object-ui/types/zod will not find it, but the table's type column names TS types throughout.
  • Process, not contract: the PR is still draft: true (mergeable_state: clean); it must be un-drafted to land.

Implemented-by: claude/issue-8290-dashboard-reference-widget-keys
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS

Generated by Claude Code

@os-elon-musk
os-elon-musk marked this pull request as ready for review September 25, 2026 02:44
@os-elon-musk
os-elon-musk added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit ab50ccc Sep 25, 2026
43 checks passed
@os-elon-musk
os-elon-musk deleted the claude/issue-8290-dashboard-reference-widget-keys branch September 25, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants