Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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";
Expand All @@ -153,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: {
Expand All @@ -172,9 +180,6 @@ export default defineConfig({
}),
react({}),
tailwindcss(),
viteStaticCopy({
targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }],
}),
],
},
});
82 changes: 82 additions & 0 deletions frontend/app/monaco/monaco-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

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";
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() {
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: [],
});
monaco.editor.setTheme("wave-theme-dark");
// 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,
});
}
171 changes: 171 additions & 0 deletions frontend/app/monaco/monaco-react.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// 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;
onChange?: (text: string) => void;
onMount?: (editor: MonacoTypes.editor.IStandaloneCodeEditor, monacoApi: typeof monaco) => () => void;
path: string;
options: MonacoTypes.editor.IEditorOptions;
};

export function MonacoCodeEditor({
text,
readonly,
language,
onChange,
onMount,
path,
options,
}: CodeEditorProps) {
const divRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<MonacoTypes.editor.IStandaloneCodeEditor | null>(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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statement.

This debug logging should be removed before merging.

🔎 Proposed fix
         const model = createModel(text, path, language);
-        console.log("[monaco] CREATE MODEL", path, model);

         const editor = monaco.editor.create(el, {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("[monaco] CREATE MODEL", path, model);
const model = createModel(text, path, language);
const editor = monaco.editor.create(el, {
🤖 Prompt for AI Agents
In frontend/app/monaco/monaco-react.tsx around line 45, remove the debug
console.log("[monaco] CREATE MODEL", path, model) statement; simply delete that
line (or replace with a non-debug logger call if persistent logging is required)
so no debug output is left in production code.


const editor = monaco.editor.create(el, {
...options,
readOnly: readonly,
model,
});
editorRef.current = editor;

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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statement.

This debug logging should be removed before merging.

🔎 Proposed fix
             editor.dispose();
             model.dispose();
-            console.log("[monaco] dispose model");
             editorRef.current = null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("[monaco] dispose model");
editor.dispose();
model.dispose();
editorRef.current = null;
🤖 Prompt for AI Agents
In frontend/app/monaco/monaco-react.tsx around line 68, remove the debug
console.log("[monaco] dispose model"); statement so no stray debug logging
remains; if structured logging is required instead, replace it with the
project’s logger call, otherwise simply delete the line and ensure no other
console.* debug calls remain in this file.

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 in sync
useEffect(() => {
const editor = editorRef.current;
if (!editor) return;
editor.updateOptions({ ...options, readOnly: readonly });
}, [options, readonly]);

// 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 <div className="flex flex-col h-full w-full" ref={divRef} />;
}

type DiffViewerProps = {
original: string;
modified: string;
language?: string;
path: string;
options: MonacoTypes.editor.IDiffEditorOptions;
};

export function MonacoDiffViewer({ original, modified, language, path, options }: DiffViewerProps) {
const divRef = useRef<HTMLDivElement>(null);
const diffRef = useRef<MonacoTypes.editor.IStandaloneDiffEditor | null>(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;

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);
}, [options]);

return <div className="flex flex-col h-full w-full" ref={divRef} />;
}
50 changes: 50 additions & 0 deletions frontend/app/monaco/schemaendpoints.ts
Original file line number Diff line number Diff line change
@@ -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<string>;
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 };
Loading
Loading