From e60ea3f44b9f174cf0bb1ed55c6cf6f7c6e8b6e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 06:47:39 +0000 Subject: [PATCH 1/3] fix(plugin-editor): load Monaco from the Console's own build, not jsdelivr WIP: shared bundledMonaco loader (ESM core + ?worker language workers), MonacoImpl textarea fallback, app-shell probe configures before init. Claude-Session: https://claude.ai/code/session_0144tqLAVSHy6hGKUSH4LhVL Co-authored-by: Claude --- pnpm-lock.yaml | 3 + .../views/metadata-admin/JsonSourceEditor.tsx | 11 +- .../previews/SourcePageEditor.tsx | 8 +- .../views/metadata-admin/useMonacoFallback.ts | 40 ++++-- .../src/views/metadata-admin/widgets.tsx | 8 +- ui/packages/plugin-editor/package.json | 3 +- ui/packages/plugin-editor/src/MonacoImpl.tsx | 55 +++++++- .../plugin-editor/src/bundledMonaco.ts | 121 ++++++++++++++++++ ui/packages/plugin-editor/src/index.tsx | 5 + ui/packages/plugin-editor/tsconfig.json | 3 + 10 files changed, 228 insertions(+), 29 deletions(-) create mode 100644 ui/packages/plugin-editor/src/bundledMonaco.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8edf18fec3..a31b8cff10c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5276,6 +5276,9 @@ importers: '@object-ui/types': specifier: workspace:* version: link:../types + monaco-editor: + specifier: ^0.55.1 + version: 0.55.1 react: specifier: 19.3.0 version: 19.3.0 diff --git a/ui/packages/app-shell/src/views/metadata-admin/JsonSourceEditor.tsx b/ui/packages/app-shell/src/views/metadata-admin/JsonSourceEditor.tsx index 689ec3463d7..7b1e1344366 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/JsonSourceEditor.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/JsonSourceEditor.tsx @@ -99,11 +99,12 @@ export function JsonSourceEditor({ const editorRef = React.useRef(null); const monacoRef = React.useRef(null); - // Monaco's core is fetched lazily and, by default, from a public CDN, and it - // also spins up web workers. When any of that is blocked — offline / - // air-gapped / CSP-restricted installs — the editor mounts an empty shell - // with no error and the Source tab looks blank. `useMonacoFallback` fast-fails - // to a plain textarea the moment the CDN loader rejects, and also backstops + // Monaco's core is a lazy chunk of the host's own build (plugin-editor's + // `configureBundledMonaco`, never a CDN), and it also spins up web workers. + // When either is blocked — a chunk that cannot be fetched, a worker refused — + // the editor mounts an empty shell with no error and the Source tab looks + // blank. `useMonacoFallback` fast-fails to a plain textarea the moment the + // chunk or the loader rejects, and also backstops // the "resolved but painted nothing" case via a `.view-line` DOM poll, so the // source is always readable and editable. The editor mounts only once the // loader has resolved: mounted earlier, its own `loader.init()` on a failing diff --git a/ui/packages/app-shell/src/views/metadata-admin/previews/SourcePageEditor.tsx b/ui/packages/app-shell/src/views/metadata-admin/previews/SourcePageEditor.tsx index 50ce684020d..2df6f1c61dd 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/previews/SourcePageEditor.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/previews/SourcePageEditor.tsx @@ -177,10 +177,10 @@ export function SourcePageEditor({ } }, [source]); - // Monaco-unavailable fallback (headless / CSP / air-gapped) → plain textarea. - // Fast-fails the moment the CDN loader rejects instead of waiting the full - // grace period, and mounts the editor only once the loader has resolved - // (objectui#11800); see useMonacoFallback. + // Monaco-unavailable fallback (headless, a chunk that cannot be fetched) → + // plain textarea. Fast-fails the moment the editor chunk or the loader + // rejects instead of waiting the full grace period, and mounts the editor + // only once the loader has resolved (objectui#11800); see useMonacoFallback. const [monacoStatus, containerRef] = useMonacoFallback(fallbackDelayMs); const [theme, setTheme] = React.useState<'vs-dark' | 'light'>(() => diff --git a/ui/packages/app-shell/src/views/metadata-admin/useMonacoFallback.ts b/ui/packages/app-shell/src/views/metadata-admin/useMonacoFallback.ts index 78031f858ae..03ba8a010cb 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/useMonacoFallback.ts +++ b/ui/packages/app-shell/src/views/metadata-admin/useMonacoFallback.ts @@ -11,15 +11,16 @@ * fallback for the metadata designer's code surfaces (JSON source tab, * html/react page source). * - * Monaco's core is fetched lazily from a public CDN (jsdelivr) and spins up - * web workers. On offline / air-gapped / CSP-restricted installs — and the - * console is explicitly meant to embed in ANY ObjectStack server, many of - * which ship a strict CSP — that fetch fails. We detect it two ways: + * Monaco ships in the host's own build: `@object-ui/plugin-editor`'s + * `configureBundledMonaco` loads it as a lazy chunk (its language workers as + * same-origin files) and hands it to the loader, so `loader.init()` never + * reaches for the loader's default CDN — which an offline / air-gapped install + * cannot reach and the Console's content-security-policy refuses. What can + * still fail is loading that chunk. We detect it two ways: * - * - **Fast path:** `loader.init()` rejects the moment the CDN loader script - * fails to load, so we flip to the textarea immediately instead of making - * the user stare at a loading state for the full grace period - * (previously a hard-coded 4s of dead air on every CSP-blocked install). + * - **Fast path:** the probe rejects the moment the editor chunk (or the + * loader) fails, so we flip to the textarea immediately instead of making + * the user stare at a loading state for the full grace period. * - **Backstop:** some failures still resolve the loader but paint nothing * (e.g. blocked workers). A one-shot DOM poll after `fallbackDelayMs` * checks for a rendered `.view-line` row and falls back if the editor @@ -51,7 +52,8 @@ * rejection carrying the loader's own failure (compared by identity), and * nothing else. * - The failure is reported once per page, as one console line that names the - * cause — for the CDN case, the loader URL that could not be fetched. + * cause — for a chunk that could not be fetched, the browser's error names + * its URL. * * Returns `[status, containerRef]`: render the textarea when `status` is * `'unavailable'`, the editor when it is `'ready'`, and a loading placeholder @@ -71,9 +73,11 @@ let loaderProbe: Promise | null = null; let loaderOutcome: LoaderOutcome | null = null; /** - * Name what failed. The CDN case rejects with the loader `