diff --git a/ui/src/components/DiffViewer.tsx b/ui/src/components/DiffViewer.tsx index 2bd4c67d7ff14e75c362834421d54ea64d12644f..a990dc29e51377db14cb2a1bd55bd57867ad7584 100644 --- a/ui/src/components/DiffViewer.tsx +++ b/ui/src/components/DiffViewer.tsx @@ -113,6 +113,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit } const monacoRef = useRef(null); const commentInputRef = useRef(null); const modeRef = useRef(mode); + const hoverDecorationsRef = useRef([]); // Keep modeRef in sync with mode state and update editor options useEffect(() => { @@ -252,7 +253,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit } minimap: { enabled: false }, scrollBeyondLastLine: true, // Enable scroll past end for mobile floating buttons wordWrap: "on", - glyphMargin: false, // No glyph margin - click on lines to comment + glyphMargin: !isMobile, // Enable glyph margin for comment indicator on hover lineDecorationsWidth: isMobile ? 0 : 10, lineNumbersMinChars: isMobile ? 0 : 3, quickSuggestions: false, @@ -346,6 +347,54 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit } }); } + // Add hover highlighting with comment indicator (comment mode only) + let lastHoveredLine = -1; + modifiedEditor.onMouseMove((e: Monaco.editor.IEditorMouseEvent) => { + // Only show hover effects in comment mode + if (modeRef.current !== "comment") { + if (hoverDecorationsRef.current.length > 0) { + hoverDecorationsRef.current = modifiedEditor.deltaDecorations( + hoverDecorationsRef.current, + [], + ); + } + return; + } + + const position = e.target.position; + const lineNumber = position?.lineNumber ?? -1; + + if (lineNumber === lastHoveredLine) return; + lastHoveredLine = lineNumber; + + if (lineNumber > 0) { + hoverDecorationsRef.current = modifiedEditor.deltaDecorations(hoverDecorationsRef.current, [ + { + range: new monaco.Range(lineNumber, 1, lineNumber, 1), + options: { + isWholeLine: true, + className: "diff-viewer-line-hover", + glyphMarginClassName: "diff-viewer-comment-glyph", + }, + }, + ]); + } else { + hoverDecorationsRef.current = modifiedEditor.deltaDecorations( + hoverDecorationsRef.current, + [], + ); + } + }); + + // Clear decorations when mouse leaves editor + modifiedEditor.onMouseLeave(() => { + lastHoveredLine = -1; + hoverDecorationsRef.current = modifiedEditor.deltaDecorations( + hoverDecorationsRef.current, + [], + ); + }); + // Add content change listener for auto-save contentChangeDisposableRef.current?.dispose(); contentChangeDisposableRef.current = modifiedEditor.onDidChangeModelContent(() => { diff --git a/ui/src/styles.css b/ui/src/styles.css index 438a30a46a7e14f9f9b47ae27e482e06bf18932c..77e953137778e5533dde47e08b056b2d0a138d61 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -3009,6 +3009,38 @@ svg { width: 100%; } +/* Diff viewer line hover highlighting (via decoration API) */ +.diff-viewer-editor .monaco-editor .diff-viewer-line-hover { + background-color: rgba(37, 99, 235, 0.08) !important; +} + +.dark .diff-viewer-editor .monaco-editor .diff-viewer-line-hover { + background-color: rgba(96, 165, 250, 0.12) !important; +} + +/* Comment indicator glyph in margin for diff viewer */ +.diff-viewer-editor .monaco-editor .diff-viewer-comment-glyph { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Cline x1='9' y1='10' x2='15' y2='10'%3E%3C/line%3E%3C/svg%3E"); + background-size: 14px 14px; + background-repeat: no-repeat; + background-position: center; + opacity: 0.6; + cursor: pointer; +} + +.diff-viewer-editor .monaco-editor .diff-viewer-comment-glyph:hover { + opacity: 1; +} + +.dark .diff-viewer-editor .monaco-editor .diff-viewer-comment-glyph { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Cline x1='9' y1='10' x2='15' y2='10'%3E%3C/line%3E%3C/svg%3E"); +} + +/* Make diff viewer Monaco lines clickable with pointer cursor */ +.diff-viewer-editor .monaco-editor .view-lines { + cursor: pointer; +} + .diff-viewer-comment-badge { position: absolute; bottom: 1rem;