Thanks for your interest in contributing! This guide will help you get started.
# Clone the repo
git clone https://github.com/eigenpal/docx-editor.git
cd docx-editor
# Install dependencies
bun install
# Start the dev server
bun run dev
# Open http://localhost:5173Working on the parser, serializer, or layout engine? bun run reference:fetch pulls the gitignored ECMA-376 PDFs and supplementary ZIPs (~58 MB). The handwritten quick-refs and XSD schemas under reference/ stay committed.
# Type checking (fast, run often)
bun run typecheck
# Unit tests
bun test
# E2E tests (requires Playwright browsers)
npx playwright install --with-deps chromium
npx playwright test --timeout=30000 --workers=4
# Single test file
npx playwright test e2e/tests/formatting.spec.ts --timeout=30000The project uses ESLint and Prettier with pre-commit hooks (Husky + lint-staged), so formatting is handled automatically on commit.
# Manual lint/format
bun run lint:fix
bun run formatContributors are required to sign our Contributor License Agreement. The CLA assistant will leave a comment on your first pull request with signing instructions — one short comment, about 30 seconds. That signature covers all of your future contributions.
- Fork the repository and create a branch from
main - Read the code before modifying it — understand the dual rendering system (see Architecture)
- Make your changes — keep them focused and minimal
- Add/update tests for your changes (see
e2e/for E2E tests) - Verify everything works:
bun run typecheck && bun test && bun run build:packages
- Submit a PR against
main— the CLA bot will prompt you on your first one
The editor has two rendering systems:
- Hidden ProseMirror — the real editing state (selection, undo/redo, keyboard input)
- Visible Pages (layout-painter) — what the user sees, rebuilt from PM state on every change
See docs/ARCHITECTURE.md for the full architecture and CLAUDE.md for the agent-facing quick reference (also useful for humans).
Every published package's @public exports are locked in docs/api/<pkg-slug>/<entry>.api.md snapshots generated by API Extractor. CI runs bun run api:check and fails on undocumented drift.
If you change a @public symbol — or add a new one — regenerate and commit the snapshot:
bun run --filter '@eigenpal/docx-editor-<pkg>' build
bun run api:extract
git add docs/api/<pkg-slug>/The CI error message points at the source file for each drifted entry, so the fix is mechanical. Full details live in CLAUDE.md under "Public API surface".
Adding a DocxEditorProps field or DocxEditorRef method to either adapter also requires updating scripts/parity/parity.contract.json — the cross-adapter parity contract that tracks which fields are shared, deliberately Vue-deferred, or Vue-exclusive. bun run check:parity-contract (also run in CI) fails until the contract acknowledges the new symbol. The error message names the symbol and tells you which bucket to add it to.
Adding a new Vue composable: declare a Use<Name>Return interface and annotate the function's return type with it. Without the annotation the snapshot recursively inlines core's internal types into Vue's public surface.
Adding a new published package: edit scripts/lib/packages.mjs (one entry — name, root, slug, tsconfig, build hint). Add matching api:extract / api:check scripts in the new package's package.json delegating to ../../scripts/api-extractor.mjs --package <name>. Then run bun run api:extract && bun run docs:json to generate snapshots.
The same @public surface is also emitted as structured JSON for downstream docs sites: bun run docs:json writes docs/json/<pkg-slug>/<subpath>.json per published subpath, plus a root docs/json/index.json. The JSON is gitignored — downstream sites (e.g. docx-editor-page) clone the repo and run the script themselves. CI runs bun run docs:json as a smoke test so generator breakage surfaces in this repo, not in the consumer's build.
The editor ships first-party adapters for React (packages/react) and Vue (packages/vue). Both share @eigenpal/docx-editor-core, which owns the parser, ProseMirror schema, layout engine, layout bridge (page mapping, footnote convergence, header/footer measurement), and serializer. Adapters only own their framework-specific shell, components, and lifecycle wiring.
When you touch layout, parsing, or rendering logic, put it in core, not in an adapter. If you copy a 30-line helper from React to Vue, you've created a divergence trap. The footnote convergence loop (stabilizeFootnoteLayout in packages/core/src/layout-bridge/footnoteLayout.ts) is the canonical example: one helper, both adapters call it.
Parity smoke tests live under e2e/tests/parity/smoke/ and run each spec against both demos. Add one when you fix a bug that could plausibly affect rendering on either side.
Open an issue at github.com/eigenpal/docx-editor/issues with:
- Steps to reproduce
- Expected vs actual behavior
- Attach a
.docxfile if relevant (remove sensitive content first)
By contributing, you agree that your contributions will be licensed under the MIT License.