From abb3f671348d929e5dad8948414f9ec1fe062c2b Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 2 Jan 2026 12:02:59 -0800 Subject: [PATCH 1/4] update monaco and monaco loader --- package-lock.json | 40 ++++++++++++++++++++++++++++------------ package.json | 4 ++-- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8fbf58f84..a725799784 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "dependencies": { "@ai-sdk/react": "^2.0.104", "@floating-ui/react": "^0.27.16", - "@monaco-editor/loader": "^1.5.0", + "@monaco-editor/loader": "^1.7.0", "@monaco-editor/react": "^4.7.0", "@observablehq/plot": "^0.6.17", "@react-hook/resize-observer": "^2.0.2", @@ -46,7 +46,7 @@ "immer": "^10.1.1", "jotai": "2.9.3", "mermaid": "^11.12.1", - "monaco-editor": "^0.52.0", + "monaco-editor": "^0.55.1", "monaco-yaml": "^5.4.0", "overlayscrollbars": "^2.12.0", "overlayscrollbars-react": "^0.5.6", @@ -6609,9 +6609,9 @@ } }, "node_modules/@monaco-editor/loader": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", - "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", "license": "MIT", "dependencies": { "state-local": "^1.0.6" @@ -14883,9 +14883,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -22875,10 +22875,26 @@ } }, "node_modules/monaco-editor": { - "version": "0.52.2", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", - "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", - "license": "MIT" + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "license": "MIT", + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, + "node_modules/monaco-editor/node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } }, "node_modules/monaco-languageserver-types": { "version": "0.4.0", diff --git a/package.json b/package.json index 23c2427c0b..bc485b84b2 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "dependencies": { "@ai-sdk/react": "^2.0.104", "@floating-ui/react": "^0.27.16", - "@monaco-editor/loader": "^1.5.0", + "@monaco-editor/loader": "^1.7.0", "@monaco-editor/react": "^4.7.0", "@observablehq/plot": "^0.6.17", "@react-hook/resize-observer": "^2.0.2", @@ -107,7 +107,7 @@ "immer": "^10.1.1", "jotai": "2.9.3", "mermaid": "^11.12.1", - "monaco-editor": "^0.52.0", + "monaco-editor": "^0.55.1", "monaco-yaml": "^5.4.0", "overlayscrollbars": "^2.12.0", "overlayscrollbars-react": "^0.5.6", From a9c5d08334d7bc3c93ba58b3ebd37b1a0fe4c880 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 2 Jan 2026 12:57:08 -0800 Subject: [PATCH 2/4] checkpoint on migration --- electron.vite.config.ts | 7 +- frontend/app/monaco/monaco-env.ts | 81 +++++++++++++++++++ frontend/app/monaco/schemaendpoints.ts | 50 ++++++++++++ frontend/app/view/codeeditor/codeeditor.tsx | 81 +------------------ .../app/view/codeeditor/schemaendpoints.ts | 40 --------- package-lock.json | 1 - package.json | 3 +- 7 files changed, 137 insertions(+), 126 deletions(-) create mode 100644 frontend/app/monaco/monaco-env.ts create mode 100644 frontend/app/monaco/schemaendpoints.ts delete mode 100644 frontend/app/view/codeeditor/schemaendpoints.ts diff --git a/electron.vite.config.ts b/electron.vite.config.ts index ae5de1887c..c912f02bd4 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -5,7 +5,6 @@ import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react-swc"; import { defineConfig } from "electron-vite"; import { ViteImageOptimizer } from "vite-plugin-image-optimizer"; -import { viteStaticCopy } from "vite-plugin-static-copy"; import svgr from "vite-plugin-svgr"; import tsconfigPaths from "vite-tsconfig-paths"; @@ -134,8 +133,7 @@ export default defineConfig({ manualChunks(id) { const p = id.replace(/\\/g, "/"); if (p.includes("node_modules/monaco") || p.includes("node_modules/@monaco")) return "monaco"; - if (p.includes("node_modules/mermaid") || p.includes("node_modules/@mermaid")) - return "mermaid"; + if (p.includes("node_modules/mermaid") || p.includes("node_modules/@mermaid")) return "mermaid"; if (p.includes("node_modules/katex") || p.includes("node_modules/@katex")) return "katex"; if (p.includes("node_modules/shiki") || p.includes("node_modules/@shiki")) { return "shiki"; @@ -172,9 +170,6 @@ export default defineConfig({ }), react({}), tailwindcss(), - viteStaticCopy({ - targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }], - }), ], }, }); diff --git a/frontend/app/monaco/monaco-env.ts b/frontend/app/monaco/monaco-env.ts new file mode 100644 index 0000000000..0f851fe6c4 --- /dev/null +++ b/frontend/app/monaco/monaco-env.ts @@ -0,0 +1,81 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import type * as MonacoTypes from "monaco-editor"; +import "monaco-editor/esm/vs/language/css/monaco.contribution"; +import "monaco-editor/esm/vs/language/html/monaco.contribution"; +import "monaco-editor/esm/vs/language/json/monaco.contribution"; +import "monaco-editor/esm/vs/language/typescript/monaco.contribution"; +import { configureMonacoYaml } from "monaco-yaml"; + +import { MonacoSchemas } from "@/app/monaco/schemaendpoints"; +import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; +import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"; +import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"; +import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"; +import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"; +import ymlWorker from "./yamlworker?worker"; + +let monacoConfigured = false; + +window.MonacoEnvironment = { + getWorker(_, label) { + if (label === "json") { + return new jsonWorker(); + } + if (label === "css" || label === "scss" || label === "less") { + return new cssWorker(); + } + if (label === "yaml" || label === "yml") { + return new ymlWorker(); + } + if (label === "html" || label === "handlebars" || label === "razor") { + return new htmlWorker(); + } + if (label === "typescript" || label === "javascript") { + return new tsWorker(); + } + return new editorWorker(); + }, +}; + +export function loadMonaco(monaco: typeof MonacoTypes) { + if (monacoConfigured) { + return; + } + monacoConfigured = true; + monaco.editor.defineTheme("wave-theme-dark", { + base: "vs-dark", + inherit: true, + rules: [], + colors: { + "editor.background": "#00000000", + "editorStickyScroll.background": "#00000055", + "minimap.background": "#00000077", + focusBorder: "#00000000", + }, + }); + monaco.editor.defineTheme("wave-theme-light", { + base: "vs", + inherit: true, + rules: [], + colors: { + "editor.background": "#fefefe", + focusBorder: "#00000000", + }, + }); + configureMonacoYaml(monaco, { + validate: true, + schemas: [], + }); + // Disable default validation errors for typescript and javascript + monaco.typescript.typescriptDefaults.setDiagnosticsOptions({ + noSemanticValidation: true, + }); + monaco.json.jsonDefaults.setDiagnosticsOptions({ + validate: true, + allowComments: false, + enableSchemaRequest: true, + schemas: MonacoSchemas, + }); +} diff --git a/frontend/app/monaco/schemaendpoints.ts b/frontend/app/monaco/schemaendpoints.ts new file mode 100644 index 0000000000..2b3134e215 --- /dev/null +++ b/frontend/app/monaco/schemaendpoints.ts @@ -0,0 +1,50 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import settingsSchema from "../../../schema/settings.json"; +import connectionsSchema from "../../../schema/connections.json"; +import aipresetsSchema from "../../../schema/aipresets.json"; +import bgpresetsSchema from "../../../schema/bgpresets.json"; +import waveaiSchema from "../../../schema/waveai.json"; +import widgetsSchema from "../../../schema/widgets.json"; + +type SchemaInfo = { + uri: string; + fileMatch: Array; + schema: object; +}; + +const MonacoSchemas: SchemaInfo[] = [ + { + uri: "wave://schema/settings.json", + fileMatch: ["*/WAVECONFIGPATH/settings.json"], + schema: settingsSchema, + }, + { + uri: "wave://schema/connections.json", + fileMatch: ["*/WAVECONFIGPATH/connections.json"], + schema: connectionsSchema, + }, + { + uri: "wave://schema/aipresets.json", + fileMatch: ["*/WAVECONFIGPATH/presets/ai.json"], + schema: aipresetsSchema, + }, + { + uri: "wave://schema/bgpresets.json", + fileMatch: ["*/WAVECONFIGPATH/presets/bg.json"], + schema: bgpresetsSchema, + }, + { + uri: "wave://schema/waveai.json", + fileMatch: ["*/WAVECONFIGPATH/waveai.json"], + schema: waveaiSchema, + }, + { + uri: "wave://schema/widgets.json", + fileMatch: ["*/WAVECONFIGPATH/widgets.json"], + schema: widgetsSchema, + }, +]; + +export { MonacoSchemas }; diff --git a/frontend/app/view/codeeditor/codeeditor.tsx b/frontend/app/view/codeeditor/codeeditor.tsx index c77c4d9b7c..76bc748afc 100644 --- a/frontend/app/view/codeeditor/codeeditor.tsx +++ b/frontend/app/view/codeeditor/codeeditor.tsx @@ -1,87 +1,13 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { loadMonaco } from "@/app/monaco/monaco-env"; import { useOverrideConfigAtom } from "@/app/store/global"; -import loader from "@monaco-editor/loader"; +import { boundNumber } from "@/util/util"; import { Editor, Monaco } from "@monaco-editor/react"; -import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; -import { configureMonacoYaml } from "monaco-yaml"; +import type * as MonacoTypes from "monaco-editor"; import React, { useMemo, useRef } from "react"; -import { boundNumber } from "@/util/util"; -import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; -import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"; -import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"; -import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"; -import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"; -import { SchemaEndpoints, getSchemaEndpointInfo } from "./schemaendpoints"; -import ymlWorker from "./yamlworker?worker"; - -// there is a global monaco variable (TODO get the correct TS type) -declare var monaco: Monaco; - -window.MonacoEnvironment = { - getWorker(_, label) { - if (label === "json") { - return new jsonWorker(); - } - if (label === "css" || label === "scss" || label === "less") { - return new cssWorker(); - } - if (label === "yaml" || label === "yml") { - return new ymlWorker(); - } - if (label === "html" || label === "handlebars" || label === "razor") { - return new htmlWorker(); - } - if (label === "typescript" || label === "javascript") { - return new tsWorker(); - } - return new editorWorker(); - }, -}; - -export async function loadMonaco() { - loader.config({ paths: { vs: "monaco" } }); - await loader.init(); - - monaco.editor.defineTheme("wave-theme-dark", { - base: "vs-dark", - inherit: true, - rules: [], - colors: { - "editor.background": "#00000000", - "editorStickyScroll.background": "#00000055", - "minimap.background": "#00000077", - focusBorder: "#00000000", - }, - }); - monaco.editor.defineTheme("wave-theme-light", { - base: "vs", - inherit: true, - rules: [], - colors: { - "editor.background": "#fefefe", - focusBorder: "#00000000", - }, - }); - configureMonacoYaml(monaco, { - validate: true, - schemas: [], - }); - // Disable default validation errors for typescript and javascript - monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ - noSemanticValidation: true, - }); - const schemas = await Promise.all(SchemaEndpoints.map((endpoint) => getSchemaEndpointInfo(endpoint))); - monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ - validate: true, - allowComments: false, // Set to true if you want to allow comments in JSON - enableSchemaRequest: true, - schemas, - }); -} - function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions { const opts: MonacoTypes.editor.IEditorOptions = { scrollBeyondLastLine: false, @@ -166,6 +92,7 @@ export function CodeEditor({ blockId, text, language, fileName, readonly, onChan
; - schema: object; -}; - -const allFilepaths: Map> = new Map(); -allFilepaths.set(`${getWebServerEndpoint()}/schema/settings.json`, ["*/WAVECONFIGPATH/settings.json"]); -allFilepaths.set(`${getWebServerEndpoint()}/schema/connections.json`, ["*/WAVECONFIGPATH/connections.json"]); -allFilepaths.set(`${getWebServerEndpoint()}/schema/aipresets.json`, ["*/WAVECONFIGPATH/presets/ai.json"]); -allFilepaths.set(`${getWebServerEndpoint()}/schema/bgpresets.json`, ["*/WAVECONFIGPATH/presets/bg.json"]); -allFilepaths.set(`${getWebServerEndpoint()}/schema/waveai.json`, ["*/WAVECONFIGPATH/waveai.json"]); -allFilepaths.set(`${getWebServerEndpoint()}/schema/widgets.json`, ["*/WAVECONFIGPATH/widgets.json"]); - -async function getSchemaEndpointInfo(endpoint: string): Promise { - let schema: Object; - try { - const data = await fetch(endpoint); - schema = await data.json(); - } catch (e) { - console.log("cannot find schema:", e); - schema = {}; - } - const fileMatch = allFilepaths.get(endpoint) ?? []; - - return { - uri: endpoint, - fileMatch, - schema, - }; -} - -const SchemaEndpoints = Array.from(allFilepaths.keys()); - -export { getSchemaEndpointInfo, SchemaEndpoints }; diff --git a/package-lock.json b/package-lock.json index a725799784..d4bb274d8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "dependencies": { "@ai-sdk/react": "^2.0.104", "@floating-ui/react": "^0.27.16", - "@monaco-editor/loader": "^1.7.0", "@monaco-editor/react": "^4.7.0", "@observablehq/plot": "^0.6.17", "@react-hook/resize-observer": "^2.0.2", diff --git a/package.json b/package.json index bc485b84b2..2af55284a3 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "dependencies": { "@ai-sdk/react": "^2.0.104", "@floating-ui/react": "^0.27.16", - "@monaco-editor/loader": "^1.7.0", "@monaco-editor/react": "^4.7.0", "@observablehq/plot": "^0.6.17", "@react-hook/resize-observer": "^2.0.2", @@ -131,9 +130,9 @@ "rehype-slug": "^6.0.0", "remark-flexible-toc": "^1.2.4", "remark-gfm": "^4.0.1", - "semver": "^7.7.3", "remark-github-blockquote-alert": "^1.3.1", "rxjs": "^7.8.2", + "semver": "^7.7.3", "shell-quote": "^1.8.3", "shiki": "^3.13.0", "sprintf-js": "^1.1.3", From a7a8e3e450ae50feeb28f4787f9b24b99818970a Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 2 Jan 2026 17:25:53 -0800 Subject: [PATCH 3/4] monaco working again without monaco loader or react... --- electron.vite.config.ts | 12 +- frontend/app/monaco/monaco-env.ts | 4 +- frontend/app/monaco/monaco-react.tsx | 183 ++++++++++++++++++ .../{view/codeeditor => monaco}/yamlworker.js | 0 frontend/app/view/codeeditor/codeeditor.tsx | 21 +- frontend/app/view/codeeditor/diffviewer.tsx | 18 +- frontend/app/view/preview/preview-edit.tsx | 6 +- frontend/app/view/preview/preview-model.tsx | 2 +- .../app/view/waveconfig/waveconfig-model.ts | 2 +- frontend/app/view/waveconfig/waveconfig.tsx | 2 +- frontend/wave.ts | 2 +- package-lock.json | 30 --- package.json | 1 - 13 files changed, 228 insertions(+), 55 deletions(-) create mode 100644 frontend/app/monaco/monaco-react.tsx rename frontend/app/{view/codeeditor => monaco}/yamlworker.js (100%) diff --git a/electron.vite.config.ts b/electron.vite.config.ts index c912f02bd4..9176f2574c 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -151,7 +151,17 @@ export default defineConfig({ server: { open: false, watch: { - ignored: ["dist/**", "**/*.go", "**/go.mod", "**/go.sum", "**/*.md", "**/*.json", "emain/**"], + ignored: [ + "dist/**", + "**/*.go", + "**/go.mod", + "**/go.sum", + "**/*.md", + "**/*.json", + "emain/**", + "**/*.txt", + "**/*.log", + ], }, }, css: { diff --git a/frontend/app/monaco/monaco-env.ts b/frontend/app/monaco/monaco-env.ts index 0f851fe6c4..2421f87acf 100644 --- a/frontend/app/monaco/monaco-env.ts +++ b/frontend/app/monaco/monaco-env.ts @@ -1,7 +1,7 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import type * as MonacoTypes from "monaco-editor"; +import * as monaco from "monaco-editor"; import "monaco-editor/esm/vs/language/css/monaco.contribution"; import "monaco-editor/esm/vs/language/html/monaco.contribution"; import "monaco-editor/esm/vs/language/json/monaco.contribution"; @@ -39,7 +39,7 @@ window.MonacoEnvironment = { }, }; -export function loadMonaco(monaco: typeof MonacoTypes) { +export function loadMonaco() { if (monacoConfigured) { return; } diff --git a/frontend/app/monaco/monaco-react.tsx b/frontend/app/monaco/monaco-react.tsx new file mode 100644 index 0000000000..003d0be4de --- /dev/null +++ b/frontend/app/monaco/monaco-react.tsx @@ -0,0 +1,183 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { loadMonaco } from "@/app/monaco/monaco-env"; +import type * as MonacoTypes from "monaco-editor"; +import * as monaco from "monaco-editor"; +import { useEffect, useRef } from "react"; + +function createModel(value: string, path: string, language?: string) { + const uri = monaco.Uri.parse(`wave://editor/${encodeURIComponent(path)}`); + return monaco.editor.createModel(value, language, uri); +} + +type CodeEditorProps = { + text: string; + readonly: boolean; + language?: string; + fileName?: string; + onChange?: (text: string) => void; + onMount?: (editor: MonacoTypes.editor.IStandaloneCodeEditor, monacoApi: typeof monaco) => () => void; + // keep your computed path behavior outside if you want; or compute inside like today + path: string; + options: MonacoTypes.editor.IEditorOptions; + theme: string; +}; + +export function MonacoCodeEditor({ + text, + readonly, + language, + onChange, + onMount, + path, + options, + theme, +}: CodeEditorProps) { + const divRef = useRef(null); + const editorRef = useRef(null); + const onUnmountRef = useRef<(() => void) | null>(null); + const applyingFromProps = useRef(false); + + useEffect(() => { + loadMonaco(); + + const el = divRef.current; + if (!el) return; + + const model = createModel(text, path, language); + console.log("[monaco] CREATE MODEL", path, model); + + const editor = monaco.editor.create(el, { + ...options, + readOnly: readonly, + theme, // note: editor.create uses global theme via setTheme, but keeping your mental model + model, + }); + editorRef.current = editor; + + // Monaco theme is global; set it here + monaco.editor.setTheme(theme); + + const sub = model.onDidChangeContent(() => { + if (applyingFromProps.current) return; + onChange?.(model.getValue()); + }); + + if (onMount) { + onUnmountRef.current = onMount(editor, monaco); + } + + return () => { + sub.dispose(); + if (onUnmountRef.current) onUnmountRef.current(); + editor.dispose(); + model.dispose(); + console.log("[monaco] dispose model"); + editorRef.current = null; + }; + // mount/unmount only + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Keep model value in sync with props + useEffect(() => { + const editor = editorRef.current; + if (!editor) return; + const model = editor.getModel(); + if (!model) return; + + const current = model.getValue(); + if (current === text) return; + + applyingFromProps.current = true; + model.pushEditOperations([], [{ range: model.getFullModelRange(), text }], () => null); + applyingFromProps.current = false; + }, [text]); + + // Keep options + theme in sync + useEffect(() => { + const editor = editorRef.current; + if (!editor) return; + editor.updateOptions({ ...options, readOnly: readonly }); + monaco.editor.setTheme(theme); + }, [options, readonly, theme]); + + // Keep language in sync + useEffect(() => { + const editor = editorRef.current; + if (!editor) return; + const model = editor.getModel(); + if (!model || !language) return; + monaco.editor.setModelLanguage(model, language); + }, [language]); + + return
; +} + +type DiffViewerProps = { + original: string; + modified: string; + language?: string; + path: string; + options: MonacoTypes.editor.IDiffEditorOptions; + theme: string; +}; + +export function MonacoDiffViewer({ original, modified, language, path, options, theme }: DiffViewerProps) { + const divRef = useRef(null); + const diffRef = useRef(null); + + // Create once + useEffect(() => { + loadMonaco(); + + const el = divRef.current; + if (!el) return; + + const origUri = monaco.Uri.parse(`wave://diff/${encodeURIComponent(path)}.orig`); + const modUri = monaco.Uri.parse(`wave://diff/${encodeURIComponent(path)}.mod`); + + const originalModel = monaco.editor.createModel(original, language, origUri); + const modifiedModel = monaco.editor.createModel(modified, language, modUri); + + const diff = monaco.editor.createDiffEditor(el, options); + diffRef.current = diff; + monaco.editor.setTheme(theme); + + diff.setModel({ original: originalModel, modified: modifiedModel }); + + return () => { + diff.dispose(); + originalModel.dispose(); + modifiedModel.dispose(); + diffRef.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Update models on prop change + useEffect(() => { + const diff = diffRef.current; + if (!diff) return; + const model = diff.getModel(); + if (!model) return; + + if (model.original.getValue() !== original) model.original.setValue(original); + if (model.modified.getValue() !== modified) model.modified.setValue(modified); + + if (language) { + monaco.editor.setModelLanguage(model.original, language); + monaco.editor.setModelLanguage(model.modified, language); + } + }, [original, modified, language]); + + useEffect(() => { + const diff = diffRef.current; + if (!diff) return; + diff.updateOptions(options); + monaco.editor.setTheme(theme); + }, [options, theme]); + + return
; +} diff --git a/frontend/app/view/codeeditor/yamlworker.js b/frontend/app/monaco/yamlworker.js similarity index 100% rename from frontend/app/view/codeeditor/yamlworker.js rename to frontend/app/monaco/yamlworker.js diff --git a/frontend/app/view/codeeditor/codeeditor.tsx b/frontend/app/view/codeeditor/codeeditor.tsx index 76bc748afc..993db50455 100644 --- a/frontend/app/view/codeeditor/codeeditor.tsx +++ b/frontend/app/view/codeeditor/codeeditor.tsx @@ -1,11 +1,11 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { loadMonaco } from "@/app/monaco/monaco-env"; +import { MonacoCodeEditor } from "@/app/monaco/monaco-react"; import { useOverrideConfigAtom } from "@/app/store/global"; import { boundNumber } from "@/util/util"; -import { Editor, Monaco } from "@monaco-editor/react"; import type * as MonacoTypes from "monaco-editor"; +import * as MonacoModule from "monaco-editor"; import React, { useMemo, useRef } from "react"; function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions { @@ -36,7 +36,7 @@ interface CodeEditorProps { language?: string; fileName?: string; onChange?: (text: string) => void; - onMount?: (monacoPtr: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => () => void; + onMount?: (monacoPtr: MonacoTypes.editor.IStandaloneCodeEditor, monaco: typeof MonacoModule) => () => void; } export function CodeEditor({ blockId, text, language, fileName, readonly, onChange, onMount }: CodeEditorProps) { @@ -65,21 +65,24 @@ export function CodeEditor({ blockId, text, language, fileName, readonly, onChan }; }, []); - function handleEditorChange(text: string, ev: MonacoTypes.editor.IModelContentChangedEvent) { + function handleEditorChange(text: string) { if (onChange) { onChange(text); } } - function handleEditorOnMount(editor: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) { + function handleEditorOnMount( + editor: MonacoTypes.editor.IStandaloneCodeEditor, + monaco: typeof MonacoModule + ): () => void { if (onMount) { unmountRef.current = onMount(editor, monaco); } + return null; } const editorOpts = useMemo(() => { const opts = defaultEditorOptions(); - opts.readOnly = readonly; opts.minimap.enabled = minimapEnabled; opts.stickyScroll.enabled = stickyScrollEnabled; opts.wordWrap = wordWrap ? "on" : "off"; @@ -91,10 +94,10 @@ export function CodeEditor({ blockId, text, language, fileName, readonly, onChan return (
- { const opts = defaultDiffEditorOptions(); @@ -56,8 +63,9 @@ export function DiffViewer({ blockId, original, modified, language, fileName }: return (
- void { + function onMount(editor: MonacoTypes.editor.IStandaloneCodeEditor, monacoApi: typeof monaco): () => void { model.monacoRef.current = editor; const keyDownDisposer = editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => { diff --git a/frontend/app/view/preview/preview-model.tsx b/frontend/app/view/preview/preview-model.tsx index d1eed4cfbc..3431a9663f 100644 --- a/frontend/app/view/preview/preview-model.tsx +++ b/frontend/app/view/preview/preview-model.tsx @@ -17,7 +17,7 @@ import { formatRemoteUri } from "@/util/waveutil"; import clsx from "clsx"; import { Atom, atom, Getter, PrimitiveAtom, WritableAtom } from "jotai"; import { loadable } from "jotai/utils"; -import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; +import type * as MonacoTypes from "monaco-editor"; import { createRef } from "react"; import { PreviewView } from "./preview"; diff --git a/frontend/app/view/waveconfig/waveconfig-model.ts b/frontend/app/view/waveconfig/waveconfig-model.ts index 7c2af88d6d..cd7d4e45e3 100644 --- a/frontend/app/view/waveconfig/waveconfig-model.ts +++ b/frontend/app/view/waveconfig/waveconfig-model.ts @@ -12,7 +12,7 @@ import { WaveConfigView } from "@/app/view/waveconfig/waveconfig"; import { isWindows } from "@/util/platformutil"; import { base64ToString, stringToBase64 } from "@/util/util"; import { atom, type PrimitiveAtom } from "jotai"; -import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; +import type * as MonacoTypes from "monaco-editor"; import * as React from "react"; type ValidationResult = { success: true } | { error: string }; diff --git a/frontend/app/view/waveconfig/waveconfig.tsx b/frontend/app/view/waveconfig/waveconfig.tsx index 8eff903b3c..9c5e873bc9 100644 --- a/frontend/app/view/waveconfig/waveconfig.tsx +++ b/frontend/app/view/waveconfig/waveconfig.tsx @@ -9,7 +9,7 @@ import type { ConfigFile, WaveConfigViewModel } from "@/app/view/waveconfig/wave import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil"; import { cn } from "@/util/util"; import { useAtom, useAtomValue } from "jotai"; -import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; +import type * as MonacoTypes from "monaco-editor"; import { memo, useCallback, useEffect, useRef } from "react"; import { debounce } from "throttle-debounce"; diff --git a/frontend/wave.ts b/frontend/wave.ts index 62d1794573..1f6792bdbd 100644 --- a/frontend/wave.ts +++ b/frontend/wave.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { App } from "@/app/app"; +import { loadMonaco } from "@/app/monaco/monaco-env"; import { GlobalModel } from "@/app/store/global-model"; import { globalRefocus, @@ -14,7 +15,6 @@ import { modalsModel } from "@/app/store/modalmodel"; import { RpcApi } from "@/app/store/wshclientapi"; import { makeBuilderRouteId, makeTabRouteId } from "@/app/store/wshrouter"; import { initWshrpc, TabRpcClient } from "@/app/store/wshrpcutil"; -import { loadMonaco } from "@/app/view/codeeditor/codeeditor"; import { BuilderApp } from "@/builder/builder-app"; import { getLayoutModelForStaticTab } from "@/layout/index"; import { diff --git a/package-lock.json b/package-lock.json index d4bb274d8f..25a2bbfdca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "dependencies": { "@ai-sdk/react": "^2.0.104", "@floating-ui/react": "^0.27.16", - "@monaco-editor/react": "^4.7.0", "@observablehq/plot": "^0.6.17", "@react-hook/resize-observer": "^2.0.2", "@table-nav/core": "^0.0.7", @@ -6607,29 +6606,6 @@ "langium": "3.3.1" } }, - "node_modules/@monaco-editor/loader": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", - "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", - "license": "MIT", - "dependencies": { - "state-local": "^1.0.6" - } - }, - "node_modules/@monaco-editor/react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", - "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", - "license": "MIT", - "dependencies": { - "@monaco-editor/loader": "^1.5.0" - }, - "peerDependencies": { - "monaco-editor": ">= 0.25.0 < 1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -29691,12 +29667,6 @@ "node": ">= 6" } }, - "node_modules/state-local": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", - "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", diff --git a/package.json b/package.json index 2af55284a3..2914350a56 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "dependencies": { "@ai-sdk/react": "^2.0.104", "@floating-ui/react": "^0.27.16", - "@monaco-editor/react": "^4.7.0", "@observablehq/plot": "^0.6.17", "@react-hook/resize-observer": "^2.0.2", "@table-nav/core": "^0.0.7", From 203be20e5e04e4bcbc43e77f6233568d0b1bb3f8 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 2 Jan 2026 21:25:48 -0800 Subject: [PATCH 4/4] fix nits, remove vite static copy, remove theme prop, remove unused filename prop --- frontend/app/monaco/monaco-env.ts | 1 + frontend/app/monaco/monaco-react.tsx | 20 +---- frontend/app/view/codeeditor/codeeditor.tsx | 2 - frontend/app/view/codeeditor/diffviewer.tsx | 2 - package-lock.json | 97 --------------------- package.json | 1 - 6 files changed, 5 insertions(+), 118 deletions(-) diff --git a/frontend/app/monaco/monaco-env.ts b/frontend/app/monaco/monaco-env.ts index 2421f87acf..a11c44b60a 100644 --- a/frontend/app/monaco/monaco-env.ts +++ b/frontend/app/monaco/monaco-env.ts @@ -68,6 +68,7 @@ export function loadMonaco() { validate: true, schemas: [], }); + monaco.editor.setTheme("wave-theme-dark"); // Disable default validation errors for typescript and javascript monaco.typescript.typescriptDefaults.setDiagnosticsOptions({ noSemanticValidation: true, diff --git a/frontend/app/monaco/monaco-react.tsx b/frontend/app/monaco/monaco-react.tsx index 003d0be4de..aee722e314 100644 --- a/frontend/app/monaco/monaco-react.tsx +++ b/frontend/app/monaco/monaco-react.tsx @@ -15,13 +15,10 @@ type CodeEditorProps = { text: string; readonly: boolean; language?: string; - fileName?: string; onChange?: (text: string) => void; onMount?: (editor: MonacoTypes.editor.IStandaloneCodeEditor, monacoApi: typeof monaco) => () => void; - // keep your computed path behavior outside if you want; or compute inside like today path: string; options: MonacoTypes.editor.IEditorOptions; - theme: string; }; export function MonacoCodeEditor({ @@ -32,7 +29,6 @@ export function MonacoCodeEditor({ onMount, path, options, - theme, }: CodeEditorProps) { const divRef = useRef(null); const editorRef = useRef(null); @@ -51,14 +47,10 @@ export function MonacoCodeEditor({ const editor = monaco.editor.create(el, { ...options, readOnly: readonly, - theme, // note: editor.create uses global theme via setTheme, but keeping your mental model model, }); editorRef.current = editor; - // Monaco theme is global; set it here - monaco.editor.setTheme(theme); - const sub = model.onDidChangeContent(() => { if (applyingFromProps.current) return; onChange?.(model.getValue()); @@ -95,13 +87,12 @@ export function MonacoCodeEditor({ applyingFromProps.current = false; }, [text]); - // Keep options + theme in sync + // Keep options in sync useEffect(() => { const editor = editorRef.current; if (!editor) return; editor.updateOptions({ ...options, readOnly: readonly }); - monaco.editor.setTheme(theme); - }, [options, readonly, theme]); + }, [options, readonly]); // Keep language in sync useEffect(() => { @@ -121,10 +112,9 @@ type DiffViewerProps = { language?: string; path: string; options: MonacoTypes.editor.IDiffEditorOptions; - theme: string; }; -export function MonacoDiffViewer({ original, modified, language, path, options, theme }: DiffViewerProps) { +export function MonacoDiffViewer({ original, modified, language, path, options }: DiffViewerProps) { const divRef = useRef(null); const diffRef = useRef(null); @@ -143,7 +133,6 @@ export function MonacoDiffViewer({ original, modified, language, path, options, const diff = monaco.editor.createDiffEditor(el, options); diffRef.current = diff; - monaco.editor.setTheme(theme); diff.setModel({ original: originalModel, modified: modifiedModel }); @@ -176,8 +165,7 @@ export function MonacoDiffViewer({ original, modified, language, path, options, const diff = diffRef.current; if (!diff) return; diff.updateOptions(options); - monaco.editor.setTheme(theme); - }, [options, theme]); + }, [options]); return
; } diff --git a/frontend/app/view/codeeditor/codeeditor.tsx b/frontend/app/view/codeeditor/codeeditor.tsx index 993db50455..2f93264cbf 100644 --- a/frontend/app/view/codeeditor/codeeditor.tsx +++ b/frontend/app/view/codeeditor/codeeditor.tsx @@ -46,7 +46,6 @@ export function CodeEditor({ blockId, text, language, fileName, readonly, onChan const stickyScrollEnabled = useOverrideConfigAtom(blockId, "editor:stickyscrollenabled") ?? false; const wordWrap = useOverrideConfigAtom(blockId, "editor:wordwrap") ?? false; const fontSize = boundNumber(useOverrideConfigAtom(blockId, "editor:fontsize"), 6, 64); - const theme = "wave-theme-dark"; const uuidRef = useRef(crypto.randomUUID()).current; let editorPath: string; if (fileName) { @@ -95,7 +94,6 @@ export function CodeEditor({ blockId, text, language, fileName, readonly, onChan
=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/vite-plugin-static-copy/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/vite-plugin-static-copy/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/vite-plugin-static-copy/node_modules/p-map": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vite-plugin-static-copy/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/vite-plugin-static-copy/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/vite-plugin-svgr": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz", diff --git a/package.json b/package.json index 2914350a56..2fc677bfac 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "typescript-eslint": "^8.49.0", "vite": "^6.4.1", "vite-plugin-image-optimizer": "^2.0.3", - "vite-plugin-static-copy": "^3.1.4", "vite-plugin-svgr": "^4.5.0", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.0.9"