fix(front): run a single Monaco instance across the app (#23855)
## Problem Three Sentry issues, all `Missing requestHandler or method: <method>`, first seen in v2.27.0: | Method | Page | Events | |---|---|---| | `findDocumentColors` | `/settings/mcp-apis` | 170 | | `resetSchema` | `/settings/mcp-apis` | 22 | | `getCodeFixesAtPosition` | `/object/workflow/…` | 4 | The first two are Monaco **JSON worker** methods, the third a **TypeScript worker** method. All of them bottom out in `monaco-editor/esm/vs/editor/common/services/editorSimpleWorker.js` at `$fmr` — the foreign-module dispatcher — with `_foreignModule` still `null`. ## Root cause Two Monaco copies end up on the page at **different versions**: 1. **ESM `monaco-editor@0.52.2`**, bundled by Vite — what GraphiQL 5 uses. 2. **AMD `monaco-editor@0.55.1` from jsDelivr** — `@monaco-editor/react` → `@monaco-editor/loader@1.7.0`, whose default CDN path is hardcoded to `monaco-editor@0.55.1/min/vs`. Nothing calls `loader.config({ monaco })`, so it goes to the CDN. `setupGraphiqlMonacoWorkers.ts` assigns **`globalThis.MonacoEnvironment`**, a single global both instances read, as a module side effect of the lazily-routed `GraphQLPlayground`. So once the playground has been opened, the 0.55.1 CDN instance stops using its own AMD workers and starts getting Vite-bundled 0.52.2 ones. The two versions don't share a worker protocol: 0.52 routes language-service calls through `$loadForeignModule` + `$fmr`, which 0.55's client never sends. `_foreignModule` stays `null`, and every call rejects. On `/settings/mcp-apis` the consumer is `SettingsMcpSetup.tsx` — `<CodeEditor language="json">` for the MCP config. Monaco fires `resetSchema` on `onWillDisposeModel` / `onDidChangeModelLanguage` and `findDocumentColors` continuously, which is why one bug produces 170 events and 22. There is a second, independent bug in the same file: the `switch` only maps `json` and `graphql`, so `typescript` / `javascript` / `css` / `html` fall through to the bare `EditorWorker`, which carries no language service at all. That's the workflow-page `getCodeFixesAtPosition`, and it would break even with matching versions. Impact is worse than the log noise suggests: after visiting the playground, JSON validation/colors in the MCP config editor and TS intellisense/quick-fixes in the workflow code editor silently stop working for the rest of the session. ## Changes - **`twenty-ui/src/input/CodeEditor/CodeEditor.tsx`** — configure `@monaco-editor/react` with the bundled Monaco (`loader.config({ monaco })`) instead of letting it fetch its own from jsDelivr. The import stays dynamic so Monaco is still only downloaded when an editor actually renders; the component shows its existing `Loader` until the loader is configured. - **`twenty-front/src/modules/app/utils/setupMonacoEnvironment.ts`** (new, replaces `settings/mcp-and-apis/utils/setupGraphiqlMonacoWorkers.ts`) — app-level worker factory mapping every label Monaco can ask for: `json`, `css`/`scss`/`less`, `html`/`handlebars`/`razor`, `typescript`/`javascript`, `graphql`, and the generic editor worker as the fallback. - **`twenty-front/src/index.tsx`** and **`.storybook/preview.tsx`** — set it up once for the app and for stories, rather than as a side effect of one lazy route. Dropping the CDN also means the code editors work in self-hosted and air-gapped deployments, which today silently fall back to a broken editor when jsDelivr is unreachable. ## Verification - `nx build twenty-front` passes; `css.worker`, `html.worker` and `ts.worker` chunks are now emitted alongside the existing `editor`/`json`/`graphql` ones. - Monaco stays lazy — `edcore.main` is not statically imported by the entry chunk and is absent from `index.html`'s modulepreloads. Measured against a baseline build of `main`, the entry chunk goes from 2,598,088 B to 2,599,040 B (**+952 B**). - `oxlint` and `oxfmt --check` clean on all touched files; `tsc --noEmit` clean for `twenty-ui` and reports nothing new for the touched `twenty-front` files. Not verified in a browser — worth a manual pass on the playground → MCP tab → workflow code editor sequence that reproduced the original errors. Fixes TWENTY-FRONT-8YV Fixes TWENTY-FRONT-8YW Fixes TWENTY-FRONT-ADE --- _Generated by [Claude Code](https://claude.ai/code/session_01RgPXkmUHANwD7ooUMqNYr9)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23855?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { type Environment } from 'monaco-editor';
|
||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
|
||||
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker.js?worker';
|
||||
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker.js?worker';
|
||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker.js?worker';
|
||||
import TypeScriptWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker.js?worker';
|
||||
import GraphqlWorker from 'monaco-graphql/esm/graphql.worker.js?worker';
|
||||
|
||||
// Monaco resolves every language worker through this single global, shared by
|
||||
// all editors on the page (GraphiQL's and CodeEditor's alike), so it has to be
|
||||
// set up once for the whole app rather than per feature.
|
||||
//
|
||||
// Every label Monaco can ask for must be mapped: an unmapped label silently
|
||||
// falls back to the generic editor worker, which carries no language service.
|
||||
// Requests to it then reject with "Missing requestHandler or method: <method>"
|
||||
// (`resetSchema`, `findDocumentColors`, `getCodeFixesAtPosition`, ...) and the
|
||||
// language features go quietly dead.
|
||||
const monacoEnvironment: Environment = {
|
||||
getWorker: (_workerId, label) => {
|
||||
switch (label) {
|
||||
case 'json':
|
||||
return new JsonWorker();
|
||||
case 'css':
|
||||
case 'scss':
|
||||
case 'less':
|
||||
return new CssWorker();
|
||||
case 'html':
|
||||
case 'handlebars':
|
||||
case 'razor':
|
||||
return new HtmlWorker();
|
||||
case 'typescript':
|
||||
case 'javascript':
|
||||
return new TypeScriptWorker();
|
||||
case 'graphql':
|
||||
return new GraphqlWorker();
|
||||
default:
|
||||
return new EditorWorker();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
(
|
||||
globalThis as unknown as { MonacoEnvironment?: Environment }
|
||||
).MonacoEnvironment = monacoEnvironment;
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
import '@/settings/mcp-and-apis/utils/setupGraphiqlMonacoWorkers';
|
||||
import {
|
||||
isPlaygroundApiKeyFresh,
|
||||
playgroundApiKeyState,
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
import { type Environment } from 'monaco-editor';
|
||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
|
||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker.js?worker';
|
||||
import GraphqlWorker from 'monaco-graphql/esm/graphql.worker.js?worker';
|
||||
|
||||
// GraphiQL 5's Monaco editors need a worker factory; without it Monaco throws "Cannot read properties of undefined (reading 'toUrl')".
|
||||
const monacoEnvironment: Environment = {
|
||||
getWorker: (_workerId, label) => {
|
||||
switch (label) {
|
||||
case 'json':
|
||||
return new JsonWorker();
|
||||
case 'graphql':
|
||||
return new GraphqlWorker();
|
||||
default:
|
||||
return new EditorWorker();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
(
|
||||
globalThis as unknown as { MonacoEnvironment?: Environment }
|
||||
).MonacoEnvironment = monacoEnvironment;
|
||||
Reference in New Issue
Block a user