Skip to content

feat(docs): docs snippet typecheck for editor/superdoc/** + fix stale examples (SD-673)#3528

Closed
caio-pizzol wants to merge 1 commit into
mainfrom
caio-pizzol/SD-docs-snippet-typecheck
Closed

feat(docs): docs snippet typecheck for editor/superdoc/** + fix stale examples (SD-673)#3528
caio-pizzol wants to merge 1 commit into
mainfrom
caio-pizzol/SD-docs-snippet-typecheck

Conversation

@caio-pizzol

Copy link
Copy Markdown
Contributor

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 onReady body and runs it against a mocked host, so it never sees the destructure bug in the outer config - a typecheck does.

How it works:

  • Extracts "Full Example" code blocks from apps/docs/editor/superdoc/** (JS + TS fences) via the existing extractExamples() (extended to keep fence language)
  • Writes each snippet to a temp file, JS with // @ts-check, plus a shared placeholders.d.ts for docs idioms
  • Runs tsc --noEmit --strict (allowJs + checkJs for JS) against packages/superdoc/dist via tsconfig paths
  • Wired as the last stage of check:public:superdoc; also available standalone via pnpm --filter @superdoc/docs run check:types

Coverage 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-websocket upgrade-to-collaboration example: consumer BYO, not part of SuperDoc's typed surface - added to SKIP_IMPORTS).

Placeholder stubs (11 ambient declarations)

Pure test-infra; doesn't change any docs. Stubbed in a shared placeholders.d.ts so docs examples can reference common identifiers without inline declarations.

Kind Identifiers
Files / strings yourFile, file, doc1, doc2, content
Helper functions cleanup, autoSave, adjustLayout, showOnlineUsers, updateUserCursors, showLockBanner

Real 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.

File Fix
methods.mdx onReady: (superdoc) =>onReady: ({ superdoc }) => in 22 examples (typed payload is { superdoc }, not the instance)
methods.mdx setActiveEditor example rewritten to capture editors via onEditorCreate (was reaching into Document.editor, not on the public type)
methods.mdx search / goToSearchResult null guards on the optional SearchMatch[] | undefined return
methods.mdx addCommentsList signature corrected to HTMLElement (was documented as string | HTMLElement; actual is HTMLElement)
methods.mdx scrollToComment / scrollToElement: superdoc.editorsuperdoc.activeEditor with null guards (activeEditor is Editor | null; editor doesn't exist as a public member); narrowed BlockNodeAddress before .nodeId; switched .entityId.id on DiscoveryItem
methods.mdx off example: handler annotated with /** @param {import('superdoc').SuperDocReadyPayload} payload */ so the standalone-handler shape pins to the typed payload
import-export.mdx onReady: (superdoc) =>onReady: ({ superdoc }) => in 5 examples
import-export.mdx Null guards on superdoc.activeEditor before .getHTML / .getJSON / .getMarkdown / .commands access
events.mdx Subscribing-events handler now uses JSDoc to type the standalone handler
events.mdx editor-update: null guard on the optional editor field (EditorUpdateEvent.editor?: Editor)
events.mdx content-error: removed documentId from destructure (SuperDocContentErrorPayload is { error, editor }; no documentId)
events.mdx comments-update: { type, data }{ type, comment, changes } (matches SuperDocCommentsUpdatePayload; same drift class as #3503)
events.mdx awareness-update: { context, states }{ states, added, removed } (runtime emits superdoc not context; same drift class)
events.mdx locked: added && lockedBy guard before reading .name (lockedBy: User | null)
events.mdx pagination-update: removed pagination: true from Config example (no such field on the typed Config)
events.mdx exception: rewrote to take payload and read payload.error, with a comment on narrowing the discriminated union (document/editor only exist on specific variants)
collaboration/configuration.mdx lockedBy type cell: ObjectUser | null with a null-check note

Must stay the same: check:public:superdoc keeps passing.

Review:

  • The new wrapper stage runs after build and depends on packages/superdoc/dist existing. CI builds dist before this stage; local devs need pnpm --filter superdoc build (or pnpm check:public:superdoc without --skip-build).
  • If this fails locally, rebuild packages/superdoc/dist before 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: true is intentional in the temp tsconfig: this gate checks docs usage against the public surface, not library declarations (deep-type-audit-supported-root already covers that).

Verified:

  • pnpm --filter @superdoc/docs run check:types → OK, 47/47 typechecked clean
  • Synthetic regression: re-introducing one onReady: (superdoc) => locally produced the expected failure with correct source file:line
  • pnpm check:public:superdoc --skip-build → PASS (12 ran, 1 skipped, 130.8s)

…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)
@caio-pizzol
caio-pizzol requested a review from a team as a code owner May 27, 2026 11:32
@linear-code

linear-code Bot commented May 27, 2026

Copy link
Copy Markdown

SD-673

@github-actions

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread apps/docs/editor/superdoc/methods.mdx

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Re-trigger cubic

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants