ADR-0032: Monaco Editor as the code editor for kaged UI
- Status: Accepted
- Date: 2026-06-03
- Deciders: @karasu, with colleagues
- Supersedes: —
- Superseded by: —
Context
The kaged UI provides a web-based editing surface for project DSL (YAML), file browsing, and diff viewing. The current editor is CodeMirror 6, which provides basic syntax highlighting and linting integration. As the project matures, the operator needs a richer editing experience that does not feel like "using Notepad inside a dashboard."
Key requirements:
- LSP integration: The daemon already runs LSP bridges (per
agent-tooling.md). The editor should be able to consume those bridges for real-time diagnostics, hover, go-to-definition, and autocomplete. - Diff viewing: The UI needs to show diffs (e.g., DSL changes, file browser deltas). A unified diff view is a core operator workflow.
- Battle-tested: The editor must be production-grade, actively maintained, and used by millions of developers daily.
- Language support: YAML is the primary language today, but the editor must support TypeScript, JSON, Markdown, and any language the operator might encounter in the file browser or plugin authoring workflow.
- Themeability: The editor must be themeable to match the kaged brand palette (dark, amber/cyan/magenta accents).
Decision
kaged will adopt Monaco Editor (the editor that powers VS Code) as the code editor for the web UI. It will replace CodeMirror 6 in all editing surfaces: project DSL, subproject DSL, synthesized config, and the file browser (once implemented). Monaco Editor will also be used for the diff viewer.
kaged will use Monaco Editor (
@monaco-editor/react) for all code editing and diff surfaces in the web UI, replacing CodeMirror 6.
Consequences
What this commits us to
- Monaco Editor as the single code-editing primitive in the UI. All editing surfaces (DSL, file browser, plugin authoring) use the same component.
@monaco-editor/reactwrapper for React integration, withmonaco-editoras the peer dependency.- A custom Monaco theme (
kaged-dark) that maps the brand palette to Monaco's token colors so the editor feels native to the UI. - LSP bridge consumption via Monaco's built-in language client capabilities (or a thin adapter) once the file browser and LSP integration are wired.
- Diff viewer built on Monaco's
diffEditorAPI, not a separate dependency. - Schema-driven YAML editing via
monaco-yaml+ a JSON Schema generated from the canonicalProjectDslSchema(Zod) usingzod-to-json-schema. The schema is generated at build time (not runtime) and committed as a static JSON file in the UI package so the browser never loads the Zod/@kaged/dslserver-side code. This gives the operator inline structural validation, autocomplete for DSL keys, hover tooltips, and enum suggestions as they type.
What this forecloses
- CodeMirror 6 and its ecosystem (
@codemirror/lang-yaml,@codemirror/language,@codemirror/lint,@codemirror/state,@codemirror/view,@lezer/highlight) are removed from the UI package. - Any CodeMirror-specific plugins or themes are abandoned.
- The "lightweight" argument for CodeMirror (smaller bundle) is traded for Monaco's feature richness. Bundle size is accepted as a trade-off.
What becomes easier
- LSP integration: Monaco has first-class support for the Language Server Protocol. Connecting the daemon's existing LSP bridges (
lsp-bridge-runtime.ts) to the editor is a thin adapter, not a ground-up implementation. - Diff viewing: Monaco's built-in
diffEditorprovides a production-quality side-by-side or inline diff view with no extra dependencies. - Language breadth: Monaco ships with 50+ language modes out of the box. YAML, TypeScript, JSON, Markdown, and shell are all supported without additional language packages.
- Developer familiarity: Operators who use VS Code already know the editor's keybindings, command palette, and behavior.
- Theme consistency: Monaco's theming API is straightforward; mapping the kaged brand palette to editor tokens is a single JSON object.
What becomes harder
- Bundle size: Monaco Editor is larger than CodeMirror 6. The UI's JS bundle is already over budget (361 KB gzipped vs. 200 KB target per
STATUS.md). Adding Monaco will increase this. The mitigation is: (1) lazy-load the editor only on editing routes (not the dashboard), (2) usemonaco-editor-webpack-plugin/ Vite equivalent to split workers and languages, (3) revisit the budget in a dedicated perf ADR if needed. - Worker setup: Monaco uses Web Workers for parsing and tokenization. Vite requires explicit worker configuration in
vite.config.ts. - Mobile UX: Monaco is desktop-optimized. On mobile, the editor remains functional but may require touch-specific tweaks (e.g., larger scrollbars, custom touch handling). The current mobile strategy is "editor is a secondary surface on mobile; the primary workflow is messaging and status."
- ARM64 build: The default Monaco worker files are JS, not native binaries, so ARM64 is unaffected. However, the
@kaged/nativesRust N-API bindings already handle ARM64; no new concern here.
Alternatives considered
Alternative A — Keep CodeMirror 6
What it was: Continue with CodeMirror 6 and invest in LSP adapter plugins (@codemirror/lint → LSP bridge, custom autocomplete, etc.).
Why it was tempting: Smaller bundle, already integrated, no migration work.
Why we didn't pick it: CodeMirror's LSP integration is third-party and immature compared to Monaco's built-in support. Diff viewing would require a separate dependency (e.g., diff-match-patch + custom UI). The operator's editing experience would remain "Notepad-like" rather than "IDE-like." The gap between what CodeMirror can do and what the daemon's LSP bridges can offer is too large to close elegantly.
Alternative B — Ace Editor
What it was: Cloud9's Ace Editor, another mature browser-based editor.
Why it was tempting: Smaller than Monaco, has LSP support via third-party adapters, diff viewing via ace-diff.
Why we didn't pick it: Ace is less actively maintained than Monaco. Its LSP integration is weaker. The VS Code ecosystem alignment is weaker. Monaco is the de facto standard for in-browser code editing; Ace is a legacy choice.
Alternative C — ProseMirror + custom
What it was: ProseMirror for structured text, custom DSL tree view for YAML. Why it was tempting: ProseMirror is excellent for structured editing; a tree view could be more intuitive for YAML. Why we didn't pick it: Too bespoke. The operator is a developer who expects a code editor, not a form builder. The LSP bridge output is line/column-based; a tree view would need a reverse mapping that adds complexity without clear benefit.
References
- Monaco Editor
@monaco-editor/react— React wrapper for Monaco- ADR-0002 — web-first, terminal is a capability
- ADR-0004 — Bun runtime, build tooling
docs/specs/ui/README.md— UI spec (editor surfaces defined there)docs/specs/agent-tooling.md— LSP bridge runtimeSTATUS.md— bundle size budget and known tech debt