Skip to content

Ship the Monaco editor as a builtin plugin - #2127

Open
andrewkchan wants to merge 2 commits into
get-bb:mainfrom
andrewkchan:feat/monaco-builtin-plugin
Open

Ship the Monaco editor as a builtin plugin#2127
andrewkchan wants to merge 2 commits into
get-bb:mainfrom
andrewkchan:feat/monaco-builtin-plugin

Conversation

@andrewkchan

@andrewkchan andrewkchan commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Human comments

This basically upstreams https://github.com/andrewkchan/bb-plugin-monaco as a built-in plugin.

Likely the big thing to be aware of is that it adds 25MB to the packaged app. We should be able to optimize later if needed


What was wrong

BB's file panel is read-only. Opening a file from chat, the file search, or bb thread open gives a preview, so any edit means leaving BB for an editor — even for a one-line change to a file the agent just wrote. Sawyer asked to upstream the Monaco plugin I built out-of-tree (andrewkchan/bb-plugin-monaco) so the panel can edit as well as show.

What changed

New builtin: plugins/monaco. A fileOpener that replaces the preview with Monaco for ~86 text and code extensions:

  • Editing with ⌘S saves guarded by expectedSha256, so a save that would clobber a concurrent write (usually the agent's) stops and offers Reload or Overwrite.
  • Find (⌘F), line numbers, syntax highlighting, and Monaco's usual editing affordances.
  • A file tree with path filtering, expand/collapse, reveal-the-open-file, and a right-click menu to copy absolute path / relative path / filename.
  • Type and theme matched to BB's own preview (font-mono text-xs leading-5, --font-mono, light/dark).

Registered defaultEnabled: true under Interface. That is the significant product change: the file panel becomes an editor for nearly every text file by default. Per-extension opt-out lives in Settings → File openers, and binaries fall through to BB's preview via experimental_Original.

Monaco is served from disk, not bundled. The plugin build has no loader for Monaco's codicon.ttf and cannot code split, so bundling would fail outright and, if patched around, add ~4.4 MB to every app boot for all users. Instead the server hands the frontend a files.createPreview lease over Monaco's prebuilt AMD build, and nothing loads until a file tab opens.

A generic staging hook (apps/server/scripts/copy-builtin-plugins.ts). Packaging copies only a builtin's dist/ and skills/, and builds plugins itself without running per-plugin scripts — so a plugin needing runtime files on disk has nowhere to put them. copyBuiltinPlugin now runs <pluginRoot>/scripts/stage-assets.mjs when present; plugins/monaco uses it to copy monaco-editor/min/vs into dist/vs. Running from source there is no dist/, and the server falls back to resolving monaco-editor from node_modules.

⚠️ This adds ~25 MB to the packaged app (Monaco's min/vs). Worth an explicit decision. Roughly 14 MB of it is the TypeScript language service and its worker, which this plugin deliberately disables — Monaco's checker can only see the open file, so every import reads as unresolved and the diagnostics are simply wrong. A curated subset could cut it to ~7-8 MB, but the AMD loader fetches lazily and omitting a file it later requests is a runtime 404, so I did not want to guess at the boundary without testing each claimed language. Happy to do that work if the size matters.

Also updated: smoke-tarball.mjs, and the two "declare metadata for every builtin" tables in the server tests (both failed before I added the entries, which is them working as intended).

How you verified

pnpm exec turbo run typecheck test --filter=bb-plugin-monaco     # 9 tests
pnpm exec turbo run test --filter=@bb/server -- builtin-plugins official-plugins   # 32 tests
pnpm exec turbo run test --filter=@bb/app -- useThreadFileTabs   # 20 tests
pnpm exec turbo run typecheck --filter=@bb/server --filter=bb-plugin-monaco

New unit tests cover the tree logic that is easy to get subtly wrong: nesting flat paths, synthesising directories a truncated listing omitted, case-insensitive sorting, and filtering (which must expand every ancestor of a match, or the match stays hidden behind a collapsed row).

Ran the real packaging path (tsx apps/server/scripts/copy-builtin-plugins.ts) and confirmed the shipped builtin contains dist/vs/loader.js, dist/vs/editor/editor.main.js, dist/vs/editor/editor.main.css, dist/server.js, and dist/app.js — 25 MB total.

Not yet verified live as a builtin. Everything above is build-and-test; the editor has been exercised extensively as a path-installed plugin but not once loaded through the builtin registry. Two paths in particular are new in this port and untested: project-backed workspace sources (no environment, resolved through the project's sources[]) and the experimental_hostId field main added to PluginFileOpenerSource — my schema was .strict() and would have rejected those files outright before this change.

Known gaps

Documented in the plugin README, with upstream issues where the fix is not ours:

Fixes #

AGENT GENERATED: by Claude Opus 5

andrewkchan and others added 2 commits August 20, 2026 19:00
Replaces BB's read-only file preview with Monaco for ~86 text and code
extensions: editing with compare-and-swap saves, find, syntax
highlighting, and a file tree with filtering and copy actions. Enabled
by default under the Interface category, so the panel becomes an editor
rather than a viewer; per-extension opt-out stays in Settings → File
openers, and binaries still fall through to BB's preview via
experimental_Original.

Monaco loads its prebuilt AMD bundle from disk rather than being
bundled: the plugin build has no loader for its font asset, cannot code
split, and inlining it would add ~4.4 MB to every app boot. Packaging
copies only a builtin's dist/, so scripts/stage-assets.mjs stages
monaco-editor/min/vs into dist/vs and the server serves it over a
files.createPreview lease. copy-builtin-plugins.ts gains a generic hook
that runs a plugin's stage-assets.mjs when it has one — the mechanism
any plugin needing runtime files on disk would use. This adds ~25 MB to
the packaged app.

Ported from the standalone plugin: imports @bb/shared-ui instead of
vendored components, resolves project-backed workspace sources (which
carry no environment and may name a host), and accepts the
experimental_hostId field main added to PluginFileOpenerSource.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A 400 KB PNG in the repo for a README heading is weight the tree does
not need to carry; the standalone plugin keeps it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant