types(superdoc): define public type contract and phase 1 gates (SD-2828)#3017
types(superdoc): define public type contract and phase 1 gates (SD-2828)#3017caio-pizzol merged 14 commits intomainfrom
Conversation
48c398e to
9513f20
Compare
Classifies every workspace package and superdoc subpath into a tier (public package, public subpath, public type contract, internal runtime, internal implementation, dev/test). Defines the type ownership and dependency direction rules the audit gate (SD-2832) will enforce. Captures four open questions with tentative recommendations: superdoc/super-editor scope, document-api delivery, layout-engine sub-package count, runtime-only subpath classification. Draft for team review.
…ions (SD-2829) Adds a fourth tier for legacy public compatibility surface so the RFC can capture surfaces that consumers may already depend on without forcing them into a binary public-or-private choice. Reclassifies superdoc/super-editor and the runtime-only subpaths into this tier, with superdoc itself as the migration target. Converts the four open questions into decisions: super-editor as legacy public, document-api as supported public type contract recommended for publishing under the public-package convention, layout-engine sub-packages stay separate, runtime-only subpaths as legacy public. Adds a 6-step playbook for handling legacy public surface (stop advertising, finish the supported replacement, keep compiling, minimal type coverage, deprecation docs, remove only after a documented window). Pending inputs (usage scrape, ai clarification, missing-symbols list) refine the migration plans but no longer block the RFC's classifications.
…es (SD-2829) Reconciles three internal inconsistencies in the RFC after Decision 1 was added: the subpath table still showed `./super-editor` as TBD, the dependency direction rule still hedged with "if it stays public", and a type ownership rule still referenced "open question 2". All three now match the decisions section.
…tions (SD-2829) Updates Decision 2 with the SD-2830 spike evidence: rollup-plugin-dts plus an alias plugin successfully inlines internal types (DocumentApi, FlowBlock, Layout all resolve to real interfaces) but corrupts declare-module augmentation patterns the codebase uses for command-map type augmentation, so the bundled output fails strict tsc validation today. D1 stays viable as a future improvement; D2 (publish the package) is the recommended customer-facing path. Splits the published-name choice into two explicit options: keep `@superdoc/document-api` (fast, no source rewrites, weakens the @superdoc/* taxonomy) versus rename to `@superdoc-dev/document-api` (taxonomy-clean, requires migrating import sites). Both are valid; the team picks one explicitly. Adds a future-state audit rule for the CI section: if we adopt D1 anywhere, the gate must also check that inlined shapes belong to an explicit public-surface allowlist, not just that no private package specifier remains. Inlining hides the package name but does not make the shape contracted.
…rdoc/document-api taxonomy exception (SD-2829) Adds a sequencing section that frames the seven-step plan we converged on after the SD-2830 spike: land the gates first, fix the acute Document API issue, drive supported entries to green, then folder reorganization, then surgical TS migration of public contract files, then long-tail migration on evidence. Reorganization is explicitly out of scope for the current type-contract PRs but recommended once the gates have run for at least one release. Calls out that the fast-path Document API delivery (keeping the name @superdoc/document-api) is an explicit taxonomy exception, so the @superdoc/* default-internal rule does not become muddy.
Two corrections after SD-2842 shipped a different path than the RFC originally recommended. Decision 2 said "publish the package" with two delivery sub-options. What actually shipped is source-rewrite curated emit: vite-plugin-dts include extended to cover the relocated packages, postbuild rewrite of bare specifiers to relative dist paths, shim-skip for those packages, plus a build-time check that they cannot leak back into the shim file. Updated Decision 2 to describe this implementation as the current mechanism, and moved "publish under @superdoc-dev/document-api" and "bundler-based bundling" to a "future paths still on the table" section. Audit-gate enforcement said "no _internal-shims.d.ts in dist/". The honest current state is that the file may exist for legacy or internal-only declaration reachability; the relocated packages are what must not leak. Reworded to "no public type may resolve through _internal-shims.d.ts" and pointed at the build-time check that enforces it.
…be (SD-2831) (#3019) * test(consumer-typecheck): pack-and-install + guarded public types probe (SD-2831) The matrix now auto-packs superdoc and reinstalls the fixture before running so each run measures the customer-visible declaration surface, not the source repo. The existing fixture was already pointed at the tarball but relied on the operator remembering to pack first. Adds an informational scenario that imports a guarded list of public types and asserts they are not any (using the same `@ts-expect-error` trick the prosemirror-coexistence test introduced). Today nine of fifteen probed types collapse to any; the scenario stays mustPass: false until SD-2830 ships the curated emit and the regression is fixable. At that point this scenario flips to required and any future any regression breaks CI. Honors mustPass: false for src-error scenarios as well as deps-only ones, so informational scenarios do not fail CI while the underlying class is being fixed. Generated tsconfig.matrix.json and pnpm-lock.yaml are now gitignored. Stacked on the SD-2829 RFC branch. * test(consumer-typecheck): isolate informational probes from default tsc (SD-2831) The previous version of this PR placed the guarded-public-types probe under src/, which the fixture's base tsconfig.json picks up via include: src/**/*.ts. A plain `tsc --noEmit` (the `typecheck` script) compiled the probe under required rules, so the unused @ts-expect-error directives broke CI even though the matrix would have run the probe under mustPass: false. Move the probe to src/informational/ and exclude that subdirectory from the base tsconfig. The matrix references the new path explicitly. Bare tsc passes; the matrix still surfaces the probe as INFO with nine guarded types currently collapsing to any. Future informational probes follow the same convention. * test(consumer-typecheck): align Document API note with RFC Decision 2 (SD-2831) The probe's comment claimed the missing Document API types "belong in @superdoc-dev/document-api once that package ships." The RFC now presents the package name as an explicit team decision between the fast path (@superdoc/document-api) and the taxonomy-clean rename (@superdoc-dev/document-api). Update the comment to reflect that. * ci(superdoc): replace bare tsc with matrix script in consumer typecheck (SD-2831) The previous `Consumer typecheck` step packed `superdoc`, installed the tarball into the standalone fixture, then ran `npx tsc --noEmit`. That covered the base scenarios via the fixture's tsconfig include but never invoked `tests/consumer-typecheck/typecheck-matrix.mjs`, so the pack-and-install scenarios SD-2831 added (and the guarded-public-types probe, and the SD-2842 all-public-types and editor.doc smoke scenarios) ran locally but were not gated by CI. Replace with a single call to the matrix script. The script owns the full path: pack `superdoc`, install the tarball into the fixture with `pnpm install --ignore-workspace`, then run each scenario under its declared `module` / `moduleResolution` / `strict` / `skipLibCheck` settings and honour `mustPass`. Required scenarios still fail the build; informational scenarios still warn. Verified locally: 12 required scenarios pass, 3 informational warnings (the existing `skipLibCheck=false` dep noise plus the two guarded-public-types informational probes; the latter flip to required once SD-2842 lands and the layout/contracts type-collapse class is fully closed).
Three small alignment fixes after the previous commit reshaped Decision 2 to match the SD-2842 implementation. - The super-editor inventory row pointed at "the published Document API package" as part of what supersedes the legacy subpath. Document API is not a separate published package per Decision 2; it is the relocated types reachable from superdoc itself. Updated the row to say so. - Type ownership rule 4 said the Document API package is "the immediate candidate" for being re-exported from a published namesake. With Decision 2 settled on relocation, no @superdoc/* workspace package is currently in that tier; rephrased to make the current state explicit while keeping the published-package option open. - Sequencing step 2 said "publish or otherwise expose Document API"; rewritten to describe the customer-visible target (editor.doc returns real types; named imports resolve) and to point at Decision 2 for implementation. Avoids implying that publishing is the recommendation when Decision 2 settled on relocation. No other content changes.
e8b0702 to
915c64f
Compare
…ck fixture install (SD-2829) Two fixes after a review noticed the previous matrix wiring weakened CI. The bare `tsc --noEmit` step the matrix replaced compiled every file under the fixture's `src/`, which included `customer-scenario.ts`. The matrix only runs files explicitly listed by a scenario, so that file was no longer being type-checked. Added two scenarios (bundler + node16) that compile it as `mustPass: true`. The matrix tsconfig generator now propagates a per-scenario `noPropertyAccessFromIndexSignature` flag so the customer scenario keeps its strict access rule from the base tsconfig. Switched the install step from `pnpm install --ignore-workspace --frozen-lockfile` to `npm install ../../packages/superdoc/superdoc.tgz --no-save`. Strict-mode installs (`pnpm --frozen-lockfile`, `npm ci`) require the lockfile to match the file: tarball's content hash, but the tarball's bytes change on every fresh pack, so a strict install fails on every CI run. The pre-SD-2831 workflow used `--no-save` to avoid this; the matrix script now does the same. Dev deps (typescript, prosemirror-*, @types/node) still install from `package.json` semver ranges, matching the prior behavior; tightening that is a separate concern. Local result: 19 passed, 0 failed, 1 informational warning.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Walks every .d.ts file under dist/ after build and reports any of three forbidden patterns the package boundary RFC (SD-2829) classifies: private @superdoc/* specifiers, package-manager-internal paths (node_modules/.pnpm/...), and presence of dist/_internal-shims.d.ts. Wired into the existing postbuild chain so every build runs the audit. Default exit code is zero so the build does not break today; the published surface still has all three classes of leak (181 files reach private workspace specifiers; the internal shims file is present at 24.7 KB with 575 `= any` aliases). Pass --strict or set SUPERDOC_AUDIT_REQUIRED=1 to fail on findings; once SD-2830 ships and the leaks are gone, flip the script's default to strict and remove the informational note. Stacked on the SD-2829 RFC branch.
…rs (SD-2832) The original audit treated three things as failures: any private @superdoc/* import in dist, any pnpm path leak, and the existence of _internal-shims.d.ts. Two of those don't match the RFC the audit is supposed to encode. The shim file legitimately exists for legacy and internal-only declaration reachability; the RFC's rule is "no public type may resolve through it", not "the file must not exist." Treating mere presence as a failure trains reviewers to ignore the audit. Replaced with an informational section that lists what's currently shimmed so drift stays visible. The "any private specifier is a failure" rule was too broad. The shim file is the registry of "known unresolved" private modules; specifiers it covers are tolerated. The legacy public surface (@superdoc/super-editor) resolves through the dist tree at runtime via the existing rewrite rules. Rule 1 now FAILS only on private specifiers that are NEITHER shimmed NOR on the legacy allowlist — i.e. real leaks a strict-mode consumer would hit. Added Rule 3 explicitly: if a relocated package (@superdoc/document-api, @superdoc/contracts, @superdoc/layout-bridge, @superdoc/painter-dom) reappears in the shim file, that's a regression of the SD-2842 relocation pipeline and the audit fails. Mirrors the build-time check in ensure-types.cjs; keeping both lets the audit run as a standalone gate against any tarball, not just during a fresh build. Default mode stays informational so existing pre-existing leaks (an `@superdoc/common` leak in a d.ts the inlining missed, two deep `@superdoc/super-editor/converter/internal/...` paths) don't break the build today, but they're now visible as real findings instead of masked by an over-broad shim-presence rule. Strict mode is the next step once those leaks are fixed.
…urrent state (SD-2829) The sequencing section read like Document API and the gates were future work; they're not. The Document API relocation shipped (SD-2842), the matrix and audit script are folded into this PR alongside the RFC, and the audit defaults to informational while three pre-existing leaks (one @superdoc/common reference the inlining missed, two deep @superdoc/super-editor/converter/internal paths the rewrite missed) are addressed before strict mode flips on. Updated: - Sequencing now distinguishes Done / In progress / Future work, naming SD-2842 as the implementation that closed the customer-acute Document API regression. - Decision 1's wording about the audit gate "enforcing no growth" softened to "future follow-up"; the audit script does not enforce that constraint today. - CI enforcement section softened from "future PRs that violate these rules fail CI" to "once strict mode is enabled, future PRs ..." since the audit is currently informational. - Last-updated bumped to 2026-04-30. No content changes to the decisions, tier definitions, inventory, or rules.
…t coverage (SD-2829) Three changes that tighten the matrix without adding scope. The `informational/guarded-public-types.ts` probe was redundant after SD-2842 shipped. The 18-name list it asserted is fully subsumed by the `all-public-types.ts` test (105 names, required) that the SD-2842 follow-up added. Keeping the informational probe in the matrix makes it look like there is coverage where there is just a weaker overlap. Removed the file and its two scenarios; the surface is still required-tested via `all-public-types.ts`. The `imports-sub-export.ts` test imported only `Editor` and `PresentationEditor` and instantiated one. That confirmed the sub-export resolves but did not prove its types were real interfaces (a `superdoc/super-editor` collapse to `any` would still pass). Expanded to import seven runtime symbols and six type names from the legacy public surface, asserting `AssertNotAny<T>` on each. A future regression that loosens the sub-export's typing now fails the matrix. The install-step comment in `typecheck-matrix.mjs` said dev deps come from "package.json's semver ranges; locking them strictly is a separate concern." With `package-lock.json` committed, dev deps are pinned at deterministic versions; the only reason for `--no-save` (vs `npm ci`) is that strict-mode installs hash the file: tarball whose bytes change on every rebuild. Rewrote the comment to match. Removed the now-unused `src/informational/**` exclude from the base tsconfig. Local result: 17 passed, 0 failed, 1 informational warning.
…D-2829) The previous classification ordered branches as: PASS (exitCode 0), DEPS (any error with srcErrors === 0), INFO (errors but not mustPass), FAIL (errors and mustPass). The DEPS branch fired before the mustPass check, so a required scenario whose only errors landed in node_modules/ was classified as a warning instead of a failure. This is the exact class of regression the gate is supposed to catch. A malformed published declaration surfaces as parse errors under `node_modules/superdoc/dist/...` even with `skipLibCheck: true` (lib-check skips type errors but not parse errors). Verified empirically: injecting a one-line parse error into the installed `superdoc/dist/superdoc/src/index.d.ts` was previously classified as DEPS warnings on 13 required scenarios; after the fix, those 13 scenarios FAIL as intended. Reorder branches to prioritize mustPass: PASS, then warning if !mustPass (informational), then FAIL. Add an explicit `allowNodeModuleErrors` flag on scenario definitions for the case where dep noise is the expected outcome (the existing `skipLibCheck=false` informational probe whose job is to surface 30+ pre-existing errors in node_modules); the flag preserves the DEPS classification for that one scenario without weakening the gate elsewhere. The FAIL branch's error-line filter now includes `node_modules/` paths so the cause is visible in the matrix output, capped at 20 lines per failure.
|
🎉 This PR is included in @superdoc-dev/react v1.2.0-next.66 The release is available on GitHub release |
|
🎉 This PR is included in vscode-ext v2.3.0-next.68 |
|
🎉 This PR is included in @superdoc-dev/mcp v0.3.0-next.23 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-cli v0.8.0-next.41 The release is available on GitHub release |
|
🎉 This PR is included in superdoc v1.30.0-next.25 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-sdk v1.8.0-next.27 |
|
🎉 This PR is included in superdoc-cli v0.8.0 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-sdk v1.8.0 |
|
🎉 This PR is included in @superdoc-dev/mcp v0.3.0 The release is available on GitHub release |
|
🎉 This PR is included in superdoc v1.31.0 The release is available on GitHub release |
|
🎉 This PR is included in vscode-ext v2.3.0 |
Phase 1 of the SuperDoc TypeScript public type contract work. Combined integration: the RFC defines what is public, legacy public, and private; the consumer matrix proves the packed
superdoctarball compiles for real-world TypeScript projects; the declaration audit reports drift in the published surface; CI wiring runs all of the above on every PR.The customer-acute regression that started this thread (Document API and layout types collapsing to
anyfor strict-mode consumers) shipped via SD-2842 / #3022. This PR is the contract that locks the fix in place and makes the next regression catchable at PR time instead of after a customer reports it.What the PR contains:
docs/architecture/package-boundaries.md— the RFC. Tier definitions, full inventory of workspace packages andsuperdocsubpath exports, type ownership rules, dependency direction rules, four decisions (super-editor as legacy public, document-api delivered via relocation, layout-engine sub-packages stay split, runtime-only subpaths as legacy public), recommended sequencing.tests/consumer-typecheck/— pack-and-install matrix that runs 17 required scenarios across resolution modes (bundler, node16, nodenext), strictness, framework coexistence (prosemirror withskipLibCheck: false), and includes thecustomer-scenario.tsstrict broad-API test plus the SD-2842 all-public-types and editor.doc smoke tests. Matrix logic now correctly classifiesnode_modules/errors in required scenarios as failures, not warnings.packages/superdoc/scripts/audit-declarations.cjs— standalone declaration audit. Three FAIL-level rules (private specifiers outside the shim allowlist, pnpm path leaks, relocated packages reappearing in shims) plus an informational listing of remaining shimmed modules so drift stays visible. Defaults to informational while three pre-existing leaks are addressed; SD-2859 tracks flipping to strict..github/workflows/ci-superdoc.yml— CI now invokes the matrix, replacing the baretsc --noEmitstep.What this is not:
This is Phase 1, not the finish line. The follow-ups under SD-2828's children harden the contract over time: SD-2859 (audit → strict default once pre-existing leaks close), SD-2860 (auto-derive the public type guard from the actual exported surface so a new export cannot ship untested), SD-2861 (matrix coverage for every supported subpath), SD-2862 (process: new public exports require a type test), SD-2863 (
checkJson public-contract JSDoc files).Verified locally: 17/17 required matrix scenarios pass, audit reports remaining drift as informational, full repo type-check / superdoc unit tests / super-editor unit tests / template-builder build / react type-check all green.