Commit fc7db05
spec(types,plugin-detail,vscode-extension): TS twins of the zod-only node arms, the README's Activity tab on its declared properties, and Export to React's typed schema constant (objectui#11515) (#11519)
Fixes #11515
Clause-②: yes (widening)
Part of the preparation for objectui#11466 (V1, draft PR #11512). On PR
#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 #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
#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 #11512 when it merges
`main`.
- Merging this branch into PR #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 #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>1 parent 95e58a3 commit fc7db05
19 files changed
Lines changed: 539 additions & 62 deletions
File tree
- .changeset
- content/docs/utilities
- packages
- cli/src/__tests__
- plugin-detail
- types/src
- __tests__
- zod
- vscode-extension
- src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
Lines changed: 7 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
89 | 93 | | |
90 | 94 | | |
91 | 95 | | |
92 | 96 | | |
93 | 97 | | |
94 | 98 | | |
95 | | - | |
| 99 | + | |
96 | 100 | | |
97 | 101 | | |
98 | 102 | | |
99 | 103 | | |
100 | | - | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
101 | 107 | | |
102 | 108 | | |
103 | 109 | | |
| |||
Lines changed: 29 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
27 | 29 | | |
28 | 30 | | |
29 | 31 | | |
| |||
130 | 132 | | |
131 | 133 | | |
132 | 134 | | |
133 | | - | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
134 | 155 | | |
135 | 156 | | |
136 | 157 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
134 | 134 | | |
135 | 135 | | |
136 | 136 | | |
137 | | - | |
138 | 137 | | |
139 | | - | |
140 | 138 | | |
141 | 139 | | |
142 | 140 | | |
| |||
169 | 167 | | |
170 | 168 | | |
171 | 169 | | |
| 170 | + | |
172 | 171 | | |
173 | 172 | | |
174 | | - | |
| 173 | + | |
175 | 174 | | |
176 | 175 | | |
177 | 176 | | |
| |||
200 | 199 | | |
201 | 200 | | |
202 | 201 | | |
203 | | - | |
204 | | - | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
209 | 211 | | |
210 | 212 | | |
211 | 213 | | |
| |||
0 commit comments