docs(plugin-markdown): compile the README's snippets against the shipped surface - #8260
Conversation
…ped surface
Burns down `packages/plugin-markdown/README.md`'s `UNGATED_DOCS` entry
(objectui#5174, batch 34): the page moves from the gate's ledgered tier into
its compiled tier, so its snippets are judged on every commit instead of being
recorded as debt.
- The "Schema API" block was a TYPE shape written as an object literal — the
page's only parse failure and both ledgered diagnostics. It becomes a
Markdown members table, so the page points at `MarkdownSchema` instead of
carrying a second private copy of it.
- The Usage example's bare `const schema = { … }` is annotated
`MarkdownSchema` with the type import; measured beforehand, the unannotated
binding accepted a genuine type error with a byte-identical ledger row.
- The manual-registration loop now passes the `{ namespace: 'plugin-markdown' }`
third argument the plugin's own registration makes; the two-argument form
still compiles but `ComponentRegistry.register` warns that it is deprecated.
No `src/**` or type edit: `content`'s contract-versus-runtime reading
(`schema.content || ''`) and the two ADR-0049 tombstones are stated, not
changed.
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
|
|
Armed —
Landing stroke follows the merge: content probe on re-fetched Generated by Claude Code |
|
LANDED — Merged at 2026-09-07T06:40:22Z as Batch 35 ( Generated by Claude Code |
Part of #5174 (batch 34:
packages/plugin-markdown/README.md)Base:
cdbc852e9ec2c92307a85a5d0460229bccb0c8d0(carries PR #8251, batch 33). One commit, no amend, no force-push.packages/plugin-markdown/README.mdmoves out of the gate's ledgered tier into its compiled tier, and itsUNGATED_DOCSrow is deleted. Two files changed: the page and the gate's ledger row.Census reading 1 — the ledger-literal reading, on the base
Taken with the gate's own exported
analyze/compileSnippets(no hand-written regex), the page lifted out ofUNGATED_DOCSin memory, against the built--build-filterclosure (35/35 turbo tasks successful).Fences on the base: four
typescript(24, 37, 51, 62) and twobash(16, 106). Thebashpair is reported, not relabeled.TS1005at snippet 3:10 (;expected) andTS1109at snippet 4:13 (Expression expected)bash, not collected; reported, not relabeledBlind-spot count: 1 of 4 collected
typescriptfences never reached the semantic phase.The ledger row was wrong in three places. It read
2 parse diagnostic(s) — blocks fencedtsthat are bare object literals or elided bodies:ts" — all four fences are labelledtypescript;Negative control (length-preserving), on the base page's fence 24
The base binding at fence 24 was
const schema = { … }with no annotation. A genuine type error was injected into it in memory only —contentgiven a decimal numeric literal padded to the exact byte length of the string it replaced (238 bytes before, 238 after), so no offset moves for an unrelated reason (batch 33's lesson).parse=1 bound=0 semantic=0;TS1005@3:10,TS1109@4:13parse=1 bound=0 semantic=0;TS1005@3:10,TS1109@4:13The ledger row is the same with and without a real type error in the fence it says nothing about. The binding had no type at all, so there was nothing to check it against. The blind spot is measured, not argued.
First attempt, reported rather than quietly retried. The first padding used all zeros, which is an octal literal: it produced
TS1121— a parse error, not a type error — and the run measured nothing about the semantic phase. That reading is void and is recorded here; the anchor was changed to a decimal literal (1followed by zeros) and the control re-run.Census reading 2 — after the minimum in-memory parse repair
Fence 62 given a name in memory (
type SchemaAPI = { … };); nothing else touched, nothing written to disk.Total 0 — and green here means NOT LOOKED AT for three of the four:
registeroverload still exists;Only fence 51 was genuinely judged.
Census reading 3 — after the repairs in this PR
Three
typescriptfences remain (24, 38, 52);parse=0 bound=0 semantic=0, all three judged, blind spot 0 of 3.Per-fence decision
const schema: MarkdownSchemaplusimport type { MarkdownSchema }alongside the existing side-effect importComponentRegistry.register(type, component, { namespace: 'plugin-markdown' })— the call the plugin's own registration makes atpackages/plugin-markdown/src/index.tsx:47-51bash, unchanged, reported not relabeled;check:doc-fencesgreen on base and headNo fragment marker, no widened type, no
@ts-expect-error, no edit underpackages/plugin-markdown/src/**orpackages/types/src/**.Fence 62: route (a), a members table
Weighed on the four axes:
MarkdownSchemahas one authority (packages/types/src/data-display.ts:1168), re-exported by this plugin (src/index.tsx:14via./types, whose docblock records the 2026-08-25 one-authority ruling and forbids a second local declaration). A fence re-declaring it is exactly the private copy that ruling removed; contract-first says point at the type.Route (b) was not needed — the page's teaching already has its compilable example. Route (c) was never in play.
The unread member: the brief's premise is stale in the dev's favour
The dispatch expected a member declared
?: booleanwith@default trueand read by nothing, to be stated as declared-but-unread. On this base it is already stronger than that.MarkdownSchemacarries two ADR-0049 retirement tombstones, both spelled?: never, both citing objectui#6972:sanitize(data-display.ts:1176-1208) — the?: boolean/@default truemember the brief names. It is no longer declared-but-unread; it is retired, and?: neveractively refuses any authored value. Its docblock records why the enforce arm was unavailable: sanitization is unconditional, so the switch it implied does not exist in either direction.components(data-display.ts:1209-1237) — the same treatment, same card.So the "state it as declared-but-unread" branch would have been false on this tree. The table takes the other branch the ruling allows: both members are omitted, with a sentence saying why and citing the card the JSDoc names (objectui#6972, ADR-0049). No edit to the type.
Per-key table
MarkdownSchema extends BaseSchema, andBaseSchemacarries[key: string]: anyatpackages/types/src/base.ts:467— so an annotation checks member types and never keys (objectui#7927). Measured, not assumed: see positive-control leg 2 below.typedata-display.ts:1169,'markdown'src/index.tsx:47-51under namespaceplugin-markdowncontentdata-display.ts:1172,content: string(required)src/index.tsx:39, forwarded toMarkdownImpl(MarkdownImplProps.content: string, non-optional)classNamebase.ts:171viaBaseSchemasrc/index.tsx:40, forwarded toMarkdownImplsanitizedata-display.ts:1208,?: nevercomponentsdata-display.ts:1237,?: neverPhantoms (undeclared and never read) written by any example: none. Undeclared-but-read keys: none —
MarkdownRendererforwards exactlycontentandclassName.Two line-number corrections to the dispatch's citations, measured on this base: the renderer's reads are at
src/index.tsx:39-40(not:20-21), theinputsmanifest at:54-61(not:35), anddefaultPropsat:62-64(not:43). The registration itself is at:47-51as the brief states.Defaults check, both directions
Authority:
packages/plugin-markdown/src/index.tsx(the renderer) andMarkdownImpl.tsx.// Markdown content …,// Tailwind classes), not defaults — so there was no wrong documented default to repair.content, stated on the page and neither widened nor narrowed. The type declarescontent: stringrequired, and theinputsmanifest declaresrequired: true— but the renderer readsschema.contentwith an empty-string fallback, so a node that omits it renders empty instead of failing. Both statements are true; the page now says so.className: no@defaulttag, absent fromdefaultProps, forwarded as-is — agree.@defaulttags on live members: none. The only@defaulton the interface belonged tosanitize, now a tombstone.Proposal, not a card and not an edit — the objectui#8246 shape. The registration's
defaultProps(src/index.tsx:62-64) records acontentsample (# Hello World …), while the renderer's default for an omittedcontentis the empty string. Re-verified on this head: the only non-test read ofdefaultPropsinpackages/core/srcorpackages/react/srcisWidgetRegistry.ts:189, which writes manifest defaults into the widget it builds rather than feeding the render path. The value an author infers from the manifest is not the value they get. Same shape as objectui#8246, so it is recorded here rather than filed again, andsrc/**is not touched.Ledger decision
Ruling 6, first branch: the file reads zero after the repairs, so its
UNGATED_DOCSrow is deleted rather than rewritten. No fragment marker was added — nothing on this page is parked on a filed defect. The ledger reads 3 rows on the head (README.md,packages/fields/README.md,packages/plugin-map/README.md).The pin (
scripts/__tests__/check-doc-snippet-types.test.ts:596-601) enumerates only the rootREADME.mdrow and otherwise iterates the ledger generically, so no pin edit was owed — the same finding batches 28-33 recorded.Strictness region
The region from the
Fence scanningbanner to EOF, sha256:cdbc852e92749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084ba614ee8382749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084bIdentical, recomputed on both trees. The banner moved from line 786 to 784; the two deleted lines are the ledger row, which sits above it.
Positive control, two legs
Both run against the committed tree, under a
trapwith absolute paths, with a unique multi-line anchor (batch 32's first attempt matched two fences; here the anchor includes the// Now you can use markdown type in your schemascomment, which occurs once). On-disk arrival was proven by marker counts, never by an editor exit code: marker 0 before, 1 after; the untouched control anchor stable at 1.Leg 1 — a type error on a DECLARED member.
content: 42on the newly typed binding at fence 24.4faeb86ae4a10179999019b0b62d44fe8a2189e0, mutated blob9ef5a597f11fb56591968423cabdf5ca5fd6fa6cpnpm check:doc-snippetsexit 1:[semantic] packages/plugin-markdown/README.md:32:3 TS2322: Type 'number' is not assignable to type 'string'.andSemantic phase: 621 of 621 block(s) judged, 1 failed.Leg 2 — an invented key on the same binding.
inventedKeyNobodyDeclared: 'x'added to the same object.9a2274edae3ca73c215f085e480ae9f7621b11bcpnpm check:doc-snippetsexit 0,621 of 621 judged, 0 failedLeg 2 is the measured statement of the objectui#7927 bound, reported as such: because
BaseSchemacarries an index signature, the annotation this PR adds checks member types and never keys. It buys real checking (leg 1 proves it) and it does not buy key checking.Restore proven by STATE, both legs, not by an exit code:
git diff HEAD -- packages/plugin-markdown/README.mdempty andgit hash-objectback to4faeb86ae4a10179999019b0b62d44fe8a2189e0, equal to the HEAD blob.Gates
All pinned to head
a614ee838with a clean tree; every exit code captured by redirect-then-capture, never through a pipe.pnpm 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-markdown/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 hits)pnpm check:entry-guardnode scripts/check-changeset-presence.mjsnode scripts/check-governed-queue-guard.mjs --test(both paths)pnpm check:esm-specifierspnpm check:doc-example-readerspnpm check:node-esm-loadobjectui-issue-5174-b33's entry), 0 with--force-build(provenance 37/37 built by this tree, load leg 34/39)Because both changed paths report NOT GOVERNED, this PR carries no maintainer speed-read section.
Assumptions checked
TS1005(not two of one code):TS1005at 3:10 forcontent: string,andTS1109at 4:13 forclassName?: string.register(type, component, meta?)atpackages/core/src/registry/Registry.ts:433; the no-namespace form warns it is deprecated at:439-450. The page taught the deprecated call.git logon both paths tops out at the basecdbc852e9; agit ls-remote --heads originscan of 1011 heads finds no other branch namingdoc-snippet,5174or this page. Re-fetched after the work:origin/mainstillcdbc852e9, so no merge was needed.markdownComponents(dist/index.d.ts:18) andMarkdownSchema(dist/index.d.ts:2) are both real exports;check:readme-exportsgreen on base and head.packages/plugin-markdown/src/types.ts:40re-exportsMarkdownSchemafrom@object-ui/typesand declares nothing of its own.Generated by Claude Code