docs(plugin-ai): compile the README's snippets against the shipped surface, retire its UNGATED_DOCS row - #8180
Conversation
…rface
Burns down the `packages/plugin-ai/README.md` entry in this gate's
`UNGATED_DOCS` ledger (objectui#5174, batch 27). The entry is deleted
rather than rewritten because the page now reads zero.
The page taught an API the components do not have. All three exported
components take a single `schema` prop plus callbacks
(`AIFormAssistProps`, `AIRecommendationsProps`, `NLQueryInputProps`),
while every example passed flat props -- `formId=`, `objectName=`,
`fields=`, `maxResults=`, `layout=`. The README was wrong against
components that are right, so the README is what moves: one typed
binding per example, annotated with the schema type `@object-ui/types`
exports, passed as `schema={...}`, with `declare const` stand-ins for
the two ambient names the page never defined.
Three of the four `tsx` fences were bare JSX with no import line, so
the undefined component name short-circuited the prop check on the
same element: the ledger's `TS2322x3` was the visible half of six.
The three latent ones surface only once the names resolve, and this
diff pays down all six.
No `packages/plugin-ai/src/**` or `packages/types/src/**` edit, no
widened type, no fragment marker, no loosened gate. The gate file's
strictness region is byte-identical.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
…27-plugin-ai Keeps the batch-27 work on a head that carries #8159. The merged commit touches packages/plugin-charts, packages/types/src/data-display.ts and content/docs/plugins/plugin-charts.mdx -- disjoint from this branch's file surface (packages/plugin-ai/README.md, scripts/check-doc-snippet-types.mjs), so every gate reading in the PR body is pinned to the merged head. 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-06T23:07:02Z as Batch 28 ( Generated by Claude Code |
Part of #5174 (batch 27:
packages/plugin-ai/README.md)Burns down the
packages/plugin-ai/README.mdrow inscripts/check-doc-snippet-types.mjs'sUNGATED_DOCSledger. The page's fourtsxfences now compile against the built types, so the row is deleted rather than rewritten.Base:
origin/main9bfd61848(carries PR #8158, which re-baselined this gate's strictness region).origin/mainmoved to967e5d803mid-task and was merged in — never rebased — so every reading below is pinned to the merged head61759cfcc.Takeover
The first dev of this batch died at ~22:14Z (container restart) leaving one local, unpushed commit
80210b0d4on a clean tree, no PR and no report. This PR inherits that commit rather than restarting; it was treated as an unreviewed patch and re-verified against every ruling before anything was added to it.What
80210b0d4held:packages/plugin-ai/README.md+103/−50 andscripts/check-doc-snippet-types.mjs−2.What was re-verified, independently: the census on both sides was re-taken with the gate's own exported analyzer (
scanFences/analyze/compileSnippets, never a hand-written regex); the per-key table below was re-derived from the schema declarations and the components' destructures; the strictness region was re-hashed on both sides; the ledger diff was confirmed to be the two entry lines and nothing else; and the positive control — the step the first dev died inside — was redone from scratch on a tree proven clean first (git status --porcelainempty,git diff HEADzero bytes, so no half-applied injection survived the restart).What changed on top: one merge commit for
origin/main967e5d803. No fix-up commit was needed —80210b0d4violated no ruling. Nothing was amended and nothing was force-pushed.Census, before and after
Measured with the gate's own analyzer, with this page forced into the compiled tier and every other ledger row left ungated, so exactly one page is under measurement.
Before — base
9bfd61848, 8 diagnostics. Reproduces the ledger row (5 undefined-name … plus TS2322x3) exactly:tsxTS2322×3 (all three components' props),TS2552Cannot find name 'recommendationsData'tsxTS2304Cannot find name 'AIFormAssist'tsxTS2304Cannot find name 'AIRecommendations',TS2304Cannot find name 'data'tsxTS2304Cannot find name 'NLQueryInput'By code:
TS2322×3,TS2304×4,TS2552×1.After — head
61759cfcc, 0 diagnostics. Fences moved to lines 29, 87, 114, 140; all four CLEAN. Fourtsxfences before and after, zero fragment markers on either side.The blind spot: the ledger's
TS2322x3was the visible half of sixThe ledger counted 3 prop mismatches. There were 6. An unresolved JSX tag name short-circuits the prop check on that same element, so fences 69, 84 and 99 — bare JSX with no import line — hid one
TS2322each behind theirTS2304. Measured by giving those fences, in memory only, the import line the page never wrote and ananystand-in for the two ambient names, touching nothing about the props: the count goes 3 → 6, one per fence, and no other code appears.This is a property of TypeScript's diagnostic suppression, not a gate defect, but it means any ledger row that mixes undefined-name diagnostics with type codes is a lower bound on that page's real debt. Worth knowing for the remaining rows. The page pays down all 11 real diagnostics (5 undefined-name + 6 prop mismatches).
What the repair was, and why the README moved rather than the components
All three exported components take exactly one
schemaprop plus callbacks —AIFormAssistProps,AIRecommendationsProps,NLQueryInputPropsinpackages/plugin-ai/src/*.tsx. Every README example passed flat props (formId=,objectName=,fields=,maxResults=,layout=, …). So the page taught an API the components do not have, against components that are right: the README is what moves. One typed binding per example from the schema type@object-ui/typesexports, passed asschema={…}, withdeclare conststand-ins for the two ambient names the page never defined (typed asAIRecommendationItem[], notany).No
@ts-expect-error, no fragment marker, no widened or loosened type, no edit topackages/plugin-ai/src/**orpackages/types/src/**, no gate strictness change.The registry
typediscriminators were read from theComponentRegistry.registercalls inpackages/plugin-ai/src/index.tsx, not from the README — and one of them is not the component name:NLQueryInputregisters asnl-query. The page now says so in a table, because that was undocumented and is exactly the kind of thing a metadata author guesses wrong.The
Schema-Driven Usageheading was promoted from###to##: it documents registry usage, not a component, so it did not belong inside theComponentssection.Per-key table (ruling 3 — the key-surface bound)
BaseSchemacarries[key: string]: anyatpackages/types/src/base.ts:398, so aBaseSchema-derived annotation checks member types and never member keys (objectui#7927). Every key each example writes was therefore classified by hand against the schema type's own declaration — the compiler cannot do it.Result: all 14 keys the examples write are declared. Nothing was dropped, and no key is "undeclared but read at runtime".
type(all 3)ai.ts:92/:204/:316formIdai.ts:97objectName(all 3)ai.ts:102/:209/:321fieldsai.ts:107autoFillai.ts:127AIFormAssist.tsx:31showConfidenceai.ts:132AIFormAssist.tsx:29showReasoningai.ts:137AIFormAssist.tsx:30recommendationsai.ts:224AIRecommendations.tsx:28maxResultsai.ts:229showScoresai.ts:234AIRecommendations.tsx:29layoutai.ts:239AIRecommendations.tsx:30emptyMessageai.ts:259AIRecommendations.tsx:32placeholderai.ts:326NLQueryInput.tsx:26suggestions(NLQuery)ai.ts:341NLQueryInput.tsx:28showHistoryai.ts:346NLQueryInput.tsx:29Finding proposal, filed as #8178, not fixed here. Seven of those declared members are read by nothing:
formId,objectName(all three schemas),fields,maxResults, andautoFill(destructured atAIFormAssist.tsx:31and never referenced again).maxResultsis the sharp one — its doc comment saysMaximum number of results to displayandAIRecommendationsrenders every item with no slice anywhere.grep -rn maxResults packages/*/srcreturns exactly two hits, the declaration and the designer input metadata, and zero reads. Five of the seven are also advertised as authorable inputs byComponentRegistry.registerinpackages/plugin-ai/src/index.tsx, so the designer offers keys the renderer ignores. That is the same class objectui#7742 already carries forKanbanSchema; enforce-or-remove is a maintainer call and #8178 does not pick one. It is out of scope for this PR under the ruling that declared keys are kept — the examples keep them, and the snippets compile, because the gate can see types and not inertness.Ledger decision
The page reads zero, so its row is deleted, not rewritten. The gate diff is exactly the two lines of that row (
git show 80210b0d4 -- scripts/check-doc-snippet-types.mjs:-2, no additions). Package ledger rows: 11 → 10. Gate counters on the head: 229 documents scanned, 219 covered, 10 ungated; compiled blocks 583 → 587 (this page's four fences joined the compiled tier); declared fragments 158 → 158 — no marker was added to buy a pass.The
jsonfence (now line 167) writestype,formId,objectName,fields,showConfidence— all declared, and no key was dropped under ruling 3, so it is left alone. It is read bycheck:doc-types, which is green.Strictness region
The region from the
Fence scanningbanner to EOF, sha256, re-baselined from PR #8158:9bfd618482749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084b61759cfcc2749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084bByte-identical. The deleted ledger row sits above the banner.
Positive control
Redone from scratch against the committed tree, under a
trap … EXIT INT TERMwith absolute paths.git status --porcelainempty,git diff HEAD0 bytes, README on disk680ca1be68f88d4f658be017f244beca503c3978= its HEAD blob.showConfidence: 42(wastrue) into the Quick Start fence. Landed on disk, proven by anchor counts (1 occurrence of the injected text, one fewer of the anchor) and by the blob hash moving to4a3cd757a606cdff5fa987bc8506607244951179— not by the editor's exit code.pnpm check:doc-snippets→ exit 1, naming the page and the line:[semantic] packages/plugin-ai/README.md:45:3 TS2322: Type 'number' is not assignable to type 'boolean | undefined'.git diff HEAD -- packages/plugin-ai/README.md0 bytes, on-disk hash back to680ca1be68f88d4f658be017f244beca503c3978= HEAD blob,git status --porcelainempty.So the page is genuinely in the compiled tier and the gate would catch a regression on it.
Gates — all on head
61759cfcc, 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__/(whole directory)pnpm check:doc-typespnpm check:readme-exportspnpm check:doc-fencespnpm type-check:scriptspnpm lint:rootpnpm check:control-bytesgrep -naPcontrol-byte self-scan of both changed pathsnode scripts/check-changeset-presence.mjsnode scripts/check-governed-queue-guard.mjs --test(both paths)pnpm check:entry-guardBuild closure rebuilt on the merged head before any measurement:
pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2→ exit 0, 35/35 tasks.No changeset:
check-changeset-presence.mjsis the authority and it says none is owed. No label was applied.Live E2E (informational)is red on every branch today for an upstream reason (#7990 / objectstack#16186) — not from this change.Generated by Claude Code