Type the editor handle as Muya, from the engine's own declarations (#4257) by Jocs · Pull Request #5533 · marktext/marktext · GitHub
Skip to content

Type the editor handle as Muya, from the engine's own declarations (#4257) - #5533

Merged
Jocs merged 5 commits into
developfrom
refactor/muya-core-typings-4257
Sep 23, 2026
Merged

Jocs merged 5 commits into
developfrom
refactor/muya-core-typings-4257

Conversation

@Jocs

@Jocs Jocs commented Sep 23, 2026

Copy link
Copy Markdown
Member

Finishes item 2 of #4257 — the MuyaInstance alias that #5521 had to leave as
any.

Reopened from #5523, which GitHub auto-closed when its stacked base branch was
deleted on merge; same branch, rebased onto develop. CI was green there.

Why it was still any

Not "waiting for upstream" any more: packages/muya is in the tree, the
desktop depends on it as workspace:*, and the real Muya class exposes every
member editor.vue calls. The blocker was type resolution. @muyajs/core's
exports map points . at ./src/index.ts and the package ships no built
typings, so tsconfig.base.json redirected the import to a hand-written
src/types/muya-core.d.ts shim whose editor surface was [key: string]: any.

Pointing the path at the source instead was the obvious alternative, and I
measured it: 37 errors, all from muya's ambient declarations being absent from
the desktop's program. Pulling those in means merging two packages' global
scopes — Window.DIRNAME is already declared in both, with different
optionality — and it makes the desktop's compiler options the ones muya's
source has to satisfy (src/utils/prism/loadLanguage.ts already fails under
them while passing muya's own lint:types). So: built declarations, which
skipLibCheck keeps at arm's length.

What changed

muya — a declaration-only build (pnpm build:types, ~4s; pnpm build
already emits the same lib/types through vite-plugin-dts). Two
declaration-emit errors blocked it, both invisible to tsc --noEmit:
sanitize's inferred type named TrustedHTML through a transitive
@types/trusted-types path (TS2883), and loadLanguage's return type named
the unexported ILangLoadStatus (TS4023). Muya CI now runs the emit.

Two plugin defects surfaced the moment Muya.use(...) was checked against real
typings:

  • ParagraphFrontButton was the only UI plugin with no static pluginName, so
    the instance registry keyed it under undefined. Harmless today — nothing
    reads that map by name — but two unnamed plugins would evict each other.
  • ImageEditTool typed its options parameter as the fully-populated
    Options while Muya.use hands every plugin a plain object; the constructor
    merges the defaults in anyway.

desktop — @muyajs/core resolves to ../muya/lib/types, the shim is gone,
and editor is a shallowRef<Muya | null> (shallow: the instance is
markRawd anyway, and a deep ref would map the class through UnwrapRef).
pnpm typecheck and postinstall build the declarations first.

The real type found 26 unchecked call sites:

Kind Count Examples
bus payloads passed straight into the engine 9 search/replace options, find direction, paragraph insert location, pre-source-mode selection, per-tab engine history
the handle used before onMounted assigns it 12 four preference watches, the search/replace/find handlers, the export handler
structural stand-ins that no longer describe the engine 5 applyCursor's CursorEditor declared setCursor(cursor: unknown); getHtmlToc's TocEntry and listToTree's ListItem carried index signatures the engine's ITocItem cannot satisfy

The unknown payloads now cast to the engine's own parameter types
(Parameters<Muya['search']>[1] and friends) rather than to nothing.

Known gap

The Lint workflow still has paths-ignore: packages/muya/**, so a muya-only PR
that changes the public API would not re-run the desktop typecheck. That is the
pre-existing gap; the new declaration-emit step in Muya Lint at least catches
the emit breaking.

Verification

  • pnpm typecheck from a clean tree (rm -rf packages/muya/lib) — the script
    regenerates the declarations and vue-tsc is clean.
  • pnpm lint — 0 errors (148 pre-existing warnings). pnpm -C packages/muya lint
    and lint:types clean.
  • pnpm test:unit 770 passed and pnpm -C packages/muya test 1990 passed —
    both identical to the branch point (the failures in my worktree are a local
    node_modules symlink artifact).
  • Full Playwright suite against a real build:unpack; the built bundle was
    grepped to confirm it carries these sources.

🤖 Generated with Claude Code

Jocs and others added 5 commits September 23, 2026 19:26
`exports` points `.` at `./src/index.ts` and nothing ships built typings, so
a TypeScript consumer either type-checks the whole muya tree under its own
compiler options or falls back to a hand-written shim. Add a declaration-only
build (`pnpm build:types`, ~4s) so consumers can resolve `lib/types` instead;
`pnpm build` already emits the same directory through vite-plugin-dts.

Two declaration-emit errors had to go first - both invisible to
`tsc --noEmit`, which is all `lint:types` runs:

- `sanitize` inferred a type naming `TrustedHTML` from a transitive
  `@types/trusted-types` path that cannot be referenced from outside the
  package (TS2883).
- `loadLanguage`'s return type named the unexported `ILangLoadStatus`
  (TS4023).

CI now runs the emit as well, so neither can come back unnoticed.

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

Both surfaced the moment a consumer type-checks `Muya.use(...)` against real
typings rather than an `any` shim:

- `ParagraphFrontButton` was the only UI plugin without a static
  `pluginName`, so the instance registry keyed it under `undefined`. Nothing
  reads that map by name today (destroy iterates the values), but two unnamed
  plugins would have evicted each other.
- `ImageEditTool` declared its `options` parameter as the fully-populated
  `Options` while `Muya.use` hands every plugin a plain object, and the
  constructor merges the defaults in anyway. `Partial<Options>`, like every
  sibling plugin.

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

`tsconfig.base.json` redirected the import to a hand-written
`src/types/muya-core.d.ts` shim, because the package's `exports` map points
at TS source and it shipped no built typings. The shim's editor surface was
`[key: string]: any`, which is what kept `MuyaInstance` in editor.vue an
`any` (item 2 of #4257).

Now that muya emits `lib/types`, point the path there and delete the shim.
`typecheck` builds those declarations first (~4s) and `postinstall` emits
them too, so an editor opened straight after a clone resolves the import.
Pointing the path at the source instead was the alternative, but that
type-checks the whole muya tree under the desktop's compiler options -
different lib/target, and its ambient globals (`Element.__MUYA_BLOCK__`,
`window.MUYA_VERSION`) would have to move into the desktop's global scope,
where `Window.DIRNAME` already conflicts. `skipLibCheck` keeps the built
declarations from leaking muya's internals into desktop CI.

`ListItem`'s index signature goes with it: `listToTree` only ever reads
`lvl`/`content`/`slug`, and the signature was the one thing stopping the
engine's own `ITocItem` from satisfying it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the last `any` alias from item 2 of #4257. `editor` becomes a
`shallowRef<Muya | null>` - shallow because the instance is `markRaw`d
anyway and a deep ref would map the class through `UnwrapRef`.

What the real type surfaced, all of it previously unchecked:

- Handlers that reach the engine from the `bus` (whose payloads are
  `unknown`) now cast to the engine's own parameter types rather than to
  nothing at all: search/replace options, the find direction, the paragraph
  insert location, the pre-source-mode selection, the per-tab engine
  history.
- `sequenceTheme` is a free-form string in the preferences store but
  `'hand' | 'simple'` to the engine.
- Four watches and four bus handlers called straight through a handle that
  is null until `onMounted`.
- `applyCursor`'s `CursorEditor` and `getHtmlToc`'s `TocEntry` were
  structural stand-ins that no longer described the engine: the former
  declared `setCursor(cursor: unknown)`, the latter carried an index
  signature that the engine's `ITocItem` cannot satisfy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Jocs
Jocs merged commit 98bb858 into develop Sep 23, 2026
17 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Website preview ready: https://pr-5533-marktext-website.ransixi.workers.dev

Built from 745cab7733af22547e12a4771baa5895ca8ba16d · Worker: marktext-website · Version: f34790a0-6c8b-4b39-9708-e5ba9d1f5e7d · Alias: pr-5533

@github-actions

Copy link
Copy Markdown
Contributor

Build artifacts for PR #5533:

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

Artifact Size Link
marktext-windows-arm64 280.1 MB Download
marktext-macos-arm64 288.2 MB Download
marktext-windows-x64 288.2 MB Download
marktext-macos-x64 298.5 MB Download
marktext-linux 640.5 MB Download

@Jocs
Jocs deleted the refactor/muya-core-typings-4257 branch September 23, 2026 11:56
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.

1 participant