feat(types): explicit public facade root scaffold (SD-3178)#3358
Merged
caio-pizzol merged 3 commits intoMay 17, 2026
Conversation
Phase 3 of SD-3175. Lands the first real source-side public facade for
SuperDoc: `packages/superdoc/src/public/index.ts`. Narrow scope on
purpose - same four symbols SD-3177 validated (`SuperDoc`, `Config`,
`Editor`, `EditorCommands`) so the augmentation regression path stays
covered, and nothing else.
The facade file uses named exports only, with explicit `.js` source
specifiers. Path-as-contract: anything under `src/public/**` is public
by intent, anything outside is implementation detail.
No `package.json#exports` change. Phase 4 owns the contract flip.
Today the facade emits to `dist/superdoc/src/public/index.{d.ts,d.cts,es.js,cjs}`
but is not advertised as a published subpath.
Wiring:
- vite.config.js: add `'public': 'src/public/index.ts'` to rollupOptions.input
- ensure-types.cjs: add the facade entry to cjsDeclarationShims so a
`.d.cts` is generated next to the ESM declaration
- new postbuild step: scripts/verify-public-facade-emit.cjs runs after
ensure-types to assert four invariants on the emitted declarations:
1. expected symbol set
2. ESM/CJS export parity
3. command-map augmentation visible
(`EditorCommands['setBold']` resolves through the facade)
4. no private workspace specifiers, package-manager internals, or
absolute local paths in the emit
Empirically verified the verifier rejects:
- a renamed symbol (drift in EXPECTED_NAMES) - exit 1
- an injected `@superdoc/internal` import in the emitted .d.ts - exit 1
- clean tree - exit 0
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87db4186b3
ℹ️ 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".
Three small follow-ups on PR #3358: - Promoted the two highest-risk-of-violation constraints in the facade scaffold to `AIDEV-NOTE:` anchors per `comment-policy.md`: - "Named exports only" in src/public/index.ts (an agent that "simplifies" via `export *` re-introduces the leak this exists to close). - "package.json#exports is intentionally not yet updated; Phase 4 owns the contract switch" in vite.config.js (an agent that "finishes" the wiring ships a new public subpath under the radar). - The "update EXPECTED_NAMES in verify-public-facade-emit.cjs in the same PR" rule, so the postbuild gate stays meaningful. - Coverage smoke test for src/public/index.ts. The two runtime re-exports showed 0% in the unit-test coverage report. The postbuild verifier covers the declaration surface but not the JS runtime. Two trivial assertions.
Codex review on PR #3358 caught that the augmentation probe was a no-op. Verified at ChainedCommands.ts:141 - EditorCommands is intersected with `Record<string, AnyCommand>`, so the indexer always resolves and `type Probe = EditorCommands['setBold']` passes even when the specific signatures are dropped. Replaces the indexer-only probe with a return-type check on two commands from two signature sources: setBold - from FormattingCommandAugmentations insertComment - from CommentCommands Both must resolve to `(...args: any[]) => boolean`. If the facade falls back to the AnyCommand indexer, the return type is `unknown` and the conditional resolves to `false`, failing the assignment with TS2322. A previous attempt at the fix used `true as Result` to assign the conditional value; that laundered the failure through `never` and the probe still passed. The literal stays un-cast on purpose, with an inline note. Reworded the surrounding prose to say "command signature surface" instead of "augmentation surface" - the codebase composes EditorCommands via explicit imports (CoreCommandSignatures, FormattingCommandAugmentations, etc.), not exclusively via `declare module` augmentation. Empirically verified: simulated regression (EditorCommands replaced with `Record<string, AnyCommand>`) now fails with TS2322 from each of the two probe lines, verifier exits 1. Clean tree exits 0.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
caio-pizzol
added a commit
that referenced
this pull request
May 17, 2026
Stacked on PR #3358 (SD-3178). Second facade file in the path-as-contract scaffold under SD-3175 / Phase 3. `superdoc/headless-toolbar`, `superdoc/headless-toolbar/react`, and `superdoc/headless-toolbar/vue` are reclassified as legacy public compatibility surface. Existing consumers keep compiling with full types; new custom UI integrations should use `superdoc/ui` and `superdoc/ui/react` instead. The next-generation typed UI controller replaces the default headless-toolbar path. Source facade: - packages/superdoc/src/public/legacy/headless-toolbar.ts: 16 named exports (3 runtime + 13 types) mirroring src/headless-toolbar.js + src/headless-toolbar.d.ts. Lives under legacy/ to mark it compat-only. - vite.config.js + ensure-types.cjs: build wiring for the new entry. - verify-public-facade-emit.cjs: refactored to be config-driven via a FACADE_ENTRIES list. Adding a new facade file is now a single append. - src/public/legacy/headless-toolbar.test.ts: 3 smoke assertions. Policy: - docs/architecture/package-boundaries.md: inventory table rows for `./headless-toolbar` and its react/vue subpaths reclassified from Public subpath to Legacy public compatibility surface. Decision 4's migration table extended to cover all three. No-growth enforcement (extends SD-3176): - tests/consumer-typecheck/snapshot-superdoc-legacy-exports.mjs: adds `./headless-toolbar`, `./headless-toolbar/react`, and `./headless-toolbar/vue` to the SUBPATHS list. - snapshots/: three new baselines (16/1/1 resolved exports). - README updated with the new entries. No package.json#exports change. Phase 4 owns the contract flip. Empirically verified the multi-entry facade verifier: - clean: OK, 2 entries (root: 4 exports, legacy/headless-toolbar: 16) - drift on legacy/headless-toolbar facade .d.ts: exit 1 - leak in legacy/headless-toolbar facade .d.ts: exit 1 - command-signature probe still fires on root entry only Empirically verified the no-growth gate: - clean across all 7 subpaths: OK - drift on superdoc/headless-toolbar published .d.ts: exit 1
This was referenced May 17, 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.
Phase 3 of SD-3175 (path-as-contract public facade umbrella). Lands the first real source-side public facade file for SuperDoc, plus the pipeline support and a postbuild verifier. No runtime, contract, or package-shape change.
What this PR does
Adds
packages/superdoc/src/public/index.tsas the first explicit facade entry. It re-exports the same four symbols SD-3177 validated as a feasibility spike:SuperDoc,Config,Editor,EditorCommands. The narrow surface is intentional - it covers a runtime value, a config type, and the augmentation-bearing pair that exercises the SD-2965 regression vector.Path-as-contract: anything under
src/public/**is public by intent. Anything outside is implementation detail. The rule is enforced by the verification script described below, not by the JSON policy in PR #3294 (closed unmerged).Pipeline wiring
vite.config.js: adds'public': 'src/public/index.ts'torollupOptions.input.scripts/ensure-types.cjs: adds an entry tocjsDeclarationShimsso the facade gets a.d.ctsalongside its.d.ts.scripts/verify-public-facade-emit.cjs(new): postbuild verifier with four invariants.package.json#scripts.postbuild: runs the verifier betweencheck-export-coverageandreport-declaration-reachability.What is NOT in this PR
package.json#exportschange. The facade emits todist/superdoc/src/public/index.{d.ts,d.cts,es.js,cjs}but is not advertised as a published subpath. Phase 4 owns the contract switch.types.ts,ui.ts,headless-toolbar.ts,legacy/*). Each lands in its own follow-up.Verifier invariants
verify-public-facade-emit.cjsruns afterensure-types.cjsand asserts:EXPECTED_NAMESlists the four facade exports. New entries must be added here in the same PR, and the PR must link to SD-3175 for reviewer sign-off.index.d.tsandindex.d.ctsenumerate the same names.EditorCommands['setBold']resolves through the facade. This is the SD-2965 regression vector: command-mapdeclare moduleaugmentations getting silently dropped on the way to consumers. If this assertion fails, the facade emit broke augmentations.@superdoc/*specifiers, no.pnpm/, no absolute local paths into the repo ornode_modules.Relative declaration references into the per-package dist tree (e.g.
from '../../../super-editor/src/index.js') are expected at this phase. The dts pipeline relocates@superdoc/super-editorspecifiers viaensure-types.cjs. Later SD-3178 follow-ups reduce how much the facade depends on the broader declaration graph.Verified
pnpm --filter superdoc run build:espasses. Verifier output:Facade emits cleanly: 4 exports, ESM/CJS in parity, augmentations survive.EditorCommands→EditorCommands2in the emitted.d.ts→ verifier exit 1 withexpected: ..., actual: ....import { Foo } from "@superdoc/internal-thing";into the emitted.d.ts→ verifier exit 1 withprivate workspace specifier: from "@superdoc/.Related
types.ts,ui.ts,headless-toolbar.ts,legacy/*.ts, then the Phase 4 contract switch