Skip to content

feat(types,react,core,plugin-dashboard)!: a node slot and SchemaRenderer's schema prop take the declared-node union, V1; the dashboard producers name declared nodes and an envelope's object-metric retires (objectui#11466) - #11512

Merged
objectstack-fleet[bot] merged 20 commits into
mainfrom
claude/issue-11466-node-slot-union-v1
Oct 4, 2026

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11466

This PR carries ruling ① (V1) and the card's residue, with main merged in, the plugin-dashboard producers closed, and the maintainer's ruling A on the last inline metric form (comment 5966278275). The census reads 0 lines across all 88 workspace type-check programs, with BaseSchema's index signature present.

Clause-②: yes (narrowing)

objectui#11543 (governed PR #11558) has landed as 5cde2c6fe. Skill Example Check is green at cae2224d5: 18 of 18 ts fences judged, 0 failed. All 43 check-runs on this head are green or expected skips.

What changes

  • @object-ui/types exports DeclaredNode. It is the discriminated union, keyed by the literal type, of:
    • every component schema AnySchema declares, minus its BaseSchema arm (whose type is string) and minus the app-level document, which is left out by { type: 'app' } (AppSchemaRenderer reads that document structurally and ComponentRegistry never dispatches it);
    • every spec-declared AuthoringNode;
    • every entry of the new CustomNodeRegistry interface. An entry joins under its KEY: the arm is the entry intersected with { type: KEY }, so even a loose entry adds no type: string arm.
  • SchemaNode is DeclaredNode | string | number | boolean | null | undefined. It was BaseSchema | string | number | boolean | null | undefined.
  • SchemaRendererProps['schema'] is DeclaredNode | string | null | undefined. It was BaseSchema | AuthoringNode | string | null | undefined. toRenderableSchema follows by reference.
  • @object-ui/core's schema builder takes DeclaredNode where it took BaseSchema: .child() and .children() on the grid and flex builders, and the card builder's .content().
  • @object-ui/components' kind: 'html' page crosses its runtime-parsed tree into DeclaredNode at ONE documented boundary, right after validateTree reports no error (page.tsx, objectui#11479 Q2 A). No other cast to DeclaredNode exists under src/.
  • @object-ui/test-support gains undeclaredNode, the one crossing for deliberately undeclared test input (objectui#11479 Q3 A). It has 25 call sites in 15 test files, and none outside tests.
  • @object-ui/plugin-dashboard:
    • plugin-dashboard:metric is declared through CustomNodeRegistry (objectui#11479 Q1 A). DashboardMetricNodeSchema, a new export, is the node MetricWidget renders under that key, with the keys it reads off the node, typed by MetricWidgetProps. widgetDispatch.ts enters it in the registry beside DASHBOARD_NODE_TYPES, whose values are now literal types (a string-keyed view serves the by-type lookup). The package entry exports the type, so its published typings load the declaration.
    • Both surfaces' node builders (getComponentSchema in DashboardRenderer and DashboardGridLayout) return SchemaRendererProps['schema'], so each node is checked against its declared type where it is built. The metric arms narrow widgetType for the required label; classifyWidgetType answers metric only for a named type, so no verdict moves.
    • DashboardGridLayout's static pivot node states the keys PivotTable draws (its PivotTableSchema keys, and className), read from options, instead of spreading options whole, so it names PivotTableSchema with no cast.
    • The casts before SchemaRenderer are gone: as BaseSchema | string | null | undefined in DashboardGridLayout and as BaseSchema in DashboardRenderer. No new cast.
    • An object-metric node in a widget's legacy component envelope draws LEGACY_RETIRED_WIDGET_SCHEMA on both surfaces (the maintainer's ruling A on objectui#11466, extending ruling C on objectui#11525), under its bare key and under plugin-dashboard:object-metric, and sends no query. One shared detector, isRetiredEnvelopeNode in legacyRetiredWidget.ts, is read by both surfaces before the envelope pass-through, and the placeholder is the same imported object the metric and pivot arms return. object-metric leaves the filter broadcast's FilterableComponentType / FILTERABLE_COMPONENT_TYPES, so the flat-filter write the declared object-metric node refuses is gone. The broadcast now covers object-chart and object-data-table, built by the renderer or written in an envelope.
  • Not here: a type: string arm, an index signature, a transitional BaseSchema arm, BaseSchema's own index signature, and the component slot's BaseSchema fallback (all objectui#8347's). Every zod face and every TypeScript authoring face of object-metric stays as it was, and so does the authored object-metric block on a page. Two runtime moves, both in @object-ui/plugin-dashboard: an option key the static pivot does not state no longer reaches its node, and an envelope's object-metric draws the rebind prompt.

The accept set, before and after (TypeScript face only)

Judged by packages/types/src/__tests__/node-slot-union-11466.test.ts, by the react prop pin, and by the premise probes below.

  • Undeclared type, nested or at the prop, e.g. { type: 'card', children: [{ type: 'txt' }] }. Before: compiles through the BaseSchema arm. After: TS2322.
  • A value typed BaseSchema at the prop or in a slot. Before: compiles. After: refused, because it names no declared type.
  • A misspelled key on a closed node type (an AuthoringNode, or a closed CustomNodeRegistry entry), nested at any depth. Before: compiles through the BaseSchema arm. After: refused, because the slot discriminates on type.
  • A misspelled key on a node type that extends BaseSchema. Before: compiles. After: still compiles, through that type's own index signature, until objectui#8347 removes it. Measured below as the signature-present control leg.
  • A missing REQUIRED key. Before: compiles through the BaseSchema arm. After: refused. Examples: a stored home page without label, a data-table without columns, a static pivot without its axes.
  • A registered custom type. Before: any type compiled. After: it compiles once declared in CustomNodeRegistry; an unregistered one is refused.
  • A plugin-dashboard:metric node. Before: compiled through the BaseSchema arm, like any string. After: in a program that loads @object-ui/plugin-dashboard's typings it is a declared node, checked against DashboardMetricNodeSchema (which extends BaseSchema, so a misspelled key still compiles until objectui#8347); in one that does not, it is refused as undeclared.
  • Narrowing on type. 'app' narrows to PageDocumentNode (the stored page kind) and 'list' to ListSchema. PageDocumentNode excludes the interface-mode list kind, which PageView renders through InterfaceListPage and never hands to SchemaRenderer. SchemaByType reads AnySchema and does not move.

The merge of main

  • Measured first with git merge-tree against main at 160c6c6e: one conflict, packages/types/src/__tests__/zod-mirror-parity.test.ts, as expected. content/docs/api/schema-reference.md, packages/core/src/builder/schema-builder.ts, packages/plugin-detail/README.md and packages/types/src/index.ts auto-merged. The merge is the merge commit c7c37b73; no rebase, no force-push.

  • The resolution is the union, re-derived on today's main. WiderThanDeclared holds main's objectui#11515 entry (app.zod.ts#AppSchemaRendererNodeSchema::schema, one SCHEMA-NODE arm) and this branch's two objectui#11466 entries (complex.zod.ts#DashboardComponentSchema::widgets and complex.zod.ts#DashboardWidgetSchema::component, two CONCRETE arms each). The header bullet reads the union's figures, with both histories in landing order; the file's own pin 'the WIDER ledger's header figures are derived from its ARMS' re-derives them from WIDER_ARMS and the mirrors, and the type-level ratchet confirms each entry against the mirrors (types tsconfig.test.json and vitest, both green). objectui#11514's KnownDrift move (type left complex.zod.ts#DashboardWidgetSchema's entry) merged without conflict.

  • MeasuredArmDrift11466 (node-recursion-point-8344.test.ts) drops detail-section, app-schema-renderer and cloud:plan-status, which objectui#11515 twinned and joined to AnySchema. On the merged tree before that edit the two-way ratchet read red (TS2344 at NodeRecursionPointDeclarationDrift), which is what the ratchet is for; a dated note records the reconciliation.

  • Landing-prep round (director seat, claim 5970151706; dev report 5970915352): two more merge commits, both conflict-free, with pnpm install leaving the lockfile unchanged.

    main brought no new V1 producer.

  • Final landing round (report 5974814539):

    • 318447867 merges main 0a53c67f9 (16 squash commits, objectui#11543 among them). There is no conflict and no conflict-resolution edit inside this PR's 88 files, and the lockfile is unchanged. The merge brought 105 files, two of them shared with this PR (schema-reference.md, zod-mirror-parity.test.ts), both auto-merged with only main's change.
    • cae2224d5 corrects MeasuredArmDrift11466's docblock (contract review R1, prose only): the app / record / home / utility / page / dashboard drifts are recorded for objectui#8347's family, not carried by their own cards.

H1: the cycle

  • Before, with the V1 candidate on the first base (tsc -p packages/types/tsconfig.json), there were 9 errors: TS2456 on FlexBlockNode, AuthoringNode, DeclaredNode and SchemaNode; TS4109 in zod/base.zod.ts; TS7022 on FlexSchema, FLEX_BAG_CHILDREN, FlexPropsBag and FlexBlockSchema.
  • The spelling: FlexBlockNode is an interface that extends the closed arm input of FlexBlockSchema, with no members of its own; zod/base.zod.ts writes the node slot's zod type out through two type aliases (SchemaNodeZodType, NodeSlotZodType). The schema objects do not change.
  • After: 0 errors in tsconfig.json and in tsconfig.examples.json, on the merged tree as well.

H3: premise ②, re-run on the merged tree

Superseded reading (contract review R3): the figures below were taken at b13093d6. Re-measured at cb687dfad under the objectui#8347 mutation: types 39/40 and react 15/15. The one row is f02, the flex bag's ARRAY child list, which is unknown[] on the TS face and carried by objectui#11564. See the corrected acceptance note.

Throwaway probe files, never committed: 40 type-level rows in @object-ui/types and 15 in @object-ui/react, each labelled OK (must compile) or REFUSE (must error), each run on two legs at b13093d6.

  • objectui#8347's P1 mutation (BaseSchema's index signature deleted; restore proven by blob c0d9ad44 equal before and after, signature lines back to 2): types 40 of 40 as labelled, react 15 of 15, with no error on an unlabelled probe line. So (a) a nested misspelling is refused at every slot depth and at the prop, and (b) a registered custom type is authorable, its misspelling refused, and its keys do not escape onto a declared type.
  • Signature present (the control, and what lands now): types 29 of 40, react 9 of 15, the same rows as at the first fork. Every row that does not pass is a misspelling on a node type that extends BaseSchema, which is the signature objectui#8347 removes. Undeclared types, wrong-value keys, the tombstone, AuthoringNode bag misspellings and unregistered custom types are refused on both legs.
  • (c) timing, main at 160c6c6e against this branch at b13093d6. Instantiations are deterministic (one value across 3 runs each); seconds are medians of 3 on a shared box, so read them as ratios:
program instantiations (main → V1) check time (median)
types tsconfig.json 1,377,922 → 1,381,453 (+0.26%) 3.21 s → 3.05 s
types tsconfig.examples.json 1,191,109 → 1,195,701 (+0.39%) 2.97 s → 3.58 s
types tsconfig.test.json 3,395,239 → 3,409,600 (+0.42%) 13.24 s → 13.43 s
react tsconfig.json 367,923 → 454,978 (+23.66%) 1.66 s → 2.02 s
react tsconfig.test.json 467,312 → 554,995 (+18.76%) 3.02 s → 3.47 s
components tsconfig.json 853,561 → 913,643 (+7.04%) 5.53 s → 4.80 s
components tsconfig.test.json 1,655,321 → 1,753,308 (+5.92%) 10.56 s → 9.29 s

The first fork read +23.95% (react src), +19.01% (react tests) and +6.48% / +5.83% (components). Memory medians moved between 0.0% and +6.7%.

H2: the census

The instrument is objectui#11479's census, copied: all 88 workspace type-check programs, each run on its own, after a full build. BaseSchema's index signature is present on every reading.

  • On the merged tree, before any other edit (c7c37b73): 9 lines in 3 of 88 programs. plugin-dashboard tsc --noEmit 4 and tsconfig.test.json 4 (the four SchemaRenderer render sites, two per surface, fed by the two casts); types tsconfig.test.json 1 (MeasuredArmDrift11466).
  • At f90ba8d2 (the previous round): 2 lines in 2 of 88 programs, both the return of DashboardRenderer's filter merge, whose object-metric member wrote a flat filter onto an object-metric node. Those programs read a plugin-dashboard dist built with a census-only cast at that line, restored blob-equal before the census.
  • At cae2224d5 (this head, the final round): 0 lines across all 88 programs, with the signature present (2 lines in src, 2 in dist) and no cast added.
  • At cb687dfad (after the landing-prep merges): 0 lines across all 88 programs, the same at 5afa0269a. BaseSchema's index signature is present (2 lines in src, 2 in dist), and this round adds no cast.
  • At fab635a2 (the previous head): 0 lines; all 88 programs exit 0. No census-only cast: plugin-dashboard's own build, which fails on a type error in its declaration program, passes.

The corpus, re-judged

  • Type pins restated: SchemaRenderer.propsResolution (the prop equality, and BaseSchema is not an arm); node-recursion-point-8344 (the measured set of zod arms no declared node type admits, now app, record, home, utility, page, dashboard and the retired kanban arm); overlay-trigger-union-7081, overlay-node-slot-doc-types-7082 and page-body-arity-8310; zod-mirror-parity's Wider ledger (the union above); authoring-nodes-11364 (dated note); new node-slot-union-11466.
  • Test fixtures: react suites that register test-only types declare them in CustomNodeRegistry (13 files); deliberately undeclared input crosses undeclaredNode (15 files); stored page fixtures gained the required label; the plugin-gantt and plugin-map bag fixtures are annotated with their declared node types.
  • Docs: the plugin-dashboard README and content/docs/plugins/plugin-dashboard.mdx name the one retired envelope node and what still draws. 14 documents name the declared node type; a registered custom type is shown declared in CustomNodeRegistry (core/schema-renderer.mdx). The two blocks held at the first round (vscode-extension.mdx, the plugin-detail README) now compile, answered by their owner card (objectui#11515).
  • Pending changesets: 13 pre-existing entries carry 15 append-only dated notes (0 lines removed): 11364, 8331, 8908, schema-input-bridge-permanent-4622, 7767, 11479, 8572, 8310 (twice: the prop's annotation, and the root README's example authors children, not body), 11354, 10528-grid-object-table-fetch and 11514-dashboard-slot-entry-types (their "unchanged" / "draws as before" readings of the grid's static pivot), and for the envelope retirement 11525-inline-metric-retired (its envelope object-metric "still receives the dashboard filter bar's values"), 11348-dashboard-widget-reads (the broadcast's guard over "the same three object-* types", now two) and 11514-dashboard-slot-entry-types again (an envelope "draws under its card heading as before" whatever it held). New entries: .changeset/11466-node-slot-union.md (types, react and core minor; components patch), .changeset/11466-dashboard-metric-node.md and .changeset/11466-envelope-object-metric-retired.md (plugin-dashboard minor each, the second stating before, now and the fix: bind a dataset). Each states its breaking meaning in the body, because objectui never declares major.

The envelope's object-metric (the maintainer's ruling A)

The previous round held one line: the filter broadcast kept object-metric in its filterable set for a node in a widget's legacy component envelope (objectui#11525 kept it), and wrote a flat filter that the declared object-metric node (the spec's properties bag, ObjectMetricBlockNode) refuses by name on both faces. The maintainer ruled A (comment 5966278275): that envelope node is the last inline metric form, retired as ruling C retired the rest.

  • Measured basis (from the report): an object-metric inside a component envelope appears in 1 file across objectui and objectstack, the objectui#11525 pin itself, against a control of 3 files holding an envelope of another type. Stored customer dashboards are unmeasured, the gap ruling C accepted.
  • The pin. inlineObjectMetricRetired-11525.test.tsx carries a dated note in its header. Its envelope case flips: on both surfaces, under both keys, the envelope node yields the placeholder object (checked by identity), the prompt is on screen, and no aggregate or find call is made. A new control keeps the broadcast on the envelope's object-chart and object-data-table, which still receive the filter bar's value.
  • Reverse verification, run on the committed fix (20fb2278) with a trap-restored mutation. isRetiredEnvelopeNode was forced false (anchor hit once, marker on disk 1, blob 71c7335c to c2706404). Expected direction: the four retired-envelope cases red and everything else green. Read: "Tests 4 failed | 30 passed (34)", exactly those four (both keys on both surfaces). Restore: blob 71c7335c equals HEAD, git diff HEAD 0 bytes, marker 0.

Gates

HEAD is cae2224d5 (final round, report 5974814539; CI 40 success, 3 skipped, 0 failure). The readings below were taken at fab635a2 unless the report re-measured them at cb687dfad. Toolchain: the container's Node v22.22.0 (the merged lockfile carries objectui#11366's lowered jsdom floor). Heavy runs went through the shared verify lock.

  • type-check, all 88 workspace programs (the census): 88 exit 0, 0 lines.
  • vitest, run from the worktree root with repo-relative paths:
    • packages/plugin-dashboard/, every reader test of the dashboard README, guide and touched changesets, and the changeset-reading script suites, at fab635a2: 182 files passed (2,325 tests, 6 skipped).
    • packages/types/, packages/react/, packages/core/src/builder/, every reader test of a doc, README or changeset the earlier rounds touched, and the changeset suites, at f90ba8d2, before the last round, whose diff touches plugin-dashboard's sources, its README and guide, and changesets, all re-read by the run above: 542 files and 14,116 tests passed.
  • Doc gates: check:doc-snippets exit 0, "777 of 777 block(s) judged, 0 failed"; check:doc-examples exit 0; check:doc-types exit 0.
  • check-changeset-no-major 0, check-changeset-presence 0 (3 changesets declared), check-changeset-fixed 0, check:pending-changeset-literals 0, check-changeset-overwrite 0 (report-only), check:changeset-claims 0 (report-only), check:new-line-citations 0 (0 new), check:control-bytes 0, check:component-surface-parity 0.
  • eslint, narrowed to each round's changed .ts/.tsx files: 0 errors, and the same warning count per file as before the change (the last round's four files against f90ba8d2; the previous round's six against main and ff7f748e). eslint.config.js sets no parser project, so linting is not type-aware and these diffs cannot move the verdict on an untouched file. The repo-wide lint is CI's.
  • Not measured: check:sdui-registration-pins, which needs a console build (exit 2, prerequisite); no registration input moved.

Acceptance notes

  • Corrected (landing-prep round, report 5970915352): the flex bag's child list is unknown[] | SchemaNode on the TS face. A SINGLE child is checked, and under the objectui#8347 mutation a misspelled key on it is refused (the earlier f03). An ARRAY of children is unknown[] and unchecked (probe f02 compiles under the mutation, at cb687dfad and at fab635a2). That comes from FLEX_BAG_CHILDREN's z.array(z.unknown()) arm, which is on main and untouched here. With the signature present, no reading moves today. The arm is carried by objectui#11564, ordered before objectui#8347's removal PR (seat ruling A on the report's open question).
  • Premise H3, re-measured at cb687dfad: under the P1 mutation, types reads 39/40 (f02, the array arm above) and react 15/15. The control leg reads 29/40 and 9/15.
  • DeclaredNode has same-type pairs that are intended: flex (FlexSchema and FlexBlockNode) and the object-* renderer faces next to their authored bag nodes.
  • The static pivot's runtime move: an option key it does not state (a host prop of PivotTable's such as rowLabels, or a BaseSchema node key other than className such as id, style or hidden) no longer reaches the node. Each stated key draws as before; DashboardGridLayout.objectProviderFetch-10528 and DashboardGridLayout.datasetPath pass.
  • DashboardGridLayout.tsx keeps twelve stale cross-file line addresses, none in a paragraph this PR edits, so they are listed on the card for a later edit of that file rather than repaired here: four in the dataSource prop's docblock (into DashboardRenderer, DatasetWidget, and SchemaRenderer twice) and eight in the render-site comments (five into DashboardRenderer, three into legacyRetiredWidget).

Generated by Claude Code

claude added 10 commits October 2, 2026 19:39
… the declared-node union (objectui#11466)

`SchemaNode`'s object arm is `DeclaredNode`: the discriminated union, keyed
by the literal `type`, of the component schemas `AnySchema` declares (without
its `BaseSchema` arm, whose `type` is `string`), the spec-declared
`AuthoringNode`s and the types an application declares in the new
`CustomNodeRegistry` interface. `SchemaRendererProps.schema` takes the same
type beside a string. No `type: string` arm, no index signature.

The cycle: `SchemaNode` names `AuthoringNode`, whose `FlexBlockNode` is the
input of `FlexBlockSchema`, whose bag names `SchemaNodeSchema`, whose type
names `SchemaNode`. `FlexBlockNode` becomes an interface (members resolved
lazily), and `zod/base.zod.ts` writes the node slot's zod type out through
type aliases. Typing only; no schema object moves.

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

`AppComponentSchema` (the app.json document, read structurally by
`AppSchemaRenderer`, never dispatched by `ComponentRegistry`) is not a node,
so `DeclaredNode` leaves it out: the registry's `app` key serves the page
kind. `PageDocumentNode` admits every page kind but the interface-mode
`list`, which `PageView` renders through `InterfaceListPage` and never hands
to `SchemaRenderer`; the registry's `list` key is the `list` node's.

Narrowing a `DeclaredNode` on `type === 'app'` now gives `PageDocumentNode`
alone (was `AppComponentSchema | PageDocumentNode`), and on `'list'` gives
`ListSchema` alone (was `ListSchema | PageDocumentNode`). `SchemaByType`
reads `AnySchema` and does not move.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…fter validateTree; the builder's node slots take DeclaredNode (objectui#11466)

- `kind: 'html'` pages: the runtime-parsed `SchemaElement` tree becomes a
  `DeclaredNode` at ONE documented boundary, reached only when `compile`'s
  `validateTree` reported no error (seat answer Q2 A on objectui#11479).
- `@object-ui/core`'s schema builder: `.child()` / `.children()` (grid,
  flex) and the card's `.content()` take `DeclaredNode` instead of
  `BaseSchema`, so a child passed in is checked against its own type.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…input crosses one helper, and the old-shape pins state the declared-node union (objectui#11466)

- `@object-ui/test-support` gains `undeclaredNode`, THE one crossing for
  deliberately undeclared node input in a test: unknown or retired types,
  refused spellings (`props` / `visibility` on `element:text`, a node-level
  `params`, a retired `drillDown`), the three runtime-flat action suites,
  a host slot merged into a node, bare `{ type }` smoke nodes.
- Types a test registers with `ComponentRegistry` are declared in
  `CustomNodeRegistry`, as an application would (react: test-widget,
  test-debug-div, crashing-widget / stable-widget, test-component,
  probe-7088, test-4548-div, identity-probe / self-updating-probe,
  test:probe-4795, valid-host / host-wrap, test-ctx-consumer, probe-3955,
  probe-7530, test:schema-input-bridge-8908).
- Fixtures that are declared nodes name their type: plugin-gantt and
  plugin-map's propertiesBag fixtures (bag node and TS twin), the four
  `home` page documents (now with the spec-required `label`).
- Pins: react propsResolution and schema-input.bridgeFidelity state
  `DeclaredNode`; types overlay-trigger-union-7081 likewise;
  node-recursion-point-8344 states the measured exclusion set the
  `BaseSchema` arm used to hide; zod-mirror-parity records the two Wider
  rows the `component` slot's `BaseSchema` fallback produces (objectui#8347's).
- New pin `node-slot-union-11466.test.ts`: a nested misspelling on a
  closed node is refused with the signature present, no `type: string`
  arm, the `app` / `list` arms, registry entries join under their key.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…is declared in CustomNodeRegistry (objectui#11466)

- Examples that held a node as `BaseSchema` or as an unannotated literal
  name `DeclaredNode` (the schema-rendering, expressions, architecture,
  data-source and runner guides; the root, components, data-objectstack
  and react READMEs; the blocks index).
- `schema-renderer.mdx`'s custom widget declares `MyWidgetSchema` in
  `CustomNodeRegistry` beside its `ComponentRegistry.register` call.
- `data-source.md` teaches a node that reads `objectName`
  (`object-grid`, its props in the bag) instead of `table`.
- `plugin-detail`'s related-list example writes its props in the
  `properties` bag; the types README's `data-table` carries its required
  `columns`.
- The schema reference, the types README and the react README describe
  `SchemaNode`, `DeclaredNode` and `CustomNodeRegistry` as they now are.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…tries its change falsifies

One changeset (types, react, core minor with the breaking meaning stated;
components patch). Dated, append-only notes on the pending entries whose
standing reading of `SchemaNode`, the prop union, the bridge's types or the
node-recursion pin this change makes false: 11364, 8331, 8908, 4622,
7767, 11479, 8572.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…e`; two source pins and the schema reference follow (objectui#11466)

- `DeclaredNode` excludes the app-level document as
  `Exclude<AnySchema, { type: 'app' }>` rather than by name, so `base.ts`
  does not name that interface (the `AppComponentSchema.actions[]` reader
  census in handler-keys-string-any-mirrors-7344 keys on the name).
- page-body-arity-8310 reads the root README's typed example
  (`const schema: DeclaredNode = {`); overlay-node-slot-doc-types-7082
  reads `SchemaNode`'s new spelling in `base.ts`.
- The schema reference points at the SchemaRenderer page's registration
  walkthrough instead of a placeholder `my-widget` block.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…typed example (objectui#11466)

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…onger uses (objectui#11466)

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…op as `BaseSchema` (objectui#11466)

8310-page-node-body-arity and 11354-plugin-form-literals each said, in the
present tense, that `SchemaRenderer`'s `schema` prop is typed `BaseSchema`.
After this change it is `DeclaredNode | string | null | undefined`. Each entry
gets an append-only dated note; no existing line moves.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 67 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/10337-widget-input-serializer-key-list.md

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

    Clause 2 of the WidgetInput docblock said sdui-parser's manifest serializer forwards six keys per input and listed them without of. manifestFromConfigs has forwarded of since objectui#8067, so a reader of the published declaration learned that of is not published when it is. The clause now reads "a fixed key list per input, of included since objectui#8067" and lists all of them, the uncounted wording ComponentInput's own docblock in base.ts already uses. The clause's conclusion is unchanged: none of the five tombstoned ComponentInput keys is on that list. Comment only, with no type or runtime change.

.changeset/10859-dashboard-node-keys.md

  • names widgetDispatch.ts → packages/plugin-dashboard/src/widgetDispatch.ts — edited by this change

    • dashboard-grid is unregistered (with plugin-dashboard:dashboard-grid). It was builder chrome published as a node key: objectui validate refused it at type, and nothing authored or emitted it. DashboardGridLayout stays a named export; mount it directly. - metric and metric-card register with skipFallback: true. Both dashboard surfaces (DashboardRenderer, DashboardGridLayout) now hand SchemaRenderer plugin-dashboard:metric / plugin-dashboard:metric-card through one table, DASHBOARD_NODE_TYPES in widgetDispatch.ts. That covers the static metric tile, the metric-card widget-slot entry and a metric / metric-card node in a widget's legacy component envelope. The dashboard WIDGET vocabulary is unchanged: widgets[].type: 'metric' and the 2026-08-14 metric-card slot entry author exactly as before. - dashboardComponents drops dashboard-grid, and its metric / metric-card entries are keyed plugin-dashboard:metric / plugin-dashboard:metric-card, the type each now serves, so iterating the map cannot re-create the retired bare keys. - A rendered KPI tile's data-obj-type reads plugin-dashboard:metric (or plugin-dashboard:metric-card) instead of the bare spelling.

.changeset/11068-grid-declared-keys.md

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

    What was wrong. ObjectGridSchema declared eight keys the grid never read. An author who wrote description, emptyState, name, placeholder, rowSpecActions or bulkSpecActions on an object-grid got no type error, no validator refusal and no effect. The reference example in schema-reference.md taught description and showFilters as if they worked. It now authors only keys the grid reads.

.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/11483-metric-card-needs-value.md

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

    • What changed. DashboardWidgetTypeSchema is now the spec's families plus list and custom, so the slot reads a metric-card through its component arm alone. The TypeScript twin, DashboardWidgetTypeName, drops DashboardComponentWidgetType the same way. DASHBOARD_COMPONENT_WIDGET_TYPES still lists metric-card: it is the component arm's type, and only that. - What now refuses that did not. Directly in widgets[], both faces refuse a metric-card with no value. That holds whatever else the card carries: title, description, id and layout, a dataset with values, or its figure under options (options: { value }), which the tolerant face accepted. On its own, DashboardWidgetSchema (zod) refuses type: 'metric-card' at type. On the TypeScript face, 'metric-card' is no longer a DashboardWidgetTypeName, so assigning it to that type is a compile error. - Why no widget key stands in for value. This was measured through the real DashboardRenderer and DashboardGridLayout. A dataset makes the dashboard draw DatasetWidget in the card's place, by a rule that does not read the type; the card is never mounted. options is spread onto the node as literal props: a second spelling of value, not a binding. No other widget key reached the figure. - What does not move. On the TypeScript face, DashboardWidgetSchema['type'] still includes 'metric-card'. That interface is also the read type of every widgets[] entry, and the component arm stays assignable to it. So a metric-card literal with no value still compiles directly in widgets[], while the validator refuses it on both faces. A metric-card in a widget's legacy component envelope is judged as before: there, the tolerant face's BaseSchema fallback still accepts a card that fails the arm. Every metric-card in the schema catalog, the READMEs and the docs fences carries its value. The one docs example that did not, under "TypeScript Support" in content/docs/plugins/plugin-dashboard.mdx, is rewritten.

.changeset/11515-vscode-export-react-annotation.md

  • names SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    The compile pin gains a leg against the real prop type, read from SchemaRenderer.tsx, with a positive control. The two documented copies of the preamble (DESIGN.md and the docs page) follow it. The docs page's sample keeps its h1 node: h1 is a declared type (HtmlElementSchema), and the annotated sample compiles both today and with objectui#11466 applied.

.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/5674-retire-plugin-component-input.md

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

    Migration. Replace PluginComponentInput with ComponentInput — they have named the identical declaration since objectui#4972 converged the plugin-scoped interface onto base.ts, so this is a rename with no shape change:

.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/5905-componentinput-inputtype-tombstone.md

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

    Stale wording corrected in the same pass, because this change falsifies it: base.ts and zod/base.zod.ts both said the fork was "recorded for a ruling; until then this stays a live, writable key", and widget.ts called it "the open fork". All three now record the ruling. A reader who greps the source instead of the card thread was meeting an open fork that no longer existed.

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

    Stale wording corrected in the same pass, because this change falsifies it: base.ts and zod/base.zod.ts both said the fork was "recorded for a ruling; until then this stays a live, writable key", and widget.ts called it "the open fork". All three now record the ruling. A reader who greps the source instead of the card thread was meeting an open fork that no longer existed.

.changeset/5905-componentinput-retire-constraint-keys.md

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

    The retirement kit: ?: never on the interface (packages/types/src/base.ts), so authoring one is a tsc error at the registration site; retirementTombstone() on the Zod mirror (packages/types/src/zod/base.zod.ts), so an authored value is REFUSED at parse time with code: 'invalid_type', the key named in the issue path, and the migration note as the message. Deleting the members outright was the option NOT taken: ComponentInputSchema is a non-strict z.object, which strips an undeclared key silently — one silent no-op traded for another. Pinned in packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts.

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

    The retirement kit: ?: never on the interface (packages/types/src/base.ts), so authoring one is a tsc error at the registration site; retirementTombstone() on the Zod mirror (packages/types/src/zod/base.zod.ts), so an authored value is REFUSED at parse time with code: 'invalid_type', the key named in the issue path, and the migration note as the message. Deleting the members outright was the option NOT taken: ComponentInputSchema is a non-strict z.object, which strips an undeclared key silently — one silent no-op traded for another. Pinned in packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts.

.changeset/5926-empty-action-visible-when.md

  • names packages/react/src/SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    visibleWhen is not a per-block concern in this platform. It is enforced once, generically, in packages/react/src/SchemaRenderer.tsx: shouldHide tests visibleWhen ahead of the hoisted visible (objectui#5454), sets _hidden, and the _hidden early return fires before the registry dispatches. A block renderer cannot ignore the gate, because it never sees the node.

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

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

    Why the name had to move. The const declares exactly two keys — className and style, the CSS passthrough attributes a node exposes. The TypeScript StyleProps (base.ts) is the Tailwind-SCALE vocabulary: padding, margin, gap, backgroundColor, textColor, borderWidth, borderColor, borderRadius. Measured on this branch's base with an AST read of both files: 2 keys against 8, sharing ZERO keys. In this package the …Schema suffix otherwise means "runtime mirror of the like-named declaration", so the shared name asserted a mirror relationship that does not exist — and building objectui#5684's parity registry by name pairing duly put the two together and reported drift on a pair that has no counterpart at all.

  • 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/index.ts → packages/types/src/index.ts — edited by this change

    GanttConfig itself gains nine members and is a published type, exported by name from packages/types/src/index.ts: lockField, objectField, summaryExtent, defaultCollapsedDepth, borderColorField, dependencyTypes, timeZone, exportFileName, interactions. The entry file's diff is empty only because the export list already named the type — the widening happened at the declaration.

  • 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/6067-component-meta-derive-from-canonical.md

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

    packages/core/src/registry/Registry.ts declared its own ComponentMeta: thirteen keys, of which nine were restated from @object-ui/types' base.ts, four were registry-only (tier, namespace, skipFallback, labelling), and tags / description were absent — although both are declared on the canonical type and on the ComponentMetaSchema zod mirror. Two of the three authorities agreed and the registration surface did not, so those two keys were unwritable at exactly the declaration most component registrations import. That is the same two-key delta objectui#5893 had just closed inside @object-ui/types, arriving a third time on a third declaration, and objectui#5671 had already made the identical move for the sibling type ComponentInput in this very file.

.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/6170-retire-timeline-dead-keys.md

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

    Also in this change: the in-repo example packages/types/examples/data-display-examples.json (its timeline node authored all three) is migrated to items / variant; the two content/docs/api/schema-reference.md snippets that authored events: [] now author items: []; and the plugin-timeline docs callout says retired rather than deprecated.

.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/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 777e5c6f4 (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/7087-disabled-twin-symmetry.md

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

    Those 18 redeclarations are removed, on both faces. The interfaces inherit BaseSchema.disabled the way they always inherited visible; the zod mirrors inherit base.zod.ts's z.union([z.boolean(), z.string()]) through .extend()'s merged .shape, so there is no second spelling of the union to drift from — the route ChatbotSchema took in objectui#6169.

.changeset/7088-hidden-jsdoc-single-hide-path.md

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

    Why a comment was worth a changeset: the JSDoc is the authority a later docs correction is measured against, and this one nearly propagated. While splitting the schema-reference hidden row, a reader checked it against base.ts and almost "corrected" the table's "Inverse of visible" — the half that describes shipped behaviour — toward the declaration. That row is unchanged and stays. SchemaRenderer.hiddenVisibleSynonymy.test.tsx now pins the claim the comment makes: the two keys produce the same rendered output, and hidden: true leaves no node that could carry a visibility style.

  • names SchemaRenderer.hiddenVisibleSynonymy.test.tsx → packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx — edited by this change

    Why a comment was worth a changeset: the JSDoc is the authority a later docs correction is measured against, and this one nearly propagated. While splitting the schema-reference hidden row, a reader checked it against base.ts and almost "corrected" the table's "Inverse of visible" — the half that describes shipped behaviour — toward the declaration. That row is unchanged and stays. SchemaRenderer.hiddenVisibleSynonymy.test.tsx now pins the claim the comment makes: the two keys produce the same rendered output, and hidden: true leaves no node that could carry a visibility style.

.changeset/7319-dead-schemarenderer-retired.md

  • names packages/react/src/SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    packages/components/src/SchemaRenderer.tsx was a 28-line component carrying the same export name as the real renderer in packages/react/src/SchemaRenderer.tsx. Nothing reached it: it is absent from the package barrel, no file in the repo imports it by any form, and the package's exports map has no subpath that resolves to it.

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

.changeset/7415-action-type-input-rename.md

  • names SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    Census of the authored corpus, measured before the rename, not assumed. Across 4,797 files in examples/, apps/console/, content/docs/ and packages/: zero authored nodes set the input. 517 JSON files (433 of them the schema catalog) contain 2,410 type-bearing nodes and no action:button / action:icon node at all — control: 127 plain button nodes on the same walk. 207 parsed fenced JSON blocks in md/mdx carry 5 action:button nodes, all of them { type, label, icon?, action } — the discriminator plus the action channel, none setting an execution type — control: 28 plain button nodes, and the count independently matches the corpus census already recorded in SchemaRenderer.tsx ("action:button (5 nodes)"). No docs page documents the input; no catalog entry uses the components. So the rename breaks no authored document in this repo.

.changeset/7493-componentinput-retire-label-defaultvalue-advanced.md

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

    The retirement kit: ?: never on ComponentInput (packages/types/src/base.ts), so authoring one is a tsc error at the registration site; retirementTombstone() on ComponentInputSchema (packages/types/src/zod/base.zod.ts), so an authored value is REFUSED at parse time with code: 'invalid_type', the key named in the issue path, and the migration note as the message (one string, both channels). Pinned in packages/types/src/__tests__/component-input-retired-keys-7493.test.ts, which also holds a tree-scoped absence census over every inputs: array under packages/** and apps/**.

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

    The retirement kit: ?: never on ComponentInput (packages/types/src/base.ts), so authoring one is a tsc error at the registration site; retirementTombstone() on ComponentInputSchema (packages/types/src/zod/base.zod.ts), so an authored value is REFUSED at parse time with code: 'invalid_type', the key named in the issue path, and the migration note as the message (one string, both channels). Pinned in packages/types/src/__tests__/component-input-retired-keys-7493.test.ts, which also holds a tree-scoped absence census over every inputs: array under packages/** and apps/**.

.changeset/7530-predicate-envelope-declared.md

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

    • ExpressionWire (type, main entry) — the TypeScript wire union, in packages/types/src/expression.ts. - ExpressionWireSchema (@object-ui/types/zod) — its runtime twin, hoisted out of zod/form.zod.ts (where it was module-private) into zod/expression.zod.ts and imported by both base.zod.ts and form.zod.ts. One envelope type, reused by reference; no second spelling.

.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/7658-toc-code-span-anchor-parity.md

  • names content/docs/utilities/runner.mdx → content/docs/utilities/runner.mdx — edited by this change

    Seven live headings in this repo's own docs were affected (content/docs/utilities/cli.mdx, content/docs/utilities/runner.mdx, packages/cli/README.md). Pinned against the real render pipeline rather than a second derivation of the slug rules: the new test renders each heading through MarkdownImpl and compares extractToc's id to the id attribute rehype-slug actually emitted.

.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/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/7912-schema-renderer-datasource-contract.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    Five as any reads of this context in @object-ui/fields are gone — they were redundant the moment the seam became honest — and LookupField's local re-declaration of the imported context as a Context of any, which laundered its dataSource read while looking typed, is gone with them. Both directions of the contract are pinned against the real compiler in SchemaRendererContext.dataSourceType.pin.test.ts, and the card's planted documentation probe (a bare string in packages/react/README.md's provider example) now fails pnpm check:doc-snippets, where it used to exit 0 with zero diagnostics.

.changeset/7918-zod-lazy-getter-identity.md

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

    The card that found this did not claim the ten were wrong. It asked whether the spelling was buying a temporal-dead-zone dodge, and that check is what shipped. Each of the ten was rewritten in place to const inner = ANGLE-BRACKETS(body); z.lazy(() => inner), the package rebuilt, and the built barrel imported in a fresh process. Eight refuse to load. Seven name the very const being declared (children: z.array(TreeNodeSchema) sits inside TreeNodeSchema's own initialiser); SchemaNodeSchema names BaseSchemaCore, which base.zod.ts declares below it. For those eight the z.lazy is load-bearing, so ActionSchema, AppMenuItemSchema, FilterGroupSchema, MenuItemSchema, NavLinkSchema, NavigationMenuItemSchema, SchemaNodeSchema and TreeNodeSchema keep the spelling they have. The two that loaded clean are memoised: FilterBuilderConditionSchema is not recursive at all, and NavigationItemSchema already defers its self-reference through an inner z.lazy(() => NavigationItemSchema) on children.

.changeset/7926-page-node-refuses-actions.md

  • names packages/components/src/renderers/layout/page.tsx → packages/components/src/renderers/layout/page.tsx — edited by this change

    Accept-set change, deliberately. A page document carrying actions used to parse GREEN and render nothing. PageNodeSchema never declared the key and PageRenderer never read it — git grep -ni action on packages/components/src/renderers/layout/page.tsx returns only the PageVariableActionBridge import and its render — so the array survived purely through BaseSchema's .passthrough(). Measured through the real SchemaRenderer: a page node with actions: [{type:'button',label:'Add Product'}, …] drew 0 buttons and the label appeared nowhere in the DOM, while the SAME two buttons in the page's child list drew 2. Until objectui#7933 the array also reached the wrapper element as actions="[object Object],[object Object]".

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

  • names packages/plugin-detail/README.md → packages/plugin-detail/README.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.

  • 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/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/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 c1fe272ad 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, at this change ObjectGridSlotKey / ObjectFormSlotKey pin it, SchemaBuilder.testId() writes it, and ADR-0054 C4 — shipped — reads "the renderer emits data-testid … derived from metadata".

.changeset/8284-content-channel-per-component.md

  • names packages/react/README.md → packages/react/README.md — edited by this change

    Migration. Nothing that renders today stops rendering: a document authoring the channel its renderer reads is unchanged, and a document authoring the other one rendered an empty element before and is now refused instead. The repo-wide census found five documents in this state — packages/react/README.md, content/docs/guide/expressions.md, two blocks in content/docs/guide/schema-rendering.md and packages/components/TESTING.md — every one of them a form or container authoring body; all five are corrected in this change. If your own metadata authors the refused channel on one of these twelve node types, the component was already drawing nothing there; rename the key to the one in the table.

  • names content/docs/guide/expressions.md → content/docs/guide/expressions.md — edited by this change

    Migration. Nothing that renders today stops rendering: a document authoring the channel its renderer reads is unchanged, and a document authoring the other one rendered an empty element before and is now refused instead. The repo-wide census found five documents in this state — packages/react/README.md, content/docs/guide/expressions.md, two blocks in content/docs/guide/schema-rendering.md and packages/components/TESTING.md — every one of them a form or container authoring body; all five are corrected in this change. If your own metadata authors the refused channel on one of these twelve node types, the component was already drawing nothing there; rename the key to the one in the table.

  • names content/docs/guide/schema-rendering.md → content/docs/guide/schema-rendering.md — edited by this change

    Migration. Nothing that renders today stops rendering: a document authoring the channel its renderer reads is unchanged, and a document authoring the other one rendered an empty element before and is now refused instead. The repo-wide census found five documents in this state — packages/react/README.md, content/docs/guide/expressions.md, two blocks in content/docs/guide/schema-rendering.md and packages/components/TESTING.md — every one of them a form or container authoring body; all five are corrected in this change. If your own metadata authors the refused channel on one of these twelve node types, the component was already drawing nothing there; rename the key to the one in the table.

.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/8344-node-recursion-point-redirect.md

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

    Two mechanical notes for anyone editing the wiring. AnyComponentSchema is built in zod/index.zod.ts from all 13 category modules and 14 modules import zod/base.zod.ts, so the arm cannot be an import — z.lazy defers evaluation, not the module graph. It is a written option slot that index.zod.ts fills inside AnyComponentSchema's own initializer, and it is a z.union option rather than a z.lazy holder because z.lazy memoises its getter: a holder would let whichever module graph parsed first decide the accept set for the whole process. Both constraints are measured, and the reasoning lives on defineNodeComponentUnion in zod/base.zod.ts.

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

    ⛔ It is NOT closed here, and the reason is measured rather than argued. The route that closes it by binding the union inside SchemaNodeSchema's z.lazy getter was implemented and pushed, and CI refused it: Build Docs failed with ReferenceError: Cannot access 'BaseSchema' before initialization out of packages/types/dist/zod/app.zod.js, because that import makes base.zod.ts depend on the barrel and a bundler is free to evaluate the resulting cycle category-module-first. Reproduced locally in one line — importing dist/zod/app.zod.js throws with the binding in place and loads clean without it. The other three candidates were measured too: a narrowed sideEffects array is not a legal declaration for this package (one gate requires every entry form to be named, another refuses a named entry with no load-time effect, and this package's entry forms are pure), a bare top-level call is dropped by the same flag, and dropping the flag costs 16,078 gzipped bytes on the console framework chunk and moves a workspace census a guard pins.

.changeset/8499-node-slot-registered-arms.md

  • names content/docs/utilities/runner.mdx → content/docs/utilities/runner.mdx — edited by this change

    The defect, and the direction it ran. Nine type spellings sat at DECLARED node slots in this repository's own corpora and resolved in no arm of the component union. Eight were registered renderers with fixtures proving they draw; the ninth (my-component) is the reader's own plugin component and carries a written exemption in scripts/check-doc-component-types.mjs. A reader following content/docs/utilities/runner.mdx's own instruction — "copy one, wrap it in a page document … and save it as src/app-data/pages/index.json" — got a document that renders correctly in the browser and is refused by objectui validate. That is the expensive direction: the likely reaction is to stop trusting the validator.

.changeset/8568-retire-dollar-dialect-lowercase-aliases.md

  • names packages/data-objectstack/README.md → packages/data-objectstack/README.md — edited by this change

    packages/data-objectstack/README.md's operator tables follow the implementation, as does the reconciliation test that holds them to it.

.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/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 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/8871-page-node-refuses-breadcrumbs.md

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

    Accept-set change, deliberately. A page document carrying breadcrumbs used to parse GREEN and render nothing. PageNodeSchema never declared the key and no renderer ever read it, so the array survived purely through BaseSchema's .passthrough(). On the TypeScript face, tsc previously accepted it too, through BaseSchema's own [key: string]: any index signature (the [key: string]: any member of BaseSchema in packages/types/src/base.ts) — the same open door the zod mirror walked through at runtime.

.changeset/9162-node-slot-guard-class-closure.md

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

    What leaked. {schema.footer && ANGLE-BRACKETS(CardFooter)…ANGLE-BRACKETS(/CardFooter)} does not evaluate to false when the slot is falsy — it evaluates to the slot, and React renders numbers. A node slot's published zod face carries a z.number() arm (nodeUnionOptions, packages/types/src/zod/base.zod.ts), so footer: 0 is legal authored input, and it painted a stray 0 into the DOM. NaN painted three characters. The && also short-circuits, so renderChildren's own if (!children) return null first leg was never reached — which is why the objectui#8908 bridge repair could not cover any of these sites.

.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/9308-data-root-unbound-from-adapter.md

  • names content/docs/guide/schema-rendering.md → content/docs/guide/schema-rendering.md — edited by this change

    The Data Context passages of content/docs/guide/schema-rendering.md and packages/react/README.md teach the scope channel accordingly.

  • names packages/react/README.md → packages/react/README.md — edited by this change

    The Data Context passages of content/docs/guide/schema-rendering.md and packages/react/README.md teach the scope channel accordingly.

.changeset/9365-bridgelistview-present-tense.md

.changeset/9375-base-i18n-vocabulary-docblock.md

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

    base.ts said of the INLINE locale map (label / description) and the KEYED bundle reference (ariaLabel) that they "each accept the other's shape vacuously", and the label docblock said the same thing one property over ("a keyed ref typed into this slot is accepted only vacuously"). That was true when objectui#4580 Q2-B wrote it; the installed pin has since closed it, and the sentence now reads as live in published .d.ts bytes while the tree says the opposite.

.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/9659-node-recursion-point-inert-clause.md

.changeset/9959-refused-data-prop-warn-reset.md

  • names SchemaRenderer.tsx → packages/react/src/SchemaRenderer.tsx — edited by this change

    The dedupe Set behind the objectui#9571 / objectui#9758 diagnostic — "the authored data key was NOT passed to the component as a React prop" — lived in SchemaRenderer.tsx with nothing able to clear it, while every other dev diagnostic in this package's utils/ directory pairs its Set with an exported test-only reset. propsBagDiagnostic.ts states the consequence in its own reset's docblock: the second test to assert the same warning reads the first test's dedupe entry and sees silence, "a green run that checked nothing". The absence direction is the dangerous one — a test asserting this warning did NOT fire for a type#id an earlier test already warned for is green whatever the renderer does, and it reports that green as coverage.

.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/component-meta-converge-and-deprecate-alias-5893.md

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

    @object-ui/types published ComponentMeta twice, from two different declarations: base.ts and plugin-scope.ts (the latter published as PluginComponentMeta). They were structural copies, not an alias pair. plugin-scope.ts' ComponentMeta is now export type { ComponentMeta } from './base.js' — the disposition objectui#4580 ruled for the identical shape, a structural copy would reproduce the defect the moment either side moved, and the same move objectui#5671 made for the sibling type ComponentInput in the same file.

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

    Either side had already moved. base.ts declared eleven keys; the plugin-scoped copy declared nine — the same nine, minus tags and description. So a plugin author typing against the plugin-facing declaration could not write two keys the main surface advertises, and which the runtime validator already accepted: ComponentMetaSchema in zod/base.zod.ts declares all eleven, so two of the three authorities agreed and the plugin-facing one did not. resizeConstraints' six members were identical in both, so the delta was exactly those two keys.

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

    ⚠️ Dated note, 2026-09-29 — the one-occurrence reading above has since moved — objectui#10979. "In-repo, PluginComponentMeta has exactly one occurrence — its own export line" above is the reading of the commit this change landed on (575b71c850), where a whole-tree search for the name returned that one line, in packages/types/src/index.ts. This change then added its own pins and this entry, and later in this same release objectui#5674's retirement of the sibling alias PluginComponentInput (PR objectui#10826) added mentions in its test and its entry. Re-measured on main at 2eaf5be27, the name occurred on 19 lines in 7 files. In non-test source it appeared only on the export line and on one doc-comment line in plugin-scope.ts, and the one file that imported it was this change's own component-meta-single-declaration.test.ts. The text above is kept as the reading of this change.

.changeset/componentinput-reexport-4972.md

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

    @object-ui/core's ComponentInput (registry/Registry.ts) and @object-ui/types' plugin-scoped ComponentInput (plugin-scope.ts, published as PluginComponentInput) were structural copies of the interface in @object-ui/types' base.ts. Both are now re-exports of that one declaration, which is the disposition objectui#4580 ruled for the identical shape — a structural copy would reproduce the defect the moment either side moved — and the way core/src/types/index.ts already handles SchemaNode.

.changeset/default-children-retired-5051.md

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

    If you author plugins against the published register-meta table, drop the key. It is gone from skills/objectui/guides/plugin-development.md, which had been teaching it. A meta that still declares it stays valid: ComponentMetaSchema is a plain z.object, and measured on zod 4.4.3 that STRIPS unknown keys rather than rejecting them — so the key is silently dropped from the parse output instead of failing validation. TypeScript authors get the loud signal instead: all three ComponentMeta declarations (@object-ui/types base.ts and plugin-scope.ts, @object-ui/core Registry.ts) no longer offer it, so re-declaring it is now a compile error.

.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 refused the named views this package's own README and content/docs/api/schema-reference.md taught when this entry was written ({ label: 'All Users' } fails at columns; filter: [["owner", "=", "..."]] fails at filter.0), and objectui#8255 has since rewritten them in the spec shape. Mirroring the spec value would have lost 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().

  • 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.

.changeset/page-source-tailwind-prose-retraction-5469.md

  • names src/renderers/layout/page.tsx → packages/components/src/renderers/layout/page.tsx — edited by this change

    • @object-ui/react-runtime: README.md is published to npm (npm includes README.md in the tarball regardless of files). Its "no sandbox" callout is the paragraph that routes untrusted-author work to the html tier, and it carried the retracted framing line-wrapped across :17-18. It also gains the §Styling section it was missing — the absence is why the framing survived here. - @object-ui/sdui-parser: the corrected header of src/types.ts projects verbatim into the published dist/types.d.ts. - @object-ui/components: the corrected header of src/renderers/basic/html-elements.tsx projects verbatim into the published dist/renderers/basic/html-elements.d.ts. The kind === 'html' dispatch-arm comment in src/renderers/layout/page.tsx does not project (it is inside a function body) and is included here only because the same package already owes an entry.

.changeset/plugin-component-input-deprecate-5674.md

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

    @object-ui/types' entry point publishes ComponentInput as PluginComponentInput. Until objectui#4972 that alias pointed at a genuinely different declaration: plugin-scope.ts restated its own nine-key ComponentInput. objectui#5671 converged that declaration onto base.ts, so the alias became a second published name for the same type, carrying no information the first does not. Two published names for one type is a shape that costs readers a step and gives AI-authored code a coin-flip between spellings.

.changeset/plugin-detail-8937-parent-scope-residue.md

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

    • packages/plugin-detail/README.md (it is in files[], so it ships). It said the node's filter is AND-combined with { [relationshipField]: parentId }, full stop. Since objectui#7299 the parent condition is compiled to match the relationship field's arity, so a multi-valued relationship gets { [relationshipField]: { $contains: parentId } } instead. The paragraph now states both spellings and names the arbiter (@objectstack/spec/data's isMultiValueField). - The claim that the SQL driver decides arity on that same predicate. It does not: driver-sql gates the equality family on its own storage question, which reads multiple as truthy on ANY type. The two rules therefore disagree for a type outside MULTI_CAPABLE_TYPES carrying multiple: true. objectui#9184 moved the arity compiler into @object-ui/core's parent-scope seam and carried the claim with it, so the correction is recorded there — the seam now states the driver's measured rule, records the divergence as a divergence, and points at the upstream card that owns which of the two rules is right (objectstack#17469). RelatedList.tsx's pointer comment and the objectui#7299 test header carried the same sentence and are corrected to match.

.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.

.changeset/zod-base-schema-mirror-parity-4605.md

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

    The new pin is DERIVED rather than a hand-written key list: it reads the mirror's own .shape and compares each key against the declaration, so the next widening of base.ts that forgets this file turns it red with no list to maintain. It reads .shape and not keyof z.inputANGLE-BRACKETS(…) because that spelling was measured vacuous — .passthrough() collapses the inferred key union to bare string, and a pin written over it resolved never while five keys were demonstrably narrow. Two guards pin the derivation against both degenerations (never and string).

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 0a53c67f9 (merge-base with origin/main): 72 file(s) changed outside .changeset/, read against 2086 pending declaration(s) that publish a body (2719 pending in total). · run

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Console Performance Budget — not measured

This run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.

Step Outcome
Build packages failure
Check console performance budget skipped

See the workflow run for details.

No package size report: it is only generated from a complete package build, so a partial one is never shown.

This was referenced Oct 2, 2026
@objectstack-fleet objectstack-fleet Bot changed the title feat(types,react)!: a node slot and SchemaRenderer's schema prop take the declared-node union, V1 (objectui#11466) feat(types,react,core,plugin-dashboard)!: a node slot and SchemaRenderer's schema prop take the declared-node union, V1; the dashboard producers name declared nodes and an envelope's object-metric retires (objectui#11466) Oct 3, 2026
…ctui#11466)

Brings in objectui#11533, #11531 (@objectstack/spec 17.6.0), #11547,
#11539, #11542, #11344 and #11068. No textual conflict.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3308.4 KB 3330.4 KB
Main entry chunk (gzip) 150.3 KB 350 KB
Entry file index-BrOf2VzY.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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 231.08KB 64.07KB
fields (index.js) 261.62KB 66.20KB
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) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.83KB 38.55KB
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.96KB 44.52KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.05KB 64.60KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.46KB 28.93KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
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.31KB 2.07KB
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 (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 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

…ctui#11466)

Brings in objectui#10813 (PR #11555), which landed after the previous
merge. No textual conflict.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3308.7 KB 3330.4 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-Me_URvki.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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 261.62KB 66.20KB
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) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.83KB 38.55KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.59KB 64.56KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.96KB 44.52KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.03KB 64.66KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.46KB 28.93KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
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.31KB 2.07KB
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 (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 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: cb687dfad7cf71915a59b64e52e66d0a9be2beec
Local-runs: none

What was read: card objectui#11466 (its body and all 19 comments), PR #11512 (its body, its 88-file list, and its net diff against the merge-base d0c0c7fe9: +1276 / -299, the same file headers as the REST diff), the head's 43 check-runs and the Skill Example Check job log, objectui#11525's ruling C (5964914121), objectui#11543 / PR #11558, objectui#11564, and the body and comments of objectui#8347. Nothing was built, run or re-run.

① Derived judgments

1. The narrowing (TypeScript face only). SchemaNode's object arm and the object member of SchemaRendererProps['schema'] are now DeclaredNode. It is the union of AnySchema's literal-typed arms (without the type: 'app' document), AuthoringNode, and one arm per CustomNodeRegistry entry. It has no type: string arm and no index signature. BaseSchema's own signature is untouched: base.ts still declares it, and the diff adds none.

  • Refused now, compiled before:
    • an undeclared type, nested or at the prop;
    • a value typed BaseSchema. The same holds for a value typed AnySchema, which carries the BaseSchema arm, and for an unannotated const whose type widens to string (71 of the first census's 155 test lines);
    • a missing required key of a declared type (a home page's label, a data-table's columns, a pivot's axes);
    • a wrong value type or a tombstoned key on a declared type. These are refused even with the signature present, because the signature does not override declared members (the first fork's control leg);
    • a misspelled key on a closed arm (an AuthoringNode, a closed registry entry), at any depth;
    • the app-level document at the prop, and a PageDocumentNode under type: 'list';
    • a type registered at runtime with ComponentRegistry but not declared in CustomNodeRegistry;
    • BaseSchema arguments to core's grid and flex .child() / .children(), to the card builder's .content(), and to toRenderableSchema.
  • Still accepted:
    • a string, null and undefined at the prop (number and boolean stay in SchemaNode only, as before);
    • every declared literal type, and every AuthoringNode;
    • a registered custom type once it is declared in CustomNodeRegistry;
    • a misspelled key on a type that extends BaseSchema, until objectui#8347 removes the signature.
  • Judged: this matches ruling ① exactly. It is pinned by node-slot-union-11466 (type-level; each directive holds both ways through TS2578) and by propsResolution assertions 1 and 1b. CI Type Check is green.

2. New public types and exports.

  • @object-ui/types gains DeclaredNode. Necessary: the object arm needs a name that consumers can annotate with (the READMEs, the migration), and that core's builder and page.tsx's boundary can use.

  • @object-ui/types gains CustomNodeRegistry. Necessary: it is premise ②(b)'s mechanism and the only route for a custom type. Each entry is intersected with { type: KEY }, so even a loose BaseSchema entry adds no type: string arm (pinned by _LooseEntryJoinsUnderItsKey). The helpers LiteralTypedArm and RegisteredCustomNode are not exported.

  • @object-ui/react and @object-ui/core: no new export. The prop and the builder parameters are retyped, and toRenderableSchema follows by reference.

  • @object-ui/plugin-dashboard gains DashboardMetricNodeSchema, plus a declare module '@object-ui/types' augmentation for plugin-dashboard:metric. Necessary:

    • the node both surfaces build must be declared before their casts can go (objectui#11479 Q1 A);
    • the type-only entry export is what loads the augmentation into consumer programs.

    It extends BaseSchema, so it keeps the signature until objectui#8347. Every program that imports the package gains this arm, which matches what the registry mounts.

  • @object-ui/test-support gains undeclaredNode. The package is private: true, so this is not public surface. It has 25 call sites in 15 test files and none under src/.

  • Two published types change shape, and the changeset states both. FlexBlockNode becomes an empty interface with the same members, and PageDocumentNode excludes list. Not named: an interface gets no implicit index signature, so a FlexBlockNode value no longer assigns to a string-keyed record type. The census shows no in-repo reader that this breaks.

3. The plugin-dashboard producers.

  • plugin-dashboard:metric is declared through CustomNodeRegistry. Both getComponentSchema builders return SchemaRendererProps['schema'], and both casts are gone. Under src/, the only cast to DeclaredNode is page.tsx's documented boundary. That boundary is reached only after the errors early return, and it renders exactly what it rendered before. The pre-existing as any / as never crossings at schema= props in src/ number the same at the base and the head.
  • The added widgetType !== undefined test moves no verdict. classifyWidgetType returns passthrough for a falsy type, so the metric family implies a non-empty string; I read this in widgetDispatch.ts.
  • Ruling A's envelope object-metric: isRetiredEnvelopeNode runs before the envelope pass-through on both surfaces and returns the same imported LEGACY_RETIRED_WIDGET_SCHEMA.
    • Both keys are retired, the bare key and plugin-dashboard:object-metric. That is the same registration under its full name, so this is within the ruling, and the changeset, README and guide state it.
    • The broadcast's FilterableComponentType, FILTERABLE_COMPONENT_TYPES and FilterableComponentSchema lose object-metric. All three are internal, not exported.
    • The ruling's three ⛔ hold. The new control asserts that an envelope object-chart and an envelope object-data-table still receive the filter. ObjectMetricBlock's registration is not edited. There is no cast.

4. The runtime move: the static pivot.

  • Where it applies. Only in DashboardGridLayout, the editable grid. DashboardRenderer draws the retired placeholder for every non-dataset pivot.
  • What changed. The node used to spread options whole. It now states ten keys plus data, under satisfies PivotTableSchema.
  • Can a user see it? Yes, in the editor, for a stored static-data pivot whose options carry a key outside the stated set:
    • a BaseSchema node key such as hidden, style or id (an options.hidden: true tile used to be hidden and is not now);
    • a PivotTable host prop such as rowLabels or columnLabels.
  • Is it stated? Yes: in 11466-dashboard-metric-node (before and now, with the keys named), in dated notes on the 10528 and 11514 changesets, and in the PR body.
  • What is missing. It is not labelled breaking, and it is not measured against stored dashboards. The diff does not show that the TypeScript face required dropping the spread; stating the required axes beside it was possible. It is a choice in the contract's direction, inside the claimed surface.
  • A stale sentence remains. objectui#10528's sentence a few lines above, in the same arm, still reads "the static-data pivot under this branch is unchanged". Non-blocking.

5. The ledgers.

  • WiderThanDeclared is the union of main's objectui#11515 entry and this branch's two (complex.zod.ts#DashboardComponentSchema::widgets and #DashboardWidgetSchema::component, two CONCRETE arms each).
    • Read at the head: 6 entries, 6 keys and 8 arms, split 3 SCHEMA-NODE, 3 CONCRETE, 0 MIXED and 2 unions. That equals the header. main read 4 / 4 / 4, split 3 / 1 / 0 / 0.
    • The growth honestly records a TypeScript face that narrowed while the mirror's BaseSchema fallback stays objectui#8347's, and each entry names its exit. Accepted.
  • MeasuredArmDrift11466: NodeRecursionPointDeclarationDrift went from never to an exact two-way list. never was vacuous while BaseSchema absorbed every arm. The list is app, record, home, utility, page, dashboard, plus the retired kanban wide arm.
    • The three arms objectui#11515 twinned left the list, with a dated note. This strengthens the pin.
    • The zod runtime does not move. SchemaNodeSchema's static output does narrow, because its type is the SchemaNode alias, and for those six arms that type now overstates what parse returns. So the changeset's "every zod face does not move" holds for the runtime accept set only.

6. The flex bag's array child list.

  • The correction is true. FLEX_BAG_CHILDREN is a union of z.array(z.unknown()) and SchemaNodeSchema (zod/layout.zod.ts, which this PR does not change). So FlexBlockNode's properties.children is unknown[] | SchemaNode: a single child is checked, and an array is not.
  • It is not a V1 regression. Without V1 the arm is unknown[] too, and with the signature present no reading moves.
  • Seat ruling A (5970941895) is within the seat's authority. It orders technical tasks: land V1, then close the arm in objectui#11564 before objectui#8347. It widens nothing and adds no exemption.
  • Premise ②(a) still holds. The list is not a slot typed SchemaNode, and premise ②(a) names "a nested slot and the SchemaRenderer prop". So the fork rule ③ is not triggered.
  • The gaps are in ③.

7. The tests.

  • Nothing is skipped or suppressed. No .skip, .only, .todo, xit, @ts-ignore, @ts-nocheck, as any or as never is added.
  • Seven @ts-expect-error are added, all in node-slot-union-11466, and none is removed.
  • Two expect lines are removed, both in the objectui#11525 envelope case that ruling A flips. The new assertions are stronger: the placeholder by identity, no node of that type, no aggregate or find call, and a two-type control.
  • The old pins are restated, not loosened. propsResolution keeps an exact Equal and adds 1b. 7081, 7082 and 8310 are re-anchored. authoring-nodes-11364 keeps PostRemovalSchema as a dated model, and the live prop is pinned in 11466.
  • The census claim is consistent with the diff and with CI. The claim is 0 lines across 88 programs at 5afa0269a and cb687dfad, with the signature present and no cast. The diff adds one cast under src/ (the boundary). On the head, Type Check, all 8 test shards, Test (dist pins), Spec Main Shape Gate and Doc Snippet Type Check are green. The dev measured the census; it was not re-run here.

8. The check-runs. 39 success, 3 skipped (two coverage jobs and dependabot) and 1 failure, Skill Example Check.

  • From its log: the build and the self-test pass. The semantic phase reads 18 of 18 fences judged, 3 failed. All three are TS2322, 'BaseSchema' is not assignable to 'string | DeclaredNode | null | undefined', in skills/objectui/guides/auth-permissions.md, data-integration.md and schema-expressions.md. JSON, bare any and shadowed types each read 0.
  • PR docs(skills): type three marked fences' schema as SchemaRendererProps['schema'] (objectui#11543) #11558 edits exactly those three fences to SchemaRendererProps['schema'], and its own head reads 36 success and 3 skipped.
  • So the failure is that and nothing else.

② Semver level

  • The rule. objectui's version policy (AGENTS.md): no changeset declares major. objectui's own breaking changes are also marked minor, with the breaking meaning written in the body. The fixed group moves together.
  • 11466-node-slot-union: types, react and core minor; components patch. It has a "BREAKING (TypeScript authoring face only)" heading, what moves, what does not, and a migration that includes CustomNodeRegistry. patch is correct for components: only the internal page.tsx boundary and the README change, and no published type moves.
  • 11466-dashboard-metric-node: plugin-dashboard minor. The new export is additive, and the pivot move is stated as before and now. Its "No runtime change" is scoped to the cast bullet, where it is true. Advisory: the pivot move carries no breaking label.
  • 11466-envelope-object-metric-retired: plugin-dashboard minor. It says "Breaking for stored dashboards that still carry this form; minor because objectui never declares major", gives before, now and the fix, and says what does not move. This meets the rule.
  • Pre-existing changesets: 13 pending entries carry 15 append-only dated notes, with 0 lines removed. The bump-policy, declaration, fixed-group, claim re-read and overwrite checks are green.
  • Clause-②: yes (narrowing) is the correct arm. The net change narrows the TypeScript face (item 1) and adds one declaration surface (CustomNodeRegistry). It also makes two runtime narrowings in plugin-dashboard, both stated in the changesets and the PR body.
  • The BREAKING statement for TS consumers who pass a BaseSchema to the prop is present and adequate: "So is a value typed BaseSchema, which names no declared type", with a migration. Advisory:
    • The census's most common break, an unannotated const whose type widens to string, and a value typed AnySchema are covered only by implication.
    • No published narrowing exists for untyped server JSON (objectui#11479 Q2 B was deferred until such a producer is measured), so for that input the advice amounts to a validated cast.

③ Boundary flags

Implemented-by: claude/issue-11466-node-slot-union-v1
Reviewed-by: session_016tKoy8NJa35Yih1FdzrVmn

VERDICT: PASS

claude added 2 commits October 3, 2026 23:33
…ctui#11466)

Brings in objectui#11543 (PR #11558: the three skill fences typed as
SchemaRendererProps['schema']) and the fifteen other squash commits that
landed after d0c0c7f: objectui#11556, #11541, #11550 (three), #11536,
#11544, #11553, #11562, #11216, #10380, #10872, #11572, #11574 and
#11576. No textual conflict.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…ng drifts are recorded (objectui#11466)

The docblock said each listed drift "is reported on objectui#11466 for
its own card". Only detail-section, app-schema-renderer and
cloud:plan-status got one (objectui#11515). app, record, home, utility,
page and dashboard have none; the sentence now says they are recorded
for objectui#8347's family, which reads them before the signature
removal. Prose only: no assertion moves.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3311.8 KB 3330.4 KB
Main entry chunk (gzip) 151.3 KB 350 KB
Entry file index-CnYy2FQN.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) 17.22KB 6.37KB
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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 261.62KB 66.20KB
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) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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.60KB 22.89KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 142.83KB 38.55KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.60KB 64.57KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 175.44KB 45.28KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.69KB 64.78KB
plugin-kanban (index.js) 49.59KB 15.58KB
plugin-list (index.js) 116.46KB 28.93KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
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.31KB 2.07KB
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 (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.16KB 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

✅ ACCEPT of the landing head cae2224d5, with the delta reading the contract review PASS 5971070466 (on cb687dfad) requires; ready and auto-merge follow in this act · 2026-10-04T00:03Z

Director seat, summon #32, session_016tKoy8NJa35Yih1FdzrVmn, claim 5970151706. Read against the pushed commits and the check-runs, not the report (5974814539).

The landing conditions, each checked:

  1. objectui#11543 landed (PR docs(skills): type three marked fences' schema as SchemaRendererProps['schema'] (objectui#11543) #11558 → 5cde2c6fe, approved by os-zhuang).
  2. main merged once more: 318447867 merges 0a53c67f9.
  3. CI is green on the head: 43 check-runs, 40 success, 3 expected skips, 0 failure, 0 pending. Skill Example Check reads 18 of 18 judged, 0 failed.
  4. The delta reading:
    • No conflict-resolution edit inside this PR's 88 files. This seat compared each of the 88 files' changed-line body against main at the head with the reviewed head's body against its base. Exactly one file differs, node-recursion-point-8344.test.ts: the R1 docblock correction, comment lines only, with the type assertions byte-unchanged.
    • No new V1 producer from main: Type Check is green, and the dev's census reads 88 of 88 programs at 0 lines.
    • The skills surface: main brought objectui#11543's three fences AND objectui#11550's page-builder.md edit (a JSON fence; the gate's JSON phase reads 70 parsed, 0 failed). The review's "the three fences only" was one file short. That is main's change, not this PR's, and none of this PR's 88 files is under skills/.
  • R3 (the PR body's H3 figures) is fixed in this act: a superseded-reading pointer to 39/40 / f02 and objectui#11564. R2 was wired earlier (5971076887 on objectui#8347).
  • NOT GOVERNED (check-governed-merges --pr), and under the human-merge line threshold.
  • V1's type-instantiation cost (react src about +24%) stays recorded for the maintainer's veto window, as the review states. Landing does not close that window.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 4, 2026 00:05
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 4, 2026 00:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit 83e3f83 Oct 4, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11466-node-slot-union-v1 branch October 4, 2026 00:23
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…ec chart families plugin-charts draws (objectui#11513) (objectstack-ai#11518)

Fixes objectstack-ai#11513

Clause-②: yes (widening)

Why `yes (widening)`: both faces now accept five spec families they
refused. Nothing narrows. The changeset is `minor`.

Dispatched by the `domain:spec @ objectui` seat (objectui#10217) under
the seat's ruling A in the card body, claim comment `5962492298`,
session `https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC`. This
card prepares objectui#11466's V1 (draft PR objectstack-ai#11512). objectui#11514, the
dashboard producers, is blocked on it.

## The accept-set change

| surface | before (base `059bf1b5`) | after (head `ae16cff8`) |
|---|---|---|
| zod `ObjectChartSchema.chartType` (`@object-ui/types/zod`) | a local
`z.enum` of 8: bar, column, horizontal-bar, line, area, pie, donut,
scatter. Zod's default refusal. |
`stripImportedDefaults(SpecChartTypeSchema).extract(OBJECT_CHART_FAMILIES,
{ error })`: the spec's 13 families plugin-charts draws. The refusal
names the set. |
| authored bag `ObjectChartBlockSchema`, `properties.chartType` | the
same member, by reference (8) | the same member, by reference (13) |
| TS `ObjectChartSchema['chartType']` | a literal union of the 8 |
`Extract` of the spec's `ChartType` over the 13 |
| TS `ObjectChartBlockNode` (derived from the zod arm) | 8 | 13. A
throwaway probe: `sankey` compiles and `gauge` is refused. |
| `specType` (the react tier's key) | the spec's `ChartTypeSchema`, by
reference (20) | unchanged |

- **Newly accepted:** `funnel`, `treemap`, `sankey`, `combo`, `radar`.
- **Still refused:** `gauge`, `solid-gauge`, `metric`, `kpi`, `bullet`,
`table`, `pivot`, and every off-spec spelling.
- **`.extract` is the by-reference spelling.** It throws when the module
loads if the spec drops a member. A spec bump that adds a family
declares nothing new here.

The refusal, read at head: code `invalid_value`, at path
`properties.chartType` in the bag and `chartType` on the flat mirror.
Its `values` are the 13. Message:

> `chartType` on an `object-chart` is one of bar, horizontal-bar,
column, line, area, pie, donut, funnel, scatter, treemap, sankey, combo,
radar (objectui#11513): the `@objectstack/spec` chart families this
chart block draws. The spec's single-value and tabular families draw no
chart here: write a single number as an `object-metric`, rows as an
`object-data-table`, a cross-tab as an `object-pivot`.

Render time does not change. plugin-charts is read and probed only, and
no renderer file is edited.

## Measurements (dispatch zone 2)

**H1, the installed spec.** The lockfile pins `@objectstack/spec`
17.5.0, and `packages/types` resolves 17.5.0 (17.6.0 is not installed).
Its `ChartTypeSchema.options`, read at runtime, are 20: bar,
horizontal-bar, column, line, area, pie, donut, funnel, scatter,
treemap, sankey, combo, gauge, solid-gauge, metric, kpi, bullet, radar,
table, pivot.

**H2, what plugin-charts draws.** A throwaway probe (never committed)
rendered an `object-chart` for each of the 20 families plus off-spec
`sunburst`. It went through the real `SchemaRenderer`,
`SchemaRendererProvider` and this package's registrations, with
recharts' container fixed at 480x320. Each family was rendered in five
spellings:

- flat inline rows;
- the authored `properties` bag;
- the dashboard producer's exact node (`objectName` + `aggregate` +
`xAxisKey` + `series[{dataKey}]`, rows served by the data source's
`aggregate`);
- `specType`;
- a dimensionless `dataset` node.

Controls:

- **Stability:** the whole matrix ran twice in one process. 0 of the 105
cells differed, after stripping the React `useId` in the chart's style
tag.
- **Lit:** `bar` draws bar marks.
- **Dark:** `sunburst` draws the unknown-type note.

| family | `chartType` (flat, bag and producer arms) | class |
|---|---|---|
| bar, column | bar marks, categories on the x axis, one value axis |
drawn |
| horizontal-bar | bar marks, categories on the y axis | drawn |
| line / area | line / area marks | drawn |
| pie, donut | pie sectors. The DOM cannot tell a donut's hole apart;
the family value is pinned by objectui#7401's `normalizeChartSchema`
row. | drawn |
| funnel | funnel trapezoids | drawn |
| scatter | scatter symbols | drawn |
| treemap | treemap cells | drawn |
| sankey | sankey nodes and links | drawn |
| combo | bar + line marks on two value axes (two series). One series
draws its first mark on the two axes. | drawn |
| radar | radar polygon | drawn |
| gauge, solid-gauge, bullet, metric, kpi |
`advanced-chart-single-value`: one number from row 0's first series. On
the producer's grouped aggregate it is the first bucket's value (`1` of
buckets 1/2/3). | substituted |
| table, pivot | `advanced-chart-tabular-notice` ("render it with the
data-table / pivot components") | nothing |

The declared set is the 13 drawn families, a strict subset of the spec's
20.

**H3, by reference.** A strict subset, so `.extract` off
`stripImportedDefaults(SpecChartTypeSchema)`. The wrap keeps
objectui#8317's import-boundary census green, and the strip is
reference-equal here because the enum carries no default. The subset is
pinned against the spec, with the complement named, in
`object-chart-families-11513.test.ts`. `specType` stays the spec's enum
by identity, and that is pinned too.

**H4, readers of `ObjectChartSchema['chartType']`.** Each was measured
with a type-check after the change (a type-checker census).

- **`ChartRendererProps.schema.chartType`** (plugin-charts):
`ObjectChart` hands `ChartRenderer` a spread of the node. Its declared
domain is exactly the 13, so it needs nothing.
`@object-ui/plugin-charts` type-check is green. A reverse leg in that
program, with throwaway `@ts-expect-error`s, left the `funnel` directive
unused and the `gauge` one used, which proves it read the rebuilt
`.d.ts`.
- **`chartTypeIgnoresCompareTo`** (`@object-ui/core`): it takes a
`string`, so it needs nothing.
- **`FilterableComponentSchema`** in `DashboardRenderer`: it reads
`filter`, so it needs nothing. `@object-ui/plugin-dashboard` type-check
is green.
- **`ObjectChartBlockNode` / `ObjectChartBlockSchema`:** they widen with
the member by reference, so no edit.
- **Producers that widen with it:**
- `DashboardRenderer` and `DashboardGridLayout` emit
`widgetDispatch.SERIES_CHART_TYPES` (12 families, all inside the 13).
Today they build the node untyped; objectui#11514 types them.
- The list-view relays in `app-shell` `ObjectView`, `plugin-list`
`ListView` and `plugin-view` `ObjectView` write `chartCfg.chartType ||
'bar'` from `any`. The spec's list-view chart config extracts bar, line,
pie, area and scatter, all declared. They need nothing, and plugin-list,
plugin-view and app-shell type-checks are green.
- **Not readers:** `CHART_TYPE_MAP` in `DatasetWidget` feeds a bare
`chart` node (`ChartRenderer`), not `ObjectChartSchema`.
`WidgetConfigPanel`'s `CHART_TYPES` is the dashboard widget picker.

## V1 compile probe (objectui#11466, PR objectstack-ai#11512, never committed from)

I added a throwaway detached worktree at the V1 tip `ff7f748e`, applied
this branch's diff, and added a throwaway types test.

- It assigns the 12 producer families to `NonNullable` of
`ObjectChartSchema['chartType']`.
- It builds an `ObjectChartSchema` node with `chartType: 'sankey'`.
- It assigns that node, and an inline `treemap` node, to V1's
`DeclaredNode` with no cast.
- It holds a `gauge` node behind `@ts-expect-error`.

Results:

- `@object-ui/types` type-check is green.
- **Control leg:** the same tree with V1's own `objectql.ts` and
`objectql.zod.ts` (the 8-family face) is red on exactly the producer
list, the `sankey` node and the `treemap` node (TS2322). It is also red
on this PR's two `Equal` assertions.
- **Deviation:** V1's base predates objectui#11366's jsdom pin, so `pnpm
install` in that probe tree ran with `--config.engine-strict=false`.
Only in the probe; the probe tree is removed.

## Pins

- **`packages/types/src/__tests__/object-chart-families-11513.test.ts`**
(new):
- Type level, read by `tsc -p tsconfig.test.json`: TS = zod = the 13.
Every declared family is a spec `ChartType`, and the declared and
undeclared lists cover the spec. Two non-vacuity rows check that `Equal`
can fail.
- Runtime: the set read off the mirror is the 13. It is a strict subset
of the installed spec, with the complement named. `specType` keeps its
identity.
- Each of the 13 parses in the bag on `safeValidateSchema` and on
`StrictAnyComponentSchema`, and on the flat mirror.
- Each undeclared spec family, plus `sunburst`, `Bar` and `bar-chart`,
is refused at `properties.chartType` with `values` equal to the 13 and a
message naming each of them. The flat mirror refuses at `chartType`,
with a lit control.
-
**`packages/plugin-charts/src/__tests__/object-chart-declared-families-11513.test.tsx`**
(new):
- Every family of the installed spec is authored as a bag document on
the inline object path, parsed on both faces, and rendered through the
real `SchemaRenderer`.
- The pin is two-way: a family parses exactly when it draws a chart. A
declared family must draw its own marks, so a family silently drawn as a
bar fails. An undeclared one must render the number card or the tabular
notice.
  - Lit control `bar`, dark control `sunburst`.
- **Re-judged fixtures:**
- `object-chart-properties-bag-11276.test.ts`: the "outside the
vocabulary" row and the flat-mirror refusal move from `radar` (now
declared) to `gauge`. The TS twin equality pin now reads TS = zod; the
members are held by the new file.
- plugin-charts `ObjectChart.schemaAnchor-7946.test.ts`: its
`@ts-expect-error` family moves from `radar` to `gauge`, with the reason
in the directive.

## Reverse verification and ablation

- **Runtime ablation**, at committed `0703053b` (the mirror's blob is
the same at head `ae16cff8`), through objectstack's
`scripts/ablation-replace.mjs` (wrap mode, anchor hit 1 to 0, blob
`04c4bbc2` to `9c207bd2`):
- The mutation replaced the mirror's `.extract(OBJECT_CHART_FAMILIES, {
error: OBJECT_CHART_FAMILY_REFUSAL })` with `.extract([... the old 8
...])`.
- Both new pins ran: 24 failed and 25 passed. The red rows are the 5 new
families (parse and render, twice each), the set and subset rows, and
every refusal row (its `values` became the 8). The 8 old families stayed
green.
- Restore was proven: blob equals `HEAD` `04c4bbc2`, and `git diff HEAD`
is empty.
  - Direction: turned red, as expected.
- **Type-level:** the V1 control leg above is red on both `Equal` rows
when the face reverts.
- **Docs:** `content/docs/plugins/plugin-charts.mdx`'s new typed snippet
with `chartType: 'gauge'` fails `pnpm check:doc-snippets` (TS2322 at
that block, 777 judged and 1 failed). Restore was proven the same way.
So the block is on the gate's surface.

## Corpus re-judged

- **objectui:** no authored `object-chart` document names a family
outside the old 8. `git grep` over `examples/`, `apps/`, `content/`,
`skills/` and `packages/*/examples/` found one hit,
`chart-examples.ts`'s `radar`, and that is a bare `chart` node.
- **objectstack showcase** (`examples/**`): its `object-chart` nodes use
`bar`, `donut` and `area`. The react-tier ObjectChart block with
`type="bar"` goes through `specType`, which is unchanged.
- **Runs:** `examples/schema-catalog` is green (40 files, 2259 tests).
So are the 19 downstream test files that name `object-chart` (597
tests), across app-shell, core, plugin-dashboard, plugin-list,
plugin-view, react and sdui-parser.

## Pending changesets read

I read every `.changeset/*.md` that names `chartType`, `object-chart`,
`ObjectChartSchema` or a chart family (58 entries besides this PR's),
and none is made false. The ones that state something about this key,
left alone:

- `5927-zod-mirror-group-a-widenings.md` lists what that change added
(`column`, `horizontal-bar`, `donut`). It is still the reading of that
change.
- `objectchart-schema-anchor-7946.md`: "`chartType` must be the declared
union" still holds.
- `10770-object-chart-react-tier-node.md`: `specType` is unchanged, and
so is `chartType`'s optionality.
- `11276-object-chart-properties-bag.md`: "unchanged in shape" is about
the key set, which does not move.
- `11468-bag-carrier-node-types.md`: `ObjectChartBlockNode` is "derived
by reference", and it widened by that derivation.
- `10584-object-chart-series-describe.md`: it is about the per-series
`chartType` override (bar / line / area), not this member.

`pnpm check:changeset-claims` named 30 entries by file name
(`objectql.ts`, `objectql.zod.ts`). Each is about another key (forms,
kanban, grid, drill-down, tree, gallery, the bare `chart` node's data
model), so all were left alone.

## Gates

The source gates ran at `0703053b` and were re-run at head `ae16cff8`,
which adds only `.md` / `.mdx`. The doc gates and suites ran at head
`ae16cff8`.

- `pnpm --filter @object-ui/types run type-check`: exit 0.
`@object-ui/plugin-charts` and `@object-ui/plugin-dashboard`: exit 0 at
head. `plugin-list`, `plugin-view` and `app-shell`: exit 0. The
dependency closures were built first.
- `pnpm exec vitest run packages/types/`: 343 files, 9158 passed.
- `pnpm exec vitest run packages/plugin-charts/`: 87 files, 1043 passed
and 9 skipped. The skips are the existing `describe.runIf(DRIVEN)` in
`AdvancedChartImpl.dateOnlyZone-10866.test.tsx`.
- `pnpm exec vitest run examples/schema-catalog/`: 40 files, 2259
passed.
- These gates exited 0 with their own verdict lines:
- `pnpm check:doc-snippets` (777 of 777 judged, 0 failed) and `pnpm
check:doc-examples` (every covered example compiles or fails as its
ledger row says);
- `pnpm check:doc-types`, `node scripts/check-changeset-no-major.mjs`
and `node scripts/check-changeset-presence.mjs`;
- `pnpm check:new-line-citations` (0 new) and `pnpm
check:control-bytes`;
- `pnpm check:spec-symbols`, `check:vi-mock-specifiers`,
`check:vi-mock-inherit` and `check:vi-mock-override-shape`;
- `check:test-path-roots`, `check:self-import`, `check:doc-fences` and
`check:pending-changeset-literals`;
- `check:phantom-deps`, `check:installed-pin-claims`,
`check:doc-example-ids` and `check:unreferenced-sources`.
- **Not reached:** `check:component-surface-parity` and
`check:sdui-registration-pins`. No registration's inputs move, because
`object-chart`'s registration declares no `chartType` input.
- **Lint, a declared narrowing:** `eslint --no-inline-config --format
json` ran over the 6 changed TS files, read as 6 entries in the JSON. It
found 0 errors and 39 warnings, all `no-explicit-any`. Two are in the
new plugin-charts pin's `vi.mock('recharts')` block, the sibling
pattern. `eslint.config.js` sets no `parserOptions.project` /
`projectService`, so linting is not type-aware and this diff cannot move
a verdict on an untouched file. The repo-wide `pnpm lint` belongs to CI.

## Acceptance notes

1. **Found, not fixed here (plugin-charts is read-only for this card).**
A spec single-value or tabular family named through `specType` silently
draws a BAR chart. Through `chartType`, the same family draws a number
or a notice.
- Probe `specType` arm: `gauge`, `solid-gauge`, `metric`, `kpi`,
`bullet`, `table` and `pivot` each drew bar marks on one value axis,
with no note.
- Both faces accept `{ "type": "object-chart", "properties": {
"specType": "gauge", … } }` (measured on the built `dist`).
- Mechanism: `normalizeChartSchema` reads `specType` but gates it to
unset when it is not `RENDERABLE`. `ChartRenderer`'s `schema.chartType
?? spec.chartType` is then unset, and `AdvancedChartImpl` defaults to
`bar`.
- Seam: `spec:ChartConfigSchema.type (ChartTypeSchema) →
runtime:normalizeChartSchema RENDERABLE gate, ChartRenderer chartType
fallback | renderer:AdvancedChartImpl default bar`.
   - The report carries it for the seat to file.
2. **Found, not fixed here (the `chart` node, another schema).**
`content/docs/api/schema-reference.md`'s ChartSchema table lists
`chartType` values ending in `"heatmap"`.
- `safeValidateSchema({ type: 'chart', chartType: 'heatmap', … })`
refuses it at `chartType` (`invalid_value`, measured).
- The table also omits families the node accepts and draws (`funnel`,
`treemap`, `sankey`, `combo`, `column`, `horizontal-bar`).
   - The report carries it for the seat.
3. **The substituted and nothing families on `chartType`** (H2) have no
reach after this PR:
   - the face refuses them at `chartType`;
- the dashboard routes the single-value families to the metric card and
the tabular ones to the table and pivot (`widgetDispatch`);
- the spec's list-view chart config extracts only bar, line, pie, area
and scatter.
   Noted, not filed.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…node arms, the README's Activity tab on its declared properties, and Export to React's typed schema constant (objectui#11515) (objectstack-ai#11519)

Fixes objectstack-ai#11515
Clause-②: yes (widening)

Part of the preparation for objectui#11466 (V1, draft PR objectstack-ai#11512). On PR
objectstack-ai#11512's head, `check:doc-snippets` judges 776 blocks and refuses 2: the
plugin-detail README's `detail-view` "With Tabs" example and the Export
to React "Output Example" in
`content/docs/utilities/vscode-extension.mdx`. This PR settles both.
Measured under a throwaway probe (this branch plus PR objectstack-ai#11512's commits,
merged without a commit, never pushed), the same gate judges 776 and
refuses 0. With V1's own copies of the two documents in that probe, it
refuses exactly those 2.

## Block 1 — TS twins of the zod-only node arms (ruling A)

**Before.** `@object-ui/types` declared no TypeScript type for three
arms its zod face already validates: `detail-section`
(`views.zod.ts#DetailSectionNodeSchema`), `app-schema-renderer`
(`app.zod.ts#AppSchemaRendererNodeSchema`) and `cloud:plan-status`
(`cloud.zod.ts#CloudPlanStatusSchema`). `SchemaByType` of each literal
was `never`, and the parity census carried all three as `EXCLUSIONS`
("no TS declaration in this package restates the node").

**After.** Each gains a twin beside its arm's module, member for member:

| zod arm | TS twin | members |
| --- | --- | --- |
| `views.zod.ts#DetailSectionNodeSchema` | `DetailSectionNodeSchema`
(`views.ts`, in `ViewComponentSchema`) | the ten members the arm
`.pick`s from `DetailViewSectionSchema` (`title`, `description`, `icon`,
`fields` required, `collapsible`, `defaultCollapsed`, `columns`,
`showBorder`, `headerColor`, `hideEmpty`), each `DetailViewSection`'s
own member by indexed access; `body` / `children` `?: never` |
| `app.zod.ts#AppSchemaRendererNodeSchema` |
`AppSchemaRendererNodeSchema` (`app.ts`, in `AnySchema`) | `schema?:
AppComponentSchema` (the arm's member IS `AppComponentSchema`),
`basePath?: string`, `mobileNavMode?: 'drawer' or 'bottom_nav'`; `body`
/ `children` `?: never` |
| `cloud.zod.ts#CloudPlanStatusSchema` | `CloudPlanStatusSchema` (new
`cloud.ts`, in `AnySchema`) | `properties: { plan: string }`, required
and closed; `body` / `children` `?: never` |

No zod schema, validator verdict or runtime behaviour changes. The
widening is the TS face's declared surface only, matching what the zod
face already publishes (the `Clause-②` line above).

**Pins.**

- `zod-mirror-parity.test.ts`: the three pairs move from `EXCLUSIONS`
into `MIRRORS` / `Declared`; `EXPECTED_MIRROR_PAIRS` 191 to 194.
`cloud:plan-status` measures clean in all four directions. Two pairs are
born ledgered, each with the reading its by-reference member already
carries one level up (same cause, not a new finding):
- `KnownDrift` gains `views.zod.ts#DetailSectionNodeSchema: 'fields'`,
the `DetailViewField` `options` EXPECTED DIVERGENCE, as on
`views.zod.ts#DetailViewSectionSchema`. Header figures 49 / 87 to 50 /
88, with the history sentence.
- `WiderThanDeclared` gains `app.zod.ts#AppSchemaRendererNodeSchema:
'schema'`, one SCHEMA-NODE arm: the app document's own wider reading
through `NavigationItemSchema`'s `z.ZodType` of `any` annotation, the
`app.zod.ts#AppComponentSchema` `areas` row's cause. Header figures 3 /
3 / 3 (2 / 1 / 0 / 0) to 4 / 4 / 4 (3 / 1 / 0 / 0).
- `anyschema-declared-node-types-11478.test.ts` stays green: the three
types are `AnySchema` members.
- **`node-recursion-point-8344`'s measured set exists only on PR
objectstack-ai#11512's head.** On `main` that pin reads `never`, because
`SchemaNode`'s object arm is still `BaseSchema`. Measured in the probe:
with these twins, V1's `MeasuredArmDrift11466` pin goes red (TS2344)
until exactly `detail-section`, `app-schema-renderer` and
`cloud:plan-status` are deleted from it, and is green after (types
`type-check` exit 0). That deletion lands on PR objectstack-ai#11512 when it merges
`main`.
- Merging this branch into PR objectstack-ai#11512 conflicts in
`zod-mirror-parity.test.ts` (three hunks, both sides' rows kept). The
union measures 6 / 6 / 8 (3 / 3 / 0 / 2) for `WiderThanDeclared`,
confirmed by the derived-figure pin in the probe.

**The README's Activity tab.** It authored `record:activity` with
`items: activityData`, the host feed slot the node refuses by name
(objectui#11321). It now authors the declared input, `properties: {
limit: 20, showCompleted: false }`, as the plugin-detail guide does; the
`FeedItem` import and `activityData` go. The note under the example says
where the feed comes from. Rendered through `DetailTabs` and the real
registry (a one-off probe, not kept), the tab draws `Activity (0)`, the
`All Activity` filter and `No activity recorded`, with no unknown-type
panel and no error banner. The zod docblock in `public-blocks.zod.ts`
that named this README as a host composition passing `items` gets a
dated parenthesis.

## Block 2 — the Export to React generator (ruling A, route changed by
measurement)

**Before.** The emitted constant was unannotated, so every `type`
widened to `string`; under V1 the one JSX line is refused for every
schema (TS2322 against the declared-node union).

**The card's annotation, measured.** `const schema: SchemaNode` does NOT
compile, today or under V1: `SchemaNode` also admits `number` and
`boolean`, which `SchemaRendererProps['schema']` leaves out, and the
narrowing at the initializer does not reach the body of
`GeneratedComponent`. With it, the JSX line was refused for every schema
on both trees, including the documented sample. That is the ruling's own
premise ("it exists today and compiles both now and after V1") measured
false, so the route follows the ruling's intent instead: a type that
exists today, is not `DeclaredNode`, and compiles against the prop on
both trees.

**After.** The constant is typed `SchemaRendererProps['schema']`,
imported type-only beside `SchemaRenderer` from the package the file
already imports (no new dependency). Emitted output for the documented
sample, before and after:

```text
-import { SchemaRenderer } from '@object-ui/react';
+import { SchemaRenderer, type SchemaRendererProps } from '@object-ui/react';
 ...
-const schema = {
+// Typed as the schema SchemaRenderer takes, so each "type" below stays a literal
+// and the compiler checks the schema against the node types it accepts.
+const schema: SchemaRendererProps['schema'] = {
```

`tsc` (strict, `react-jsx`, `noUnusedLocals`) on the emitted file,
against the BUILT packages of each tree:

| fixture | main, before | main, after | V1 probe, before | V1 probe,
after |
| --- | --- | --- | --- | --- |
| documented sample (`div` with an `h1` child) | 0 errors | 0 errors | 1
(TS2322) | 0 errors |
| an undeclared child type | 0 | 0 | 1 | 1 (TS2322 at the child) |
| an object with no `type` | 1 | 1 | 1 | 1 |

The `SchemaNode` annotation and a `satisfies SchemaNode` variant were
measured on the same grid; the table is in the report on the card.

**The documented sample keeps `"type": "h1"`.** The card reads `h1` as
undeclared; it is declared, one of the HTML passthrough tags
`HtmlElementSchema` declares (objectui#8499), and
`scripts/__tests__/check-doc-component-types.test.ts` pins `"type":
"h1"` in this very sample as the registered replacement for `heading`.
Annotated, the `h1` sample compiles on both trees (table above, and
`check:doc-snippets` on both). A rewrite to `text` with `variant: 'h1'`
was tried and reverted when that pin went red; the PM may still order it
(one sample edit plus that pin).

**Pins.** The emitter has tests: `export-to-react-compiles.test.ts` and
`export-to-react-preamble.test.ts`.
- The compile pin's stub typed the prop `unknown`, so it could not see
an annotation. A new leg compiles the emitted file against the REAL prop
type with no build: `@object-ui/types` resolves to its source entry, and
the `@object-ui/react` declaration carries the `SchemaRendererProps`
body and its `@object-ui/types` import read out of
`packages/react/src/SchemaRenderer.tsx` at test time, so it follows the
prop when V1 retypes it. Its positive control is a schema with no
`type`, refused on the constant's own line.
- Ablation, run once and not kept (generator emitting the BASE
unannotated constant, restored by blob equality to HEAD): on `main` the
control turns red (the refusal moves from the constant's line to the JSX
line); in the V1 probe both legs turn red (the sample is refused with
TS2322, widened `type: string`).
- The preamble pin names the new import line and the annotated constant;
the two documented mirrors (`DESIGN.md` section 4 and the docs page)
follow the new preamble, held by the existing objectui#7976 pin.

## Corpus documents re-judged

- `packages/plugin-detail/README.md`, "With Tabs": re-authored (above).
Its `detail-section` tab was refused under V1 for want of a TS type;
with the twins it compiles under V1 unchanged.
- `content/docs/utilities/vscode-extension.mdx`, "Output Example": new
preamble and annotated constant; sample unchanged.
- `packages/vscode-extension/DESIGN.md`, section 4: new preamble.
- `packages/cli/src/__tests__/validate-passing-keys-11440.test.ts`
described the README's second tab as the host feed. It now validates the
full example document as the README writes it (green on `objectui
validate`), and keeps the host-feed row as the refusal's control.

## Pending changesets

- Dated notes added: `8114-detail-tab-activity-timeline.md` (its `items,
not data` bullet and the `activityData` sentence),
`11321-record-feed-host-slots.md` (the README as the `items`
composition), `11478-anyschema-declared-node-types.md`
(`ViewComponentSchema`'s four members; `AnySchema` gains three).
- Read and left alone, each still true as written:
`10919-cloud-plan-status`, `11440-arm-passing-types`,
`11494-types-app-schema-renderer-schema`,
`11494-layout-app-schema-renderer-adapter` (all state the zod face and
the registration; none says the TS face lacks a type), `7837`, `7862`
and `7976` (Export to React preamble history; the side-effect import,
the absent React import and the mirror binding all still hold), and the
25 entries `check:changeset-claims` lists as naming a touched file (each
describes another pair, key or README section).
- New: `11515-types-ts-twins-zod-only-arms.md` (`@object-ui/types`
minor), `11515-vscode-export-react-annotation.md` (`object-ui` patch),
`11515-plugin-detail-readme-activity-properties.md`
(`@object-ui/plugin-detail` patch).

## Verification (head `afb660b7`)

- `pnpm --filter @object-ui/types run type-check` (all three `tsc`
legs), `pnpm --filter @object-ui/cli run type-check`, `pnpm --filter
object-ui run type-check`: exit 0.
- `pnpm exec vitest run packages/types/ packages/vscode-extension/` plus
the cli validate test, the plugin-detail README reader and
`scripts/__tests__/check-doc-component-types.test.ts`: `Test Files 349
passed (349)`, `Tests 9299 passed (9299)`.
- `pnpm check:doc-snippets`: `776 of 776 block(s) judged, 0 failed`.
`check:doc-types`, `check:doc-examples`, `check-changeset-no-major`,
`check-changeset-presence`, `check:new-line-citations`,
`check:control-bytes`: green. `check:changeset-claims` is report-only
(read above).
- V1 probe: types `type-check` exit 0 with the three names out of the
8344 set; the parity, 8344, node-slot-union and AnySchema suites pass;
`check:doc-snippets` judges 776 and refuses 0. The probe's
plugin-dashboard build needed four throwaway `as never` casts at the
producer sites PR objectstack-ai#11512 already reports red; they never left the probe.
- NOT MEASURED: `check:sdui-registration-pins` (no registration input
moved; it needs a console build), repo-wide lint (CI's).

## Acceptance notes

- `export-to-react-compiles.test.ts`'s `SAMPLE_SCHEMA` still writes the
retired `body` while its comment calls it the `empty` template's shape
(the template writes `children`). Inert under the hermetic stub; not
touched here.

---

_Generated by [Claude
Code](https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…ries read by the slot's element type, a typeless widget draws as `metric` and an unknown type the placeholder, the object-chart producers name ObjectChartSchema (objectui#11514) (objectstack-ai#11524)

Fixes objectstack-ai#11514

Clause-②: no (narrowing)

objectui#11514 is rescoped by the seat's answers (comment 5964120765) to
three things, all in this PR: objectui#11483's closure
(`DashboardWidgetSchema['type']` drops the component type, and every
reader of a `widgets[]` entry reads it by the slot's element type), both
`object-chart` producers named `ObjectChartSchema` with no cast, and Q2
A (what a widget with no `type`, or with a `type` that names nothing,
draws). The `object-metric` producer moved to objectui#11525 and is
untouched here. The class-a finding this PR's first report raised (a
rule-list `filter` with a structured `groupBy` draws an error) is filed
as objectui#11526.

## What changed — before and after

| | before (`fc7db059f`) | after |
|---|---|---|
| `DashboardWidgetSchema['type']` (TS face) | `DashboardWidgetTypeName
\| DashboardComponentWidgetType` | `DashboardWidgetTypeName`, the zod
twin's set |
| `{ type: 'metric-card', title }` directly in `widgets[]` | compiles
(the MEASURED LIMIT pin); both zod faces refuse | `tsc` refuses it too;
zod faces unchanged |
| read type of a `widgets[]` entry in `DashboardRenderer`,
`DashboardGridLayout`, `DashboardWithConfig`, `isLegacyRetiredWidget`
and the designer's `DashboardEditor` | `DashboardWidgetSchema` |
`DashboardComponentSchema['widgets'][number]` |
| `DashboardRendererProps['onWidgetsReorder']` parameter, and app-shell
`DashboardPreview`'s handler | `DashboardWidgetSchema[]` |
`DashboardComponentSchema['widgets']` |
| `WidgetDispatch['chartType']` | `string` | `SeriesChartFamily`: the 12
series families, each a member of `ObjectChartSchema['chartType']` |
| both `object-chart` producers | untyped literal, `chartType` typed
`string` | `satisfies ObjectChartSchema`, no cast |
| a widget with no `type`, both surfaces | the slot-component
passthrough: the red "Unknown component type" (OBJUI-001) panel;
dataset-bound with a dimension: a bar chart | draws exactly what the
same widget with `type: 'metric'` draws: inline, dataset-bound, and in
`DashboardRenderer`'s mobile metric row |
| a `type` that names no family and no component type, both surfaces |
the passthrough: the OBJUI-001 panel | the labelled placeholder
"「type」chart type is not supported yet", as `heatmap` draws |
| the slot-component passthrough | every entry no family matched | the
component arm (`metric-card`) alone, drawn as before |
| the legacy `component` envelope | its node, handed to `SchemaRenderer`
bare | the same node; a typeless envelope keeps its card heading (the
spec default is not applied to it); a number or `true` in it goes
through `toRenderableSchema`, which draws the same text |

What renders differently is exactly the two cases Q2 A lifts: a typeless
widget and an unknown-type widget. Every widget that names a known
family draws as before, and so does every legacy envelope (control
below).

## Q2 A: the ruling and how it lands

「协议为基准」: `@objectstack/spec` 17.5.0's `DashboardWidgetSchema.type`
defaults to `metric`. objectui's validator accepts a typeless widget and
does not write the default in, so the surfaces are where it resolves.

- `specDefaultWidgetType()` reads the default from the spec
(`DashboardWidgetSchema.shape.type.parse(undefined)`), not a literal; a
row pins it to `metric` at the installed spec.
- `resolveWidgetType(entry)` is the authored `type`, or that default
when the entry names none. Every read of an entry's `type` on both
surfaces goes through it: the dispatch, the metric span and
self-contained chrome, the mobile metric row. `DatasetWidget` takes the
same default.
- The legacy `component` envelope (`{ id, component, layout }`,
objectui's own format, no member on the spec's widget) is not given the
default: `resolveWidgetType` keeps `undefined` for an entry whose
`component` is truthy, the same test both surfaces use to take the
envelope branch.
- `isSlotComponentEntry(entry)` gates the passthrough to the component
arm; anything else that reaches it draws `unsupportedWidgetSchema`, the
placeholder an unsupported family already drew, now declared once for
both surfaces.
- `entryComponent(entry)` reads the envelope's `component` typed as the
widget arm declares it. A bare `entry.component` read through
`BaseSchema`'s index signature and made both `getComponentSchema`s
return `any`, which is what had let an earlier cast removal look clean.

## The `object-metric` producer

Not touched, and no type added for it: it moved to objectui#11525. The
premise measurement (branch B: the broadcast's `$and` carries the
widget's MongoDB-style `filter`, and the bar's rule list makes the
spec-shape aggregate branch draw an error) is in this card's first
`os-dev-report` (comment 5964044891). It stays red under V1, as
expected.

## V1 census

Probe: PR objectstack-ai#11512's branch (`ff7f748e`) merged with this branch at
`2fb1b92b` through `git merge-tree` in a throwaway worktree (the one
known conflict, `zod-mirror-parity.test.ts`'s comment opener, restored
by hand). Nothing was committed or pushed from it; the worktree and its
ref are removed.

- **Cast sites:** 8 lines, the card's 4 sites in plugin-dashboard's src
and test programs: `DashboardGridLayout.tsx` 648 and 701,
`DashboardRenderer.tsx` 1077 and 1118, each in both programs. 0 new
lines. The types test program: 1 line, `node-recursion-point-8344`'s
drift ratchet, objectui#11515's (unchanged from the first report).
- **What holds them, per branch** (a probe-only annotation typing both
`getComponentSchema`s as the renderer's prop, `DeclaredNode | string |
null | undefined`): the `object-metric` branch (`DashboardGridLayout`
381, 383; `DashboardRenderer` 876, 878, 879), objectui#11525;
`plugin-dashboard:metric` (`DASHBOARD_NODE_TYPES.metric` typed `string`;
396 and 894), objectui#11466's registry entry; `DashboardGridLayout`'s
static `pivot` (472), objectui#11466. The passthrough and the
unknown-type branch: 0. The envelope branch: 0. Before the
`toRenderableSchema` bridge it was 2 (`string | number | true |
DeclaredNode`).
- The two `SchemaRenderer` casts stay: the branches they cover are
objectui#11525's and objectui#11466's.
- **Not re-probed:** the merge of `main` `9ed8d0f1` (objectui#11517)
that followed. NOT MEASURED, because rebuilding the probe means a fresh
install and closure build on a shared box. What was checked instead:
none of objectui#11517's 15 files is among V1's own 74 changed files;
`git merge-tree` of the new head with `ff7f748e` shows only the same one
conflict; and `main`'s type-check at the new head is green.

## Pins, controls, reverse verification, ablation

- `widget-type-default-and-unknown-11514.test.tsx` (12 tests). On both
surfaces: a typeless widget draws byte-for-byte what `type: 'metric'`
draws, inline and dataset-bound (the control is the explicit `metric`),
and in the mobile layout (non-vacuity: its 2-column metric row is
drawn). An unknown `type` (`gauge2`) draws the placeholder and no
OBJUI-001 panel. Controls: `heatmap` draws the same placeholder; a
`metric-card` still draws through the component arm. Plus one row: the
spec default is `metric`.
- **Reverse verification:** the three surface files at `b92e80dc`
(before Q2 A): 7 failed, 5 passed (the 5 are the controls and the
spec-default row). Restored from `HEAD`, blob-matched.
- `dashboard-producer-node-types-11514.test.tsx` (32 tests): round one's
28, plus a number and `true` in a legacy `component` envelope, on both
surfaces, drawn under the widget's heading.
- **Envelope control:** those 4 rows against the surfaces at `b92e80dc`
pass (4 passed, 28 skipped), so the envelope draws as it did.
**Ablation:** the envelope exemption removed from `resolveWidgetType`
(`ablation-replace.mjs`, anchor 1 to 0, blob `7f60e1c6` to `da48a7f9`):
all 4 rows fail. Restored: blob equal to `HEAD`, `git diff HEAD` empty.
- Round one's: the `SERIES_CHART_FAMILIES` ablation (`'gauge'` planted:
2 `tsc` errors at both `satisfies ObjectChartSchema` sites, 2 vitest
rows red), the `DashboardPreview` reverse verification (TS2322 at its
`onWidgetsReorder` prop), and the objectui#11483 `@ts-expect-error` row.

## Readers of the narrowed type

Needed an edit: plugin-dashboard's `DashboardRenderer`,
`DashboardGridLayout`, `DashboardWithConfig`, `isLegacyRetiredWidget`,
`DatasetWidget` (the default `type`), and two test readers; app-shell's
`DashboardPreview` (type-only); plugin-designer's `DashboardEditor` and
three test readers (its build went red on 3 lines once the component
type left the widget arm); the types pins for objectui#11483 and
objectui#7952 and `zod-mirror-parity`'s `KnownDrift` row; the plugin
page and the plugin-dashboard README, which taught `const widgets:
DashboardWidgetSchema[] = dashboard.widgets` and now narrow on `type`
and say what a typeless or unknown-type widget draws.

Needed nothing: core's `buildWidgetScopedFilter`, `useWidgetSubCaption`,
app-shell's `DashboardWidgetInspector`, and `apps/*`. Render suites
outside the package that mount a dashboard (app-shell's `DashboardView`
and previews, `StudioDesignSurface`, the widget DOM-leak sweep, the
schema-catalog galleries, the console's drill-down and chart-stub
suites) pass unchanged.

## Changeset sweep

- **This PR's entries:** `11514-types-widget-arm-type`
(`@object-ui/types` minor), `11514-dashboard-slot-entry-types`
(`@object-ui/plugin-dashboard` minor; now also states the typeless and
unknown-type renders, the passthrough serving the component arm, and the
envelope exemption), `11514-designer-dashboard-entry-type` and
`11514-app-shell-dashboard-preview-reorder` (patch, type-only).
- **Dated notes (2026-10-03), from round one:**
`11483-metric-card-needs-value`, `7952-dashboard-widgets-component-arm`,
`11348-dashboard-widget-reads`, `dashboard-widget-type-closed-enum`.
- **Swept again for Q2 A, no new note needed:**
`dashboard-widget-type-closed-enum` (its OBJUI-001 sentence describes
the hatch before that change, still true of it),
`8760-unfulfilled-chart-stubs` (its OBJUI-001 sentence is about a
document node, not a `widgets[]` entry),
`7952-dashboard-widgets-component-arm` ("What still refuses" is about
validation), `10859-dashboard-node-keys` and
`11483-metric-card-needs-value` (unchanged), and objectui#11517's three
entries (drill-down only).

## Acceptance notes

- **Filed by the seat:** the class-a finding (an authored
`object-metric` with a rule-list `properties.filter` and a structured
`aggregate.groupBy` draws `UnloweredAggregateWhereError` instead of its
number), as objectui#11526.
- **File surface beyond the claim's list:** plugin-designer's
`DashboardEditor` and its three tests, `DatasetWidget`, the two docs
pages, the objectui#7952 pin, the two plugin-dashboard test readers,
`isLegacyRetiredWidget`, and the static `chart` producer's `chartType`.
Each is the same retype or the Q2 A ruling, and is named above.
- **Merges of `main`:** `b92e80dc` (`8366accd1`), `9b0ec494`
(`6903eafbc`, objectui#11484's docs) and `7939c9a0` (`9ed8d0f1`,
objectui#11517). All three merged cleanly.
- The title drops "part 1": the card is rescoped to what this PR holds.

## Tests and gates, at `7939c9a0`

- `type-check` for `@object-ui/types`, `@object-ui/plugin-dashboard`,
`@object-ui/plugin-designer` and `@object-ui/app-shell` (each script
echoed, after rebuilding the designer and app-shell closures): exit 0.
- vitest, `pnpm exec vitest run packages/plugin-dashboard/`: 164 files
passed; 1591 tests passed, 6 skipped. Readers: the types pins and
objectui#11517's drill-down suites, the schema-catalog, console and
script suites (24 files, 1136 tests), and the designer and app-shell
dashboard suites (212 files, 2140 tests): all passed.
- `check:doc-snippets`: 777 of 777 blocks judged, 0 failed.
`check:doc-examples`, `check:doc-types`, `check:doc-fences`,
`check:doc-example-ids`, `check:doc-example-readers`,
`check:readme-exports`, `check:changeset-claims`,
`check:pending-changeset-literals`, `check:new-line-citations` (0 new),
`check:control-bytes`, `check:handler-key-reads`, `check:spec-symbols`,
`check:test-path-roots`, `check:component-surface-parity`,
`check:phantom-deps`, `check:esm-specifiers`,
`check:installed-pin-claims`, `check-changeset-no-major`,
`check-changeset-presence`: exit 0. Governed-surface guard over the 29
paths: NOT GOVERNED.
- `check:eager-closure` and `check:sdui-registration-pins`: NOT
MEASURED, reason: both read a built console bundle (exit 2,
precondition). The one new runtime import, `@objectstack/spec/ui` in
`widgetDispatch.ts`, is a module app-shell already imports eagerly
(`UnifiedSidebar`, `MetadataProvider`), and no registration input moved.
- eslint, narrowed to the 19 changed TS files (`eslint.config.js` lints
`**/*.{ts,tsx}` with no parser project, so it is not type-aware and this
diff cannot move an untouched file's verdict): 0 errors, 66 warnings.
The 17 files that exist at the merge base: 58 warnings there, 58 now,
file for file; the new producer pin carries 8 `no-explicit-any`, the
recorder and fixture casts. Repo-wide lint is CI's.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…get draws the retired-format placeholder on both surfaces (objectui#11525) (objectstack-ai#11538)

Fixes objectstack-ai#11525

Clause-②: no (narrowing)

The maintainer's ruling C on objectui#11525 (record `5964914121`)
retires one stored widget form. The form is a dataset-less single-value
widget whose `options.data` (or widget-level `data`) is `{ provider:
'object', … }`. The single-value family is `metric`, `gauge`,
`solid-gauge`, `kpi` and `bullet`, plus a typeless widget, which
resolves to `metric` since objectui#11514.

- **Both surfaces.** The metric arms of `DashboardRenderer` and
`DashboardGridLayout` now return the shared
`LEGACY_RETIRED_WIDGET_SCHEMA`. It is the same imported object the pivot
arms already return for that input (objectui#10528).
- **The producer is gone.** The flat `object-metric` node both arms used
to build is removed. That node carried an ObjectQL-dialect `filter` that
no node type declares.
- **What does not move.** The inline chart and table arms are untouched.
No zod or TS authoring face moves.

Implemented by the `domain:spec @ objectui` seat's dev, session
`session_01XvhGmGAP79ZB8swnkapxPC`, under the PM claim `5965174453`.

## Before and after, per surface

The before/after run went through the real `SchemaRenderer` and real
registrations. The adapter's `aggregate` answers 4242 and `queryDataset`
answers 510000. "Before" is base `9d7419b9`; "after" is this branch.

| surface | widget | before | after |
| --- | --- | --- | --- |
| `DashboardRenderer` | `metric`, provider `options.data` | "Pipeline
4,242", 1 `aggregate` call | the placeholder, 0 calls |
| `DashboardRenderer` | `kpi`, same data | heading "Pipeline", then
"Pipeline 4,242" | heading "Pipeline", then the placeholder, 0 calls |
| `DashboardRenderer` | no `type`, same data | "Pipeline 4,242", 1 call
| the placeholder, 0 calls |
| `DashboardRenderer` | `pivot`, same data (control) | heading "Deals",
then the placeholder | unchanged |
| `DashboardRenderer` | `metric` bound to a dataset (control) | "Revenue
510000", 1 `queryDataset` call | unchanged |
| `DashboardGridLayout` | the same five rows | the same readings as
above | the same readings as above |

The placeholder text is "This widget uses a retired data format. Edit it
to bind a dataset."

**Chrome, measured (the dispatch's H2).** The tile's chrome follows the
widget's family, not the placeholder.

- A dataset-less `metric` (and a typeless widget) is self-contained on
both surfaces, so its tile draws the placeholder without the widget's
heading. That is the same tile the retired top-level `object` metric
shape already drew, and a pin holds the two DOMs equal.
- `gauge`, `solid-gauge`, `kpi` and `bullet` take the Card chrome, so
their heading is drawn above the placeholder, as the pivot's is.
- I did not change the chrome. Doing so would also move the legacy-shape
metric's tile, which no ruling covers. This is listed under Acceptance
notes and in the report's open question.

## What stays, and why: the filter broadcast's `object-metric` member

`DashboardRenderer`'s `FilterableComponentType` /
`FILTERABLE_COMPONENT_TYPES` keeps `object-metric`. The member is not
dead after the producer goes, measured:

- An author's legacy `component` envelope holding an `object-metric`
node still reaches the merge.
- The filter bar scopes it today. A probe got `filter: { region: 'EMEA'
}` on that node, for both an untyped and a `custom` envelope.
- Dropping the member would silently stop that, which the ruling did not
ask for.

Its docblock now says so, and a pin holds it. `DashboardGridLayout` has
no filter bar, so it has no such plumbing.

## The pins

The pins are in
`packages/plugin-dashboard/src/__tests__/inlineObjectMetricRetired-11525.test.tsx`:
29 tests, both surfaces.

- **Subjects.** Each single-value type with provider `options.data` is
read off `METRIC_LIKE_TYPES`, not restated. Widget-level `data` and a
typeless widget are cases too. Each draws
`LEGACY_RETIRED_WIDGET_SCHEMA`, checked by identity on the recorded
node. The test also reads the placeholder text on screen, finds no
`object-metric` node, and sees no `aggregate`, `find` or `queryDataset`
call. One more case holds that the retired `metric` tile's DOM equals
the retired legacy-shape metric tile's.
- **Controls.** The provider pivot draws the same placeholder object,
and a dataset-bound metric draws "510000". A static metric draws its
`options.value` through `plugin-dashboard:metric`. A provider bar still
builds an `object-chart` node naming `deal`, and a provider table still
builds `object-data-table` and fetches. An envelope `object-metric`
receives `{ region: 'EMEA' }` from the filter bar and queries.

**Reverse verification.** The fix was committed first (`856f56f0`). The
two producer files were then checked out at `9d7419b9`, with a trap
restoring them from `HEAD`.

- **Before running.** On disk, the base `object-metric` producer lines
were 2 in `DashboardRenderer.tsx` and 1 in `DashboardGridLayout.tsx`.
The renderer's 2 are the producer and the kept union member. The
retired-arm marker read 0 and 0.
- **Result.** The run gave **16 failed, 13 passed (29)**. The failures
are every subject (8 per surface); the passes are every control. This is
the direction stated in the file header.
- **Restore.** The restored blob hashes equal `HEAD`'s, `git diff HEAD`
on both files is 0 bytes, and the rerun is 29 passed.

## Corpus re-judged (H3)

**The census.** I ran `git grep` over the whole tracked tree for a file
that pairs a `provider: 'object'` literal with a metric-family `type`.
It found no test, fixture, story or example that authors a dataset-less
inline provider metric through a dashboard surface or asserts its
number. The hits are prose, CHANGELOGs, this card's own files, and two
plugin-dashboard tests whose provider widget is a `bar`.

The full plugin-dashboard suite was green before this branch's pins were
added. Verdicts:

- `DashboardGridLayout.legacyRetired.test.tsx`: its header said the
nested provider config is live for every family. It gets a dated note.
Its provider control is a `bar`, so the control stands as written.
- `DashboardGridLayout.datasetPath.test.tsx`,
`widgetDataProviderRetired-7353.test.tsx`,
`dashboard-producer-node-types-11514.test.tsx` and the three
`DashboardChart.*` files: these are provider `bar`, `table` or `pivot`
widgets, all live or already retired. Left alone.
- `DashboardRenderer.legacyRetired.test.tsx`: it covers the top-level
`object` shape only. Left alone.
- Docs. `content/docs/guide/dashboard-filters.md` and
`content/docs/plugins/plugin-dashboard.mdx` each get one paragraph
naming the form that now draws the prompt. In
`packages/plugin-dashboard/README.md`, the filters example's comment
does the same, and the accent table's "inline `object-metric`" now reads
"an `object-metric` block".
- Comments falsified by this change: `legacyRetiredWidget.ts` (the
dated, append-only correction the ruling names),
`ObjectMetricWidget.tsx`'s `aggregate` docblock (it said both relays
compose this node), and the render-site and broadcast comments in the
two surfaces.

## Pending changesets

- **New entry.** `.changeset/11525-inline-metric-retired.md`
(`@object-ui/plugin-dashboard`: `minor`) states the render change: what
the tile showed before, what it shows now, and the fix, which is to bind
a dataset.
- **Dated note.** `.changeset/11514-dashboard-slot-entry-types.md` gets
one: its "Every widget that names a known family draws as before" no
longer holds for this form. Its frontmatter is unchanged.
- **Left alone, and why.** I read every pending entry that names the
inline provider, `object-metric`, `ObjectMetricWidget`,
`legacyRetiredWidget`, objectui#4612, objectui#10528, objectui#11479 or
the filter broadcast.
- `11348-dashboard-widget-reads` still holds: the type guard still
covers the same three `object-*` types.
- `10528-grid-object-table-fetch` and `7353-retire-widget-dataprovider`
cover the table and pivot only.
- The `object-metric` block entries (`8613`, `8970`, `9002`, `10221`,
`10356`, `10572`, `10993`, `11254`, `11444`, `11475`, `7352`, `7752`,
`11506`) describe the widget, which stays live as an authored block.
None says a dataset-less provider widget reaches it.
- `check:changeset-claims` named three entries for files this branch
edits: `11483-metric-card-needs-value`,
`8114-detail-tab-activity-timeline` and `8760-unfulfilled-chart-stubs`.
Their paragraphs concern `metric-card`, a detail tab and `line-chart`,
which this branch does not move.

## V1 census (H4)

This was a throwaway worktree detached at PR objectstack-ai#11512's head `ff7f748e`. I
applied `git merge-tree` of that head and this branch, which had one
conflict, `zod-mirror-parity.test.ts`; I took V1's side because no
plugin-dashboard program reads that file. After a fresh install and a
closure build, I ran plugin-dashboard's two programs. Nothing was
committed or pushed from the probe, and the probe was removed.

- **As is, with this change:** 4 lines per program (8 in all), only the
four `SchemaRenderer` cast sites, two in each surface.
- **With the probe-only annotation that types both `getComponentSchema`
returns as the renderer prop:**
- **Base producers:** 12 lines per program. That is 5 `object-metric`
lines (3 in `DashboardRenderer`, 2 in `DashboardGridLayout`), 2
`plugin-dashboard:metric`, 1 static `pivot` and the 4 casts.
- **This branch:** 7 lines per program, with `object-metric` at **0**.
Left are 2 `plugin-dashboard:metric`, 1 static `pivot` and the 4 casts,
which belong to objectui#11466.

## Gates

Read at `29b4ab28` unless stated. objectui has no `dispatch-gates.mjs`;
the list is hand-derived from `package.json`.

- **Closure build.** `pnpm --filter '@object-ui/plugin-dashboard^...'
build` exited 0 under the verify lock.
- **`pnpm --filter @object-ui/plugin-dashboard run type-check`** exited
0. The script echoed `tsc --noEmit && tsc -p tsconfig.test.json`. The
test program covers the new pin file: its first run failed on that
file's case-table typing, which was then fixed.
- **`pnpm exec vitest run packages/plugin-dashboard/`** (from the
worktree root): 165 files and 1620 tests passed, 6 skipped. This ran at
`a1c7e645`; the only later change is the pin file's stub registration,
whose own rerun gave 29 passed.
- **Readers of the touched files, 29 test files:** 720 passed. They are
the doc readers, `sdui-parser`'s options census, the app-shell
`DashboardView` and preview suites, the console drill-down suites, the
schema-catalog gallery render and the widget DOM-leak sweep.
- **Light gates, all exit 0:**
  - `check:new-line-citations`: 0 new.
- `check:control-bytes`, `check:doc-types` and
`check-changeset-no-major`.
- `check-changeset-presence`: 1 changeset for 6 published source files.
- `check:changeset-claims` and `check-changeset-overwrite`: reports
only, judged above.
- **Targeted eslint** over the six touched TS files: 0 errors. Each
edited source file has the same warning count as at base. The new test
carries 2 `any` warnings, the same two the objectui#7353 listener
carries. This is a targeted run, not the repo lint, which CI owns.
- **NOT MEASURED:** `check:doc-snippets` and `check:doc-examples`. They
exited 2, PREREQUISITE NOT MET, because they need a 34-package build.
They compile `ts`/`tsx` fences only; this branch edits no such fence and
moves no published type.
- **Not reached:** `check:component-surface-parity` and
`check:sdui-registration-pins`. No registration input moves.

## Acceptance notes

- **Chrome.** A retired dataset-less `metric` tile (and a typeless one)
draws no heading, as the retired legacy-shape metric tile already does.
Giving retired tiles Card chrome would move the legacy shape too. Not
done; the report carries it as an open question.
- **`DashboardGridLayout.tsx` line addresses.** The one cross-file line
address in the paragraph this branch edits was replaced by a symbol
citation. Eleven others stay, in the props docblock and the render-site
comment. Two of them also carry content that is stale at base: the
sibling's `as any` read of `dataset`, and a second fork in the sibling's
self-contained branch. Their repair is a content correction outside this
card. Carrier: none.
- **`packages/sdui-parser/src/dashboard-widget-options.ts`.** Its scope
note lists `object-metric` among the legacy inline spread targets, which
no longer holds for the metric family. It is a comment in a package
outside this card's surface. Carrier: none.
- **`packages/plugin-dashboard/src/index.tsx`.** `ObjectMetricBlock`'s
docblock names "the dashboard grid path" as a props-only host; no
dashboard path builds the node now. Comment only. Carrier: none.
- **`packages/data-objectstack/src/index.ts`.** A comment there cites
`isObjectProvider` as how a metric's `groupBy` arrives. It is a dated
measurement (2026-09-08) and is left as written.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…['schema'] (objectui#11543) (objectstack-ai#11558)

Fixes objectstack-ai#11543
Clause-②: no

## What

Three marked (`os:check`) `tsx` fences in the published objectui skill
guides declared `schema: BaseSchema` and passed it to `SchemaRenderer`:

- `skills/objectui/guides/auth-permissions.md` (the `ContactsPage`
fence)
- `skills/objectui/guides/data-integration.md` (the `Prototype` fence)
- `skills/objectui/guides/schema-expressions.md` (the `Page` fence)

V1 (objectui#11466, PR objectstack-ai#11512) narrows the `schema` prop to the
declared-node union, which refuses a `BaseSchema` with TS2322. So `Skill
Example Check` is red on PR objectstack-ai#11512.

This PR is the card's route B, exactly. Each fence now declares the
prop's own type, imported as a type from `@object-ui/react`:

```tsx
import type { SchemaRendererProps } from '@object-ui/react'

declare const schema: SchemaRendererProps['schema']
```

It replaces `import type { BaseSchema } from '@object-ui/types'` and
`declare const schema: BaseSchema`. `BaseSchema` had no other use in any
of the three fences, so its import goes. The type compiles on `main`
today and after V1 lands, and it follows the prop by reference.

- Line-neutral: 3 files, +6 / −6.
- ⛔ No `os:check` marker was removed or altered. Per file, the marker
count is equal at the base and at the head: 6, 9 and 32.
- No other file is touched.

## Measured (head `2f2205315`, base `154075ab1`)

The skill-example gate is `node scripts/check-skill-examples.mjs` (`pnpm
check:skill-examples`, the `Skill Example Check` job in
`skill-examples.yml`). Each leg first built the packages the gate's own
`--build-filter` names (turbo, 29 of 29 tasks), so the fences were
judged against the built `dist/*.d.ts`. The gate's RESOLUTION control
confirmed that.

| leg | tree | gate verdict |
|---|---|---|
| control | `main` `154075ab1`, unchanged | exit 0, `Semantic phase: 18
of 18 ts fence(s) judged, 0 failed.` |
| this PR | `main` plus this change (`2f2205315`) | exit 0, `Semantic
phase: 18 of 18 ts fence(s) judged, 0 failed.` |
| probe control | PR objectstack-ai#11512's head `fab635a2d`, unchanged | exit 1,
`Semantic phase: 18 of 18 ts fence(s) judged, 3 failed.` These are the
three TS2322 failures, one in each of the three guides. |
| probe | PR objectstack-ai#11512's head `fab635a2d` plus this change | exit 0,
`Semantic phase: 18 of 18 ts fence(s) judged, 0 failed.` |

- The probe ran in a throwaway detached worktree. The change was applied
as this commit's patch, and on disk `BaseSchema` read 0 and
`SchemaRendererProps['schema']` read 1 in each file. Afterwards the
probe was restored with `git checkout HEAD` (`git diff HEAD` read 0
bytes) and removed without `--force`. Nothing from it was pushed.
- `--self-test` on `main`: exit 0, 60 cases pass. `--list` at the head
shows all three fences as `marked pass`.
- Tests that read these guides, taken from `node
scripts/markdown-test-inputs.mjs` (all three paths are recorded test
inputs), plus two more that `git grep` names. They ran from the repo
root at `2f2205315`: `Test Files 14 passed (14)`, `Tests 394 passed
(394)`.
- Every one of these gates exits 0:
  - `check-skills-paths`
  - `check-control-bytes`
  - `check-shell-escape-residue`
  - `check-skill-eval-tokens`
  - `check-new-cross-file-line-citations` (0 new)
  - `check-doc-fence-languages`
  - `check-doc-links`
- `check-changeset-presence` ("no changeset is owed": `skills/**` is no
released package's source)
- Governed-queue guard predicate, `node
scripts/check-governed-queue-guard.mjs --test` on the three paths: exit
3, `GOVERNED — 3 of 3 path(s) are on a governed surface` (`skills/**`,
the published skills catalog). The control path
`packages/react/src/index.ts` reads `NOT GOVERNED`, exit 0.
- Skills line readings:
- Per file, base → head: `auth-permissions.md` 409 → 409,
`data-integration.md` 515 → 515, `schema-expressions.md` 639 → 639.
- Whole package: all `skills/**/SKILL.md` 142 → 142 (one file), and all
of `skills/objectui/**` 5418 → 5418.
- objectui carries no line ratchet over `skills/**`. objectstack's
`check-skill-line-ratchet.mjs` covers objectstack's own instruction
files only.

## Governed landing

`skills/**` is a governed surface, so this PR stays a **draft** until an
authorized approval (`os-zhuang` / `hotlong`). The claiming seat lands
it through the merge queue. The author of this draft does not flip it
ready, request reviews, arm auto-merge or merge it.

Sequencing, from the card:
- objectui#11534 also edits `schema-expressions.md`, at a different
bullet. Whichever lands second merges `main`.
- After this lands, PR objectstack-ai#11512 merges `main`, and its `Skill Example
Check` should read 0 failed. The probe row above is that reading, taken
ahead of time.

## 维护者速读(草稿)

**改了什么**
- 三份对外发布的 objectui 技能指南(权限、数据集成、表达式)里,各有一段受门禁检查的示例代码。
- 这三段示例把传给 `SchemaRenderer` 的 schema 的类型标注,从 `BaseSchema` 换成渲染器自身的入参类型
`SchemaRendererProps['schema']`。
- 每段只改两行(换一个 import、换一个类型),三个文件合计 +6 / −6,行数不变。
- 门禁标记一个没动,其它文件一个没碰。

**为什么改**
- V1(objectui#11466,PR objectstack-ai#11512)把渲染器的 schema 入参收紧为「已声明节点」的联合类型,这是有意的契约收紧。
- 收紧之后,这三段示例原来的写法会编译报错。AI 作者照着指南写代码时,第一眼读到的就是这些示例。
- 这三段错误让 V1 的 PR 卡在最后一项红灯(`Skill Example Check`)上。
- 新写法直接引用渲染器自己的入参类型:今天的 `main` 上能编译,V1 落地后也能编译,以后入参再变也会自动跟随。
- 实测:`main` 上改前、改后都是 0 失败;在 V1 分支上套用本改动后,从 3 失败变为 0 失败。

**风险与代价(含回滚)**
- 只改文档示例,不改任何发布包的源码,不发版,不需要 changeset。
- 风险很低。代价是示例的读者看到的是 `SchemaRendererProps['schema']` 这种索引类型写法,比
`BaseSchema` 稍微抽象一些。
- 回滚:revert 这一个提交即可。但只要 V1 已经落地,回滚就会让这三段示例重新编译报错。

**席位意见**

**你要做的**
- 读一遍上面三处两行的改动。认可就在本 PR 上留一条 APPROVED review(`os-zhuang` 或 `hotlong`
账号)。
- 批准之后,由认领席位把它翻成 ready 并入合并队列落地,你不需要再做别的。

## Acceptance notes

- No finding was filed or left behind. The card's measured premise held
exactly at today's `main` (`154075ab1`) and at PR objectstack-ai#11512's head
(`fab635a2d`).

Implemented by an os-dev run dispatched from the director seat, session
`session_016tKoy8NJa35Yih1FdzrVmn`.

---
_Generated by [Claude
Code](https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn)_

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-zhuang <jack@objectstack.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment