Skip to content

Add a Monaco code editor surface (cmux edit) - #5638

Closed
lawrencecchen wants to merge 15 commits into
mainfrom
feat-monaco-editor
Closed

lawrencecchen wants to merge 15 commits into
mainfrom
feat-monaco-editor

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Adds a Monaco code editor as a first-class surface in the webviews/ React harness, openable with cmux edit <file>. Built on the surface-split groundwork from #5613.

What's here

  • cmux edit <file> opens a themed Monaco editor inside cmux (rides the existing diff-viewer custom-scheme + browser.open_split flow; no new SurfaceRole). CLI writeEditor mirrors the lean diff path; the editor page injects the file content + live cmux appearance as config.
  • Themed from the live cmux appearance (cmux-dark/cmux-light via defineTheme, light + dark).
  • Syntax highlighting for all common languages (Go, Rust, Python, Java, C/C++, C#, PHP, Ruby, Swift, Kotlin, Scala, SQL, YAML, XML, HTML, CSS/SCSS/LESS, shell, Dockerfile, Markdown, TS/JS, JSON, and ~20 more), registered eagerly so they tokenize deterministically.
  • Bundle compressed 14.5 MB → 4.2 MB (the big vendor chunks are deflated-at-rest and inflated on copy; no serving/CSP changes; --check made content-aware).

Notable fixes for cmux's WKWebView

  • Serving: the diff-viewer scheme + local HTTP server now serve text/css; the editor fetches + inlines Monaco's stylesheet (CSP-safe), no font/CSP changes.
  • Highlighting rendered plain non-deterministically because Monaco tokenizes via a throttled async scheduler that cmux's offscreen-IOSurface WKWebView starves. Fixes: register grammars eagerly (no lazy .contribution loaders), preload embedded grammars (html→css/js, markdown→code), ResizeObserver layout, and force synchronous tokenization (model.tokenization.forceTokenization) after mount. JSON uses a small inline Monarch grammar (dropped the 383 KB JSON service worker).

Verification

  • bun run typecheck, lint:ci, verify:tanstack-router, build-webviews-app.sh --check, React Compiler guard: all pass.
  • In-app (tag mono) socket DOM probe: python, go, rust, c/c++, java, sql, yaml, html, markdown, json all highlight consistently across repeated clean runs.

Follow-ups

  • Language-service IntelliSense (ts/css/html) and JSON validation (deliberately omitted to keep the bundle small).
  • File save / dirty tracking; richer ANSI-palette → token-scope theme mapping; a cmux.json editor config section.
  • Graft onto a first-class code-editor SurfaceRole (Add code editor surface type #4801) for command-palette / file-opener / session-restore entrypoints.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Monaco-based code editor with syntax highlighting for 40+ programming languages (Python, JavaScript, TypeScript, Rust, Go, Java, C++, and more)
    • Extended the CLI with a new edit command for file editing
    • Added CSS stylesheet support to the diff viewer for enhanced styling
    • Integrated theme synchronization across editor and diff viewer interfaces

Note

Medium Risk
New CLI path reads local files and opens web surfaces through the diff-viewer allowlist/HTTP server; changes broaden what the local server can serve (CSS) but follow the existing split-open model.

Overview
Adds cmux edit <file>, which reads a UTF-8 file, generates an editor HTML page (file path, content, title, live appearance config), and opens it via the existing diff-viewer custom URL + browser.open_split flow—with optional --window / --workspace / --surface / --focus and JSON output fields for path, viewer path, and URL.

Diff-viewer asset serving is extended so Monaco can load styles: allow text/css, classify .css assets correctly in the allowlist, and include css (and .deflate logical names) when enumerating/copying bundled webview assets, with raw DEFLATE inflation on copy and mtime-based skip logic for inflated targets.

Reviewed by Cursor Bugbot for commit 6b1159e. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds an `/editor` webviews surface backed by Monaco, as its own lazy
`monaco-vendor` chunk (groundwork from the surface-split PR #5613):

- `EditorApp` mounts Monaco via a React 19 callback ref (no useEffect),
  disposing editor + model on unmount.
- Theme derived from the live cmux appearance (light + dark via defineTheme),
  strictly better than the dark-only reference PR.
- One base editor.worker; editor.api + basic-languages Monarch highlighting on
  the main thread (no language-service workers / IntelliSense yet).
- Build: base "./" for relative asset URLs (Monaco worker/font), monaco-vendor
  manualChunk, ES-module worker, main.tsx dispatcher + `/editor` route.

Builds, typechecks, lints. NOT yet dogfoodable: the spike confirmed Monaco
emits external CSS + codicon font + a worker, and the diff viewer custom scheme
only registers .js/.mjs, so serving needs (1) runtime CSS injection, (2) CLI
css/ttf enumeration + scheme MIME allowlist, (3) stable worker filename, (4) a
Debug-menu open seam. See plans/feat-monaco-editor/DESIGN.md (hq) for the plan.
The webviews build has no HTML entry, so Vite does not auto-link
monaco-vendor.css. The editor surface now links it at runtime, resolved
relative to its own chunk URL (independent of where the host page is served);
the CSS references its font as relative url(./codicon.ttf).

Verified over plain HTTP (headless Chrome) that the editor mounts, applies the
cmux dark theme, syntax-highlights TypeScript on the main thread, and spawns
the base editor.worker without errors.
@vercel

vercel Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 8, 2026 2:23pm
cmux-staging Building Building Preview, Comment Jun 8, 2026 2:23pm

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR integrates Monaco editor into the webviews application with build configuration, runtime environment setup, React components, 40+ language grammar definitions, webview routing, and a CLI edit command for opening local files in an embedded editor surface.

Changes

Monaco editor webview integration

Layer / File(s) Summary
Build dependencies and Vite configuration
webviews/package.json, webviews/vite.config.mjs, scripts/build-webviews-app.sh
Added monaco-editor@0.55.1 dependency, configured Vite base path for relative assets, set worker output with stable filenames, created monaco-vendor chunk for core Monaco modules, and added build-time deflate compression for large .mjs chunks with check-aware materialization via zlib inflation.
Monaco worker environment, theme definition, and appearance helpers
webviews/src/editor/monacoEnvironment.ts, webviews/src/editor/monacoTheme.ts, Resources/markdown-viewer/webviews-app/chunks/appearance.mjs
Configured Monaco worker loading via globalThis.MonacoEnvironment, defined theme construction (defineMonacoThemes) deriving cmux-dark and cmux-light from DiffViewerTheme colors, and implemented appearance module with contrast-aware foreground/selection computation and CSS custom property generation.
Editor React component and viewport layout
webviews/src/editor/editor.css, webviews/src/editor/EditorApp.tsx
Added full-viewport CSS layout (html/body/root set to 100% height/width), implemented EditorApp component that creates Monaco model/editor via callback ref with ResizeObserver for layout updates on container resize, forces tokenization, and renders deterministically.
Language registration and grammar loading
webviews/src/editor/monacoLanguages.ts, Resources/markdown-viewer/webviews-app/chunks/{appearance,bat,clojure,...,yaml}.mjs
Registered 40+ Monaco languages with file extensions at module load, implemented preloadGrammarForPath() to dynamically load Monarch grammars with embedded language dependency resolution, and added comprehensive language definition modules for syntax highlighting across JavaScript, TypeScript, Python, Go, Rust, Java, C++, C#, Kotlin, and 30+ other languages.
Editor routing and webview surface mounting
webviews/src/main.tsx, webviews/src/router.tsx, webviews/src/surfaces/editorSurface.tsx, Resources/markdown-viewer/webviews-app/chunks/{editorSurface,installWebviewStyles}.mjs
Extended WebviewKind to include "editor" variant, added detection logic for cmuxWebviewKind dataset or cmux-editor-config element, created /editor route wired to WebviewComponent, and implemented async mountEditorSurface() that reads config from DOM, injects Monaco CSS, resolves theme via prefers-color-scheme, preloads grammar for file extension, and renders editor with computed options (readOnly, minimap, scroll constraints).
CLI edit command and diff-viewer server integration
CLI/cmux.swift, CLI/cmux_open.swift, Sources/Panels/BrowserPanel.swift
Added edit command dispatch calling runEditCommand() that loads local files, embeds UTF-8 content and appearance into editor HTML, generates allowlisted URLs via diff-viewer server, opens webview split, and optionally returns JSON; extended HTTP server to allow text/css MIME type for Monaco stylesheets; implemented deflate-aware asset copying that inflates .deflate bundles during copy and validates freshness via timestamp comparison.

Sequence Diagram(s)

sequenceDiagram
  participant main as webviews/src/main.tsx
  participant router as TanStack Router
  participant surface as editorSurface
  participant env as monacoEnvironment
  participant editor as EditorApp
  main->>main: resolveWebviewKind() → "editor"
  main->>surface: dynamic import, call mountEditorSurface()
  surface->>env: install globalThis.MonacoEnvironment
  surface->>surface: injectMonacoStylesheet()
  surface->>surface: preloadGrammarForPath(filePath)
  surface->>router: createRoot, render RouterProvider
  router->>editor: render EditorApp with theme/config
  editor->>env: create model and editor instance
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#5613: Builds on the per-surface code-splitting infrastructure by adding the new editor surface dispatch logic in webviews/src/main.tsx and further refining Vite chunk configuration in webviews/vite.config.mjs.

Poem

🐰 A rabbit hops through code so bright,
With Monaco's colors, dark and light,
Forty tongues for syntax to shine,
From Python to Rust, a lexical line,
Edit commands dance, deflate files compress—
The editor webview is blessed! ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-monaco-editor

@greptile-apps

greptile-apps Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds cmux edit <file> — a Monaco editor webview surface that reuses the diff-viewer's custom-scheme + browser.open_split plumbing. File content is injected as a JSON config blob into a generated HTML page and served via the same per-token allowlist, with CSS now added to the MIME allowlist and large vendor chunks deflated-at-rest to shrink the committed bundle.

  • CLI (cmux_open.swift): runEditCommand reads the UTF-8 file, generates the editor HTML via writeEditorHTML, enumerates allowed assets (now including .css and inflated .deflate logical paths), and opens a webview via browser.open_split. Content is escaped through the existing jsonScriptLiteral helper (which appends <\\/ escaping), making injection safe.
  • Asset pipeline: copyDiffViewerAsset gains deflate-sidecar support — raw DEFLATE files are inflated via NSData.decompressed(using: .zlib) to their logical filenames; concurrent writes are handled atomically with temp-rename and mtime-based skip guards.
  • Webviews (TypeScript): New editorSurface.tsx bootstraps Monaco with cmux-derived themes, eagerly registers 40+ Monarch language grammars (bypassing Monaco's async lazy-loader path), forces synchronous tokenization to fix WKWebView's offscreen-IOSurface scheduler starvation, and drives layout via ResizeObserver.

Confidence Score: 5/5

Safe to merge — the new cmux edit path reuses well-tested diff-viewer infrastructure with no new trust boundaries or serving changes beyond adding CSS to an already-guarded MIME allowlist.

All changed paths are either CLI-side file prep or incremental extensions to the existing diff-viewer asset pipeline. File content is escaped through the battle-tested jsonScriptLiteral helper; the deflate/inflate round-trip is atomic and idempotent; the Monaco callback-ref cleanup is correct for React 19; React Compiler is enabled, preventing inline-object ref churn. The only finding is a stale JSDoc on preloadGrammarForPath.

No files require special attention.

Important Files Changed

Filename Overview
CLI/cmux_open.swift Adds runEditCommand, writeEditor, and writeEditorHTML; extends asset pipeline for CSS and deflate sidecars. JSON content is properly escaped via jsonScriptLiteral. Mtime-based freshness check for inflated assets is sound; concurrent-write race is handled with atomic temp-rename.
Sources/Panels/BrowserPanel.swift Adds text/css to the WKURLSchemeHandler MIME allowlist and path-extension guard. Minimal and consistent with the existing pattern.
webviews/src/surfaces/editorSurface.tsx Boots Monaco editor surface: reads injected config (guarded with try/catch), injects Monaco CSS via fetch+inline, defines cmux-derived themes, preloads grammar, and renders EditorApp through the shared router.
webviews/src/editor/EditorApp.tsx React 19 callback-ref pattern correctly returns a cleanup function that disposes the editor, ResizeObserver, and model on unmount. React Compiler memoizes the inline options object at the call site, preventing ref-identity churn.
webviews/src/editor/monacoLanguages.ts Registers 40+ languages eagerly with Monarch grammars; custom inline JSON grammar bypasses the async language-service path. JSDoc on preloadGrammarForPath incorrectly says JSON is handled by the language service.
scripts/build-webviews-app.sh Adds compress_webviews_output and materialize_for_compare for deflate-aware --check comparison. No sleeps or polling.
webviews/src/editor/monacoTheme.ts Defines cmux-dark and cmux-light Monaco themes from the live Ghostty appearance, with normalizeHex guards and alpha suffix helpers.
webviews/vite.config.mjs Adds base: ./ for sub-path serving, worker ES-module output config, and manualChunks split for monaco-vendor. React Compiler is wired via the react plugin.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as cmux CLI
    participant FS as Filesystem tmp
    participant App as cmux App
    participant WKWebView
    participant EditorSurface as editorSurface.tsx

    User->>CLI: cmux edit file
    CLI->>FS: Read file UTF-8
    CLI->>FS: writeEditorHTML content via jsonScriptLiteral
    CLI->>FS: ensureDiffViewerAssets copy and inflate deflate chunks
    CLI->>App: browser.open_split with url diff_viewer_token diff_viewer_files
    App->>WKWebView: Load editor HTML via custom scheme
    WKWebView->>App: CmuxDiffViewerURLSchemeHandler serves mjs and css assets
    WKWebView->>EditorSurface: main.mjs calls mountEditorSurface
    EditorSurface->>WKWebView: fetch monaco-vendor.css then inject style
    EditorSurface->>EditorSurface: preloadGrammarForPath registers Monarch grammar eagerly
    EditorSurface->>EditorSurface: defineMonacoThemes dark and light
    EditorSurface->>WKWebView: createRoot renders EditorApp
    EditorSurface->>EditorSurface: forceTokenization then render true
Loading

Reviews (7): Last reviewed commit: "Address review-bot feedback on cmux edit" | Re-trigger Greptile

Comment thread webviews/vite.config.mjs
Comment on lines +40 to +53
// Monaco's `?worker` import builds a worker bundle. Emit it as an ES module
// worker with a stable name (no hash) so `new Worker(url, {type:"module"})`
// works and the file overwrites in place in the diff viewer asset cache,
// matching the main bundle's stable-name policy above.
worker: {
format: "es",
rollupOptions: {
output: {
entryFileNames: "chunks/[name].mjs",
chunkFileNames: "chunks/[name].mjs",
assetFileNames: "assets/[name][extname]",
},
},
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Worker output config comment doesn't match actual output

The comment on lines 40-43 says the worker will be emitted as "an ES module worker with a stable name (no hash)" and names the expected path pattern chunks/[name].mjs, but the built artifact is assets/editor.worker-CdQrwHl8.js — in assets/, not chunks/, with a content hash appended, and the file body is IIFE format ((function(){"use strict";...})), not an ES module. The worker.rollupOptions.output directives are silently not applied by this version of Vite.

The PR description explicitly notes the stable-name goal is a follow-up, but the code comment presents the goal as already achieved. Until the follow-up lands, each rebuild produces a differently-named file, and the /tmp diff-viewer cache accumulates orphaned worker copies rather than overwriting in place as the comment implies.

Comment thread webviews/src/surfaces/editorSurface.tsx Outdated
Comment on lines +21 to +33
/**
* Boots the Monaco editor surface: reads its injected config, registers
* cmux-derived themes, then renders `EditorApp` through the shared router.
* Loaded as its own lazy chunk so other surfaces never pay for Monaco.
*/
/**
* Links Monaco's emitted stylesheet. The webviews build has no HTML entry, so
* Vite does not inject `monaco-vendor.css` automatically. The CSS sits next to
* the chunks (`assets/monaco-vendor.css`), and its font is a relative
* `url(./codicon.ttf)`, so resolving the link href from this chunk's own URL
* works regardless of where the host page is served.
*/
function linkMonacoStylesheet(): void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Displaced JSDoc — first block describes mountEditorSurface but precedes linkMonacoStylesheet

There are two consecutive JSDoc comments before linkMonacoStylesheet. The first one ("Boots the Monaco editor surface…") clearly belongs on mountEditorSurface, which is left undocumented. The second JSDoc correctly describes linkMonacoStylesheet.

Suggested change
/**
* Boots the Monaco editor surface: reads its injected config, registers
* cmux-derived themes, then renders `EditorApp` through the shared router.
* Loaded as its own lazy chunk so other surfaces never pay for Monaco.
*/
/**
* Links Monaco's emitted stylesheet. The webviews build has no HTML entry, so
* Vite does not inject `monaco-vendor.css` automatically. The CSS sits next to
* the chunks (`assets/monaco-vendor.css`), and its font is a relative
* `url(./codicon.ttf)`, so resolving the link href from this chunk's own URL
* works regardless of where the host page is served.
*/
function linkMonacoStylesheet(): void {
/**
* Links Monaco's emitted stylesheet. The webviews build has no HTML entry, so
* Vite does not inject `monaco-vendor.css` automatically. The CSS sits next to
* the chunks (`assets/monaco-vendor.css`), and its font is a relative
* `url(./codicon.ttf)`, so resolving the link href from this chunk's own URL
* works regardless of where the host page is served.
*/
function linkMonacoStylesheet(): void {

Comment thread webviews/src/surfaces/editorSurface.tsx Outdated
if (!element?.textContent) {
throw new Error("Missing cmux editor config");
}
return JSON.parse(element.textContent);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unguarded JSON.parse in readConfig will crash the surface with an opaque error

If the cmux-editor-config element exists but contains malformed JSON (e.g. a partial write during Swift-side injection), JSON.parse throws and the promise-chain in main.tsx never catches it, leaving the webview silently broken. A try/catch with a clear message makes diagnosing this much easier.

Suggested change
return JSON.parse(element.textContent);
try {
return JSON.parse(element.textContent);
} catch {
throw new Error("cmux editor config is not valid JSON");
}

Makes the Monaco editor openable in the app, served through the diff viewer
custom scheme (so the module worker works), with the file content + live cmux
appearance injected as config.

- CLI: `cmux edit <file>` writes an `editor` webviews page, registers the
  bundled assets, and opens a webview via `browser.open_split` (reusing the
  diff viewer token/allowlist flow). `writeEditor` / `writeEditorHTML` mirror
  the lean diff path.
- Serving: include `.css` in the bundled-asset enumeration and register it as
  `text/css`; add `text/css` to the scheme handler's MIME allowlist. The editor
  surface fetches `monaco-vendor.css` and injects it as an inline `<style>`
  (allowed by the page CSP's `connect-src 'self'` + `style-src 'unsafe-inline'`)
  instead of an external `<link>`, so no CSP change is needed. The codicon font
  is skipped for v1 (core editing/highlighting does not need it).

The worker (.js, same-origin) is already served and allowed by `script-src
'self'`. No app-side surface/SurfaceRole changes: the editor rides the existing
browser.open_split + custom-scheme path.
The live editor/diff path uses the local HTTP server (127.0.0.1), not the
custom scheme. Its manifest validator rejected the whole token because
`monaco-vendor.css` failed the HTTP server's MIME allowlist, 500-ing every
request. Add text/css to `diffViewerHTTPIsAllowedMimeType` +
`...PathExtensionMatchesMimeType`, mirroring the scheme handler change. The
HTTP server sets no CSP, so the fetch+inline CSS path works there unchanged.
Shrinks the committed git tree + .app bundle ~14.5MB -> 3.9MB. The bloat is the
diff-vendor (10.3MB) and monaco-vendor (3.7MB) chunks, which are served only
through the diff viewer HTTP server / custom scheme, never the agent-session
file:// load. The build deflates chunks >1MB to `<name>.deflate` (raw DEFLATE
via node, matching Swift `NSData.decompressed(using:.zlib)`); the CLI inflates
them on copy into the per-token serving dir, so the scheme handler, HTTP
server, agent-session loading, and CSP are all unchanged.

`build-webviews-app.sh --check` now compares inflated CONTENT for `.deflate`
files so the reproducibility gate is immune to deflate byte-variance across
environments/zlib versions.
…ense)

Every common language already highlights via its basic-languages Monarch
grammar. JSON is the one common language with no Monarch grammar, so add the
JSON language service + its 383KB worker. Deliberately skip the CSS/HTML/
TypeScript language services: their Monarch grammars already highlight, and
their workers (especially the ~7MB TypeScript worker) only add IntelliSense
and would balloon the bundle the previous commit shrank.
The editor showed no highlighting (every line was one default token). Root
cause: `basic-languages/_.contribution.js` only *defines* `registerLanguage`;
it is not an all-languages aggregate, so no grammar was ever registered, and
collapsing all of Monaco into one chunk also dropped the lazy grammar imports.

Now register each common language via its own `*.contribution.js` (Go, Rust,
Python, Java, C/C++, C#, Ruby, PHP, Swift, Kotlin, Scala, SQL, YAML, XML, HTML,
CSS/SCSS/LESS, shell, Dockerfile, Markdown, TS/JS, JSON, and ~20 more). Each
lazy-loads its Monarch grammar as its own chunk on first open. manualChunks
keeps Monaco *core* collapsed but lets the grammars + JSON service split so
their lazy `import()` works. Verified over HTTP: python/json now tokenize with
distinct token colors.
The cmux WKWebView pane can report 0 height when the Monaco editor is created,
and Monaco's `automaticLayout` observer occasionally misses that first sizing,
leaving the editor showing zero lines even though the page is correct (the same
URL renders fine at full size). Drive `editor.layout()` from a ResizeObserver
on the container so the editor lays out as soon as the pane has a size.
Comment thread CLI/cmux_open.swift

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fa0692570a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread webviews/src/surfaces/editorSurface.tsx Outdated
fontFamily: appearance.fontFamily,
fontSize: appearance.fontSize,
lineHeight: appearance.lineHeight,
readOnly: Boolean(config.payload?.readOnly),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the editor read-only until edits can save

When cmux edit <file> opens this surface, the CLI injects only the file contents/path/title and no save endpoint or readOnly flag, so this line makes Monaco editable by default (Boolean(undefined) is false). In that scenario users can modify the buffer in the pane but no onDidChangeModelContent/save path writes those edits back to the original file, so closing or reloading the surface silently discards changes; default this to read-only (or wire persistence) until a real save flow exists.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLI/cmux_open.swift`:
- Around line 930-953: The argument parser loop for cmux edit silently accepts
malformed invocations (unknown flags, missing option values, extra positional
args, and filenames beginning with '-') so update the parsing in the while loop
that reads commandArgs (variables: index, arg, windowArg, workspaceArg,
surfaceArg, focus, filePathArg) to: 1) treat "--" as end-of-options so a
following arg starting with '-' becomes the filePathArg; 2) validate option
flags that require a value (--window, --workspace, --surface) and throw CLIError
with the Usage message if the value is missing; 3) reject any unknown flag (arg
starting with '-' that isn't one of the known flags) by throwing CLIError; and
4) reject additional positional arguments by throwing CLIError if filePathArg is
already set and another non-flag positional appears; ensure all error cases use
the existing CLIError(message:) pattern and keep focus handling as-is.
- Around line 956-960: The current use of try? String(contentsOf: fileURL,
encoding: .utf8) conflates I/O/read errors with encoding failures; change the
logic to explicitly perform a read that surfaces I/O errors (e.g., use try
Data(contentsOf: fileURL) or a do/catch around String(contentsOf:)) and then
convert the data to a UTF-8 string, throwing the existing CLIError only when the
conversion fails; update the code around the fileURL checks (the guard that
currently uses try? String(contentsOf:...)) so that read errors propagate and
only a nil String(data:..., encoding: .utf8) triggers the “not UTF-8” message.
- Around line 1001-1005: The JSON branch currently sets response["path"] =
editor.fileURL.path which exposes the temp/generated HTML path; change it to
return the original source file path used for editing (i.e. set response["path"]
to the original source file variable used elsewhere such as sourceFileURL.path
or inputFileURL.path), falling back to editor.fileURL.path only if that original
source variable is nil/unavailable; keep response, payload and the other keys
(response["url"], response["title"]) unchanged.

In `@Resources/markdown-viewer/webviews-app/chunks/hcl.mjs`:
- Line 1: The keywords array on the language object t contains entries with
trailing spaces ("if ", "else ", "endif ", "for ") so they never match; edit the
t.keywords list to remove the trailing spaces (use "if","else","endif","for") so
the tokenizer (tokenizer/root and terraform rules that reference "`@keywords`")
can properly recognize and highlight these keywords.

In `@webviews/src/surfaces/editorSurface.tsx`:
- Around line 43-44: The code reads and inlines CSS without verifying the HTTP
response; change the flow so you first await fetch(href) into response, then
check response.ok (and optionally that response.headers.get('content-type')
indicates text/css) before calling response.text(); if the response is not ok
(or content-type is unexpected) log/handle the error and skip injecting the body
to avoid inlining HTML error pages into the <style> — update the block around
the variables response, css, and href in editorSurface.tsx accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ca09602b-f408-4c6f-aa83-fcb17b173e31

📥 Commits

Reviewing files that changed from the base of the PR and between 1fd48f9 and fa06925.

📒 Files selected for processing (61)
  • CLI/cmux.swift
  • CLI/cmux_open.swift
  • Resources/markdown-viewer/webviews-app/assets/json.worker-BoL8UZqY.js
  • Resources/markdown-viewer/webviews-app/chunks/bat.mjs
  • Resources/markdown-viewer/webviews-app/chunks/clojure.mjs
  • Resources/markdown-viewer/webviews-app/chunks/coffee.mjs
  • Resources/markdown-viewer/webviews-app/chunks/cpp.mjs
  • Resources/markdown-viewer/webviews-app/chunks/csharp.mjs
  • Resources/markdown-viewer/webviews-app/chunks/css.mjs
  • Resources/markdown-viewer/webviews-app/chunks/dart.mjs
  • Resources/markdown-viewer/webviews-app/chunks/diff-vendor.mjs
  • Resources/markdown-viewer/webviews-app/chunks/diff-vendor.mjs.deflate
  • Resources/markdown-viewer/webviews-app/chunks/dockerfile.mjs
  • Resources/markdown-viewer/webviews-app/chunks/editorSurface.mjs
  • Resources/markdown-viewer/webviews-app/chunks/elixir.mjs
  • Resources/markdown-viewer/webviews-app/chunks/fsharp.mjs
  • Resources/markdown-viewer/webviews-app/chunks/go.mjs
  • Resources/markdown-viewer/webviews-app/chunks/graphql.mjs
  • Resources/markdown-viewer/webviews-app/chunks/handlebars.mjs
  • Resources/markdown-viewer/webviews-app/chunks/hcl.mjs
  • Resources/markdown-viewer/webviews-app/chunks/html.mjs
  • Resources/markdown-viewer/webviews-app/chunks/ini.mjs
  • Resources/markdown-viewer/webviews-app/chunks/java.mjs
  • Resources/markdown-viewer/webviews-app/chunks/javascript.mjs
  • Resources/markdown-viewer/webviews-app/chunks/jsonMode.mjs
  • Resources/markdown-viewer/webviews-app/chunks/julia.mjs
  • Resources/markdown-viewer/webviews-app/chunks/kotlin.mjs
  • Resources/markdown-viewer/webviews-app/chunks/less.mjs
  • Resources/markdown-viewer/webviews-app/chunks/lua.mjs
  • Resources/markdown-viewer/webviews-app/chunks/markdown.mjs
  • Resources/markdown-viewer/webviews-app/chunks/mdx.mjs
  • Resources/markdown-viewer/webviews-app/chunks/monaco-vendor.mjs.deflate
  • Resources/markdown-viewer/webviews-app/chunks/mysql.mjs
  • Resources/markdown-viewer/webviews-app/chunks/objective-c.mjs
  • Resources/markdown-viewer/webviews-app/chunks/perl.mjs
  • Resources/markdown-viewer/webviews-app/chunks/pgsql.mjs
  • Resources/markdown-viewer/webviews-app/chunks/php.mjs
  • Resources/markdown-viewer/webviews-app/chunks/powershell.mjs
  • Resources/markdown-viewer/webviews-app/chunks/protobuf.mjs
  • Resources/markdown-viewer/webviews-app/chunks/python.mjs
  • Resources/markdown-viewer/webviews-app/chunks/r.mjs
  • Resources/markdown-viewer/webviews-app/chunks/ruby.mjs
  • Resources/markdown-viewer/webviews-app/chunks/rust.mjs
  • Resources/markdown-viewer/webviews-app/chunks/scala.mjs
  • Resources/markdown-viewer/webviews-app/chunks/scss.mjs
  • Resources/markdown-viewer/webviews-app/chunks/shell.mjs
  • Resources/markdown-viewer/webviews-app/chunks/solidity.mjs
  • Resources/markdown-viewer/webviews-app/chunks/sql.mjs
  • Resources/markdown-viewer/webviews-app/chunks/swift.mjs
  • Resources/markdown-viewer/webviews-app/chunks/typescript.mjs
  • Resources/markdown-viewer/webviews-app/chunks/xml.mjs
  • Resources/markdown-viewer/webviews-app/chunks/yaml.mjs
  • Resources/markdown-viewer/webviews-app/main.mjs
  • Sources/Panels/BrowserPanel.swift
  • scripts/build-webviews-app.sh
  • webviews/src/editor/EditorApp.tsx
  • webviews/src/editor/monacoEnvironment.ts
  • webviews/src/editor/monacoLanguages.ts
  • webviews/src/main.tsx
  • webviews/src/surfaces/editorSurface.tsx
  • webviews/vite.config.mjs

Comment thread CLI/cmux_open.swift
Comment thread CLI/cmux_open.swift Outdated
Comment thread CLI/cmux_open.swift
@@ -0,0 +1 @@
const e={comments:{lineComment:"#",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"',notIn:["string"]}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}]},t={defaultToken:"",tokenPostfix:".hcl",keywords:["var","local","path","for_each","any","string","number","bool","true","false","null","if ","else ","endif ","for ","in","endfor"],operators:["=",">=","<=","==","!=","+","-","*","/","%","&&","||","!","<",">","?","...",":"],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,terraformFunctions:/(abs|ceil|floor|log|max|min|pow|signum|chomp|format|formatlist|indent|join|lower|regex|regexall|replace|split|strrev|substr|title|trimspace|upper|chunklist|coalesce|coalescelist|compact|concat|contains|distinct|element|flatten|index|keys|length|list|lookup|map|matchkeys|merge|range|reverse|setintersection|setproduct|setunion|slice|sort|transpose|values|zipmap|base64decode|base64encode|base64gzip|csvdecode|jsondecode|jsonencode|urlencode|yamldecode|yamlencode|abspath|dirname|pathexpand|basename|file|fileexists|fileset|filebase64|templatefile|formatdate|timeadd|timestamp|base64sha256|base64sha512|bcrypt|filebase64sha256|filebase64sha512|filemd5|filemd1|filesha256|filesha512|md5|rsadecrypt|sha1|sha256|sha512|uuid|uuidv5|cidrhost|cidrnetmask|cidrsubnet|tobool|tolist|tomap|tonumber|toset|tostring)/,terraformMainBlocks:/(module|data|terraform|resource|provider|variable|output|locals)/,tokenizer:{root:[[/^@terraformMainBlocks([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)(\{)/,["type","","string","","string","","@brackets"]],[/(\w+[ \t]+)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)(\{)/,["identifier","","string","","string","","@brackets"]],[/(\w+[ \t]+)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)(=)(\{)/,["identifier","","string","","operator","","@brackets"]],{include:"@terraform"}],terraform:[[/@terraformFunctions(\()/,["type","@brackets"]],[/[a-zA-Z_]\w*-*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"variable"}}],{include:"@whitespace"},{include:"@heredoc"},[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"operator","@default":""}}],[/\d*\d+[eE]([\-+]?\d+)?/,"number.float"],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/\d[\d']*/,"number"],[/\d/,"number"],[/[;,.]/,"delimiter"],[/"/,"string","@string"],[/'/,"invalid"]],heredoc:[[/<<[-]*\s*["]?([\w\-]+)["]?/,{token:"string.heredoc.delimiter",next:"@heredocBody.$1"}]],heredocBody:[[/([\w\-]+)$/,{cases:{"$1==$S2":[{token:"string.heredoc.delimiter",next:"@popall"}],"@default":"string.heredoc"}}],[/./,"string.heredoc"]],whitespace:[[/[ \t\r\n]+/,""],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"],[/#.*$/,"comment"]],comment:[[/[^\/*]+/,"comment"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],string:[[/\$\{/,{token:"delimiter",next:"@stringExpression"}],[/[^\\"\$]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@popall"]],stringInsideExpression:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],stringExpression:[[/\}/,{token:"delimiter",next:"@pop"}],[/"/,"string","@stringInsideExpression"],{include:"@terraform"}]}};export{e as conf,t as language};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix unreachable HCL keywords caused by trailing spaces.

Line 1 defines "if ", "else ", "endif ", and "for " with trailing spaces, but identifiers are tokenized without trailing whitespace, so these entries never match and won’t be highlighted as keywords.

Suggested patch
-keywords:["var","local","path","for_each","any","string","number","bool","true","false","null","if ","else ","endif ","for ","in","endfor"],
+keywords:["var","local","path","for_each","any","string","number","bool","true","false","null","if","else","endif","for","in","endfor"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const e={comments:{lineComment:"#",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"',notIn:["string"]}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}]},t={defaultToken:"",tokenPostfix:".hcl",keywords:["var","local","path","for_each","any","string","number","bool","true","false","null","if ","else ","endif ","for ","in","endfor"],operators:["=",">=","<=","==","!=","+","-","*","/","%","&&","||","!","<",">","?","...",":"],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,terraformFunctions:/(abs|ceil|floor|log|max|min|pow|signum|chomp|format|formatlist|indent|join|lower|regex|regexall|replace|split|strrev|substr|title|trimspace|upper|chunklist|coalesce|coalescelist|compact|concat|contains|distinct|element|flatten|index|keys|length|list|lookup|map|matchkeys|merge|range|reverse|setintersection|setproduct|setunion|slice|sort|transpose|values|zipmap|base64decode|base64encode|base64gzip|csvdecode|jsondecode|jsonencode|urlencode|yamldecode|yamlencode|abspath|dirname|pathexpand|basename|file|fileexists|fileset|filebase64|templatefile|formatdate|timeadd|timestamp|base64sha256|base64sha512|bcrypt|filebase64sha256|filebase64sha512|filemd5|filemd1|filesha256|filesha512|md5|rsadecrypt|sha1|sha256|sha512|uuid|uuidv5|cidrhost|cidrnetmask|cidrsubnet|tobool|tolist|tomap|tonumber|toset|tostring)/,terraformMainBlocks:/(module|data|terraform|resource|provider|variable|output|locals)/,tokenizer:{root:[[/^@terraformMainBlocks([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)(\{)/,["type","","string","","string","","@brackets"]],[/(\w+[ \t]+)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)(\{)/,["identifier","","string","","string","","@brackets"]],[/(\w+[ \t]+)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)(=)(\{)/,["identifier","","string","","operator","","@brackets"]],{include:"@terraform"}],terraform:[[/@terraformFunctions(\()/,["type","@brackets"]],[/[a-zA-Z_]\w*-*/,{cases:{"@keywords":{token:"keyword.$0"},"@default":"variable"}}],{include:"@whitespace"},{include:"@heredoc"},[/[{}()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/@symbols/,{cases:{"@operators":"operator","@default":""}}],[/\d*\d+[eE]([\-+]?\d+)?/,"number.float"],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/\d[\d']*/,"number"],[/\d/,"number"],[/[;,.]/,"delimiter"],[/"/,"string","@string"],[/'/,"invalid"]],heredoc:[[/<<[-]*\s*["]?([\w\-]+)["]?/,{token:"string.heredoc.delimiter",next:"@heredocBody.$1"}]],heredocBody:[[/([\w\-]+)$/,{cases:{"$1==$S2":[{token:"string.heredoc.delimiter",next:"@popall"}],"@default":"string.heredoc"}}],[/./,"string.heredoc"]],whitespace:[[/[ \t\r\n]+/,""],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"],[/#.*$/,"comment"]],comment:[[/[^\/*]+/,"comment"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],string:[[/\$\{/,{token:"delimiter",next:"@stringExpression"}],[/[^\\"\$]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@popall"]],stringInsideExpression:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],stringExpression:[[/\}/,{token:"delimiter",next:"@pop"}],[/"/,"string","@stringInsideExpression"],{include:"@terraform"}]}};export{e as conf,t as language};
const e={comments:{lineComment:"#",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"',notIn:["string"]}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}]},t={defaultToken:"",tokenPostfix:".hcl",keywords:["var","local","path","for_each","any","string","number","bool","true","false","null","if","else","endif","for","in","endfor"],operators:["=",">=","<=","==","!=","+","-","*","/","%","&&","||","!","<",">","?","...",":"],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,terraformFunctions:/(abs|ceil|floor|log|max|min|pow|signum|chomp|format|formatlist|indent|join|lower|regex|regexall|replace|split|strrev|substr|title|trimspace|upper|chunklist|coalesce|coalescelist|compact|concat|contains|distinct|element|flatten|index|keys|length|list|lookup|map|matchkeys|merge|range|reverse|setintersection|setproduct|setunion|slice|sort|transpose|values|zipmap|base64decode|base64encode|base64gzip|csvdecode|jsondecode|jsonencode|urlencode|yamldecode|yamlencode|abspath|dirname|pathexpand|basename|file|fileexists|fileset|filebase64|templatefile|formatdate|timeadd|timestamp|base64sha256|base64sha512|bcrypt|filebase64sha256|filebase64sha512|filemd5|filemd1|filesha256|filesha512|md5|rsadecrypt|sha1|sha256|sha512|uuid|uuidv5|cidrhost|cidrnetmask|cidrsubnet|tobool|tolist|tomap|tonumber|toset|tostring)/,terraformMainBlocks:/(module|data|terraform|resource|provider|variable|output|locals)/,tokenizer:{root:[[/^`@terraformMainBlocks`([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)(\{)/,["type","","string","","string","","`@brackets`"]],[/(\w+[ \t]+)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)(\{)/,["identifier","","string","","string","","`@brackets`"]],[/(\w+[ \t]+)([ \t]*)([\w-]+|"[\w-]+"|)([ \t]*)([\w-]+|"[\w-]+"|)(=)(\{)/,["identifier","","string","","operator","","`@brackets`"]],{include:"`@terraform`"}],terraform:[[/@terraformFunctions(\()/,["type","`@brackets`"]],[/[a-zA-Z_]\w*-*/,{cases:{"`@keywords`":{token:"keyword.$0"},"`@default`":"variable"}}],{include:"`@whitespace`"},{include:"`@heredoc`"},[/[{}()\[\]]/,"`@brackets`"],[/[<>](?!`@symbols`)/,"`@brackets`"],[/@symbols/,{cases:{"`@operators`":"operator","`@default`":""}}],[/\d*\d+[eE]([\-+]?\d+)?/,"number.float"],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/\d[\d']*/,"number"],[/\d/,"number"],[/[;,.]/,"delimiter"],[/"/,"string","`@string`"],[/'/,"invalid"]],heredoc:[[/<<[-]*\s*["]?([\w\-]+)["]?/,{token:"string.heredoc.delimiter",next:"`@heredocBody`.$1"}]],heredocBody:[[/([\w\-]+)$/,{cases:{"$1==$S2":[{token:"string.heredoc.delimiter",next:"`@popall`"}],"`@default`":"string.heredoc"}}],[/./,"string.heredoc"]],whitespace:[[/[ \t\r\n]+/,""],[/\/\*/,"comment","`@comment`"],[/\/\/.*$/,"comment"],[/#.*$/,"comment"]],comment:[[/[^\/*]+/,"comment"],[/\*\//,"comment","`@pop`"],[/[\/*]/,"comment"]],string:[[/\$\{/,{token:"delimiter",next:"`@stringExpression`"}],[/[^\\"\$]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","`@popall`"]],stringInsideExpression:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","`@pop`"]],stringExpression:[[/\}/,{token:"delimiter",next:"`@pop`"}],[/"/,"string","`@stringInsideExpression`"],{include:"`@terraform`"}]}};export{e as conf,t as language};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Resources/markdown-viewer/webviews-app/chunks/hcl.mjs` at line 1, The
keywords array on the language object t contains entries with trailing spaces
("if ", "else ", "endif ", "for ") so they never match; edit the t.keywords list
to remove the trailing spaces (use "if","else","endif","for") so the tokenizer
(tokenizer/root and terraform rules that reference "`@keywords`") can properly
recognize and highlight these keywords.

Comment thread webviews/src/surfaces/editorSurface.tsx
Files could open unhighlighted in the cmux WKWebView even though they highlight
in Chrome: the lazy Monarch grammar loads after the model exists and re-tokenizes
asynchronously, and the WKWebView does not reliably repaint that async update.
Preload the grammar for the file's inferred language before creating the editor,
so tokenization is registered before the first render (no race). Keeps the lazy
loaders as a fallback for any unmapped language.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 328dcefaf5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CLI/cmux.swift
}
if command == "open" { try runOpenCommand(commandArgs: commandArgs, socketPath: resolvedSocketPath, explicitPassword: socketPasswordArg, jsonOutput: jsonOutput, idFormat: try resolvedIDFormat(jsonOutput: jsonOutput, raw: idFormatArg)); return }
if command == "diff" { try runDiffCommand(commandArgs: commandArgs, socketPath: resolvedSocketPath, explicitPassword: socketPasswordArg, jsonOutput: jsonOutput, idFormat: try resolvedIDFormat(jsonOutput: jsonOutput, raw: idFormatArg)); return }
if command == "edit" { try runEditCommand(commandArgs: commandArgs, socketPath: resolvedSocketPath, explicitPassword: socketPasswordArg, jsonOutput: jsonOutput, idFormat: try resolvedIDFormat(jsonOutput: jsonOutput, raw: idFormatArg)); return }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register edit as a top-level command

Adding the edit dispatch here is not enough because run() calls shouldOpenAsPathArgument(command) before this point, and that helper treats any non-registered command name that exists on disk as a path. Since edit was not added to topLevelCommandNames (checked the set near CLI/cmux.swift:4990), running cmux edit <file> from a directory that contains an edit file or folder opens that path instead of invoking the editor command.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Resources/markdown-viewer/webviews-app/chunks/editorSurface.mjs`:
- Line 2: The preload map F is missing entries that match the registered
language ids used by r(...) and B(): add keys for "json" (pointing to the json
loader used by z()/jsonMode), "c" (pointing to the same loader as "cpp" i.e.
./cpp.mjs), and rename or add "proto" to match the registered id (currently F
has "protobuf") so it points to ./protobuf.mjs; update F so its keys mirror the
ids passed to r({... id: "..." ...}) and then rebuild/regenerate the bundled
file so B(o) can find F[languageId] for .json, .c/.h and .proto files.

In `@webviews/src/editor/monacoLanguages.ts`:
- Around line 118-143: Wrap the dynamic grammar load/registration inside
preloadGrammarForPath in a try-catch: call loader() and the subsequent
monaco.languages.setMonarchTokensProvider /
monaco.languages.setLanguageConfiguration inside the try, and on error catch and
log the failure (including the error and the languageId/extension) and then
return so the editor can fall back to lazy loading; reference the GRAMMARS map,
the loader() invocation, the returned grammar object, and the monaco
registration calls when adding the error handling.

In `@webviews/src/surfaces/editorSurface.tsx`:
- Around line 69-72: preloadGrammarForPath(filePath) must be made best-effort so
a failing grammar import never prevents mounting; change
mountEditorSurface/createRoot render path to start the editor immediately and
invoke preloadGrammarForPath(filePath) without awaiting it (or await it but
catch and swallow/log errors) so failures do not reject mountEditorSurface;
ensure errors from preloadGrammarForPath are caught and logged via the same
logger used in editorSurface.tsx so the existing lazy fallback language path
still functions if the warmup fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5eddb2b0-9166-45a8-ad30-77319848ea32

📥 Commits

Reviewing files that changed from the base of the PR and between fa06925 and 328dcef.

📒 Files selected for processing (3)
  • Resources/markdown-viewer/webviews-app/chunks/editorSurface.mjs
  • webviews/src/editor/monacoLanguages.ts
  • webviews/src/surfaces/editorSurface.tsx

Comment thread Resources/markdown-viewer/webviews-app/chunks/editorSurface.mjs Outdated
Comment thread webviews/src/editor/monacoLanguages.ts Outdated
Comment on lines +118 to +143
export async function preloadGrammarForPath(filePath: string): Promise<void> {
const dot = filePath.lastIndexOf(".");
const extension = dot >= 0 ? filePath.slice(dot) : "";
const probe = monaco.editor.createModel(
"",
undefined,
monaco.Uri.parse(`inmemory://cmux-grammar-probe/probe${extension}`),
);
const languageId = probe.getLanguageId();
probe.dispose();
const loader = GRAMMARS[languageId];
if (!loader) {
return;
}
const grammar = await loader();
monaco.languages.setMonarchTokensProvider(
languageId,
grammar.language as monaco.languages.IMonarchLanguage,
);
if (grammar.conf) {
monaco.languages.setLanguageConfiguration(
languageId,
grammar.conf as monaco.languages.LanguageConfiguration,
);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add error handling around grammar loading and registration.

The dynamic import at line 132 and the registration calls at lines 133-141 can throw if a grammar module fails to load or is malformed. Without error handling, this would propagate to the call site in editorSurface.tsx:72 and potentially prevent the editor from mounting. Adding a try-catch allows the editor to fall back to lazy grammar loading (from the contribution imports) while logging the failure.

🛡️ Proposed error handling
 export async function preloadGrammarForPath(filePath: string): Promise<void> {
   const dot = filePath.lastIndexOf(".");
   const extension = dot >= 0 ? filePath.slice(dot) : "";
   const probe = monaco.editor.createModel(
     "",
     undefined,
     monaco.Uri.parse(`inmemory://cmux-grammar-probe/probe${extension}`),
   );
   const languageId = probe.getLanguageId();
   probe.dispose();
   const loader = GRAMMARS[languageId];
   if (!loader) {
     return;
   }
+  try {
     const grammar = await loader();
     monaco.languages.setMonarchTokensProvider(
       languageId,
       grammar.language as monaco.languages.IMonarchLanguage,
     );
     if (grammar.conf) {
       monaco.languages.setLanguageConfiguration(
         languageId,
         grammar.conf as monaco.languages.LanguageConfiguration,
       );
     }
+  } catch (error) {
+    console.error(`Failed to preload grammar for language "${languageId}":`, error);
+    // Fall back to lazy grammar loading from contribution imports.
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function preloadGrammarForPath(filePath: string): Promise<void> {
const dot = filePath.lastIndexOf(".");
const extension = dot >= 0 ? filePath.slice(dot) : "";
const probe = monaco.editor.createModel(
"",
undefined,
monaco.Uri.parse(`inmemory://cmux-grammar-probe/probe${extension}`),
);
const languageId = probe.getLanguageId();
probe.dispose();
const loader = GRAMMARS[languageId];
if (!loader) {
return;
}
const grammar = await loader();
monaco.languages.setMonarchTokensProvider(
languageId,
grammar.language as monaco.languages.IMonarchLanguage,
);
if (grammar.conf) {
monaco.languages.setLanguageConfiguration(
languageId,
grammar.conf as monaco.languages.LanguageConfiguration,
);
}
}
export async function preloadGrammarForPath(filePath: string): Promise<void> {
const dot = filePath.lastIndexOf(".");
const extension = dot >= 0 ? filePath.slice(dot) : "";
const probe = monaco.editor.createModel(
"",
undefined,
monaco.Uri.parse(`inmemory://cmux-grammar-probe/probe${extension}`),
);
const languageId = probe.getLanguageId();
probe.dispose();
const loader = GRAMMARS[languageId];
if (!loader) {
return;
}
try {
const grammar = await loader();
monaco.languages.setMonarchTokensProvider(
languageId,
grammar.language as monaco.languages.IMonarchLanguage,
);
if (grammar.conf) {
monaco.languages.setLanguageConfiguration(
languageId,
grammar.conf as monaco.languages.LanguageConfiguration,
);
}
} catch (error) {
console.error(`Failed to preload grammar for language "${languageId}":`, error);
// Fall back to lazy grammar loading from contribution imports.
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webviews/src/editor/monacoLanguages.ts` around lines 118 - 143, Wrap the
dynamic grammar load/registration inside preloadGrammarForPath in a try-catch:
call loader() and the subsequent monaco.languages.setMonarchTokensProvider /
monaco.languages.setLanguageConfiguration inside the try, and on error catch and
log the failure (including the error and the languageId/extension) and then
return so the editor can fall back to lazy loading; reference the GRAMMARS map,
the loader() invocation, the returned grammar object, and the monaco
registration calls when adding the error handling.

Comment on lines +69 to +72
// Load the file's Monarch grammar before mounting so the editor tokenizes
// synchronously on first render (the WKWebView does not reliably repaint the
// lazy async re-tokenization).
await preloadGrammarForPath(filePath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let grammar warmup block the entire editor surface.

await preloadGrammarForPath(filePath) runs before createRoot(...).render(...). If a grammar chunk import rejects, mountEditorSurface() rejects and the editor never mounts at all, even though highlighting is only an enhancement. Make this preload best-effort so the existing lazy language path can still render the surface.

Proposed fix
   // Load the file's Monarch grammar before mounting so the editor tokenizes
   // synchronously on first render (the WKWebView does not reliably repaint the
   // lazy async re-tokenization).
-  await preloadGrammarForPath(filePath);
+  try {
+    await preloadGrammarForPath(filePath);
+  } catch {
+    // Best-effort only: keep the editor mount path alive even if a grammar
+    // chunk fails to warm up.
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Load the file's Monarch grammar before mounting so the editor tokenizes
// synchronously on first render (the WKWebView does not reliably repaint the
// lazy async re-tokenization).
await preloadGrammarForPath(filePath);
// Load the file's Monarch grammar before mounting so the editor tokenizes
// synchronously on first render (the WKWebView does not reliably repaint the
// lazy async re-tokenization).
try {
await preloadGrammarForPath(filePath);
} catch {
// Best-effort only: keep the editor mount path alive even if a grammar
// chunk fails to warm up.
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webviews/src/surfaces/editorSurface.tsx` around lines 69 - 72,
preloadGrammarForPath(filePath) must be made best-effort so a failing grammar
import never prevents mounting; change mountEditorSurface/createRoot render path
to start the editor immediately and invoke preloadGrammarForPath(filePath)
without awaiting it (or await it but catch and swallow/log errors) so failures
do not reject mountEditorSurface; ensure errors from preloadGrammarForPath are
caught and logged via the same logger used in editorSurface.tsx so the existing
lazy fallback language path still functions if the warmup fails.

…dded

Highlighting rendered inconsistently in the cmux WKWebView (correct in Chrome).
Root causes + fixes:
- The per-language `.contribution.js` lazy loaders set the Monarch tokens
  provider via a late async import after the model existed, and the WKWebView
  did not repaint that async re-tokenization. Register each common language's
  id + extensions manually and load its grammar eagerly (awaited) before the
  editor is created, so the provider is the only one set and is in place before
  the first render. Removes the lazy loaders entirely.
- Embedded-language grammars (HTML embeds CSS/JS, Markdown embeds fenced code)
  fell back to one token per line because their nested tokenizers' languages
  were not registered. Preload the embedded grammars alongside the host.
- ResizeObserver drives editor.layout (the pane reports 0 height at create).

Verified in-app (socket DOM probe) across python, go, rust, java, c/c++, sql,
yaml, html, markdown, json. A freshly-opened surface can still briefly render
plain before its first tokenization paints; re-opening or interacting settles it.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 47e4b1b. Configure here.

Comment thread CLI/cmux_open.swift

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47e4b1bfbe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Load the file's Monarch grammar before mounting so the editor tokenizes
// synchronously on first render (the WKWebView does not reliably repaint the
// lazy async re-tokenization).
await preloadGrammarForPath(filePath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid blocking editor mount on CSP-blocked preloads

In restored/custom-scheme editor surfaces, opening common file types such as .js, .html, or .py can leave the pane blank because their generated grammar imports ask Vite's preload helper to add ../assets/monaco-vendor.css as an external <link rel="stylesheet">; the custom-scheme page CSP only allows inline styles, so that preload rejects, and this awaited preloadGrammarForPath prevents EditorApp from rendering at all. Since Monaco CSS is already fetched and inlined just above, catch/ignore grammar preload failures caused by the stylesheet preload or remove the CSS dependency from those dynamic imports before blocking the mount.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webviews/src/editor/monacoLanguages.ts`:
- Around line 29-30: preloadGrammarForPath currently returns early for paths
without a dot and therefore never loads the Dockerfile grammar; update
preloadGrammarForPath to detect the basename "Dockerfile" (and other common
extensionless names if desired) and explicitly load the "dockerfile" grammar via
the existing grammar loader for id "dockerfile". Then update EditorApp where you
create the model (or immediately after) to pass the resolved language id or call
monaco.editor.setModelLanguage(model, "dockerfile") when the path basename is
"Dockerfile" so Monaco uses the dockerfile grammar even though there is no file
extension; reference preloadGrammarForPath and the EditorApp model creation /
language assignment code to make these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b9dba513-64d7-4f07-b116-4abf385dc58a

📥 Commits

Reviewing files that changed from the base of the PR and between 328dcef and 47e4b1b.

📒 Files selected for processing (2)
  • Resources/markdown-viewer/webviews-app/chunks/editorSurface.mjs
  • webviews/src/editor/monacoLanguages.ts

Comment on lines +29 to +30
{ id: "dockerfile", extensions: [".dockerfile"], grammar: () => import("monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.js") },
{ id: "elixir", extensions: [".ex", ".exs"], grammar: () => import("monaco-editor/esm/vs/basic-languages/elixir/elixir.js") },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target file
ls -la webviews/src/editor/monacoLanguages.ts

# Show the relevant portions with line numbers
sed -n '1,220p' webviews/src/editor/monacoLanguages.ts | nl -ba

# Search for preloadGrammarForPath usage and any extensionless handling elsewhere
rg -n "preloadGrammarForPath|byExtension|LANGUAGES|languages\.register|filenames" webviews/src/editor/monacoLanguages.ts
rg -n "preloadGrammarForPath\(" -S webviews/src/editor webviews/src | head -n 50

# Also search where editor decides language from URI/path
rg -n "languageFrom|uri.*language|model.*language|setLanguage|monaco\.editor\.setModelLanguage|getLanguage" webviews/src/editor webviews/src | head -n 80

Repository: manaflow-ai/cmux

Length of output: 209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== File =="
sed -n '1,260p' webviews/src/editor/monacoLanguages.ts | cat -n

echo "== Key symbols search =="
rg -n "preloadGrammarForPath|byExtension|LANGUAGES|languages\.register|filenames" webviews/src/editor/monacoLanguages.ts

echo "== Call site search (wider) =="
rg -n "preloadGrammarForPath\(" webviews/src/editor webviews/src | head -n 50

echo "== Editor model language inference search (wider) =="
rg -n "setModelLanguage|setLanguage|getLanguage|languageFrom|monaco\.editor" webviews/src/editor webviews/src | head -n 120

Repository: manaflow-ai/cmux

Length of output: 12620


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' webviews/src/surfaces/editorSurface.tsx | cat -n

echo "== Search for preloadGrammarForPath call inputs =="

rg -n "preloadGrammarForPath\(" webviews/src/surfaces/editorSurface.tsx webviews/src/editor webviews/src | cat

echo "== Inspect EditorApp model creation (path/URI -> language) =="

sed -n '1,120p' webviews/src/editor/EditorApp.tsx | cat -n

Repository: manaflow-ai/cmux

Length of output: 7756


🌐 Web query:

Monaco Editor monaco.languages.register filenames option

💡 Result:

The monaco.languages.register function does not have a filenames option [1][2]. If you are looking to associate a language with specific file types, you should use the extensions or filenames (where applicable in broader configuration) properties within the registration object, though the standard registration API primarily uses extensions and aliases [3]. Here is a summary of how language associations are typically handled: 1. Registering a language: When calling monaco.languages.register, you define the language's identity. Associations with file patterns are often defined via the extensions property [3]: monaco.languages.register({ id: 'my-language', extensions: ['.my-ext', '.other-ext'] }); 2. Handling specific filenames: If you need to associate a specific filename (like.eslintrc or Dockerfile) with a language, the Monaco Editor core API typically relies on the file's URI when creating a model [4]. When you create a model, you provide the URI, and Monaco uses the registered language metadata to automatically match the file's extension or name to the appropriate language [4]: const model = monaco.editor.createModel(content, undefined, monaco.Uri.file('path/to/my.filename')); 3. Clarification on "filenames" options: - Monaco Editor Webpack Plugin: If you encountered a reference to a filename option in a Monaco context, it is likely referring to the MonacoWebpackPlugin configuration, which uses a filename property to set custom templates for worker script files (e.g., [name].worker.js) [5][6]. This is a build-tool configuration, not a language registration option [5]. - Third-party extensions: Some third-party libraries (like those providing completion or LSP features) may introduce their own registration options that include a filename property to provide context for completion providers, but this is not part of the standard Monaco Editor API [7]. If you are trying to ensure a file is treated as a certain language regardless of its extension, use monaco.editor.setModelLanguage(model, 'language-id') after the model is created [4].

Citations:


Handle extensionless filenames like Dockerfile in Monaco language resolution

dockerfile is registered only for the .dockerfile extension (so Dockerfile won’t match), and preloadGrammarForPath() returns early when the path has no . (so the Dockerfile grammar never loads). Since EditorApp creates the model with an undefined language id, Monaco will still infer from extensions only—leaving Dockerfile as plain text. (Also, monaco.languages.register doesn’t support a filenames option.)

Fix by (1) detecting Dockerfile from the path basename in preloadGrammarForPath() and loading the dockerfile grammar, and (2) in EditorApp either pass the resolved language id to monaco.editor.createModel or call monaco.editor.setModelLanguage(model, "dockerfile") for that basename.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webviews/src/editor/monacoLanguages.ts` around lines 29 - 30,
preloadGrammarForPath currently returns early for paths without a dot and
therefore never loads the Dockerfile grammar; update preloadGrammarForPath to
detect the basename "Dockerfile" (and other common extensionless names if
desired) and explicitly load the "dockerfile" grammar via the existing grammar
loader for id "dockerfile". Then update EditorApp where you create the model (or
immediately after) to pass the resolved language id or call
monaco.editor.setModelLanguage(model, "dockerfile") when the path basename is
"Dockerfile" so Monaco uses the dockerfile grammar even though there is no file
extension; reference preloadGrammarForPath and the EditorApp model creation /
language assignment code to make these changes.

Highlighting rendered plain non-deterministically in cmux's offscreen-IOSurface
WKWebView because Monaco tokenizes via a throttled async scheduler the WKWebView
starves. Force synchronous tokenization (model.tokenization.forceTokenization)
of the viewport after mount, then re-render, so every language highlights
deterministically.

JSON used the JSON language service, which tokenizes via a separate async path
(unaffected by forceTokenization), so it stayed plain. Replace it with a small
Monarch grammar so JSON highlights through the same deterministic path as every
other language. Drops the 383KB JSON worker (bundle 4.6MB -> 4.2MB).

Verified in-app (socket DOM probe, repeated): python, go, rust, c/c++, java,
sql, yaml, html, markdown, json all highlight consistently.
@lawrencecchen lawrencecchen changed the title Add Monaco editor surface to the webviews harness (foundation) Add a Monaco code editor surface (cmux edit) Jun 8, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0b8d01626

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CLI/cmux_open.swift Outdated
index += 1
}
guard let filePathArg else {
throw CLIError(message: "Usage: cmux edit <file> [--window <w>] [--workspace <ws>] [--surface <s>] [--focus]")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Localize the edit command messages

For localized builds, this new cmux edit usage/error path is emitted as hard-coded English; the repo guideline in AGENTS.md requires user-facing command/help text and errors to have entries for every supported locale. Japanese users hitting the usage, missing-file, or non-UTF8 paths added by this command will get untranslated text, so please wrap the new edit messages/status output in localized strings and add the corresponding Localizable.xcstrings entries.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
webviews/src/editor/monacoLanguages.ts (2)

57-57: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle extensionless filenames (e.g., Dockerfile) during grammar preload.

Line 57 registers dockerfile, but Line 142-145 exits early for extensionless paths, so common filenames like Dockerfile won’t preload or resolve to dockerfile and render as plain text.

💡 Minimal fix
 const byExtension = new Map<string, LanguageDef>();
 const byId = new Map<string, LanguageDef>();
+const byBasename = new Map<string, LanguageDef>();

 for (const language of LANGUAGES) {
   monaco.languages.register({ id: language.id, extensions: language.extensions });
   byId.set(language.id, language);
   for (const extension of language.extensions) {
     byExtension.set(extension, language);
   }
 }
+
+for (const [basename, id] of Object.entries({ dockerfile: "dockerfile" })) {
+  const language = byId.get(id);
+  if (language) byBasename.set(basename, language);
+}

 export async function preloadGrammarForPath(filePath: string): Promise<void> {
-  const dot = filePath.lastIndexOf(".");
-  if (dot < 0) {
-    return;
-  }
-  const language = byExtension.get(filePath.slice(dot).toLowerCase());
+  const normalized = filePath.replace(/\\/g, "/");
+  const basename = normalized.slice(normalized.lastIndexOf("/") + 1).toLowerCase();
+  const dot = basename.lastIndexOf(".");
+  const language =
+    (dot >= 0 ? byExtension.get(basename.slice(dot)) : undefined) ??
+    byBasename.get(basename);
   if (!language) {
     return;
   }

Also applies to: 141-147

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webviews/src/editor/monacoLanguages.ts` at line 57, The grammar preload logic
skips extensionless filenames (so files named "Dockerfile" never map to the
registered language id "dockerfile"); modify the path-to-language resolution in
monacoLanguages.ts (the function that currently returns early for extensionless
paths) to also check the basename when no extension exists and map known
filenames (e.g., "Dockerfile") to their language id ("dockerfile") before
exiting; update the preload/resolve logic used by the grammar registration so
extensionless common filenames are detected and trigger
import("monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.js").

117-123: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not let grammar import failures abort editor mount.

At Line 122, a failed dynamic import rejects preloadGrammarForPath (Line 153/155 path), which can prevent the editor surface from mounting at all. Degrade to plain text instead of failing startup.

💡 Minimal fix
 async function loadGrammarById(id: string): Promise<void> {
   const language = byId.get(id);
   if (!language) {
     return;
   }
-  const grammar = await language.grammar();
-  monaco.languages.setMonarchTokensProvider(
-    id,
-    grammar.language as monaco.languages.IMonarchLanguage,
-  );
-  if (grammar.conf) {
-    monaco.languages.setLanguageConfiguration(
-      id,
-      grammar.conf as monaco.languages.LanguageConfiguration,
-    );
-  }
+  try {
+    const grammar = await language.grammar();
+    monaco.languages.setMonarchTokensProvider(
+      id,
+      grammar.language as monaco.languages.IMonarchLanguage,
+    );
+    if (grammar.conf) {
+      monaco.languages.setLanguageConfiguration(
+        id,
+        grammar.conf as monaco.languages.LanguageConfiguration,
+      );
+    }
+  } catch {
+    // Keep editor boot resilient; fallback is unhighlighted/plain tokenization.
+  }
 }

Also applies to: 150-156

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webviews/src/editor/monacoLanguages.ts` around lines 117 - 123, The dynamic
grammar import can throw and currently bubbles up, preventing the editor from
mounting; modify loadGrammarById (and the caller preloadGrammarForPath) to catch
errors from language.grammar() and from the await call so failures do not reject
startup: if language.grammar() throws, log/debug the error, skip calling
monaco.languages.setMonarchTokensProvider (leave the language as plain text) and
return gracefully (do not rethrow); ensure preloadGrammarForPath wraps its call
to loadGrammarById in a try/catch and similarly degrades to plain text instead
of propagating the error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@webviews/src/editor/monacoLanguages.ts`:
- Line 57: The grammar preload logic skips extensionless filenames (so files
named "Dockerfile" never map to the registered language id "dockerfile"); modify
the path-to-language resolution in monacoLanguages.ts (the function that
currently returns early for extensionless paths) to also check the basename when
no extension exists and map known filenames (e.g., "Dockerfile") to their
language id ("dockerfile") before exiting; update the preload/resolve logic used
by the grammar registration so extensionless common filenames are detected and
trigger import("monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.js").
- Around line 117-123: The dynamic grammar import can throw and currently
bubbles up, preventing the editor from mounting; modify loadGrammarById (and the
caller preloadGrammarForPath) to catch errors from language.grammar() and from
the await call so failures do not reject startup: if language.grammar() throws,
log/debug the error, skip calling monaco.languages.setMonarchTokensProvider
(leave the language as plain text) and return gracefully (do not rethrow);
ensure preloadGrammarForPath wraps its call to loadGrammarById in a try/catch
and similarly degrades to plain text instead of propagating the error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 47a0bd73-1c26-44e2-9262-d8fe56061875

📥 Commits

Reviewing files that changed from the base of the PR and between 47e4b1b and e0b8d01.

📒 Files selected for processing (5)
  • Resources/markdown-viewer/webviews-app/chunks/editorSurface.mjs
  • Resources/markdown-viewer/webviews-app/chunks/monaco-vendor.mjs.deflate
  • webviews/src/editor/EditorApp.tsx
  • webviews/src/editor/monacoEnvironment.ts
  • webviews/src/editor/monacoLanguages.ts

# Conflicts:
#	Resources/markdown-viewer/webviews-app/chunks/diffSurface.mjs
Addresses autoreview P1: the editor defaulted to writable with no save path, so
edits were silently discarded on close. Default readOnly to true (a future save
feature can pass readOnly: false).
@socket-security

socket-security Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @stackframe/stack-ui is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/@stackframe/stack@2.8.89 → npm/@stackframe/stack-ui@2.8.89

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@stackframe/stack-ui@2.8.89. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @tanstack/table-core is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/@stackframe/stack@2.8.89 → npm/@stackframe/js@2.8.89 → npm/@tanstack/table-core@8.21.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@tanstack/table-core@8.21.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @typescript-eslint/eslint-plugin is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/@typescript-eslint/eslint-plugin@8.55.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@typescript-eslint/eslint-plugin@8.55.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm better-sqlite3 is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/drizzle-orm@1.0.0-beta.22 → npm/better-sqlite3@12.9.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/better-sqlite3@12.9.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm damerau-levenshtein is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/eslint-config-next@16.2.6 → npm/damerau-levenshtein@1.0.8

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/damerau-levenshtein@1.0.8. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm date-fns is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/@stackframe/stack@2.8.89 → npm/date-fns@3.6.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/date-fns@3.6.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm date-fns is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/@stackframe/stack@2.8.89 → npm/date-fns@3.6.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/date-fns@3.6.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm drizzle-kit is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package.json → npm/drizzle-kit@1.0.0-beta.23

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/drizzle-kit@1.0.0-beta.23. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm effect is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package.json → npm/effect@3.21.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/effect@3.21.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm es-abstract is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/eslint-config-next@16.2.6 → npm/es-abstract@1.24.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/es-abstract@1.24.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm eslint-plugin-react is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/eslint-config-next@16.2.6 → npm/eslint-plugin-react@7.37.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/eslint-plugin-react@7.37.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm jiti is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/@tailwindcss/postcss@4.1.18 → npm/eslint@9.39.4 → npm/drizzle-kit@1.0.0-beta.23 → npm/jiti@2.6.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/jiti@2.6.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm json-schema is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ? → npm/@stackframe/stack@2.8.89 → npm/json-schema@0.4.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/json-schema@0.4.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm next is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package.json → npm/next@16.2.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/next@16.2.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm next is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package.json → npm/next@16.2.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/next@16.2.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm next is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package.json → npm/next@16.2.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/next@16.2.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm next is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package.json → npm/next@16.2.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/next@16.2.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm next is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package.json → npm/next@16.2.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/next@16.2.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Autoreview flagged a format mismatch (raw deflate vs zlib-wrapped). Apple's
`.zlib` algorithm actually decodes raw DEFLATE, so node `deflateRawSync`
output round-trips correctly through `NSData.decompressed(using:.zlib)` —
verified directly against the committed diff-vendor.mjs.deflate and end-to-end
in dogfood. Comment-only; documents the non-obvious invariant for reviewers.
- CLI: stricter arg parsing (reject unknown flags / missing option values /
  extra positionals, support --); distinguish read errors from non-UTF-8 and
  directories; return the source file path (not the temp viewer page) in --json.
- editorSurface: guard JSON.parse of the injected config; skip CSS injection on
  a non-OK fetch; move the displaced mountEditorSurface JSDoc back to its function.
- vite.config: correct the worker-output comment (the ?worker file is hashed).
(HCL trailing-space grammar finding is in Monaco's vendored chunk, not ours.)
brian-stoker added a commit to stokd-cloud/gdock that referenced this pull request Aug 16, 2026
Brings PRs manaflow-ai#5638 + manaflow-ai#5761 from manaflow-ai/cmux, re-ported onto current
upstream after two months of drift:

- `cmux edit <file>` opens a Monaco surface with syntax highlighting for
  45 languages, find-in-file, and goto-line.
- Read-write: save bridge, dirty tracking, and SHA-256 on-disk conflict
  detection (refuses to clobber a file changed behind the editor).

Neither PR ever merged upstream; both were left open with no human review
since 2026-06-12. Verified end-to-end on this tree: save writes, a save
with a stale baseline is refused, force-overwrite succeeds.
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 6b1159e2 Deployed Jun 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants