Repository navigation
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
Conversation
… 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>
|
changeset-claim-re-read
|
ℹ️ Console Performance Budget — not measuredThis 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.
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. |
schema prop take the declared-node union, V1 (objectui#11466)schema prop take the declared-node union, V1; the dashboard producers name declared nodes and an envelope's object-metric retires (objectui#11466)
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…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>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: 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 ① Derived judgments1. The narrowing (TypeScript face only).
2. New public types and exports.
3. The plugin-dashboard producers.
4. The runtime move: the static pivot.
5. The ledgers.
6. The flex bag's array child list.
7. The tests.
8. The check-runs. 39 success, 3 skipped (two coverage jobs and dependabot) and 1 failure,
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
…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>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
✅ ACCEPT of the landing head
|
…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>
…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>
…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>
…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>
…['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>
Fixes #11466
This PR carries ruling ① (V1) and the card's residue, with
mainmerged 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, withBaseSchema's index signature present.Clause-②: yes (narrowing)
objectui#11543 (governed PR #11558) has landed as
5cde2c6fe.Skill Example Checkis green atcae2224d5: 18 of 18 ts fences judged, 0 failed. All 43 check-runs on this head are green or expected skips.What changes
@object-ui/typesexportsDeclaredNode. It is the discriminated union, keyed by the literaltype, of:AnySchemadeclares, minus itsBaseSchemaarm (whosetypeisstring) and minus the app-level document, which is left out by{ type: 'app' }(AppSchemaRendererreads that document structurally andComponentRegistrynever dispatches it);AuthoringNode;CustomNodeRegistryinterface. An entry joins under its KEY: the arm is the entry intersected with{ type: KEY }, so even a loose entry adds notype: stringarm.SchemaNodeisDeclaredNode | string | number | boolean | null | undefined. It wasBaseSchema | string | number | boolean | null | undefined.SchemaRendererProps['schema']isDeclaredNode | string | null | undefined. It wasBaseSchema | AuthoringNode | string | null | undefined.toRenderableSchemafollows by reference.@object-ui/core's schema builder takesDeclaredNodewhere it tookBaseSchema:.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 intoDeclaredNodeat ONE documented boundary, right aftervalidateTreereports no error (page.tsx, objectui#11479 Q2 A). No other cast toDeclaredNodeexists undersrc/.@object-ui/test-supportgainsundeclaredNode, 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:metricis declared throughCustomNodeRegistry(objectui#11479 Q1 A).DashboardMetricNodeSchema, a new export, is the nodeMetricWidgetrenders under that key, with the keys it reads off the node, typed byMetricWidgetProps.widgetDispatch.tsenters it in the registry besideDASHBOARD_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.getComponentSchemainDashboardRendererandDashboardGridLayout) returnSchemaRendererProps['schema'], so each node is checked against its declared type where it is built. The metric arms narrowwidgetTypefor the required label;classifyWidgetTypeanswersmetriconly for a named type, so no verdict moves.DashboardGridLayout's staticpivotnode states the keysPivotTabledraws (itsPivotTableSchemakeys, andclassName), read fromoptions, instead of spreadingoptionswhole, so it namesPivotTableSchemawith no cast.SchemaRendererare gone:as BaseSchema | string | null | undefinedinDashboardGridLayoutandas BaseSchemainDashboardRenderer. No new cast.object-metricnode in a widget's legacycomponentenvelope drawsLEGACY_RETIRED_WIDGET_SCHEMAon both surfaces (the maintainer's ruling A on objectui#11466, extending ruling C on objectui#11525), under its bare key and underplugin-dashboard:object-metric, and sends no query. One shared detector,isRetiredEnvelopeNodeinlegacyRetiredWidget.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-metricleaves the filter broadcast'sFilterableComponentType/FILTERABLE_COMPONENT_TYPES, so the flat-filterwrite the declaredobject-metricnode refuses is gone. The broadcast now coversobject-chartandobject-data-table, built by the renderer or written in an envelope.type: stringarm, an index signature, a transitionalBaseSchemaarm,BaseSchema's own index signature, and thecomponentslot'sBaseSchemafallback (all objectui#8347's). Every zod face and every TypeScript authoring face ofobject-metricstays as it was, and so does the authoredobject-metricblock 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'sobject-metricdraws 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.type, nested or at the prop, e.g.{ type: 'card', children: [{ type: 'txt' }] }. Before: compiles through theBaseSchemaarm. After: TS2322.BaseSchemaat the prop or in a slot. Before: compiles. After: refused, because it names no declared type.AuthoringNode, or a closedCustomNodeRegistryentry), nested at any depth. Before: compiles through theBaseSchemaarm. After: refused, because the slot discriminates ontype.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.BaseSchemaarm. After: refused. Examples: a storedhomepage withoutlabel, adata-tablewithoutcolumns, a staticpivotwithout its axes.typecompiled. After: it compiles once declared inCustomNodeRegistry; an unregistered one is refused.plugin-dashboard:metricnode. Before: compiled through theBaseSchemaarm, like any string. After: in a program that loads@object-ui/plugin-dashboard's typings it is a declared node, checked againstDashboardMetricNodeSchema(which extendsBaseSchema, so a misspelled key still compiles until objectui#8347); in one that does not, it is refused as undeclared.type.'app'narrows toPageDocumentNode(the stored page kind) and'list'toListSchema.PageDocumentNodeexcludes the interface-modelistkind, whichPageViewrenders throughInterfaceListPageand never hands toSchemaRenderer.SchemaByTypereadsAnySchemaand does not move.The merge of
mainMeasured first with
git merge-treeagainstmainat160c6c6e: 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.mdandpackages/types/src/index.tsauto-merged. The merge is the merge commitc7c37b73; no rebase, no force-push.The resolution is the union, re-derived on today's
main.WiderThanDeclaredholds 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::widgetsandcomplex.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 fromWIDER_ARMSand the mirrors, and the type-level ratchet confirms each entry against the mirrors (typestsconfig.test.jsonand vitest, both green). objectui#11514'sKnownDriftmove (typeleftcomplex.zod.ts#DashboardWidgetSchema's entry) merged without conflict.MeasuredArmDrift11466(node-recursion-point-8344.test.ts) dropsdetail-section,app-schema-rendererandcloud:plan-status, which objectui#11515 twinned and joined toAnySchema. On the merged tree before that edit the two-way ratchet read red (TS2344 atNodeRecursionPointDeclarationDrift), which is what the ratchet is for; a dated note records the reconciliation.Landing-prep round (director seat, claim
5970151706; dev report5970915352): two more merge commits, both conflict-free, withpnpm installleaving the lockfile unchanged.5afa0269amergesmain154075ab1: objectui#11533, the@objectstack/spec17.6.0 bump objectui#11531, objectui#11547, finding(app-shell):applyViewItemmakes a container's FIRST named form its create/edit.formwhen no item is default — the first-named promotion objectstack#21500 removes from the spec #11539, plugin-form: the tabbed Edit/New form dialog renders no tab for a{ group }form section, so group-referenced fields have no editing surface there (moved from objectstack-ai/objectstack#21543, item 1) #11542, objectui#11344 and objectui#11068.cb687dfadmergesd0c0c7fe9, objectui#10813.mainbrought no new V1 producer.Final landing round (report
5974814539):318447867mergesmain0a53c67f9(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 onlymain's change.cae2224d5correctsMeasuredArmDrift11466's docblock (contract review R1, prose only): theapp/record/home/utility/page/dashboarddrifts are recorded for objectui#8347's family, not carried by their own cards.H1: the cycle
tsc -p packages/types/tsconfig.json), there were 9 errors: TS2456 onFlexBlockNode,AuthoringNode,DeclaredNodeandSchemaNode; TS4109 inzod/base.zod.ts; TS7022 onFlexSchema,FLEX_BAG_CHILDREN,FlexPropsBagandFlexBlockSchema.FlexBlockNodeis an interface that extends the closed arm input ofFlexBlockSchema, with no members of its own;zod/base.zod.tswrites the node slot's zod type out through two type aliases (SchemaNodeZodType,NodeSlotZodType). The schema objects do not change.tsconfig.jsonand intsconfig.examples.json, on the merged tree as well.H3: premise ②, re-run on the merged tree
Throwaway probe files, never committed: 40 type-level rows in
@object-ui/typesand 15 in@object-ui/react, each labelled OK (must compile) or REFUSE (must error), each run on two legs atb13093d6.BaseSchema's index signature deleted; restore proven by blobc0d9ad44equal 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.BaseSchema, which is the signature objectui#8347 removes. Undeclared types, wrong-value keys, the tombstone,AuthoringNodebag misspellings and unregistered custom types are refused on both legs.mainat160c6c6eagainst this branch atb13093d6. Instantiations are deterministic (one value across 3 runs each); seconds are medians of 3 on a shared box, so read them as ratios:tsconfig.jsontsconfig.examples.jsontsconfig.test.jsontsconfig.jsontsconfig.test.jsontsconfig.jsontsconfig.test.jsonThe 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.c7c37b73): 9 lines in 3 of 88 programs. plugin-dashboardtsc --noEmit4 andtsconfig.test.json4 (the fourSchemaRendererrender sites, two per surface, fed by the two casts); typestsconfig.test.json1 (MeasuredArmDrift11466).f90ba8d2(the previous round): 2 lines in 2 of 88 programs, both the return ofDashboardRenderer's filter merge, whoseobject-metricmember wrote a flatfilteronto anobject-metricnode. Those programs read a plugin-dashboarddistbuilt with a census-only cast at that line, restored blob-equal before the census.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.cb687dfad(after the landing-prep merges): 0 lines across all 88 programs, the same at5afa0269a.BaseSchema's index signature is present (2 lines in src, 2 in dist), and this round adds no cast.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
SchemaRenderer.propsResolution(the prop equality, andBaseSchemais not an arm);node-recursion-point-8344(the measured set of zod arms no declared node type admits, nowapp,record,home,utility,page,dashboardand the retired kanban arm);overlay-trigger-union-7081,overlay-node-slot-doc-types-7082andpage-body-arity-8310;zod-mirror-parity's Wider ledger (the union above);authoring-nodes-11364(dated note); newnode-slot-union-11466.CustomNodeRegistry(13 files); deliberately undeclared input crossesundeclaredNode(15 files); stored page fixtures gained the requiredlabel; the plugin-gantt and plugin-map bag fixtures are annotated with their declared node types.content/docs/plugins/plugin-dashboard.mdxname the one retired envelope node and what still draws. 14 documents name the declared node type; a registered custom type is shown declared inCustomNodeRegistry(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).11364,8331,8908,schema-input-bridge-permanent-4622,7767,11479,8572,8310(twice: the prop's annotation, and the root README's example authorschildren, notbody),11354,10528-grid-object-table-fetchand11514-dashboard-slot-entry-types(their "unchanged" / "draws as before" readings of the grid's static pivot), and for the envelope retirement11525-inline-metric-retired(its envelopeobject-metric"still receives the dashboard filter bar's values"),11348-dashboard-widget-reads(the broadcast's guard over "the same threeobject-*types", now two) and11514-dashboard-slot-entry-typesagain (an envelope "draws under its card heading as before" whatever it held). New entries:.changeset/11466-node-slot-union.md(types, react and coreminor; componentspatch),.changeset/11466-dashboard-metric-node.mdand.changeset/11466-envelope-object-metric-retired.md(plugin-dashboardminoreach, the second stating before, now and the fix: bind a dataset). Each states its breaking meaning in the body, because objectui never declaresmajor.The envelope's
object-metric(the maintainer's ruling A)The previous round held one line: the filter broadcast kept
object-metricin its filterable set for a node in a widget's legacycomponentenvelope (objectui#11525 kept it), and wrote a flatfilterthat the declaredobject-metricnode (the spec'spropertiesbag,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.object-metricinside acomponentenvelope 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.inlineObjectMetricRetired-11525.test.tsxcarries 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 noaggregateorfindcall is made. A new control keeps the broadcast on the envelope'sobject-chartandobject-data-table, which still receive the filter bar's value.20fb2278) with a trap-restored mutation.isRetiredEnvelopeNodewas forced false (anchor hit once, marker on disk 1, blob71c7335ctoc2706404). 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: blob71c7335cequals HEAD,git diff HEAD0 bytes, marker 0.Gates
HEAD is
cae2224d5(final round, report5974814539; CI 40 success, 3 skipped, 0 failure). The readings below were taken atfab635a2unless the report re-measured them atcb687dfad. 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.packages/plugin-dashboard/, every reader test of the dashboard README, guide and touched changesets, and the changeset-reading script suites, atfab635a2: 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, atf90ba8d2, 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.check:doc-snippetsexit 0, "777 of 777 block(s) judged, 0 failed";check:doc-examplesexit 0;check:doc-typesexit 0.check-changeset-no-major0,check-changeset-presence0 (3 changesets declared),check-changeset-fixed0,check:pending-changeset-literals0,check-changeset-overwrite0 (report-only),check:changeset-claims0 (report-only),check:new-line-citations0 (0 new),check:control-bytes0,check:component-surface-parity0..ts/.tsxfiles: 0 errors, and the same warning count per file as before the change (the last round's four files againstf90ba8d2; the previous round's six againstmainandff7f748e).eslint.config.jssets no parserproject, so linting is not type-aware and these diffs cannot move the verdict on an untouched file. The repo-wide lint is CI's.check:sdui-registration-pins, which needs a console build (exit 2, prerequisite); no registration input moved.Acceptance notes
5970915352): the flex bag's child list isunknown[] | SchemaNodeon 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 isunknown[]and unchecked (probe f02 compiles under the mutation, atcb687dfadand atfab635a2). That comes fromFLEX_BAG_CHILDREN'sz.array(z.unknown())arm, which is onmainand 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).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.DeclaredNodehas same-typepairs that are intended:flex(FlexSchemaandFlexBlockNode) and theobject-*renderer faces next to their authored bag nodes.PivotTable's such asrowLabels, or aBaseSchemanode key other thanclassNamesuch asid,styleorhidden) no longer reaches the node. Each stated key draws as before;DashboardGridLayout.objectProviderFetch-10528andDashboardGridLayout.datasetPathpass.DashboardGridLayout.tsxkeeps 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 thedataSourceprop's docblock (intoDashboardRenderer,DatasetWidget, andSchemaRenderertwice) and eight in the render-site comments (five intoDashboardRenderer, three intolegacyRetiredWidget).Generated by Claude Code