>_EXECUTIVE SUMMARY
zpwr-modal-editor is the shared Vim / Emacs modal-editing editor of the MenkeTechnologies app stack — one TypeScript source of truth for the vim and emacs editing modes, vendored from monaco-vim and monaco-emacs and adapted so we own them outright. The source lives in src/ and each consuming app bundles it with its own monaco-editor dependency.
The design turns on a surface adapter: the vim engine talks only to a CodeMirror-shaped adapter, so the same ~7.1k-line engine can drive a Monaco editor today and a raw DOM surface later. The bundle is produced with esbuild (IIFE, ES2020, minified) and exposes a single window.ZModal facade — create (Monaco-backed mount) plus the Monaco-agnostic attachVim / attachEmacs primitives.
Counts derived from the repo: src/**/*.ts = 12 files / 10,202 lines; vim engine engine/vim/keymap_vim.ts = 7,135; adapters/monaco_adapter.ts = 1,314; engine/emacs/*.ts = 1,329; modal engines = vim + emacs; surface adapters = monaco (dom = next milestone); window.ZModal methods = create + attachVim + attachEmacs; runtime deps = 0 (lodash replaced by a local util).
~ARCHITECTURE
One engine source, built per consumer, behind a swappable surface adapter. The entry wires the Monaco theme + worker and the facade; the vim engine is bolted onto whichever adapter class it imports; the bundler runs inside each app to resolve its own Monaco dep and emit vendored IIFE artifacts.
| Layer | Implementation |
|---|---|
| Vim engine | the full CodeMirror vim keymap (motions, operators, registers, marks, ex commands, search, macros) vendored via monaco-vim; it references its host editor only through the adapter it imports as "CodeMirror" |
| Surface adapter | monaco_adapter.ts implements the CodeMirror API (cursor, selections, ranges, marks, operations, scrolling) over a Monaco editor; a dom_adapter implementing the same API would drive a textarea / contenteditable with no Monaco |
| Emacs engine | monaco-emacs' extension — keybindings, kill-ring, mark, incremental search — coupled directly to the Monaco editor API; rides the Monaco adapter only |
| No runtime deps | the two lodash helpers monaco-emacs used (throttle, kebabCase) are replaced by engine/emacs/localutil.ts, so the package carries zero external runtime deps |
| Facade | window.ZModal — create (Monaco-backed mount) + attachVim / attachEmacs (Monaco-agnostic primitives) + VimMode / EmacsExtension / StatusBar |
| Build | esbuild IIFE, ES2020, minified; codicon .ttf inlined as a data URL; runs inside the consumer, writes to its frontend/lib |
&FACADE SURFACE
The bundle exposes one global, window.ZModal. create is the convenience mount (news up a Monaco editor and applies a mode); attachVim / attachEmacs are the primitives it is built on, for hosts that already own a Monaco editor and must not bundle a second one.
| Method | Role |
|---|---|
create(host, opts) | create a Monaco editor + apply mode; returns a handle (getValue / setValue / focus / setMode / layout / destroy) |
attachVim(editor, statusEl?) | attach vim to an existing Monaco editor; mode / key-buffer / ex line render into statusEl; returns the vim adapter |
attachEmacs(editor) | attach emacs to an existing Monaco editor; returns the started extension |
VimMode / EmacsExtension / StatusBar | the underlying classes, re-exported for advanced use |
!INTEGRATION — ONE MONACO PER PAGE
The default build bundles its own Monaco. If a host page also loads another Monaco bundle (e.g. zpwr-hooks-editor), a WebKit / WKWebView content process can crash on the second full Monaco — the window renders fully blank (the content process is gone). Chromium tolerates two Monacos on one page; WebKit does not. This is an integration constraint, not a bug in the engine. Two supported patterns:
| Pattern | How |
|---|---|
| Share one Monaco | the app creates its Monaco editor and calls attachVim(editor) / attachEmacs(editor); no second Monaco is bundled. A monaco-editor-external build variant (the clean way to ship this) is the next packaging task. |
| Be the only Monaco | use create(...) (bundles Monaco) only in apps that don't already load one. |
| DOM adapter | the planned dom_adapter sidesteps the constraint entirely for vim — in-place modal editing on a page surface with no Monaco at all. |
/BUNDLER & RESOLVE FIXES
scripts/build-modal-editor.mjs emits two bundles — modal-editor.bundle.js (+ .css) and modal-editor.worker.js — and carries esbuild resolve overrides so the vendored engines and Monaco resolve to ESM rather than AMD define() builds that throw in the WebView. Bare monaco-editor imports (the vendored monaco-emacs require()s it) are pinned to esm/vs/editor/edcore.main.js — the same lean ESM instance the entry uses — instead of the AMD min build. Deep monaco-editor/esm/* subpaths without a file extension get .js appended and resolve to the package dir directly (the package exports map only resolves extensioned paths). The build defaults its output to <cwd>/frontend/lib and honors MODAL_EDITOR_OUT. Because the vim/emacs engines are vendored, the consumer needs only esbuild + monaco-editor in devDependencies — no monaco-vim / monaco-emacs.
$CI GATES
This is a non-Rust (frontend / TypeScript) submodule, so it is exempt from the meta repo's cargo gates (fmt / clippy / doc / cargo test). The umbrella MenkeTechnologiesMeta repo enforces the documentation and brand gates on docs/:
| Gate | Requirement |
|---|---|
| doctype / charset / lang | <!DOCTYPE html> first line, <meta charset="utf-8">, <html lang="en"> |
| meta tags | non-empty <title>, viewport meta, description meta on both pages |
| structure | at least one <h1>; every <img> has alt; no deprecated HTML4 tags |
| links | external href/src https-only; no placeholder hrefs; target="_blank" carries rel="noopener noreferrer"; index ↔ report cross-linked |
| behavior | no inline event handlers (all via hud-theme.js); referenced css/js exist in docs/ |
| brand | "zpwr-modal-editor" present in titles + headings |
#PROJECT METADATA
| Item | Value |
|---|---|
| Version | 0.1.0 |
| Type | module (ESM); TypeScript source |
| Pinned devDeps (consumer) | esbuild ^0.28.1, monaco-editor 0.55.1 (the vim/emacs engines are vendored — no monaco-vim / monaco-emacs deps) |
| Bundle artifacts | modal-editor.bundle.js + modal-editor.bundle.css + modal-editor.worker.js |
| Vendored from | monaco-vim 0.4.4, monaco-emacs 0.3.0 (MIT; upstream licenses retained under src/engine/) |
| Author | MenkeTechnologies |
| Repository | github.com/MenkeTechnologies/zpwr-modal-editor |
| Meta umbrella | MenkeTechnologiesMeta |