Repository navigation
Console code editor: serve Monaco from the Console's own assets instead of a public CDN #25899
Description
Filing-gate category: ① user-visible defect with a named landing site (follow-up owed by landed work: objectstack#25569).
Reader who acts: the objectui card thread that takes this (app-shell + plugin-editor owner). The maintainer approved adding monaco-editor as a runtime dependency on 2026-10-25 (director thread, option A: self-host instead of a CDN allowance), so the human-floor call named in the objectstack-ai/objectui#11800 triage is already made; the PR still gets the maintainer's review as usual.
Dedupe: repo:objectstack-ai/objectui monaco in:title,body → 1 hit (#11800, closed; follow-ups objectstack-ai/objectui#11857 / objectstack-ai/objectui#11858 named there). jsdelivr → same hit. No open card covers self-hosting.
Problem
The Console's code editor (Monaco, the editor component of VS Code) fetches its core at run time from https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs through @monaco-editor/loader's default config. Any deployment that cannot or will not reach that CDN loses the editor:
- air-gapped and offline installs today;
- every install once the Console document's content-security-policy lands (objectstack#25569), which deliberately lists no CDN in
script-src.
Where it is used:
| Surface | File | Without the CDN |
|---|---|---|
| Studio JSON source tab | packages/app-shell/src/views/metadata-admin/JsonSourceEditor.tsx |
plain-text fallback (useMonacoFallback) |
| html / react page source editor | .../metadata-admin/previews/SourcePageEditor.tsx |
plain-text fallback |
| metadata form code fields | .../metadata-admin/widgets.tsx (CodeWidget) |
plain-text fallback |
plugin-editor:code-editor page component |
packages/plugin-editor/src/MonacoImpl.tsx |
no fallback: stays on "Loading..." |
reach: measured once through the public UI — browser smoke on objectstack#25569's branch (showcase, @objectstack/console@18.0.0 dist), the CDN script request is refused and reported by the policy; the metadata-admin surfaces show the textarea.
Proposed change
- Bundle
monaco-editorand point the loader at it:loader.config({ monaco }), with the language workers imported through Vite's?workerso they ship as same-origin assets under/_console/assets/. - Do it once, in a shared spot both app-shell and plugin-editor use, so the two copies of
@monaco-editor/reactdon't drift. - Keep
useMonacoFallbackfor a failed chunk load, and giveMonacoImplthe same fallback. - Load the editor lazily (it already is behind
LazyCodeEditor), so the cost is paid only on screens that open an editor.
Cost to state in the PR: monaco-editor adds a few MB of lazy chunks to the Console dist (measure before/after du -sh dist/assets); it does not change first-paint size if the lazy boundary holds.
Acceptance
- With objectstack#25569's policy on, the four surfaces above open the full editor, and the browser reports no
script-src/worker-srcviolation. - No request goes to
cdn.jsdelivr.netfrom the Console. MonacoImplshows the plain-text fallback when the editor chunk fails to load (test).
Business rationale: authors edit page source, JSON metadata and code fields daily in Studio; a textarea loses syntax highlighting, validation markers and folding on exactly those screens. Offline customers lose it today; every customer loses it once the CSP ships.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 26, 2026
Transferred 2026-10-26T03:05Z from objectstack-ai/objectui#13510, with its comments and timeline; the old URL redirects here. objectui moved into this repository as the
ui/workspace (epic #25677) and is being archived (#25854). The body is left exactly as filed.Paths. Read every repository path in the body with a
ui/prefix:packages/app-shell/src/views/metadata-admin/JsonSourceEditor.tsxis nowui/packages/app-shell/src/views/metadata-admin/JsonSourceEditor.tsx;packages/plugin-editor/src/MonacoImpl.tsxis nowui/packages/plugin-editor/src/MonacoImpl.tsx.References. The bare issue numbers in the body were written in objectui and now resolve against this repository by mistake; read them as objectstack-ai/objectui#11800, objectstack-ai/objectui#11857, objectstack-ai/objectui#11858.
No stale
pm:dispatchedor assignee to strip.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Triage: re-grade —
bug·area:studioadded · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T06:13Z⛔ Not a claim, ⛔ not a dispatch. Re-grade after the transfer from objectui (#25854). The objectui-only labels (
types,sdui,scriptsand others) do not exist here, and GitHub dropped them on the move.bug: a user-visible defect (the editor depends on a public CDN at run time).area:studio: the code editor surface. Grade otherwise unchanged (p2 ·domain:ui). The dependency call was already made by the maintainer (2026-10-25, option A, self-host). Paths are now underui/.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 1
Session:
session_017iXjUm1rujwDk7f3KT1Pkk(seat); implementing card threadsession_0144tqLAVSHy6hGKUSH4LhVLAccount:
os-steveBranch:
claude/project-thread-d433a3(platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record. Wording amended by the seat: an issue-shaped placeholder here was read by the Closing-Target Claim Guard as a second claimed branch)Worktree: the card thread's own container
Domain:
domain:uiSeat:
domain:ui#1File surface:
ui/packages/app-shell/src/views/metadata-admin/(JsonSourceEditor.tsx,previews/SourcePageEditor.tsx,useMonacoFallback.ts,widgets.tsxCodeWidget),ui/packages/plugin-editor/src/(MonacoImpl.tsx, a shared loader module),ui/packages/app-shell/package.json,ui/packages/plugin-editor/package.json,pnpm-lock.yaml(via pnpm install only); worker output config inui/apps/console/vite.config.tsif needed (stop on breach; explain in the report). ⛔ui/.github/**and the root.github/workflows/ci.ymlare out of bounds (maintainer's word, ui-merge epic in flight). Changeset in the root.changeset/.Container & model:
L,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: xhighClause-②: no (a new runtime dependency, approved by the maintainer 2026-10-25 option A per the card body; no accept set or export widened)
Metadata-protocol: none
Responsibility:
n/a: renderer defect in ui/, the producing code is the cited ui file; no platform path covers it; reached through the public ConsoleThread-read: 6409510137
Premise: holds (read-only premise check at
origin/main59d9181, 2026-10-26T06:40Z; spot-checked by the seat)Serial constraints cleared: none: no open PR touches these files or
pnpm-lock.yaml(open-PR file scan 2026-10-26T06:33Z); #25903 editsmetadata-admin/inspectors/only, disjoint from this surface; no in-flightdomain:uiclaimDispatch order handed to the card thread by the project coordinator (three partitions: ruling / mechanism hypotheses / suggested route); the dev does not claim, and starts only once this claim names its branch.
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25899, "status": "done", "branch": "claude/project-thread-d433a3", "pr": "https://github.com/objectstack-ai/objectstack/pull/25993", "head": "32c20dedd8e0e282e85f88b7c1722c7e58590f02", "session": "session_0144tqLAVSHy6hGKUSH4LhVL", "premise_still_valid": true, "summary": "Premise held: nothing in ui/ called loader.config, so @monaco-editor/loader 1.7.0 injected monaco-editor@0.55.1 from cdn.jsdelivr.net. plugin-editor now depends on monaco-editor and exports configureBundledMonaco(loader), which imports the ESM core as a lazy chunk, hands it to loader.config({ monaco }) and serves each language worker through Vite ?worker from same-origin assets; MonacoImpl mounts the editor only after that and falls back to a plain textarea when the chunk fails. app-shell useMonacoFallback awaits it before loader.init(). Console assets 54M to 73M (+13.5 MB lazy JS), first-paint closure +103 B raw, entry chunk unchanged.", "tests": "32c20ded: cd ui && pnpm exec vitest run packages/app-shell/ packages/plugin-editor/ under os-verify-lock: VERDICT command-exit 0, Test Files 1724 passed | 1 skipped (1725), Tests 15880 passed | 9 skipped. Monaco subset 21 files 907/907. plugin-editor build + type-check, app-shell type-check exit 0; eslint changed files 0 errors. Ablations via scripts/ablation-replace.mjs (blob moved, restored, git diff HEAD empty): drop loader.config in bundledMonaco.ts -> bundledMonaco.noCdn red (expected false to be true); disable the unavailable branch in MonacoImpl.tsx -> MonacoImpl.chunkFailure 2/2 red (Unable to find a label with the text of: javascript source). Browser smoke under the strict Console CSP (OS_PAGE_REACT_ENABLED=false): CodeWidget, JsonSourceEditor, SourcePageEditor all mounted Monaco; 0 securitypolicyviolation, 0 cdn.jsdelivr.net requests, 0 failed requests of 827; workers from /_console/assets/.", "gates": "dispatch-gates --commands: 53 commands, each run with its exit captured before any pipe. 51 exit 0 on first run; pnpm check:dts-closure exit 1 (missing ui/apps/console/plugin.d.ts: my measurement build ran vite build without build:plugin) and pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET: 7 backend packages had no dist). After pnpm build:plugin and a turbo build of those 7, both re-ran exit 0. --ran: 53 derived, 53 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. PR check-runs: not yet reported at report time.", "deviations": [ "Bounded in-place fix outside the claimed file surface: one-line configureBundledMonaco stub in 3 suites under ui/packages/app-shell/src/views/studio-design/ (InterfacesPillar.sourcePage-12522, StudioDesignSurface.interfacesCreatePage-11823, StudioDesignSurface.interfacesReadOnly-11136); same defect class, same gate families. Seat to amend the claim file surface.", "ui/apps/console/vite.config.ts not edited (Vite default ?worker output sufficed)." ], "files_changed": [ ".changeset/25899-monaco-self-host.md", "pnpm-lock.yaml", "ui/packages/app-shell/src/views/metadata-admin/CodeWidget.expressionEnvelope-10963.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/CodeWidget.loaderFallback-11858.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/JsonSourceEditor.fallback.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/JsonSourceEditor.monaco-export.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/JsonSourceEditor.tsx", "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.nameSurvivesLabel-12680.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.pageRequiresStamp-11357.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/SchemaForm.nonRegistryLabelling.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/SchemaForm.widgetLabelling.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/previews/SourcePageEditor.lineNumbers-12522.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/previews/SourcePageEditor.pageKind-11933.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/previews/SourcePageEditor.tsx", "ui/packages/app-shell/src/views/metadata-admin/previews/dataPreviews.i18n-10862-s3.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/useMonacoFallback.fastfail.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/useMonacoFallback.loaderRejection-11800.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/useMonacoFallback.ts", "ui/packages/app-shell/src/views/metadata-admin/widgets.tsx", "ui/packages/app-shell/src/views/studio-design/InterfacesPillar.sourcePage-12522.test.tsx", "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacesCreatePage-11823.test.tsx", "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.interfacesReadOnly-11136.test.tsx", "ui/packages/plugin-editor/package.json", "ui/packages/plugin-editor/src/MonacoImpl.chunkFailure.test.tsx", "ui/packages/plugin-editor/src/MonacoImpl.tsx", "ui/packages/plugin-editor/src/bundledMonaco.noCdn.test.tsx", "ui/packages/plugin-editor/src/bundledMonaco.ts", "ui/packages/plugin-editor/src/index.tsx" ], "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "4 — mcp__github__issue_read x4 (get x1, get_comments x3); no write tools", "api_writes": "2 — fleet-write relay POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create (POST /pulls draft + POST /issues/25993/assignees os-steve); this os-dev-report comment via post-stamped.mjs (POST /issues/25899/comments)", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed · packages/cli/src/utils/console.ts CSP doc comment near consoleContentSecurityPolicy still says Monaco loads its core from jsdelivr and shows its plain-text fallback; stale after this PR", "carrier: none · noted, not filed · ui/apps/site playground (apps/site/app/playground/page.tsx) still loads Monaco from the CDN; out of scope per dispatch" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim amendment (seat
domain:ui#1, 2026-10-26T08:21Z): the file surface of the claim above adds three test files underui/packages/app-shell/src/views/studio-design/:InterfacesPillar.sourcePage-12522,StudioDesignSurface.interfacesCreatePage-11823andStudioDesignSurface.interfacesReadOnly-11136. Each gets the same one-lineconfigureBundledMonacostub, a bounded in-place fix named in PR #25993's body. Branch, Clause-② and everything else are unchanged.objectstack-fleet commentedon Oct 26, 2026
Seat: PR #25993 ACCEPT (2026-10-26T08:40Z), checks green on head
32c20ded; ready + auto-merge requested. The out-of-scope notes (CLI comment, site playground) go to Acceptance notes.