Skip to content

feat(types)!: a metric-card in the widget slot parses only with its value; metric-card leaves the widget type vocabulary (objectui#11483) - #11498

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11483-metric-card-needs-value
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11483-metric-card-needs-value

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11483

Clause-②: no (narrowing)

Builds on objectui#11467 (PR #11482, landed as 401611b21), which declared the widget slot's metric-card component arm with a required value.

What this does

This executes triage's ruling 5957113843 on its vocabulary arm. The measurement below found no widget-arm data binding for metric-card, so metric-card leaves the widget arm's type vocabulary. The component arm's required value is now the one reading of a metric-card in widgets[], and the requirement has one spelling.

  • Zod face: DashboardWidgetTypeSchema is the spec's chart families plus list and custom. It no longer spreads DASHBOARD_COMPONENT_WIDGET_TYPES.
  • TypeScript face: DashboardWidgetTypeName, the twin, drops DashboardComponentWidgetType. DashboardWidgetSchema['type'] keeps it (see "Why the TypeScript widget interface still reads metric-card").
  • What stays: DASHBOARD_COMPONENT_WIDGET_TYPES still lists metric-card. It is the component arm's type, and nothing else reads it in src.
  • What is not added: no renderer fallback, no refinement, no second required-key declaration. MetricCard, DashboardRenderer and every renderer file are untouched.

The accept set, before and after

Measured on both validator faces at base f560ded1 and at this head. The tolerant face is DashboardComponentSchema / AnyComponentSchema; the strict face is StrictAnyComponentSchema. Each entry sits in a dashboard's widgets[].

entry tolerant, before tolerant, after strict, before strict, after
{ type: 'metric-card', title } (the card's measured node) accept refuse accept refuse
the same plus description, id, layout accept refuse accept refuse
{ type: 'metric-card', title, dataset, values } accept refuse accept refuse
{ type: 'metric-card', title, options: { value } } accept refuse refuse refuse
{ type: 'metric-card', title, value } accept accept accept accept
{ type: 'metric', dataset, values } (control) accept accept accept accept
a card in the legacy component envelope, no value accept accept (unchanged; objectui#8347's corner) refuse refuse

Standalone, the zod DashboardWidgetSchema now refuses { id, type: 'metric-card' } at type. Each refusal is one invalid_union at widgets.0. The component arm answers invalid_union at value, and the widget arm answers invalid_value at type. objectui validate was run on the valueless card through packages/cli's validate command at this head. It exits 1 and names widgets → 0 → value. The valued card exits 0.

H1: the binding measurement (binding_measured: no)

The question was whether a widgets[] entry { type: 'metric-card', … } with widget-arm keys and no value ever draws a figure, and through which read. The probe was a throwaway vitest file, never committed. It rendered through the real DashboardRenderer and DashboardGridLayout, with @object-ui/components, @object-ui/plugin-charts and the plugin barrel registered. A queryDataset double returned revenue: 4217.

For each case the probe read three things: the text of MetricCard's figure element, whether 4217 appeared anywhere, and the dataset query calls. It read them at 0, 150 and 300 ms. Both surfaces gave the same readings.

  • Lit control: { type: 'metric-card', title, value: '4217' }. MetricCard's figure reads 4217, so the probe can see a figure.
  • Measured node: { type: 'metric-card', title }. The figure is empty, and it stays empty from 150 to 300 ms. That is the stability control: the empty figure is not a loading transient.
  • Inert-key control: adding description leaves the figure empty, so a change to some key is not mistaken for a binding.
  • dataset + values, no dimensions: MetricCard never mounts. 4217 is drawn by DatasetWidget, and the query runs once. The read in DashboardRenderer (and the same in DashboardGridLayout) is const datasetBound = !!widget.dataset, which routes to DatasetWidget. That route is chosen before the type is consulted.
    • Inside DatasetWidget, the figure comes from isMetric = METRIC_TYPES.has(widgetType) || (dimensions.length === 0 && …). metric-card is not in METRIC_TYPES, so the tile comes from the dimensionless clause, which ignores the type.
    • Type-agnostic control: { type: 'bar', dataset, values } draws the same 4217.
    • With dimensions, a metric-card takes the chart branch instead (CHART_TYPE_MAP[widgetType] ?? 'bar'). That last point is a code reading; jsdom draws no chart.
    • Verdict: dataset is the generic widget binding. It is not a binding the renderer resolves into the card's value.
  • options: { value: '4217' }: the figure reads 4217. The read is the slot-component passthrough's return toDashboardNodeType({ ...widget, ...options }). That is a literal spread onto the card's props: a second spelling of value, not a data binding. The ruling forbids a second spelling.
  • No figure at all: options.data with an object provider, legacy object / valueField / aggregate, inline data, an element dataSource, and filter all leave the figure empty. The legacy keys get the retired-widget placeholder.

⇒ No widget-arm key binds the card's figure. The ruling's vocabulary arm applies. The dataset-bound single figure keeps its own spelling: { type: 'metric', dataset, values } draws the same tile and parses on both faces (the control row in the table).

Why the TypeScript widget interface still reads metric-card

On the TypeScript face, DashboardWidgetSchema is also the read type of every widgets[] entry. The component arm is assignable to it, the dashboard docs teach that, and the renderers annotate slot entries with it. The dispatch rules out moving any renderer file, and both stricter spellings would move one. Each was measured with a real build of the @object-ui/app-shell closure:

  • Drop the component type from DashboardWidgetSchema['type'] too. tsc fails 10 lines in plugin-dashboard: 7 in DashboardRenderer.tsx and 3 in DashboardGridLayout.tsx. All are slot-entry callbacks annotated DashboardWidgetSchema (TS2345, TS2322, TS2769), and the build stops there.
  • Keep it on the interface but narrow the slot's element type. tsc fails once, in DashboardWithConfig.tsx, which writes DashboardWidgetSchema values back into widgets. The build stops at plugin-dashboard, so plugin-designer and app-shell went unmeasured on that variant.

So DashboardWidgetSchema['type'] is DashboardWidgetTypeName | DashboardComponentWidgetType, and the slot's element type is unchanged. One measured limit follows: a metric-card literal with no value still compiles directly in widgets[], through that read type, while both validator faces refuse it. It is pinned two-faced in metric-card-needs-value-11483.test.ts in the same style as envelopeStray.

The parity ledger records the divergence by measurement. zod-mirror-parity.test.ts's KnownDrift gains type on complex.zod.ts#DashboardWidgetSchema, and its header figure moves from 49 / 86 to 49 / 87. The file's own pin checks that figure.

H2: who reads the vocabulary

A census with the TypeScript type checker, at base and at this head. Root files were prefiltered by name. Each verdict is checker.getSymbolAtLocation resolved through aliases to the declaration in complex.ts / zod/complex.zod.ts, with @object-ui/types mapped to source.

  • DASHBOARD_COMPONENT_WIDGET_TYPES: 21 resolved sites before and 20 after. The site removed is DashboardWidgetTypeSchema's spread. Its src readers outside packages/types number zero, before and after. Its readers inside packages/types are the arm's own zod type enum and DashboardComponentWidgetType. Its test readers are dashboard-widget-slot-component-arm-7952, dashboard-widget-strict-6002, report-chart-query-spec-parity, plugin-dashboard's metricCardRegisteredInputsStrictFace-11022 and the schema-catalog gate.
    • ⇒ It now means the component arm's closed type set and nothing else. metric-card stays its only member, and the arm's satisfies readonly ['metric-card'] check still holds.
  • DashboardWidgetTypeSchema: readers are report-chart-query-spec-parity and the schema-catalog gate. No src reader.
  • DashboardWidgetTypeName: src readers are app-shell's DashboardWidgetInspector and plugin-designer's DashboardEditor. Both are type-picker lists, and neither offers metric-card. Narrowing the type makes it a compile error for either to offer the card as a widget.
  • The strict walker (strict-authoring-face.ts) names metric-card only in a comment. It keys on nothing here.
  • measureRefusal (plugin-dashboard) asks the zod DashboardWidgetSchema. For a metric-card probe it returned undefined before, because metric-card is not in the spec's metric family. After, the type issue aborts the measure check, and it still returns undefined. No picker passes it a metric-card.
  • Structural readers of DashboardWidgetSchema['type'] are the renderers measured above. They are unchanged, because the interface keeps the type.

H3: corpus re-judged

Every tracked JSON document was walked for a type: 'metric-card' object. Every TS/TSX/JS file and every ts/tsx/js/json fence in tracked md/mdx was parsed for the same literal.

  • Schema catalog: basic-dashboard.json, e-commerce-dashboard.json and support-dashboard.json hold 11 cards between them, and every one has value. Left alone.
  • content/docs/plugins/plugin-dashboard.mdx, "TypeScript Support": const widget: DashboardWidgetSchema = { id: 'revenue', title: 'Revenue', type: 'metric-card', layout } had no value. It is a plain authoring example, so it is rewritten. It now holds a metric widget with dataset and values, plus a metric-card with its value annotated DashboardWidgetSlotComponentSchema.
  • packages/types/README.md: card({ type: 'metric-card', bogus: 1 }) taught "bogus is named". It is amended to carry value: 42, so it teaches only that, and a valueless line is added.
  • Every other doc fence and README metric-card carries value. Left alone.
  • Test fixtures:
    • The valueless literals in dashboard-widget-slot-component-arm-7952 and strict-widget-slot-registered-inputs-11022 are deliberate negatives, and they still refuse. Left alone.
    • node-recursion-point-8344's metric-card fixtures are root-level or nested "unmirrored" controls, refused because metric-card is no AnyComponentSchema arm. This change does not touch that. Left alone.
  • scripts/measure-strict-authoring-face.mjs at this head: the dashboard component reads 15 documents, 1 strict-refused and 1 red today. That matches the reading objectui#11467's dev took at that PR's head. The one red is the known mdx type: 'card' widget. That comparison is across different bases, so it is supporting, not decisive.

H4: envelopeStray

It still compiles, and the zod face still refuses it. A type-less envelope is not discriminated by type, so this change cannot reach it. Left untouched.

Pins

  • packages/types/src/__tests__/metric-card-needs-value-11483.test.ts (new):
    • The measured node is refused on all three faces. The envelope is invalid_union at widgets.0, with the component arm at value and the widget arm at type. The bare type and a card carrying description / id / layout are refused too.
    • A card with value passes.
    • No widget key stands in for value: a dataset-bound card and an options.value card are refused, and the metric + dataset control passes.
    • Vocabulary on both faces. Disjointness is a type-level Equal check.
    • The TypeScript measured limit, recorded two-faced.
  • plugin-dashboard's metricCardRegisteredInputsStrictFace-11022.test.ts (extended): the registration's required and the schema agree in the slot. Each required input is read off the live registration and dropped from an otherwise complete node. The slot must refuse that node on both faces, at that input, through the component arm. The walk has a non-empty control and a complete-node control.
  • Updated:
    • report-chart-query-spec-parity: the "admits metric-card" pin is inverted, and the closed-set equality drops the component set.
    • strict-widget-slot-registered-inputs-11022: the widget arm's refusal of { value, bogus } now also names type.
    • The schema-catalog gate's check 2 counts the slot's component set as known.
    • zod-mirror-parity: the ledger row and the header figure.

Pending changesets swept

Each pending entry that names a moved symbol, file or behaviour was read.

A dated note was appended where a standing reading is now false:

  • dashboard-widget-type-closed-enum.md: the vocabulary was the families plus two objectui sets.
  • 11467-metric-card-arm-inputs.md: a valueless card was refused only with a card-only input.
  • 11070-dashboard-keys-round11.md: "What does not move: the widget arm".
  • 10859-dashboard-node-keys.md: "The dashboard WIDGET vocabulary is unchanged".

Left alone, each read and still true:

  • 11022-strict-widget-slot-registered-inputs.md: its one affected reading is already marked false, and that note points to the 11467 entry, which now carries this change's note.
  • 6002-dashboard-widget-strict.md: a card with value keeps its props, and the routing sentence holds.
  • 7952-dashboard-widgets-component-arm.md: the arm is still assignable to DashboardWidgetSchema.
  • 8344-node-recursion-point-redirect.md: the envelope's component slot still admits metric-card.
  • 11348-dashboard-widget-reads.md, 11478-anyschema-declared-node-types.md, 8499-node-slot-registered-arms.md, 9659-node-recursion-point-inert-clause.md and 9256-public-blocks-content-channels.md: root, nested and content-channel readings that this change does not reach.
  • 10859-console-phase-2b-stubs.md, 10859-known-types-phase-2b.md and 6442-catalog-blocks-claimant-list.md: registration keys.
  • 9165-metric-percent-converged.md: tile rendering.
  • dashboard-readme-card-widget-7035.md: the README examples still parse.

pnpm check:changeset-claims listed 30 more entries that name a touched file. Each was read: they describe other symbols in those files, or ledger figures recorded at a named past change. None states a current KnownDrift total or anything about the widget vocabulary.

Verification

Every run below is at head 821d9993 unless it says otherwise.

Build and type-check

  • Build of pnpm --filter '@object-ui/app-shell...' run build: 30 projects, exit 0.
  • type-check, each script name echoed: @object-ui/types exit 0 (all three programs), @object-ui/plugin-dashboard exit 0, @object-ui/plugin-designer exit 0, @object-ui/app-shell exit 0.
  • tsc --listFilesOnly confirms the new and edited test files are inside the test programs.

Tests, run from the repo root

  • pnpm exec vitest run packages/types/: 336 files, 8848 tests passed.
  • pnpm exec vitest run packages/plugin-dashboard/: 161 files, 1542 passed and 6 skipped.
  • The schema-catalog gate file: 30 passed.
  • packages/cli's check-validity-recogniser and registered-types-validate-ratchet-10859: 37 passed.
  • app-shell's widget-dom-leak-sweep: 215 passed.

Gates, all exit 0:

  • Doc gates: check:doc-snippets (776 of 776 blocks judged, 0 failed, after building the six packages it needs), check:doc-examples, check:doc-types, check:doc-fences, check:doc-example-ids, check:doc-example-readers, check:docs-route-closure.
  • Changeset gates: scripts/check-changeset-no-major.mjs, scripts/check-changeset-presence.mjs, check:changeset-claims, check:pending-changeset-literals.
  • Other gates: check:component-surface-parity, check:new-line-citations (0 new), check:control-bytes, check:spec-symbols, check:readme-exports, check:test-path-roots, check:unreferenced-sources.
  • check-governed-queue-guard --test over the diff: NOT GOVERNED.

Not run. check:sdui-registration-pins was not run: no registration input moves. It needs a console build. NOT MEASURED, declared.

Reverse verification, after the fix was committed:

  • Mutation: ...DASHBOARD_COMPONENT_WIDGET_TYPES, is put back into DashboardWidgetTypeSchema. This was done through ablation-replace.mjs: the anchor went from 1 to 0 hits, and the blob went from 73f5fd3b to b31a539a.
  • Mutated run of the five pin files: 17 failed, 117 passed (4 files red). The red tests are every new valueless / dataset / options pin on the faces that accepted them before, the slot-level required-input walk, the inverted parity pins, and the 11022 widget-arm shape.
  • Restored: the blob equals HEAD and git diff HEAD is empty. The restored run passed 134 of 134.
  • Direction: red, as expected. The strict face's options.value pin stayed green under the mutation, as expected, because that face already refused options on a card.

Acceptance notes (out of scope, not filed)

  • The legacy envelope corner. { id, component: { type: 'metric-card', title } } still passes the tolerant face through the component slot's BaseSchema fallback. objectui validate exits 0 on it, and the dashboard draws an empty figure: the probe's envelope case reads an empty figure, stable, on both surfaces. This is the same defect class, and the dispatch names its carrier: objectui#8347's BaseSchema fallback. That card is outside this one's file surface.
  • The TypeScript measured limit above. Closing it means retyping plugin-dashboard's slot-entry callbacks in DashboardRenderer, DashboardGridLayout and DashboardWithConfig, which this card's surface forbids. Carrier candidate: objectui#8347, which already holds the sibling envelopeStray corner. PM to confirm.
  • An observation, no producer. On the tolerant face the component arm keeps an options bag through its passthrough. The renderer's { ...widget, ...options } would then let options.value override the card's value. Nothing in the corpus writes options on a card. Carrier: none.

Deviations

  • Node version. This container's Node is v22.22.0, below jsdom's ^22.22.2 engine floor. Node v22.23.3 was taken from nodejs.org, SHA256-verified against SHASUMS256.txt, and put on PATH from this session's scratch directory only.

  • No line addresses. The dispatch asked for file:line on the binding read. Reads are cited by file and quoted code instead, because AGENTS.md rule [WIP] Update documentation for project #11 bans line addresses in PR and issue bodies.

  • Two files outside the claim's file surface:

    • examples/schema-catalog/test/plugin-dashboard-component-schema.test.ts, a reader of DashboardWidgetTypeSchema that the census found.
    • packages/types/src/__tests__/zod-mirror-parity.test.ts, the parity ledger.

    Both are test-only.

The session that made this change is https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC.


Generated by Claude Code

claude added 2 commits October 2, 2026 18:27
…alue; metric-card leaves the widget type vocabulary

DashboardWidgetTypeSchema named the component type metric-card, so a card
carrying only keys both slot arms accept parsed as a widget and the
component arm's required value governed nothing. The widget vocabulary is
now the spec families plus list/custom on both faces (DashboardWidgetTypeName
follows); the slot reads a metric-card through its component arm alone.

DashboardWidgetSchema['type'] on the TypeScript face keeps the component
type: it is the read type of every slot entry, and the renderers annotate
slot entries with it.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…#11483 measured

The TS DashboardWidgetSchema keeps the component type in `type` (the read
type of every slot entry); the mirror's widget vocabulary dropped it.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types plugin examples tests labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 30 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/11355-small-p1-sites-r2.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ObjectChartSchema.isAnimationActive?: boolean is declared on the TypeScript face. Code sets it false for a render with no entrance animation: DashboardRenderer and DashboardGridLayout on the object-chart nodes they build, and DatasetWidget and DatasetReportRenderer on the nodes they hand the chart registration. ChartRenderer honours it. The zod mirror declares no member for it, so it is not an authoring key; zod-mirror-parity.test.ts files it as runtime-only.

.changeset/5903-objectgantt-declared-keys.md

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743); the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair — but at that time the ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction, not that nothing was there. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger requires every registered pair's mirrored-but-undeclared key set to equal that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/5928-classname-style-props-rename.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Where the non-pair is recorded now. zod-mirror-parity.test.ts keys its existing EXCLUSIONS entry — the mechanism that accounts for every exported const with no TypeScript declaration to mirror, each with its stated reason — to ClassNameStylePropsSchema. Named for its own two keys, the const leaves no like-named declaration for a name-derived pairing to reach for.

.changeset/6051-gantt-flat-config-declared-keys.md

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743) — the same retirement objectui#5903's entry carries, for the same clause and the same reason; the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair, at a time when that ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger reconciles every registered pair's mirrored-but-undeclared key set against that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/6150-undeclared-but-consumed-keys.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ AMENDED, and the amendment ships in this same release. The TreeViewSchema slice (604476d97) gave the key a zod arm after all — a NAMED REFUSAL (handlerKeyRefusal(key, 'runtime-slot', label)), never a shape — because "no mirror entry" is not neutral under BaseSchema.passthrough(): it meant an authored { "type": "tree-view", "onNodeClick": { "action": "toast" } } parsed GREEN, survived the parse, and reached a call site that expects a function. ⇒ the three clauses this bullet used to carry are no longer true of the code shipping beside it. The key is now a MEMBER of TreeViewSchema.shape and an authored value is refused BY NAME at path onNodeClick; it has LEFT zod-mirror-parity.test.ts's RuntimeOnlyDeclared for that file's KnownDrift; and it is no longer "the first pair to sit there without also sitting in UnmirroredDeclared" — draining it emptied that difference, so RuntimeOnlyDeclared is now a SUBSET of UnmirroredDeclared and the union of the two equals UnmirroredDeclared itself. ⛔ objectui#6152's ruling is untouched by any of this: what the key still does not have, and never will, is a z.function() shape — no serialized document could satisfy one.

.changeset/6175-column-state-persistence.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Nothing is retired. Both spellings remain declared on DataTableSchema; onColumnReorder stays declared and stays unwired, exactly as the RuntimeOnlyDeclared ledger in zod-mirror-parity.test.ts records it. Which of the two survives is a declared-surface ruling that stays open and is deliberately not settled here.

.changeset/6639-objectgrid-title-mirrored.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The gain is the typed refusal: the mirror's .passthrough() base was already admitting any title unexamined, and it now enforces the declared string. zod-mirror-parity.test.ts's UnmirroredDeclared ledger records the key as worked off — the ledger's first shrink by repair (97 + 1 mirrored + 23 reclassified is what the seeded "121" now means).

.changeset/6687-chatbot-surface-authorable.md

  • names packages/types/src/complex.ts → packages/types/src/complex.ts — edited by this change

    Measured on both declaration faces before the fix, each with a control that had to hit: schema.surface appeared 0 times in renderer.tsx against schema.placeholder at 3 (one per registration) and schema.processVisibility at 1; and ChatbotSchema (packages/types/src/complex.ts) declared 34 keys, not this one. Two faces agreeing is what made the zero a reading rather than a bad query.

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

  • names complex.ts → packages/types/src/complex.ts — 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.

  • names complex.zod.ts → packages/types/src/zod/complex.zod.ts — 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/7113-chart-data-model.md

  • names complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

.changeset/7295-chat-message-avatar-keys.md

  • names packages/types/src/complex.ts → packages/types/src/complex.ts — edited by this change

    packages/plugin-chatbot/src/index.tsx:173–178 reads message.avatar || userAvatarUrl and message.avatarFallback || userAvatarFallback (and the assistant twins), the authoring-to-runtime seam spreads every unlisted key through (chatMessageAdapter.ts, ...passthrough), and the SDUI renderer feeds the authored messages[] straight in — a per-message avatar override renders, is documented, and no authoring-facing type declared it. ChatMessage in packages/types/src/complex.ts has no index signature (objectui#5155, deliberately — none is added here), so an author annotating ChatbotSchema.messages was told a value that renders is an error (TS2353); the zod mirror ChatMessageSchema is a plain strip-mode z.object, so the value parsed green and was silently DROPPED from the parsed output.

.changeset/7344-handler-string-any-mirrors.md

.changeset/7654-floating-chatbot-trigger-icon-tombstone.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Every other tombstone in this package pairs ?: never with a retirementTombstone() refusal on the Zod twin. There is no twin here to carry one: FloatingChatbotConfig has no Zod mirror at all, and floatingConfig sits in the UnmirroredDeclared ledger (zod-mirror-parity.test.ts, complex.zod.ts#ChatbotSchema). BaseSchema is .passthrough(), so the whole floatingConfig object rides through unvalidated — before this change and after it. Minting a mirror to host a refusal would be the declared-but-UNMIRRORED axis (objectui#6152), a different defect: a key can be mirrored and inert, or unmirrored and live, and fixing one says nothing about the other. This change does not widen into it.

.changeset/7655-chatbot-registration-authoring-faces.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    New published symbol: ChatbotSharedKey, the string-literal union of the twenty keys all three registrations read. It is exported from complex.ts because an exported interface may not extend a Pick over a private name (TS4022), so it is emitted into dist/complex.d.ts and is reachable through the published @object-ui/types/complex subpath (it is not re-exported from the package entry). It is a census, not an authoring face.

.changeset/7703-chatbot-dark-keys-retired.md

  • names packages/types/src/complex.ts → packages/types/src/complex.ts — edited by this change

    Each member goes to ?: never on packages/types/src/complex.ts and to retirementTombstone(...) on packages/types/src/zod/complex.zod.ts — both halves, in lockstep, the convention MarkdownSchema.sanitize (objectui#6972), TimelineSchema.timeScale (objectui#6355) and ObjectViewSchema.viewTabBar (objectui#7779) already carry. Each refusal names the key, says why it is retired, and points at what to write instead; one string feeds both the parse-time message and the .describe() metadata, so the two cannot drift.

  • names packages/types/src/zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    Each member goes to ?: never on packages/types/src/complex.ts and to retirementTombstone(...) on packages/types/src/zod/complex.zod.ts — both halves, in lockstep, the convention MarkdownSchema.sanitize (objectui#6972), TimelineSchema.timeScale (objectui#6355) and ObjectViewSchema.viewTabBar (objectui#7779) already carry. Each refusal names the key, says why it is retired, and points at what to write instead; one string feeds both the parse-time message and the .describe() metadata, so the two cannot drift.

.changeset/7804-tree-view-handler-slot.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The pair moves from zod-mirror-parity.test.ts's RuntimeOnlyDeclared to its KnownDrift, which empties the former of the one entry the latter did not also hold — so the two unmirrored ledgers are now in a containment relation, and the cross-ledger figure that recorded their difference states the containment instead.

.changeset/8114-detail-tab-activity-timeline.md

  • names plugin-dashboard/README.md → packages/plugin-dashboard/README.md — edited by this change

    README.md ships in this package's files, so the example went out in every tarball. DetailTabs renders a tab's content through ANGLE-BRACKETS(SchemaRenderer schema={toRenderableSchema(tab.content)} /), which makes content.type an SDUI node position judged by the component registry — so a reader copying the tab got the registry's Unknown component type panel (OBJUI-001) where the timeline should be. Same shape as the line-chart widget in plugin-dashboard/README.md (objectui#7896's census; fixed by objectui#7951) and the fourth known instance.

.changeset/8338-retire-toast-action.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The parity ledgers drain with it, every figure re-derived by zod-mirror-parity.test.ts's own AST and mirror instruments rather than stepped by hand: KnownDrift 42 entries / 64 keys → 41 / 63 (the entry's whole content, so the entry went too — the ledger's first loss by RETIRING a key rather than by moving either face toward the other), and WiderThanDeclared 23 / 36 / 47 arms, split 6 / 30 / 0 / 11 → 22 / 35 / 45, split 6 / 29 / 0 / 10. The pair itself stays registered, so EXPECTED_MIRROR_PAIRS does not move.

.changeset/8415-filter-builder-condition-id.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    Breaking for authored metadata: a filter-builder CONDITION must now declare id (objectui#8415). It is declared on both published faces — the TypeScript interface FilterBuilderCondition in complex.ts and the Zod mirror FilterBuilderConditionSchema in zod/complex.zod.ts — so a key the renderer has always required is finally validated instead of silently discarded.

  • names zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    Breaking for authored metadata: a filter-builder CONDITION must now declare id (objectui#8415). It is declared on both published faces — the TypeScript interface FilterBuilderCondition in complex.ts and the Zod mirror FilterBuilderConditionSchema in zod/complex.zod.ts — so a key the renderer has always required is finally validated instead of silently discarded.

.changeset/8478-describe-line-addresses.md

.changeset/8478-zod-pins-complex.md

.changeset/8478-zod-pins-form-layout.md

  • names zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — edited by this change

    The remaining 6 addresses (zod/complex.zod.ts) stayed out of scope for this PR and returned to the queue rather than riding this PR's scope — the card did not close here.

.changeset/8572-chatbot-body-retired.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Why this key and not another. It was the ONE place in this vocabulary where body did not mean "what goes inside this component": zod-mirror-parity.test.ts carried the pair under KnownDrift as "two different meanings of one key", and the same collision was the whole reason chatbot was the single arm of the component union whose output was not assignable to SchemaNode. Both ledger rows move with this change, and the two pins that recorded the old state are INVERTED rather than deleted (see below).

.changeset/8760-unfulfilled-chart-stubs.md

  • names content/docs/plugins/plugin-dashboard.mdx → content/docs/plugins/plugin-dashboard.mdx — edited by this change

    • At render. SchemaRenderer's lazy branch re-checks hasLazy(type) on every pass and returns the Loading ANGLE-BRACKETS(type)… placeholder. Registry.register() deletes a lazy entry only for keys the loaded module actually registers, so for an unfulfilled key the entry SURVIVES the load and every later pass takes the same branch. Measured on b775500af through the real chain: { "type": "line-chart" } painted role="status" / data-lazy-loading="line-chart" / Loading line-chart…, permanently. Not the OBJUI-001 panel the card expected — no alert, no error, no console warning. A skeleton that never resolves reads to a user as a slow network. - At authoring. A stub is enough to put a key into getKnownTypes(), so check:doc-types and the CLI's generated KNOWN_SCHEMA_TYPES snapshot both blessed all three. content/docs/plugins/plugin-dashboard.mdx taught "type": "line-chart" inside a card body, and every gate was green on it.

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

  • names packages/types/src/zod/complex.zod.ts → packages/types/src/zod/complex.zod.ts — 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/8802-8257-8008-kanban-gantt-family-retirement.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    What each retirement was, measured. Three of the four were registration-only: no schema face in @object-ui/types ever declared kanban-ui, kanban-enhanced or gantt as a component node type, so unregistering is the whole retirement. The bare kanban key was the exception — it had a declared arm on both faces (KanbanSchema in complex.ts and its Zod mirror), and a plain deletion there would have been the objectui#7664 failure: BaseSchema is .passthrough(), so a document naming a dropped key validates green and renders nothing. It therefore retires as a named refusal: the Zod union keeps an arm claiming the literal and answers a { "type": "kanban" } document with a message naming object-kanban as the remedy, while the TypeScript half is the absence of the arm from ComplexSchema and of the key from SchemaRegistry, so tsc refuses it at the authoring site.

.changeset/9511-record-id-is-a-string.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ The declaration alone would not have been enough, and this is the reusable part. A TypeScript declaration does not run at parse time. The hand-written zod mirror is the only face in this repository that can refuse an authored number, so narrowing the declaration without the mirror would have shipped declared !== enforced on a published surface — and zod-mirror-parity.test.ts would have stayed GREEN through it, because that instrument asserts a mirror accepts everything its declaration declares and a mirror left WIDER passes. Both faces moved together for that reason.

.changeset/9628-kanban-column-collapsed-honoured.md

  • names complex.ts → packages/types/src/complex.ts — edited by this change

    The key was declared on both published faces of the object-kanban arm — the lane element of ObjectKanbanSchema (objectql.ts and its Zod mirror) and the runtime lane KanbanColumn (complex.ts and its mirror) — and read by KanbanEnhanced alone, a module no production source imports. An authored { "id": "todo", "title": "To Do", "collapsed": true } therefore parsed green on both faces and reached a board that did nothing with it: KanbanImpl's only collapse is the SWIMLANE row's, held in viewer state under objectui:kanban-collapsed:ANGLE-BRACKETS(swimlaneField) and never keyed to a lane's declared value. That is the ADR-0049 declared-but-unhonoured shape.

.changeset/calendar-readme-schema-keys-5045.md

  • names packages/types/src/complex.ts → packages/types/src/complex.ts — edited by this change

    README.md's "Schema API / CalendarView" block described a CalendarViewSchema that does not exist. Measured against the interface itself (packages/types/src/complex.ts) and its zod mirror: events — the schema's only required key besides type — was published as events?, so a reader following the README omits it and TypeScript rejects the node; defaultDate was string where the schema says string | Date; and onDateClick was listed as a schema key when it is a CalendarViewProps component prop, sending readers to a different package's surface for a key calendar-view does not have (the schema's key is onDateChange). The block also listed 6 of the schema's 13 keys with nothing saying it was a summary (objectui#5045).

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

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Who is NOT affected: every correctly typed document, and every document that never wrote these keys — absent stays valid on all nine. No renderer changed. The parity ledger (zod-mirror-parity.test.ts) records the move: UnmirroredDeclared 14 entries / 96 keys to 14 / 87, the ObjectViewSchema entry re-derived into the SPEC-DERIVED half because the mirror now references the spec in code.

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.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

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

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3551.1 KB 3574.6 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-DMI6fIwi.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 571.20KB 136.84KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 230.46KB 63.94KB
fields (index.js) 261.51KB 66.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.36KB 11.18KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.53KB 22.84KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.20KB 38.29KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.43KB 64.53KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 233.13KB 63.89KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 4.14KB 1.96KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 821d999357b35c7f72f132d46dc9760d9adb8f26
Local-runs: none

PR objectui#11498 (card objectui#11483), head 821d9993, net diff against main at that head: 16 files, +427 / -43. Inputs read: the card body and its four comments (triage ruling 5957113843, claim 5958121039, os-dev report 5959187554, seat answer 5959231623), the PR body, its file list, the diff, the head's check-runs, and every pending .changeset/*.md at the head off the git tree (.changeset tree ffd47bf3dba745a9c7e6be0eb53935f7250f6a50: 2668 entries plus config.json, not truncated; the blob list equals the head archive's).

Check-runs on the head, read three times, last at 2026-10-02T19:04Z: 43 check-runs, 40 success, 3 skipped (Test (coverage), the coverage-shard matrix placeholder, dependabot), none red, none pending. At the first read (2026-10-02T18:56Z) Lint, Type Check, Spec Main Shape Gate and the eight Test (shard N/8) were in_progress; every one has since concluded success. The three skips are matrix and bot placeholders and bear on nothing in this contract. Among the greens: the five changeset gates (Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read), Doc Snippet Type Check, Line Citation Gate, Governed Surface Queue Guard, Test (dist pins), the aggregate Test (which appeared after the shards concluded), Build Docs.

① Derived judgments

Judged against triage's ruling 5957113843 (the fix at the declaration, in one refinement; the vocabulary arm applies when the widget arm has no data binding the renderer reads; no renderer fallback; no second spelling) and the four pins it set.

  1. DashboardWidgetTypeSchema (zod) drops the DASHBOARD_COMPONENT_WIDGET_TYPES spread — RIGHT. The ruling's vocabulary arm, taken after H1 measured no widget-arm binding for the card's figure through the real DashboardRenderer and DashboardGridLayout (a dataset routes to DatasetWidget by a rule that ignores type, so the card never mounts; options is a literal spread onto the node, a second spelling, which the ruling forbids). The enum is now the spec's families plus DASHBOARD_WIDGET_TYPE_EXTENSIONS. Its readers at the head: none in src outside packages/types; the two test readers (report-chart-query-spec-parity, the schema-catalog gate) are updated in this diff.
  2. DashboardWidgetTypeName (TS) drops DashboardComponentWidgetType — RIGHT, the twin follows and the two are pinned disjoint from the component set. Its src readers at the head are two type-picker lists, DashboardWidgetInspector.WIDGET_TYPES (app-shell) and DashboardEditor's palette (plugin-designer); neither names metric-card, so the narrowing is a compile-time guarantee for both and breaks neither (Type Check green).
  3. DashboardWidgetSchema['type'] (TS) stays DashboardWidgetTypeName | DashboardComponentWidgetType — RIGHT as a pinned, ledgered limit. That member's accept set is what it was (the component type sat inside DashboardWidgetTypeName before), so no TS consumer moves. It is the read type of every widgets[] entry: the renderers annotate slot callbacks with it (DashboardRenderer.tsx, DashboardGridLayout.tsx, DashboardWithConfig.tsx, read at the head) and the claim's surface forbids those files. The limit is pinned two-faced in metric-card-needs-value-11483.test.ts (section 5, which says to delete itself once tsc refuses the literal) and ledgered as type on complex.zod.ts#DashboardWidgetSchema in KnownDrift. The seat's answer 5959231623 rules A stands on this head and carries the closure (retype the callbacks, then drop the type, the pin and the ledger key) on objectui#11466. Consistent.
  4. Zod DashboardWidgetSchema standalone refuses { id, type: 'metric-card' } at type — RIGHT, the necessary consequence of 1. Its one src consumer outside types, plugin-dashboard's measureRefusal, reads only a custom issue at values and returns undefined for a metric-card probe before and after (read at the head), so no picker behaviour moves.
  5. DashboardComponentSchema.widgets, AnyComponentSchema, StrictAnyComponentSchema refuse every valueless metric-card directly in widgets[] — RIGHT; the ruling's first pin. The slot is z.union([DashboardWidgetSlotComponentSchema, DashboardWidgetSchema]); after 1 the widget arm answers invalid_value@type and the component arm invalid_union@value, under one invalid_union at widgets.0, so the refusal names both ways. Rows judged: the measured node, the same plus description / id / layout, dataset + values, and options.value (tolerant-accepted at base) are refused on every face; a card with value passes (pin 2); the registration's required and the slot agree, read off the live registration (pin 4); the { type: 'metric', dataset, values } control passes. Pin 3 (a widget with its binding) is vacuous by measurement: no binding exists. objectui validate names widgets → 0 → value.
  6. DASHBOARD_COMPONENT_WIDGET_TYPES kept, metric-card its only member — RIGHT: it is the component arm's own type (z.enum(… satisfies readonly ['metric-card'])); no src reader outside packages/types at the head (grep over the tree; the doc-types gate names it in message prose only, which still reads true).
  7. No renderer file, no refinement, no second required-key declaration — RIGHT per the ruling and the claim's surface; under packages/plugin-dashboard/src the diff touches one test and nothing else.
  8. The legacy component envelope corner is left as it was (the tolerant face still admits a valueless card through the BaseSchema fallback) — RIGHT to leave: objectui#8347's fallback, named by the dispatch as its owner, outside this surface. See ③.
  9. Docs — RIGHT. The one valueless authoring example (content/docs/plugins/plugin-dashboard.mdx, TypeScript Support) becomes a metric widget plus a metric-card with value annotated DashboardWidgetSlotComponentSchema (exported from the package entry, read at the head); the types README negative example carries value: 42 and gains a valueless line; the plugin README and the mdx state the rule and point a dataset figure at metric. Doc Snippet Type Check green.
  10. Corpus — RIGHT, re-measured on the head's tree: 550 JSON documents parsed, 11 metric-card objects, 0 without value; every metric-card literal in ts, tsx, md and mdx carries value except the deliberate negatives in node-recursion-point-8344 (root and nested controls, which this change does not reach) and two non-document sites (a docblock in complex.zod.ts, the DASHBOARD_NODE_TYPES table key in widgetDispatch.ts).
  11. Pins — RIGHT. New metric-card-needs-value-11483.test.ts (both arms' refusal on three faces, the valued card, the no-stand-in rows with the metric control, disjointness as a type-level Equal, the measured limit); the plugin-dashboard required-input walk with a non-empty control and a complete-node control; the parity pin inverted and its closed-set equality shrunk; the 11022 widget-arm shape gaining invalid_value@type; the schema-catalog gate counting the slot's component set as known; the zod-mirror-parity ledger row and its header 49 / 86 → 49 / 87, which the file's headerFigures pin reads off the header's own spelling against the ledger's exact key set. Reverse verification reported 17 red under the one-line mutation and 134 / 134 restored; the eight test shards are green on this head.

Nothing in the diff widens: no face accepts anything it refused at base.

② Semver level

  • One changeset, .changeset/11483-metric-card-needs-value.md: '@object-ui/types': minor, the breaking narrowing stated on both validator faces and on the exported DashboardWidgetTypeName, the fix stated (give the card its value; a dataset-bound figure is a metric widget). It matches what the diff publishes: published source moves only in @object-ui/types (complex.ts, zod/complex.zod.ts); plugin-dashboard moves a README and one test (no published bytes, no bump owed); examples/schema-catalog is in ignore. minor is this repository's spelling of a breaking change (version alignment, check-changeset-no-major). ⛔ Not skip-changeset. All five changeset gates are green.
  • Clause-②: no (narrowing) — RIGHT. The diff widens no accept set and no public surface, and narrows published ones; no (narrowing) is the arm that says exactly that, and the changeset carries the break.
  • Pending changesets, read off the git tree (2668 entries). Readings this change makes false, and their notes: (a) dashboard-widget-type-closed-enum.md — "plus two named, closed objectui extension sets … DASHBOARD_COMPONENT_WIDGET_TYPES"; (b) 11467-metric-card-arm-inputs.md — icon, trend or trendValue with no value as the only valueless refusal; (c) 11070-dashboard-keys-round11.md — "What does not move. The widget arm …"; (d) 10859-dashboard-node-keys.md — "The dashboard WIDGET vocabulary is unchanged … author exactly as before". Each carries a dated, append-only note in the house form (⚠️ Dated note, 2026-10-02 — title — objectui#11483; what held at that change; what holds now; the 11483 entry states what ships; the rest kept as the reading of that change), frontmatter untouched (+2 / -0 each; Changeset Overwrite Report green). RIGHT, all four.
  • Entries left alone, each read and still true: 11022 (its affected reading is already noted false by the 11467 note; { type: 'metric-card', bogus: 1 } is still refused by name; root metric-card still refused at type); 6002 (a card with value keeps its props; the routing sentence holds); 7952 (the arm is still assignable to DashboardWidgetSchema, which is why the TS type member was kept; "outside both vocabularies" still refuses); 8344 (the envelope's component slot still admits metric-card); 8499 and 9659 (root and nested metric-card refusals, unreached); 9256 (the children / body refusal still arrives inside the slot's invalid_union); 11348, 11478, 10859-console-phase-2b-stubs, 10859-known-types-phase-2b ("inside a dashboard, keep the widget spelling": the slot entry is still spelled type: 'metric-card'), 6442, 9165, dashboard-readme-card-widget-7035. The 30 entries the claim-re-read gate named for touching zod-mirror-parity.test.ts, complex.ts, complex.zod.ts, the READMEs and the mdx: read; each describes other symbols in those files (chatbot, kanban, gantt, filter-builder, describe-line addresses, other ledger pairs). KnownDrift totals: no pending entry states a live total; the two that carry digits (8338-retire-toast-action 42 / 64 → 41 / 63; pin-zod-mirror-parity-header-key-totals 62 → 63) are readings at named past changes, kept as such by the house convention. No entry quotes the type member's old describe text or the ledger row's old key set. No false reading without a note was found. ② PASS.

③ Boundary flags

  • Deviation: Node toolchain (v22.23.3 on the dev's PATH for jsdom's engine floor) — nothing in the diff; accepted.
  • Deviation: two test files outside the claim's file surface. packages/types/src/__tests__/zod-mirror-parity.test.ts is inside the surface the claim names (packages/types/src/__tests__/), so no breach. examples/schema-catalog/test/plugin-dashboard-component-schema.test.ts is outside: test-only, a reader of DashboardWidgetTypeSchema the census found, whose check 2 would otherwise red on every catalog metric-card. Accepted as the vocabulary's necessary reader; the seat should append it to the claim's file surface at adoption (non-blocking).
  • Deviation: TS face not fully followed (DashboardWidgetSchema['type'] keeps the component type) — answered on the card by the seat (5959231623): A stands on this head; the retype of the slot-entry callbacks, the drop of DashboardComponentWidgetType from that member, and the deletion of the limit pin and the KnownDrift type key are carried by objectui#11466, not objectui#8347. Consistent with ① item 3. Accepted.
  • Deviation: file plus quoted code instead of file:line — right per AGENTS.md [WIP] Update documentation for project #11 (an issue or PR body has no same file); Line Citation Gate green.
  • check:sdui-registration-pins NOT MEASURED — no registration input and no sideEffects array moves in the diff; accepted.
  • open_questions[0] (close the TS-face limit, and where) — answered: B, carrier objectui#11466 (seat comment 5959231623). Nothing on this head changes.
  • out_of_scope_findings: (1) a valueless card in the legacy component envelope passes the tolerant face through the BaseSchema fallback and draws an empty figure — same defect class, carrier objectui#8347 as the dispatch names it; noted, not filed. ESCALATED to the seat, non-blocking: confirm objectui#8347's body names this envelope case so it is not lost when that fallback is removed. (2) the TS limit — answered above. (3) options.value overriding a card's value through the renderers' spread — an observation with no producer in the corpus; acceptance note only.
  • No dev flag and no open question is left unanswered.

Implemented-by: claude/issue-11483-metric-card-needs-value
Reviewed-by: session_01XvhGmGAP79ZB8swnkapxPC

VERDICT: PASS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation examples package: types plugin tests

Projects

None yet

2 participants