Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/superdoc/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,28 @@ superdoc.on('editorCreate', ({ editor }) => {

For backend or AI agent workflows, use the [SDK](https://docs.superdoc.dev/document-engine/sdks), [CLI](https://docs.superdoc.dev/document-engine/cli), or [MCP server](https://docs.superdoc.dev/document-engine/ai-agents/mcp-server) instead of browser editor access.

## Contributing to the public surface

If you're adding, removing, or moving a name on the `superdoc` root entry, the source of truth is `packages/superdoc/src/public/index.ts`. It is organized in three tiers per SD-3212:

1. **Supported root** — documented public API; first-class root surface. Clean re-exports.
2. **Legacy root** — typed for backward compatibility; not the recommended path. Per-name `@deprecated` JSDoc only where a real replacement exists (e.g. `editor.commands.*` → `editor.doc.*` Document API). Section-level framing carries "legacy compat" intent when no replacement exists.
3. **Internal candidate** — accidental implementation leak. Kept typed under compat re-export with `@internal` so a future major can remove it. Only exists at root because at least one supported/legacy export reaches it transitively.

When changing the surface, every PR must also update:

- `tests/consumer-typecheck/snapshots/superdoc-root-classification.json` (bucket + rationale per name).
- `tests/consumer-typecheck/snapshots/superdoc-root-exports.json` (the 4-source no-growth lock).
- `packages/superdoc/scripts/verify-public-facade-emit.cjs` `FACADE_ENTRIES['root (./index)'].expectedNames`.

Three CI gates enforce consistency and will fail the build if any of these drift:

- `verify-public-facade-emit.cjs` — symbol set, ESM/CJS parity, leak grep.
- `snapshot-superdoc-root-exports.mjs --check` — 4-source no-growth lock.
- `check-root-classification-closure.mjs` — no supported/legacy export references an internal-candidate type (dependency-closure rule).

For overrides on the closure gate (rare; only DOM globals / upstream / generic utility types), add an entry to `OVERRIDES` in `check-root-classification-closure.mjs` with a reason string ≥ 20 chars.

## Knowledge corrections

If your training data mentions SuperDoc, note these updates:
Expand Down
69 changes: 38 additions & 31 deletions packages/superdoc/scripts/ensure-types.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -250,39 +250,46 @@ if (!hasSuperDocExport) {
process.exit(1);
}

// @superdoc/common is a private workspace package, so consumers can't
// resolve a bare `from '@superdoc/common'` import. The main entry
// (superdoc/src/index.d.ts) imports runtime values from it — DOCX/PDF/
// HTML constants, getFileObject, compareVersions, BlankDOCX (the last
// from a Vite `?url` import that vite-plugin-dts can't type). Strip
// that import statement and inline ambient declarations for those
// values. Type-only imports of @superdoc/common from other dist files
// are handled separately by the RELOCATION_RULES rewriter below, which
// @superdoc/common is a private workspace package, so consumers can't resolve
// bare or deep `@superdoc/common` imports from emitted declarations. The root
// entry and the path-as-contract public facade both expose a small set of
// runtime values from common (DOCX/PDF/HTML constants, getFileObject,
// compareVersions, BlankDOCX). Strip those imports and inline declarations for
// the exported names. Type-only imports of @superdoc/common from other dist
// files are handled separately by the RELOCATION_RULES rewriter below, which
// maps bare @superdoc/common to dist/shared/common/comments-types.d.ts.
const hadWorkspaceImport = content.includes('@superdoc/common');
if (hadWorkspaceImport) {
// Replace the @superdoc/common import with inline declarations
content = content.replace(
/import\s*\{[^}]*\}\s*from\s*['"]@superdoc\/common['"];?\s*\n?/g,
'',
);
function inlineCommonRuntimeDeclarations(filePath) {
let fileContent = fs.readFileSync(filePath, 'utf8');
if (!fileContent.includes('@superdoc/common')) return false;

fileContent = fileContent
.replace(/import\s*\{[^}]*\}\s*from\s*['"]@superdoc\/common['"];?\s*\n?/g, '')
.replace(/import\s*\{[^}]*\}\s*from\s*['"]@superdoc\/common\/document-types['"];?\s*\n?/g, '')
.replace(/import\s*\{[^}]*\}\s*from\s*['"]@superdoc\/common\/helpers\/get-file-object['"];?\s*\n?/g, '')
.replace(/import\s*\{[^}]*\}\s*from\s*['"]@superdoc\/common\/helpers\/compare-superdoc-versions['"];?\s*\n?/g, '');

const hasExportedBlankDocxDeclaration = /\bexport\s+declare\s+const\s+BlankDOCX\b/.test(fileContent);
const declarations = [
fileContent.includes('DOCX') && "declare const DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';",
fileContent.includes('PDF') && "declare const PDF: 'application/pdf';",
fileContent.includes('HTML') && "declare const HTML: 'text/html';",
fileContent.includes('getFileObject') && 'declare function getFileObject(fileUrl: string, name: string, type: string): Promise<File>;',
fileContent.includes('compareVersions') && 'declare function compareVersions(version1: string, version2: string): -1 | 0 | 1;',
fileContent.includes('BlankDOCX') && !hasExportedBlankDocxDeclaration && '/** URL to the blank DOCX template */',
fileContent.includes('BlankDOCX') && !hasExportedBlankDocxDeclaration && 'declare const BlankDOCX: string;',
].filter(Boolean);

fs.writeFileSync(filePath, `${declarations.join('\n')}\n${fileContent}`);
return true;
}

// BlankDOCX comes from a Vite ?url import (resolves to a string at runtime)
// Declare it since vite-plugin-dts can't generate types for ?url imports
const inlineDeclarations = [
'/** Document MIME type constants */',
"declare const DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';",
"declare const PDF: 'application/pdf';",
"declare const HTML: 'text/html';",
'declare function getFileObject(fileUrl: string, name: string, type: string): Promise<File>;',
'declare function compareVersions(version1: string, version2: string): -1 | 0 | 1;',
'/** URL to the blank DOCX template */',
'declare const BlankDOCX: string;',
].join('\n');

content = inlineDeclarations + '\n' + content;
fs.writeFileSync(indexPath, content);
console.log('[ensure-types] ✓ Inlined @superdoc/common types');
const commonInlineTargets = [
path.join(distRoot, 'superdoc/src/index.d.ts'),
path.join(distRoot, 'superdoc/src/public/index.d.ts'),
];
const inlinedCommonTargets = commonInlineTargets.filter((target) => fs.existsSync(target) && inlineCommonRuntimeDeclarations(target));
if (inlinedCommonTargets.length) {
console.log(`[ensure-types] ✓ Inlined @superdoc/common runtime declarations in ${inlinedCommonTargets.length} entry point(s)`);
}

// ---------------------------------------------------------------------------
Expand Down
201 changes: 186 additions & 15 deletions packages/superdoc/scripts/verify-public-facade-emit.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,52 +77,223 @@ try {
// this gate. Link the PR to SD-3175 (path-as-contract umbrella) for
// reviewer sign-off when growth is intentional.
const FACADE_ENTRIES = [
// SD-3178 + SD-3185: root facade. The supported programmatic surface
// is the Document API (`editor.doc.*`); see `packages/superdoc/AGENTS.md`
// and the @deprecated tags on `editor.commands` in `Editor.ts`. The
// 20 Document API types preserved here mirror the JSDoc @typedef block
// in `packages/superdoc/src/index.js` and the assertions in
// `tests/consumer-typecheck/src/all-public-types.ts` — they were already
// typed via the root entry; this just makes the path-as-contract
// version explicit.
//
// EditorCommands stays in the surface for backward compatibility but is
// tagged @deprecated at the re-export site. The command-signature probe
// continues to run on this entry: it is now a *legacy command-signature
// compatibility check* (catches SD-2965-style augmentation drops on the
// deprecated surface) rather than a guarantee about supported API.
// SD-3212: root facade re-curated from the classification artifact.
// expectedNames intentionally mirrors
// tests/consumer-typecheck/snapshots/superdoc-root-classification.json.
// The root entry keeps supported public API, legacy public compatibility,
// and internal-candidate compat names typed until a major-version cleanup.
// The command-signature probe continues to run on this entry: it is a
// *legacy command-signature compatibility check* (catches SD-2965-style
// augmentation drops on the deprecated surface) rather than a guarantee
// about supported API.
{
name: 'root (./index)',
esm: path.join(PUBLIC_DIST, 'index.d.ts'),
cjs: path.join(PUBLIC_DIST, 'index.d.cts'),
expectedNames: [
'AIWriter',
'AnnotatorHelpers',
'assertNodeType',
'AwarenessState',
'BinaryData',
'BlankDOCX',
'BlockNavigationAddress',
'BlocksListResult',
'BookmarkAddress',
'BookmarkInfo',
'BoundingRect',
'buildTheme',
'CanObject',
'ChainableCommandObject',
'ChainedCommand',
'CollaborationConfig',
'CollaborationProvider',
'Command',
'CommandProps',
'Comment',
'CommentAddress',
'CommentConfig',
'CommentElement',
'CommentLocationsPayload',
'CommentsPayload',
'CommentsPluginKey',
'CommentsType',
'compareVersions',
'Config',
'ContextMenu',
'ContextMenuConfig',
'ContextMenuContext',
'ContextMenuItem',
'ContextMenuSection',
'CoreCommandMap',
'createTheme',
'createZip',
'defineMark',
'defineNode',
'DirectSurfaceRequest',
'DocRange',
'DocumentApi',
'DocumentMode',
'DocumentProtectionState',
'DOCX',
'DocxFileEntry',
'DocxZipper',
'Editor',
'EditorCommands',
'EditorEventMap',
'EditorExtension',
'EditorLifecycleState',
'EditorOptions',
'EditorState',
'EditorSurface',
'EditorTransactionEvent',
'EditorUpdateEvent',
'EditorView',
'EntityAddress',
'ExportDocxParams',
'ExportFormat',
'ExportOptions',
'ExportParams',
'ExportType',
'ExtensionCommandMap',
'Extensions',
'ExternalPopoverRenderContext',
'ExternalSurfaceRenderContext',
'fieldAnnotationHelpers',
'FieldValue',
'FindReplaceConfig',
'FindReplaceContext',
'FindReplaceHandle',
'FindReplaceRenderContext',
'FindReplaceResolution',
'FlowBlock',
'FlowMode',
'FontConfig',
'FontsResolvedPayload',
'getActiveFormatting',
'getAllowedImageDimensions',
'getFileObject',
'getMarksFromSelection',
'getRichTextExtensions',
'getSchemaIntrospection',
'getStarterExtensions',
'HTML',
'ImageDeselectedEvent',
'ImageSelectedEvent',
'IntentSurfaceRequest',
'isMarkType',
'isNodeType',
'Layout',
'LayoutEngineOptions',
'LayoutError',
'LayoutFragment',
'LayoutMetrics',
'LayoutMode',
'LayoutPage',
'LayoutState',
'LayoutUpdatePayload',
'LinkPopoverContext',
'LinkPopoverResolution',
'LinkPopoverResolver',
'ListDefinitionsPayload',
'Measure',
'Modules',
'NavigableAddress',
'OpenOptions',
'PageMargins',
'PageSize',
'PageStyles',
'PaginationPayload',
'PaintSnapshot',
'PartChangedEvent',
'PartId',
'PartSectionId',
'PasswordPromptAttemptResult',
'PasswordPromptConfig',
'PasswordPromptContext',
'PasswordPromptHandle',
'PasswordPromptRenderContext',
'PasswordPromptResolution',
'PDF',
'PermissionParams',
'PositionHit',
'PresenceOptions',
'PresentationEditor',
'PresentationEditorOptions',
'ProofingCapabilities',
'ProofingCheckRequest',
'ProofingCheckResult',
'ProofingConfig',
'ProofingError',
'ProofingIssue',
'ProofingIssueKind',
'ProofingProvider',
'ProofingSegment',
'ProofingSegmentMetadata',
'ProofingStatus',
'ProseMirrorJSON',
'ProtectionChangeSource',
'RangeRect',
'registeredHandlers',
'RemoteCursorsRenderPayload',
'RemoteCursorState',
'RemoteUserInfo',
'ResolvedFindReplaceTexts',
'ResolvedPasswordPromptTexts',
'ResolveRangeOutput',
'SaveOptions',
'Schema',
'ScrollIntoViewInput',
'ScrollIntoViewOutput',
'SearchMatch',
'SectionHelpers',
'SectionMetadata',
'SelectionApi',
'SelectionCommandContext',
'SelectionCurrentInput',
'SelectionHandle',
'SelectionInfo',
'SlashMenu',
'StoryLocator',
'SuperConverter',
'SuperDoc',
'SuperDocLayoutEngineOptions',
'SuperDocTelemetryConfig',
'SuperEditor',
'superEditorHelpers',
'SuperInput',
'SuperToolbar',
'SurfaceComponentProps',
'SurfaceFloatingPlacement',
'SurfaceHandle',
'SurfaceMode',
'SurfaceOutcome',
'SurfaceRequest',
'SurfaceResolution',
'SurfaceResolver',
'SurfacesModuleConfig',
'TelemetryEvent',
'TextAddress',
'TextSegment',
'TextTarget',
'Toolbar',
'TrackChangesBasePluginKey',
'trackChangesHelpers',
'TrackChangesModuleConfig',
'TrackedChangeAddress',
'TrackedChangesMode',
'TrackedChangesOverrides',
'Transaction',
'UnsupportedContentItem',
'UpgradeToCollaborationOptions',
'User',
'ViewingVisibilityConfig',
'ViewLayout',
'ViewOptions',
'VirtualizationOptions',
],
runsCommandSignatureProbe: true,
ticket: 'SD-3185',
ticket: 'SD-3212',
},
{
name: 'legacy/headless-toolbar',
Expand Down
Loading
Loading