feat(docs): docs snippet typecheck for editor/superdoc/** + fix stale examples (SD-673)#3528
Closed
caio-pizzol wants to merge 1 commit into
Closed
feat(docs): docs snippet typecheck for editor/superdoc/** + fix stale examples (SD-673)#3528caio-pizzol wants to merge 1 commit into
caio-pizzol wants to merge 1 commit into
Conversation
…examples (SD-673) Adds a CI-deterministic gate that extracts 'Full Example' code blocks from apps/docs/editor/superdoc/**, writes each snippet to a temp file with a small shared ambient prelude, and runs tsc --noEmit --strict against packages/superdoc/dist. JS fences are checked with allowJs + checkJs + // @ts-check; TS fences are checked with full strict. Why: the existing runtime doctest (apps/docs/__tests__/doctest.test.ts) extracts the onReady body and executes it against a mocked superdoc host, so it never catches destructure bugs in the outer config example. The bug class fixed by #3503 (typed callback payloads that silently drifted) was teaching the wrong shape in docs for months — nothing in CI caught it. This gate catches it at write time. Mechanism: - Extends lib/extract.ts to keep the fence language on each example. - Adds yjs / y-websocket to SKIP_IMPORTS (consumer BYO; not part of SuperDoc's typed surface). - New doctest-types.ts script: iterates pattern='superdoc' examples in scope, writes one temp .ts/.js per snippet plus a shared placeholders.d.ts (yourFile, doc1, doc2, cleanup, autoSave, etc.), runs tsc against the packed dist via tsconfig paths, parses errors and maps them back to source file:line. - New apps/docs script entry: pnpm --filter @superdoc/docs run check:types - Wired as the last stage of scripts/check-public-contract.mjs. Fixes (split per the user's request between placeholder-only stubs and real public-shape drift): PLACEHOLDER STUBS (11 ambient declarations in placeholders.d.ts): - File/string values: yourFile, file, doc1, doc2, content - Helper functions: cleanup, autoSave, adjustLayout, showOnlineUsers, updateUserCursors, showLockBanner REAL DOCS API DRIFT FIXES (in scope: editor/superdoc/** + the collaboration configuration cell flagged in audit): apps/docs/editor/superdoc/methods.mdx (29 examples touched): - 22 onReady: (superdoc) => fixed to onReady: ({ superdoc }) => (the typed callback payload is { superdoc }, not the instance) - setActiveEditor example rewritten to capture editors via onEditorCreate (the previous example reached into runtime-only Document.editor which isn't on the public Document type) - search / goToSearchResult: added null guards on the optional SearchMatch[] | undefined return - addCommentsList: signature corrected to HTMLElement (was wrongly documented as string | HTMLElement; the actual type is HTMLElement) - scrollToComment / scrollToElement: superdoc.editor -> superdoc.activeEditor with null guards (activeEditor is Editor | null on the typed surface; superdoc.editor doesn't exist as a public member) - off example: handler annotated with /** @param SuperDocReadyPayload */ so the standalone-handler shape pins to the typed payload - scrollToElement: narrowed BlockNodeAddress before reading nodeId; switched .entityId to .id on DiscoveryItem (the canonical field name) apps/docs/editor/superdoc/events.mdx (8 examples touched): - Subscribing handler now uses JSDoc to type the standalone handler - editor-update: added null guard on the optional editor field (EditorUpdateEvent.editor?: Editor) - content-error: removed documentId from destructure (SuperDocContentErrorPayload is { error, editor }; no documentId) - comments-update: { type, data } -> { type, comment, changes } (matches SuperDocCommentsUpdatePayload, the same drift class fixed in the typed surface by #3503) - awareness-update: { context, states } -> { states, added, removed } (same drift class; runtime emits 'superdoc' not 'context') - locked: added && lockedBy guard before reading lockedBy.name (SuperDocLockedPayload.lockedBy is User | null) - pagination-update: removed 'pagination: true' from Config example (no such field on the typed Config) - exception: rewrote to take payload and read payload.error directly, with a comment on how to narrow the discriminated union (the union has three variants and 'document'/'editor' only exist on specific ones) apps/docs/editor/superdoc/import-export.mdx (5 examples touched): - 5 onReady: (superdoc) => fixed to onReady: ({ superdoc }) => - Added null guards on superdoc.activeEditor before .getHTML / .getJSON / .getMarkdown / .commands access - Switched Usage tabs that show 'superdoc.activeEditor.getX()' to 'superdoc.activeEditor?.getX()' for the same reason apps/docs/editor/collaboration/configuration.mdx (1 cell): - lockedBy type cell: 'Object' -> 'User | null' with a note that the example needs a null check before reading .name Result: - 47 in-scope examples (was 0 type-checked before this PR) - 0 unmet — strict-zero from day 1 for this scope - 1 yjs/y-websocket example skipped (out of scope: consumer BYO, not part of SuperDoc's typed surface) Synthetic regression verified: re-introducing one onReady: (superdoc) => locally produced the expected failure 'Property getHTML does not exist on type SuperDocReadyPayload' with the correct source file:line. Verified: - pnpm --filter @superdoc/docs run check:types -> OK, 47/47 - pnpm check:public:superdoc --skip-build -> PASS (12 ran, 1 skipped, 130.8s)
Contributor
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 57383eb0cb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Contributor
There was a problem hiding this comment.
cubic analysis
No issues found across 9 files
Linked issue analysis
Linked issue: SD-673: TypeScript-first public API: SuperDoc and Document API
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Add a deterministic docs snippet type‑check gate that extracts 'Full Example' code blocks (JS/TS) and runs tsc --noEmit --strict (allowJs+checkJs for JS) against packages/superdoc/dist. | The PR adds apps/docs/__tests__/doctest-types.ts which extracts examples, prepares a temp project with a tsconfig pointing superdoc -> packages/superdoc/dist, and invokes tsc. It also extends extractExamples to record fence language for JS vs TS handling. |
| ✅ | Provide shared placeholder ambient declarations so examples can reference common doc identifiers without inlining stubs. | The script writes a shared placeholders.d.ts into the temp project with the declared identifiers used by docs examples. |
| ✅ | Fix docs examples so they match the typed public surface for SuperDoc (ex: onReady destructure, activeEditor null guards, corrected parameter/signature and event payload shapes) under apps/docs/editor/superdoc/**. | The PR updates multiple docs files to use the typed shapes (onReady: ({ superdoc }), null guards for activeEditor, corrected comments-list param type, corrected event payload destructures and guards). The diffs show these targeted fixes across the listed files. |
| ✅ | Wire the new gate into the public check flow and expose a local script to run the gate. | The PR adds a package script to run the types check and registers a docs-snippet-typecheck stage in the public contract check wrapper, so the gate runs as part of check:public:superdoc and can be run standalone. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This was referenced May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a CI-deterministic gate that catches the exact drift class #3503 had to fix manually: docs examples that teach the wrong typed shape. Existing runtime doctest extracts only the
onReadybody and runs it against a mocked host, so it never sees the destructure bug in the outer config - a typecheck does.How it works:
apps/docs/editor/superdoc/**(JS + TS fences) via the existingextractExamples()(extended to keep fence language)// @ts-check, plus a sharedplaceholders.d.tsfor docs idiomstsc --noEmit --strict(allowJs+checkJsfor JS) againstpackages/superdoc/distvia tsconfigpathscheck:public:superdoc; also available standalone viapnpm --filter @superdoc/docs run check:typesCoverage delta: real docs API drift fixes across 4 docs files; 47 in-scope Full Example snippets now typecheck clean (was 0 before this PR). 1 example skipped (
yjs+y-websocketupgrade-to-collaboration example: consumer BYO, not part of SuperDoc's typed surface - added toSKIP_IMPORTS).Placeholder stubs (11 ambient declarations)
Pure test-infra; doesn't change any docs. Stubbed in a shared
placeholders.d.tsso docs examples can reference common identifiers without inline declarations.yourFile,file,doc1,doc2,contentcleanup,autoSave,adjustLayout,showOnlineUsers,updateUserCursors,showLockBannerReal docs API drift fixes (4 files)
These are the typed-public-surface bugs the gate surfaced. None of them were caught at runtime because the runtime doctest doesn't typecheck.
methods.mdxonReady: (superdoc) =>→onReady: ({ superdoc }) =>in 22 examples (typed payload is{ superdoc }, not the instance)methods.mdxsetActiveEditorexample rewritten to capture editors viaonEditorCreate(was reaching intoDocument.editor, not on the public type)methods.mdxsearch/goToSearchResultnull guards on the optionalSearchMatch[] | undefinedreturnmethods.mdxaddCommentsListsignature corrected toHTMLElement(was documented asstring | HTMLElement; actual isHTMLElement)methods.mdxscrollToComment/scrollToElement:superdoc.editor→superdoc.activeEditorwith null guards (activeEditorisEditor | null;editordoesn't exist as a public member); narrowedBlockNodeAddressbefore.nodeId; switched.entityId→.idonDiscoveryItemmethods.mdxoffexample: handler annotated with/** @param {import('superdoc').SuperDocReadyPayload} payload */so the standalone-handler shape pins to the typed payloadimport-export.mdxonReady: (superdoc) =>→onReady: ({ superdoc }) =>in 5 examplesimport-export.mdxsuperdoc.activeEditorbefore.getHTML/.getJSON/.getMarkdown/.commandsaccessevents.mdxevents.mdxeditor-update: null guard on the optionaleditorfield (EditorUpdateEvent.editor?: Editor)events.mdxcontent-error: removeddocumentIdfrom destructure (SuperDocContentErrorPayloadis{ error, editor }; nodocumentId)events.mdxcomments-update:{ type, data }→{ type, comment, changes }(matchesSuperDocCommentsUpdatePayload; same drift class as #3503)events.mdxawareness-update:{ context, states }→{ states, added, removed }(runtime emitssuperdocnotcontext; same drift class)events.mdxlocked: added&& lockedByguard before reading.name(lockedBy: User | null)events.mdxpagination-update: removedpagination: truefrom Config example (no such field on the typed Config)events.mdxexception: rewrote to takepayloadand readpayload.error, with a comment on narrowing the discriminated union (document/editoronly exist on specific variants)collaboration/configuration.mdxlockedBytype cell:Object→User | nullwith a null-check noteMust stay the same:
check:public:superdockeeps passing.Review:
buildand depends onpackages/superdoc/distexisting. CI builds dist before this stage; local devs needpnpm --filter superdoc build(orpnpm check:public:superdocwithout--skip-build).packages/superdoc/distbefore debugging snippet errors. The gate checks against the built.d.ts, so a stale local dist produces misleading failures unrelated to the docs themselves.skipLibCheck: trueis intentional in the temp tsconfig: this gate checks docs usage against the public surface, not library declarations (deep-type-audit-supported-rootalready covers that).Verified:
pnpm --filter @superdoc/docs run check:types→ OK, 47/47 typechecked cleanonReady: (superdoc) =>locally produced the expected failure with correct source file:linepnpm check:public:superdoc --skip-build→ PASS (12 ran, 1 skipped, 130.8s)