Thanks for your interest in improving Muya! This document covers everything you need to file a useful issue or land a pull request against @muyajs/core.
Muya is a web-based Markdown editor engine extracted from MarkText. The bulk of the source today lives in packages/core/.
- Code of conduct
- Ways to contribute
- Development setup
- Repository layout
- Useful commands
- Coding conventions
- Commits and pull requests
- Testing
- Where to ask questions
Be kind, assume good intent, and keep discussions on-topic. Personal attacks, harassment, and discriminatory language are not welcome. If you witness or experience a violation, you can report it through GitHub's abuse reporting flow or by emailing the maintainer listed under author in package.json.
- Report a bug — open an issue with a clear title, a minimal reproduction (Markdown input + steps), and the muya / browser version. Search existing issues first to avoid duplicates.
- Suggest an enhancement — open an issue tagged with
enhancementand explain the use case. Larger proposals (new public API, new block type) benefit from a short design sketch before the PR. - Improve docs — README,
CLAUDE.md,MIGRATION.md,docs/, and the per-package READMEs are all fair game. - Send a pull request — bug fixes, parser conformance improvements, new UI plugins, performance wins, tests, and CI hardening are all appreciated. See Commits and pull requests below.
Prerequisites
- Node.js ≥18 for everyday development. Releases require Node ≥20.19, ≥22.13, or ≥24 (the changelog plugin pins
^20.19.0 || ^22.13.0 || >=24.0.0). - pnpm ≥8.5. The repo is pinned to
pnpm@10.22.0viapackageManager— install the matching version (e.g.corepack enable && corepack prepare pnpm@10.22.0 --activate). - A Chromium-based browser for the dev demo and Playwright E2E suite.
First-time setup
# 1. Fork on GitHub, then clone your fork.
git clone git@github.com:<your-user>/muya.git
cd muya
# 2. Add the upstream remote so you can keep master in sync.
git remote add upstream git@github.com:marktext/muya.git
# 3. Install dependencies (also wires up husky git hooks).
pnpm install
# 4. Boot the examples app to try your changes in a real editor.
pnpm devpnpm dev runs turbo dev:demo, which starts the Vite dev server in examples/ and serves @muyajs/core directly from packages/core/src/ — no rebuild step needed while iterating.
.
├── packages/
│ ├── core/ @muyajs/core — the editor library (all source today)
│ ├── facade/ README-only stub (no source yet)
│ └── findReplace/ README-only stub (no source yet)
├── examples/ Vite vanilla-TS demo, consumes core via workspace:*
├── e2e/ Playwright real-browser suite (self-contained host page)
├── docs/ Logo, ROADMAP, JSON state reference
├── CLAUDE.md Architecture + conventions deep-dive (start here for big changes)
└── CHANGELOG.md Generated by release-it (angular conventional-changelog preset)
CLAUDE.md documents the runtime architecture (Muya → Editor → JSONState, the block tree, plugin registration, state ↔ markdown round-trip) and is the fastest way to orient yourself before touching packages/core/src/.
Run from the repo root — Turbo fans tasks out across packages.
| Command | What it does |
|---|---|
pnpm dev |
Boot the examples Vite dev server (turbo dev:demo). |
pnpm build |
tsc && vite build in packages/core. Emits lib/{es,umd,cjs} and lib/types. |
pnpm test |
Vitest unit tests (--passWithNoTests). |
pnpm coverage |
Vitest with Istanbul coverage (@vitest/coverage-istanbul). |
pnpm lint / pnpm lint:fix |
ESLint (antfu base) over packages/. |
pnpm lint:types |
tsc --noEmit per package. |
pnpm lint:css |
Stylelint over all CSS. |
pnpm check-circular |
madge --circular packages/core/src/index.ts — CI enforces this. |
pnpm e2e |
Playwright suite (Chromium, port 5174). See e2e/README.md. |
pnpm e2e:ui |
Playwright UI mode for interactive debugging. |
Scoped runs:
# Run one Vitest file in core.
pnpm --filter @muyajs/core exec vitest run path/to/file.test.ts
# Watch a single package while iterating.
pnpm --filter @muyajs/core test:watch
# Run only the CommonMark / GFM conformance fixtures (baseline locked by
# packages/core/test/spec/expected-failures.json).
pnpm --filter @muyajs/core test:spec:commonmark
pnpm --filter @muyajs/core test:spec:gfmESLint and Stylelint enforce most of these — pnpm lint:fix is the source of truth — but a few are worth knowing up front:
- TypeScript first. All new source goes in
.ts. Public types belong inpackages/core/src/types.ts. - 4-space indent, semicolons required. The antfu config in
eslint.config.mjsconfigures this for the repo. - Interface names start with
Ifollowed by an uppercase letter or digit (e.g.IMuyaOptions,IPlugin). The@typescript-eslint/naming-conventionrule flags violations. - Private class members are prefixed with
_(e.g._uiPlugins,_activeContentBlock). - Complexity caps.
complexity ≤ 20andmax-lines-per-function ≤ 200are warnings for non-test TS. Refactor rather than disable. - No circular imports.
pnpm check-circularruns in CI; adding a cycle intopackages/core/src/index.tsfails the build. - Block registration. New block types must be registered in
packages/core/src/block/index.ts::registerBlocks().ScrollPage.loadBlock(name)returnsundefinedand warns otherwise. - No new code paths producing
ILinkReferenceDefinitionState. Reference link/image definitions round-trip through paragraph state — seemarkdownToState.tsandCLAUDE.md. - Avoid backwards-compat shims (renaming unused vars to
_var, leaving "removed" comments, re-exporting deleted types). Delete unused code outright.
CSS lives next to its consumer (*.css co-located with the .ts) and is linted with Stylelint.
Conventional Commits are required. The husky commit-msg hook runs commitlint and rejects subjects that don't match. Allowed types:
build, ci, chore, docs, feat, fix, perf, refactor, revert, style, test
Examples:
feat(core): add reference-image rendering
fix(inline): preserve cursor after IME composition
docs: clarify Muya.use registration order
test(spec): widen GFM table fixture coverage
Scope is optional but encouraged for packages/core/ work (core, inline, state, ui, spec, …).
Before opening a PR
-
Branch off
master:git checkout -b fix/short-description. -
Make focused commits — bundle drive-by cleanups into separate commits or PRs.
-
Run the quality gates locally:
pnpm lint pnpm lint:types pnpm test pnpm check-circular -
If your change touches the UI or editing surface, run
pnpm e2eand add coverage for the new behavior undere2e/tests/. -
If your change affects markdown parsing or HTML output, check the conformance baseline doesn't regress:
pnpm --filter @muyajs/core test:spec. Conformance can only go up — seepackages/core/test/spec/expected-failures.json. -
Update
MIGRATION.mdif you change a public API.
Opening the PR
- Base branch is
master(notmain). - Use a Conventional-Commit-style title (it becomes the squash commit subject).
- Describe the why in the body, not just the what — the diff already shows the what. Link related issues with
Closes #123/Refs #123. - Pre-commit,
lint-stagedrunseslint --fixon staged*.tsandstylelint --fixon staged*.css(see.lintstagedrc). If a hook fails, fix the issue and create a new commit — don't bypass with--no-verifyunless a maintainer asks you to.
Maintainers will review and may ask for changes. PRs are merged via squash merge to keep master linear; your branch commits don't need to be individually clean.
- Unit tests live next to their source in
packages/core/src/**/__tests__/or*.test.ts, run with Vitest. Vitest has no globalenvironmentset — tests run under the default Node environment by default, and DOM-dependent tests opt into happy-dom with a// @vitest-environment happy-domdirective at the top of the file. New parser logic, state transforms, and pure helpers should ship with unit coverage. - Conformance fixtures.
pnpm --filter @muyajs/core test:specruns the CommonMark 0.31 and GFM 0.29-gfm fixture suites againstrenderToStaticHTML(..., { sanitize: false }). The expected-failures list is locked — making a failing fixture pass requires removing it from the list in the same PR. - E2E tests. Playwright suite in
e2e/. Real browser, real contenteditable. Use this for behaviors that depend on selection, IME, clipboard, or floating UI positioning — anythinghappy-domcan't fake. Seee2e/README.mdfor the helper API ande2e/BACKLOG.mdfor what's still uncovered.
- Usage or bug reports → open an issue. Search first; tag with
enhancementif it's a feature request. - Architecture deep-dives →
CLAUDE.mdis the canonical agent/human-readable map of the codebase. - Release tooling →
README.md(Publishing section) and.release-it.json.
Thanks again for contributing — every typo fix, parser improvement, and test counts.