From 50fd7d06da2e15adbc2b4021733f956658551852 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Sat, 14 Mar 2026 19:41:49 -0400 Subject: [PATCH] refactor: replace dual-layer textarea with Monaco Editor for script body The transparent-textarea-over-highlighted-overlay approach had persistent cursor alignment issues. Replace with Monaco Editor (already used in tree editor Code Mode) using built-in PowerShell language support and our existing dark theme. Gives proper syntax highlighting, cursor positioning, line numbers, and editing for free. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../script-editor/ScriptBodyEditor.tsx | 79 +++++++++---------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/frontend/src/components/script-editor/ScriptBodyEditor.tsx b/frontend/src/components/script-editor/ScriptBodyEditor.tsx index 762311cc..c4c505be 100644 --- a/frontend/src/components/script-editor/ScriptBodyEditor.tsx +++ b/frontend/src/components/script-editor/ScriptBodyEditor.tsx @@ -1,5 +1,7 @@ -import { useRef, useCallback } from 'react' -import { PowerShellHighlighter } from '@/components/scripts/PowerShellHighlighter' +import { useCallback } from 'react' +import Editor, { type BeforeMount } from '@monaco-editor/react' +import { resolutionFlowTheme, THEME_ID } from '@/components/tree-editor/code-mode/resolutionFlowTheme' +import { Spinner } from '@/components/common/Spinner' interface Props { value: string @@ -8,50 +10,43 @@ interface Props { } export function ScriptBodyEditor({ value, onChange, disabled }: Props) { - const textareaRef = useRef(null) - const overlayRef = useRef(null) - - const handleScroll = useCallback(() => { - if (textareaRef.current && overlayRef.current) { - overlayRef.current.scrollTop = textareaRef.current.scrollTop - overlayRef.current.scrollLeft = textareaRef.current.scrollLeft - } + const handleBeforeMount: BeforeMount = useCallback((monaco) => { + // Register our dark theme if not already defined + monaco.editor.defineTheme(THEME_ID, resolutionFlowTheme) }, []) - const handleTab = useCallback((e: React.KeyboardEvent) => { - if (e.key === 'Tab') { - e.preventDefault() - const ta = e.currentTarget - const start = ta.selectionStart - const end = ta.selectionEnd - const newValue = value.substring(0, start) + ' ' + value.substring(end) - onChange(newValue) - // Restore cursor position after React re-render - requestAnimationFrame(() => { - ta.selectionStart = ta.selectionEnd = start + 4 - }) - } - }, [value, onChange]) - return ( -
- {/* Highlighted overlay (read-only visual layer) — scroll synced to textarea */} -
- -
- - {/* Editable textarea (transparent text, visible caret) */} -