Repository navigation
fix(editor): serve Monaco from our own origin so the workspace works air-gapped - #251
Merged
Merged
Conversation
…air-gapped @monaco-editor/react fetches Monaco from cdn.jsdelivr.net unless its loader is told otherwise, and nothing told it otherwise (#247). On a machine without internet the editor pane was a spinner that never resolved while the rest of the app looked healthy - connections, schema explorer and the results grid all rendered - so studio appeared fine while being unusable for its main purpose. The same dependency flaked the Channel E2E gate: 3 of the last 40 Docker builds failed waiting for .monaco-editor, every one of them a jsdelivr timeout on a change that had nothing to do with the editor. scripts/copy-monaco.mjs stages node_modules/monaco-editor/min/vs into public/monaco/vs, and src/lib/editor/monaco-loader.ts points the AMD loader at that path. monaco-editor was already a direct dependency, so the assets were on disk at build time all along - they were simply never served. Wired into the dev and build scripts, and explicitly into the Dockerfile, which calls next build directly and therefore inherits nothing from package.json. That one is what feeds the flaky gate. The tarballs, .deb/.rpm, snap, AppImage, Flatpak and npx payloads all derive from bun run build, so they inherit it. NEXT_PUBLIC_MONACO_VS_PATH overrides the path for deployments that serve the bundle elsewhere. libredb-platform needs it or its own copy step: the npm package ships only dist and bin, so an embedded studio resolves /monaco/vs against platform's origin. Recorded in the platform-integration rule, since nothing in CI can catch that from this repo. ESLint and oxlint now skip public/monaco: 16 MB of minified vendor JS in the working tree exhausts ESLint's heap outright, and because the directory only exists after something has built, a clean lint before the first build says nothing about the next one. Biome needs no change - its includes are an allowlist of first-party trees. e2e/offline-editor.spec.ts is the regression guard the existing specs could not be: they pass because the CDN is reachable, which is the wrong thing to assert for a self-hosted product. It blocks every off-origin request, then asserts the editor renders, a query against the SQLite sample returns its rows, and that nothing off-origin was attempted at all. Verified against both a production build and bun dev, and confirmed the spec fails without the loader configuration - zero .monaco-editor elements, the exact symptom from the issue. Two inert cdn.jsdelivr.net strings do survive in .next/static (the loader library's own default config literal and snapdom's font-host allowlist); no request is made to either, which the offline spec pins.
|
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #247.
Monaco was the last thing in the app fetched off-origin.
@monaco-editor/reactloads it fromcdn.jsdelivr.netunless its loader is told otherwise, and nothing told it otherwise, so an install without internet had no SQL editor — a spinner that never resolved, while connections, the schema explorer and the results grid all rendered fine. The same dependency flaked theChannel E2E (docker)gate: 3 of the last 40 Docker builds failed waiting for.monaco-editor, each one a jsdelivr timeout on a change that had nothing to do with the editor.What changed
scripts/copy-monaco.mjsnode_modules/monaco-editor/min/vsintopublic/monaco/vsat build time (16 MB).monaco-editorwas already a direct dependency, so the assets were on disk all along — just never served.src/lib/editor/monaco-loader.ts/monaco/vs, replacing the jsdelivr default. Called atQueryEditormodule load, before any<Editor>mounts.package.json,Dockerfiledevandbuild, and explicitly into the Dockerfile — it callsnext builddirectly and inherits nothing from package.json. That is the one feeding the flaky gate.e2e/offline-editor.spec.ts.oxlintrc.json,eslint.config.mjspublic/monaco/**— see the gotcha below.Every other artifact (standalone tarballs,
.deb/.rpm, snap, AppImage, Flatpak, npx) derives frombun run build, so all of them inherit the staging step.Acceptance criteria
bun dev.Channel E2E (docker)(the gate that was flaking),Channel E2E (tarball + npx)andE2E Testsall boot a packaged artifact and wait for.monaco-editor. The rest derive from the samebun run buildand the same bundle; no separate build of snap/deb/rpm/AppImage/Flatpak was run for this PR.Verification
TDD throughout: every test was watched failing first. The offline spec was confirmed to catch the regression — with the loader configuration removed it reproduces the issue symptom exactly (
.monaco-editorelement count 0), and passes with it restored.Local gates:
format,lint,typecheck,knip,test,build, plustest:coverageat 100% (25922/25922 lines),build:libandattw.Two things worth knowing
Embedded mode needs a platform-side step. The npm package ships only
distandbin, so an embedded studio resolves/monaco/vsagainst platform's origin. Platform must either copy the bundle into its ownpublic/(monaco-editor arrives transitively) or setNEXT_PUBLIC_MONACO_VS_PATH. Recorded in.claude/rules/platform-integration.md, since nothing in this repo's CI can catch it.Two
cdn.jsdelivr.netstrings still survive in.next/static, so the issue's "no string survives" check does not literally hold: one is the loader library's own default config literal (overwritten at runtime before init), the other is snapdom's font-host allowlist in the diagram exporter. Neither is ever requested — which is what the offline spec pins, and a stronger guarantee than grepping for a string.Trade: +16 MB in the image and in each packaged artifact, in exchange for never fetching per visit. For the desktop builds, which already carry a Node runtime and a Next.js payload, that is arguably the better side of the trade anyway.