docs(plugin-editor): compile the README's snippets against the shipped surface - #8249
Conversation
…d surface
The page's UNGATED_DOCS row read "6 parse diagnostic(s) — blocks fenced `ts`
that are bare object literals or elided bodies". Measured with the gate's own
analyzer, all six were ONE block: the "Schema API" fence, a TYPE shape written
as an object literal, one TS1109 per optional member. There was no elided body
anywhere on the page, and the four fences are labelled `typescript`, not `ts`.
- The "Schema API" fence becomes a Markdown members table. A fence that
re-declares a published type is a private copy of the contract; the table
points at `CodeEditorSchema` instead of restating it, and carries the one
nuance the old comment blurred — the contract is `language?: string`, and the
registration's `inputs` manifest narrows the authoring picker to six ids.
- The bare `const schema` literal is bound to `CodeEditorSchema`, with the type
import the "TypeScript Support" section already teaches. Unannotated, it had
nothing to check against: a real type error injected into it left the gate's
reading byte-identical.
- The manual-registration loop passes `{ namespace: 'plugin-editor' }`, the
third argument this plugin's own registration passes; the two-argument form
compiles but is the one `Registry.register` warns is deprecated.
- Defaults in the table are the renderer's own destructuring defaults.
The page now reads zero, so its ledger row is deleted (6 rows -> 5). The
strictness region below the `Fence scanning` banner is byte-identical.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
✅ 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
|
|
Standing down on That check is informational (not in the Generated by Claude Code |
|
Armed —
Landing stroke follows the merge: content probe on re-fetched Generated by Claude Code |
|
LANDED — Merged at 2026-09-07T04:37:50Z as Batch 33 ( Generated by Claude Code |
Part of #5174 (batch 32:
packages/plugin-editor/README.md)Burns down the page's
UNGATED_DOCSentry inscripts/check-doc-snippet-types.mjs: the page now sits in the gate's covered/compiled tier and its ledger row is deleted (6 rows → 5).Base:
origin/mainat885fa71de(carries PR #8245, batch 31). Head:5e6a3704c. One commit, no amend, no force-push.Census reading 1 — ledger-literal, on the base
Method: the gate's own exported
analyze/scanFences/compileSnippets, with this page removed fromUNGATED_DOCSso its fences are collected, against the built--build-filterclosure (35/35 turbo tasks successful). No hand-written regex anywhere in the measurement.The page carries seven fences: four
typescript(22, 38, 52, 66), one unlabeled (93), twobash(14, 101). Only the fourtypescriptones are collected.3:9,4:12,5:9,6:10,7:12,8:13— one per optional memberbash); reported, not relabeledBlind spot: 1 of 4 collected fences never reached the semantic phase.
The ledger row said
6 parse diagnostic(s) — blocks fencedtsthat are bare object literals or elided bodies. Three corrections, all measured:// ... other methods.ts" — all four fences are labelledtypescript, notts.And the shape the row names as the cause is on the wrong fence: fence 22 is a bare object literal and it parses perfectly. Fence 66 is a type shape written as an object literal (
value?: string,and friends), which is why it is the one that will not parse as an expression.Negative control — the blind spot measured, not argued
Batch 31's control, repeated here on the base page and compiled in memory through the same
compileSnippets: a real type error injected into the bare literal at fence 22,height: '400px'→height: 400(a number where the member isstring).The base reading is byte-for-byte the same with and without a genuine type error in the page. The binding had no type at all, so there was nothing to check it against — the purest form of the blind spot: a parse-only ledger row is silent about the fences it does not name, and green on the ones it does not annotate.
Census reading 2 — after the minimum in-memory parse repair
Fence 66 given a name in memory only (
type SchemaAPI = { … };), nothing else touched:Total: 0 parse, 0 bound, 0 semantic. This zero is not a verification. Two of the four are green because there is nothing to disagree with:
CodeEditorSchema.A parse-only row hides the semantic phase; this reading is the first real look, and it says the page's teaching was never checked against the shipped surface at all.
Per-fence decision
const schema: CodeEditorSchema = { … }, withimport type { CodeEditorSchema } from '@object-ui/plugin-editor';alongside the side-effect import. Same spelling the "TypeScript Support" section already teaches.ComponentRegistry.register(type, component, { namespace: 'plugin-editor' })— the call this plugin's own registration makes (packages/plugin-editor/src/index.tsx:57-61). The two-argument form compiles, andRegistry.register(packages/core/src/registry/Registry.ts:433-450) warns that it is the deprecated pattern.check:doc-fencesis green on base and head and does not flag fence 93: its content is build output (dist/index.js 0.19 kB …), not TypeScript, so the gate has nothing to say about it, and the page is not in that gate's declared list.No fragment marker was added, no type was loosened, no
@ts-expect-errorwas needed, and nothing underpackages/plugin-editor/src/**orpackages/types/src/**was touched.Fence 66 — route (a), on the four axes
The fence was a type shape, not a value: a member list with
?and type annotations, wrapped in braces. Route (a) is a Markdown members table; (b) is a compilabletypescriptfence built around a typed value; (c) is a declared fragment marker.CodeEditorSchemais the contract. It is declared once in@object-ui/types(packages/types/src/form.ts:1550) and re-exported by the plugin (packages/plugin-editor/src/types.ts:55), specifically so the type an author reads and the zod mirror that validates their document cannot drift. A fence that re-declares it is a second, private copy of the contract — objectui#7646's shape. Contract-first says point at the declaration, never restate it.?and the defaults, teaching a narrower surface than the type has. The table leaves nothing copyable that can become a second de-facto contract, and it is where thelanguagenuance below can be stated exactly.Route (b) was not taken; route (c) was never a candidate for this fence.
The one contract nuance, stated rather than settled. The page's old comment read
language?: string // 'javascript' | 'typescript' | 'python' | 'json' | 'html' | 'css'. The type declareslanguage?: stringoutright and says why (packages/types/src/form.ts:1560-1572): the renderer forwards the value verbatim to Monaco, which accepts any registered language id. The registration'sinputsmanifest (src/index.tsx:66) enumerates exactly those six as anenum. So the two are not in conflict — one is the accepted set, the other is the authoring picker. The table says which is which and narrows or widens neither.Per-key table (the objectui#7927 bound)
CodeEditorSchema extends BaseSchema, andBaseSchemacarries an index signature (packages/types/src/base.ts:467), so an annotation checks member types and never keys. Classification is therefore by hand-reading the declaration and the renderer's read sites, not by trusting the compiler.typeform.ts:1551src/index.tsxregisters'code-editor'valueform.ts:1559src/index.tsx:44(value ?? schema.value) →MonacoImpl.tsx:40languageform.ts:1572src/index.tsx:45→MonacoImpl.tsx:38themeform.ts:1579src/index.tsx:46→MonacoImpl.tsx:39heightform.ts:1585src/index.tsx:47→MonacoImpl.tsx:37readOnlyform.ts:1591src/index.tsx:49→MonacoImpl.tsx:43classNamebase.ts:171src/index.tsx:50→MonacoImpl.tsx:35Phantoms (undeclared and never read): none. Undeclared-but-read: none.
onChangeis declared as a runtime slot (form.ts:1600) and no example writes it, correctly — no JSON document can carry a function.Defaults check, both directions
Renderer, the authority:
packages/plugin-editor/src/MonacoImpl.tsx:26-32.@defaultdefaultPropsvalue'// Write your code here\nconsole.log("Hello, World!");'''language'javascript''javascript''javascript'theme'vs-dark''vs-dark''vs-dark'height'400px''400px''400px'readOnlyfalsefalsefalseclassName''The page stated no defaults at all before this change — its old comments were descriptions (
// Code content,// Editor theme) and one example (// e.g., '400px'). So there was no wrong documented default to repair; the table now carries all six, taken from the renderer.Proposal for the maintainer, citing objectui#8246 — not edited here, and not a new card. The registration's
defaultProps.value(packages/plugin-editor/src/index.tsx:72) records a code sample, while the renderer's own default for an omittedvalueis''. That is exactly the shape objectui#8246 already carries forplugin-charts'color: re-verified on this head, the only non-test read ofdefaultPropsanywhere inpackages/core/srcorpackages/react/srcisWidgetRegistry.ts:189, which writes manifest defaults into the widget it builds rather than feeding the render path. So the value an author would infer from the manifest is not the value they get. Same contract question, same two ways out (correct the manifest, or makedefaultPropsreachable);packages/plugin-editor/src/**is out of this PR's surface.Ledger decision
The page reads zero after the repairs, measured with the same analyzer:
0 parse, 0 bound, 0 semantic; blind spot 0 of 3. Ruling 6's first branch therefore applies: the
UNGATED_DOCSrow is deleted, not rewritten. Nothing on this page is parked on a filed defect, so no marker was added. The ledger reads 5 rows on the head. The pin (scripts/__tests__/check-doc-snippet-types.test.ts) enumerates only the rootREADME.mdrow, so no pin edit was owed — the same finding batches 28-31 recorded.Strictness region
The region from the
Fence scanningbanner to EOF, sha256:885fa71de:2749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084b5e6a3704c:2749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084bIdentical. The banner moved from line 790 to 788; the two deleted lines are the ledger row, which sits above it.
Positive control
Against the committed tree at
5e6a3704c, under atrap … EXIT INT TERMwith absolute paths.readOnly: 'yes',added to the newly typed binding at fence 22.height: '400px'anchor stayed at 2.768ad3b194107fe963119bf6213ae511d5ad9dcf; mutated blob:2cbcde23c5065e8491c8336e8d4279b5496b57cc(different, checked).pnpm check:doc-snippetsexit 1, printing[semantic] packages/plugin-editor/README.md:30:3 TS2322: Type 'string' is not assignable to type 'boolean | undefined'.andSemantic phase: 617 of 617 block(s) judged, 1 failed.git diff HEAD -- PATHempty andgit hash-object PATH=768ad3b194107fe963119bf6213ae511d5ad9dcf= the HEAD blob; anchors back to 0 and 2. Not read from an exit code.An earlier attempt of this control aborted loudly before writing anything, because its anchor matched two fences (22 and 52 now share the same first two lines). Reported rather than quietly retried: the run was a genuine no-op,
git diff HEADwas empty afterwards, and the anchor was made unique before the real run.Gates — all pinned to head
5e6a3704c, clean tree, exit codes captured by redirect-then-capturepnpm check:doc-snippetspnpm exec vitest run scripts/__tests__/check-doc-snippet-types.test.ts scripts/__tests__/check-doc-fence-languages.test.tspnpm exec vitest run scripts/__tests__/pnpm exec vitest run packages/plugin-editor/pnpm check:doc-typespnpm check:readme-exportspnpm check:doc-fencespnpm type-check:scriptspnpm lint:rootpnpm check:control-bytesgrep -naPself-scan of both changed paths — no hitspnpm check:entry-guardnode scripts/check-changeset-presence.mjsnode scripts/check-governed-queue-guard.mjs --teston both pathsRe-derived against the actual diff (a README plus one
scripts/file), three further gates were run:pnpm check:esm-specifiers0,pnpm check:doc-example-readers0, andpnpm check:node-esm-load. The last one exit 1 on its first run for the reason the gate itself prints — turbo shares one cache across worktrees and the build replayed a sibling worktree's entry (objectui-issue-5174-b30) — and exit 0 on--force-build, provenance leg 37 of 37 gradable entries built by this tree, load leg 34 of 39. Nothing in this diff is a built artifact or a manifest, so that gate has no dependency on it either way.Live E2E (informational)is red on every branch today for an upstream reason (#7990 / objectstack#16186) and is not this PR's.Generated by Claude Code