Drop the legacy muya ambient bridge and its aliases (#4257) by Jocs · Pull Request #5526 · marktext/marktext · GitHub
Skip to content

Drop the legacy muya ambient bridge and its aliases (#4257) - #5526

Merged
Jocs merged 16 commits into
developfrom
chore/drop-legacy-muya-bridge-4257
Sep 23, 2026
Merged

Jocs merged 16 commits into
developfrom
chore/drop-legacy-muya-bridge-4257

Conversation

@Jocs

@Jocs Jocs commented Sep 23, 2026

Copy link
Copy Markdown
Member

Item 1 of #4257, the last one. Stacked on #5523 — closes #4257 when all three
land.

Nothing under packages/desktop has imported muya/... since the renderer
moved to @muyajs/core, so src/types/muya.d.ts — 26 any shims under a
file-level no-explicit-any disable, covering muya/lib, muya/lib/utils,
muya/lib/parser/* and the dozen-plus muya/lib/ui/* overlays — has no
consumers left. Removed, along with everything that existed to make it
resolve:

  • the muya/* path in tsconfig.base.json and the ../muyajs/**/* exclude
    in tsconfig.json
  • the muya alias in all three electron.vite.config.ts sections and in
    vitest.config.ts
  • the @marktext/muyajs workspace dependency

packages/muyajs itself stays until after 0.20.0 — it still lints and carries
its own dependencies. This only cuts the desktop's last tie to it.

The lockfile

Removed by hand, not by re-resolving. A plain pnpm install --lockfile-only
also bumps the file-icons git tarball to a newer commit, lodash 4.17.21 →
4.18.1, semver 7.7.4 → 7.8.5, tinyglobby 0.2.16 → 0.2.17 and vitest's peer
resolution (@types/node@26/vite@8 → @types/node@22/vite@7) — none of
which belong in this change. pnpm install --lockfile-only --frozen-lockfile
accepts the hand-edited result, so the lockfile is consistent with the
manifests.

One wrinkle worth knowing: pnpm rewrites the lockfile in its own formatting,
while the committed one is Prettier-formatted (lint-staged runs
prettier --write on *.yaml). Run Prettier over it after any pnpm write or
the diff is ~13k lines of pure formatting.

Docs

CLAUDE.md and docs/dev/TYPESCRIPT.md both described the bridge and the
muya alias as current; both are updated, including the stale claim that the
only remaining any was in src/types/muya.d.ts plus watcher.ts (the
directives that actually remain are mostly in the CodeMirror 5 mode plumbing).

Verification

  • pnpm typecheck and pnpm lint clean; pnpm test:unit 770 passed,
    unchanged from the branch point.
  • Full Playwright suite against a real build:unpack: 275 passed, 4 skipped,
    and the same 3 pre-existing failures my machine's zh-CN locale causes
    (asserted English UI strings) — identical to the run on Type the editor handle as Muya, from the engine's own declarations (#4257) #5523.
  • The renderer bundle built from this commit is byte-identical to the one
    built before it.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Build artifacts for PR #5526:

Run: https://github.com/marktext/marktext/actions/runs/35856093520

Artifact Size Link
marktext-windows-x64 286.1 MB Download
marktext-macos-x64 295.8 MB Download
marktext-windows-arm64 278.0 MB Download
marktext-linux 634.5 MB Download
marktext-macos-arm64 285.4 MB Download

@Jocs
Jocs force-pushed the refactor/muya-core-typings-4257 branch from 6b9863a to 745cab7 Compare September 23, 2026 11:28
@Jocs
Jocs changed the base branch from refactor/muya-core-typings-4257 to develop September 23, 2026 11:30
Jocs and others added 16 commits September 23, 2026 19:34
Item 1 of #4257. Nothing under `packages/desktop` has imported `muya/...`
since the renderer moved to `@muyajs/core`, so the ambient declaration that
stood in for the legacy JavaScript engine - 26 `any` shims under a file-level
`no-explicit-any` disable - has no consumers left. With it go the `muya/*`
path in `tsconfig.base.json`, the matching alias in the three
electron.vite.config.ts sections and in vitest.config.ts, the `../muyajs/**/*`
exclude, and the `@marktext/muyajs` workspace dependency.

The lockfile entry was removed by hand rather than by re-resolving: a plain
`pnpm install --lockfile-only` also bumps the `file-icons` git tarball, lodash,
semver, tinyglobby and vitest's peer resolution, none of which belong in this
change. `pnpm install --lockfile-only --frozen-lockfile` accepts the result.

`packages/muyajs` itself stays until after 0.20.0 - it still lints and carries
its own dependencies; this only cuts the desktop's last tie to it.

The renderer bundle is byte-identical to the one built before this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ISerializedHistory` calls itself "the public, JSON-serializable shape
returned by `getHistory` and accepted by `setHistory`" but was not exported,
so the desktop had to write `ReturnType<Muya['getHistory']>`. Same for
`IHistorySelection`, `IPublicCursorInput` and `IIndexCursor`. A positional
reference couples a consumer to argument order rather than to a contract:
insert a parameter and `Parameters<...>[0]` silently starts asserting a
different type.

`search`/`replace` get the same treatment from the other side. `search(value,
opts = {})` inferred its option type as `{}` - it accepted anything - while
`replace`'s inferred `{ isSingle, isRegexp }` omitted `isCaseSensitive` and
`isWholeWord`, which `Search.replace` does honour (it spreads `...rest` over
`DEFAULT_SEARCH_OPTIONS`). Annotate both with the `ISearchOption` that already
existed and matched those defaults exactly, plus an `IReplaceOption` for the
one option only `replace` takes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`tsconfig.build.json` inherited `include: ["src"]`, and 258 of muya's 474
sources are `__tests__` specs - so 258 of the 472 emitted `.d.ts` were test
declarations no consumer can reach, and `files: ["lib"]` would have published
them. Excluding them drops the emit to 214 files; `lint:types` still
type-checks the specs through the base config.

Also make the build incremental. It runs on every `pnpm typecheck` and on
`postinstall`, and had no cache at all: 1.6s cold, 0.7s warm. The buildinfo
lives inside `lib/`, which `vite build` empties - a build that wipes the
declarations wipes the cache that claims they exist.

`outDir` goes: it is already set in the config this one extends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `paths` entry covered only the bare specifier, so a deep import still
resolved to nothing and carried a `@ts-expect-error` saying the subpath
"exposes no types". It does now - `lib/types/ui/inlineFormatToolbar/config.d.ts`
is emitted like any other - so map the subpaths and drop the directive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…natures

`ReturnType<Muya['getHistory']>` and three `Parameters<Muya[...]>[n]` were
standing in for types muya now exports by name. A positional reference asserts
"whatever argument N happens to be", which silently follows a signature change
instead of failing on it.

The search/replace payload casts were also asserting the wrong shape:
`Parameters<Muya['search']>[1]` resolved to `{}` (it checked nothing) and
`Parameters<Muya['replace']>[1]` omitted `isCaseSensitive`/`isWholeWord`, which
the engine honours and the search bar sends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`setOptions` was type-checked against the engine while the construction bag
next to it stayed `Record<string, unknown>` - and that bag is the larger
surface: ~40 options plus four callback hooks, all crossing into the engine
unchecked, where a misspelled key or a wrong value passed silently.

Typing it `Partial<IMuyaOptions>` turned up exactly one mismatch, the same
`sequenceTheme` the watcher already cast. The store keeps that field a
free-form string on purpose (it is read back from disk; ~10 fields are widened
the same way), so the cast belongs at the crossing rather than in the store -
now written once, named, and used by both paths.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The null guard added for `setOptions` was written onto the watcher condition,
so toggling the preference also skipped `activateSpellchecker` /
`deactivateSpellchecker` whenever the engine handle was absent. Those are two
independent lifetimes; the guard belongs on the call that needs it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CMInstance`/`CMCursor` existed to hold an `any` and to carry the comment
explaining why. With real types behind them they are two local names that say
less than `CodeMirror.Editor` / `CodeMirror.Position` do.

The guards that `any` allowed go with them: `cm?.getSelection?.() ?? ''` and
`cm?.listSelections?.() ?? []` are unreachable on a non-optional `Editor`
whose `DocOrEditor` base declares both as required, and the inline parameter
annotation on that `.map` is now inferred from `listSelections(): Range[]`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same reasoning the Muya handle already carries: the instance is `markRaw`d, so
a deep ref only costs an `UnwrapRef` mapping over the class. The unit spec
evaluates the SFC's setup body against a stubbed scope, so it needs the import
too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The search bar emitted `'prev'`; `Muya.find` takes `'previous' | 'next'`. It
worked because the engine reads anything that is not `'next'` as backwards,
and the cast at the bus boundary hid the mismatch. Emit the value the engine
declares.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hand-rolled structural copy had already drifted - its `ch?: number | null`
came from the call site passing `null`, not from CodeMirror - and this branch
had to hand-correct it. `Pick<CodeMirror.Editor, 'setCursor' | 'heightAtLine'>`
is the same move `util/cursor.ts` makes against the Muya type, and the unit
spec's fake editor stays structurally checked through it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The spec asserted an absence after a fixed 1.5s sleep, twice - which cannot
tell "the handler ran and did not throw" from "the IPC never arrived", and
cost 3s a run against the 100-150ms this repo settles the same check in.

Register a second listener on the same channel instead. Node's EventEmitter
runs listeners in registration order and an exception in one aborts the rest,
so the flag flipping proves the store's handler ran to completion; the
assertions then report which of the two things went wrong. 3.0s -> 47ms, and
re-verified against a build with the removed handler put back: the source-mode
case still fails there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment promised `.vue` would join section 2 "in Commit 8", and said the
SFCs were treated as JS until then. Both are long false, and now that section
5 registers the plugin itself the note reads like a leftover to delete. State
the reason that survives: `.vue` needs `vue-eslint-parser` as its top-level
parser, so it cannot share section 2's config object.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The intro says the bridge is gone; thirty lines below, the `allowJs` bullet
still sent readers to `src/types/muya.d.ts` and the `src/types/` listing still
named it. The `## muya boundary` section had shrunk to a pointer at the note
above it, so it goes too.

Also restore the IPC sample: Prettier dropped the trailing comma it needed for
the `// ...` continuation to make sense, so lead with the comment instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`lint.yml` carries `paths-ignore: packages/muya/**`, so a muya-only PR never
runs the desktop typecheck. That was harmless while the desktop consumed a
hand-written stub - muya could not break it by construction. Now that it
compiles against the real declarations, the PR that can break it is exactly
the one that skips the check: rename a method, narrow a union, change a return
shape, and the declarations still emit cleanly while `develop` breaks on the
next desktop PR.

The existing `build:types` step only proves the emit works. Run the consumer's
typecheck here too, against the declarations it just emitted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Caught while rebasing this branch: with `incremental`, `build:types` exited 0
without re-emitting after the checkout, leaving `lib/types/index.d.ts` from the
other branch's sources. The desktop then type-checked against declarations that
did not match the source it was compiled from - four "has no exported member"
errors for exports that were right there in `src/index.ts`. Deleting the
build-info file fixed it.

Silently type-checking against the wrong declarations is a worse failure than a
slow build, and the cache only bought ~0.9s on a command that spends ~3.2s in
vue-tsc afterwards. The spec exclusion, which is where most of the win was,
stays.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Jocs
Jocs force-pushed the chore/drop-legacy-muya-bridge-4257 branch from 535060f to 515c3e5 Compare September 23, 2026 11:42
@Jocs
Jocs merged commit 350acd4 into develop Sep 23, 2026
19 checks passed
@Jocs
Jocs deleted the chore/drop-legacy-muya-bridge-4257 branch September 23, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TS migration: finalize muya bridge once upstream TS muya lands

1 participant