shelley: Improve diff viewer UI for desktop and mobile

Philip Zeyliger created

Prompt: Work on the diffs view in shelley:
1. On desktop, top bar should have commit selector, file selector, nav buttons, mode toggle - no collapsibility
2. On mobile, selectors 50/50 at top, floating nav buttons at bottom (larger), extra whitespace to scroll past
3. Change prev/next file icons from >| to >> (double chevrons)
4. Fix mobile CSS: full width, fix gutter wraparound
5. Mobile commenting: clicking on line should work
6. Desktop: close button on far right, dropdowns take space
7. Comment dialog on top half of screen on mobile, auto-focus
8. Fix open/close/reopen bug
9. Comment format: > filename:123: code

Desktop:
- Remove collapsible selectors, show all controls in one row
- Selectors expand to fill space, controls (nav, mode, close) on right
- Change prev/next file icons to double chevrons (>>)

Mobile:
- Selectors 50/50 at top with close button
- Floating nav buttons at bottom (larger, with mode toggle)
- Comment dialog in top half of screen for keyboard space
- Auto-focus comment textarea
- Fix CSS: full width, hide gutter properly
- Extra padding at bottom for scrolling past buttons
- onMouseUp handler for touch commenting

Bug fixes:
- Fix open/close/reopen by resetting state and disposing editor on close

Comment format:
- Changed to: > filename:123: code snippet
  Comment text

Change summary

ui/src/components/DiffViewer.tsx | 427 +++++++++++++++++++++------------
ui/src/styles.css                | 196 +++++++++------
2 files changed, 392 insertions(+), 231 deletions(-)

Detailed changes

ui/src/components/DiffViewer.tsx 🔗

@@ -10,6 +10,33 @@ interface DiffViewerProps {
   onCommentTextChange: (text: string) => void;
 }
 
+// Icon components for cleaner JSX
+const PrevFileIcon = () => (
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
+    <path d="M8 2L2 8l6 6V2z" />
+    <path d="M14 2L8 8l6 6V2z" />
+  </svg>
+);
+
+const PrevChangeIcon = () => (
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
+    <path d="M10 2L4 8l6 6V2z" />
+  </svg>
+);
+
+const NextChangeIcon = () => (
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
+    <path d="M6 2l6 6-6 6V2z" />
+  </svg>
+);
+
+const NextFileIcon = () => (
+  <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
+    <path d="M2 2l6 6-6 6V2z" />
+    <path d="M8 2l6 6-6 6V2z" />
+  </svg>
+);
+
 // Global Monaco instance - loaded lazily
 let monacoInstance: typeof Monaco | null = null;
 let monacoLoadPromise: Promise<typeof Monaco> | null = null;
@@ -75,11 +102,12 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
   } | null>(null);
   const [commentText, setCommentText] = useState("");
   const [mode, setMode] = useState<ViewMode>("comment");
-  const [selectorsExpanded, setSelectorsExpanded] = useState(false);
+
   const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
   const editorContainerRef = useRef<HTMLDivElement>(null);
   const editorRef = useRef<Monaco.editor.IStandaloneDiffEditor | null>(null);
   const monacoRef = useRef<typeof Monaco | null>(null);
+  const commentInputRef = useRef<HTMLTextAreaElement>(null);
   const modeRef = useRef<ViewMode>(mode);
 
   // Keep modeRef in sync with mode state and update editor options
@@ -101,6 +129,16 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
     return () => window.removeEventListener("resize", handleResize);
   }, []);
 
+  // Focus comment input when dialog opens
+  useEffect(() => {
+    if (showCommentDialog && commentInputRef.current) {
+      // Small delay to ensure the dialog is rendered
+      setTimeout(() => {
+        commentInputRef.current?.focus();
+      }, 50);
+    }
+  }, [showCommentDialog]);
+
   // Load Monaco when viewer opens
   useEffect(() => {
     if (isOpen && !monacoLoaded) {
@@ -116,10 +154,25 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
     }
   }, [isOpen, monacoLoaded]);
 
-  // Load diffs when viewer opens
+  // Load diffs when viewer opens, reset state when it closes
   useEffect(() => {
     if (isOpen && cwd) {
       loadDiffs();
+    } else if (!isOpen) {
+      // Reset state when closing
+      setFileDiff(null);
+      setSelectedFile(null);
+      setFiles([]);
+      setSelectedDiff(null);
+      setDiffs([]);
+      setError(null);
+      setShowCommentDialog(null);
+      setCommentText("");
+      // Dispose editor when closing
+      if (editorRef.current) {
+        editorRef.current.dispose();
+        editorRef.current = null;
+      }
     }
   }, [isOpen, cwd]);
 
@@ -183,7 +236,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
       renderMarginRevertIcon: false,
       lineNumbers: isMobile ? "off" : "on",
       minimap: { enabled: false },
-      scrollBeyondLastLine: false,
+      scrollBeyondLastLine: true, // Enable scroll past end for mobile floating buttons
       wordWrap: "on",
       glyphMargin: false, // No glyph margin - click on lines to comment
       lineDecorationsWidth: isMobile ? 0 : 10,
@@ -195,6 +248,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
       contextmenu: false,
       links: false,
       folding: !isMobile,
+      padding: isMobile ? { bottom: 80 } : undefined, // Extra padding for floating buttons on mobile
     });
 
     diffEditor.setModel({
@@ -206,6 +260,32 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
 
     // Add click handler for commenting - clicking on a line in comment mode opens dialog
     const modifiedEditor = diffEditor.getModifiedEditor();
+    
+    // Handler function for opening comment dialog
+    const openCommentDialog = (lineNumber: number) => {
+      const model = modifiedEditor.getModel();
+      const selection = modifiedEditor.getSelection();
+      let selectedText = "";
+      let startLine = lineNumber;
+      let endLine = lineNumber;
+
+      if (selection && !selection.isEmpty() && model) {
+        selectedText = model.getValueInRange(selection);
+        startLine = selection.startLineNumber;
+        endLine = selection.endLineNumber;
+      } else if (model) {
+        selectedText = model.getLineContent(lineNumber) || "";
+      }
+
+      setShowCommentDialog({
+        line: startLine,
+        side: "right",
+        selectedText,
+        startLine,
+        endLine,
+      });
+    };
+    
     modifiedEditor.onMouseDown((e: Monaco.editor.IEditorMouseEvent) => {
       // In comment mode, clicking on line content opens comment dialog
       const isLineClick =
@@ -215,30 +295,28 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
       if (isLineClick && modeRef.current === "comment") {
         const position = e.target.position;
         if (position) {
-          const model = modifiedEditor.getModel();
-          const selection = modifiedEditor.getSelection();
-          let selectedText = "";
-          let startLine = position.lineNumber;
-          let endLine = position.lineNumber;
-
-          if (selection && !selection.isEmpty() && model) {
-            selectedText = model.getValueInRange(selection);
-            startLine = selection.startLineNumber;
-            endLine = selection.endLineNumber;
-          } else if (model) {
-            selectedText = model.getLineContent(position.lineNumber) || "";
-          }
-
-          setShowCommentDialog({
-            line: startLine,
-            side: "right",
-            selectedText,
-            startLine,
-            endLine,
-          });
+          openCommentDialog(position.lineNumber);
         }
       }
     });
+    
+    // For mobile: use onMouseUp which fires more reliably on touch devices
+    if (isMobile) {
+      modifiedEditor.onMouseUp((e: Monaco.editor.IEditorMouseEvent) => {
+        if (modeRef.current !== "comment") return;
+        
+        const isLineClick =
+          e.target.type === monaco.editor.MouseTargetType.CONTENT_TEXT ||
+          e.target.type === monaco.editor.MouseTargetType.CONTENT_EMPTY;
+
+        if (isLineClick) {
+          const position = e.target.position;
+          if (position) {
+            openCommentDialog(position.lineNumber);
+          }
+        }
+      });
+    }
 
     // Add content change listener for auto-save
     contentChangeDisposableRef.current?.dispose();
@@ -320,18 +398,13 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
   const handleAddComment = () => {
     if (!showCommentDialog || !commentText.trim() || !selectedFile) return;
 
-    // Format and append comment directly to the message input
-    let commentBlock = `**${selectedFile}**`;
-    if (showCommentDialog.startLine !== showCommentDialog.endLine) {
-      commentBlock += ` (lines ${showCommentDialog.startLine}-${showCommentDialog.endLine})`;
-    } else {
-      commentBlock += ` (line ${showCommentDialog.line})`;
-    }
-    commentBlock += ":\n";
-    if (showCommentDialog.selectedText) {
-      commentBlock += "```\n" + showCommentDialog.selectedText + "\n```\n";
-    }
-    commentBlock += commentText + "\n\n";
+    // Format: > filename:123: code
+    // Comment...
+    const line = showCommentDialog.line;
+    const codeSnippet = showCommentDialog.selectedText?.split('\n')[0]?.trim() || '';
+    const truncatedCode = codeSnippet.length > 60 ? codeSnippet.substring(0, 57) + '...' : codeSnippet;
+    
+    const commentBlock = `> ${selectedFile}:${line}: ${truncatedCode}\n${commentText}\n\n`;
 
     onCommentTextChange(commentBlock);
     setShowCommentDialog(null);
@@ -542,6 +615,101 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
   const hasNextFile = currentFileIndex < files.length - 1;
   const hasPrevFile = currentFileIndex > 0;
 
+  // Selectors shared between desktop and mobile
+  const commitSelector = (
+    <select
+      value={selectedDiff || ""}
+      onChange={(e) => setSelectedDiff(e.target.value || null)}
+      className="diff-viewer-select"
+    >
+      <option value="">Choose base...</option>
+      {diffs.map((diff) => {
+        const stats = `${diff.filesCount} files, +${diff.additions}/-${diff.deletions}`;
+        return (
+          <option key={diff.id} value={diff.id}>
+            {diff.id === "working"
+              ? `Working Changes (${stats})`
+              : `${diff.message.slice(0, 40)} (${stats})`}
+          </option>
+        );
+      })}
+    </select>
+  );
+
+  const fileSelector = (
+    <select
+      value={selectedFile || ""}
+      onChange={(e) => setSelectedFile(e.target.value || null)}
+      className="diff-viewer-select"
+      disabled={files.length === 0}
+    >
+      <option value="">{files.length === 0 ? "No files" : "Choose file..."}</option>
+      {files.map((file) => (
+        <option key={file.path} value={file.path}>
+          {getStatusSymbol(file.status)} {file.path}
+          {file.additions > 0 && ` (+${file.additions})`}
+          {file.deletions > 0 && ` (-${file.deletions})`}
+        </option>
+      ))}
+    </select>
+  );
+
+  const modeToggle = (
+    <div className="diff-viewer-mode-toggle">
+      <button
+        className={`diff-viewer-mode-btn ${mode === "comment" ? "active" : ""}`}
+        onClick={() => setMode("comment")}
+        title="Comment mode"
+      >
+        💬
+      </button>
+      <button
+        className={`diff-viewer-mode-btn ${mode === "edit" ? "active" : ""}`}
+        onClick={() => setMode("edit")}
+        title="Edit mode"
+      >
+        ✏️
+      </button>
+    </div>
+  );
+
+  const navButtons = (
+    <div className="diff-viewer-nav-buttons">
+      <button
+        className="diff-viewer-nav-btn"
+        onClick={goToPreviousFile}
+        disabled={!hasPrevFile}
+        title="Previous file"
+      >
+        <PrevFileIcon />
+      </button>
+      <button
+        className="diff-viewer-nav-btn"
+        onClick={goToPreviousChange}
+        disabled={!fileDiff}
+        title="Previous change"
+      >
+        <PrevChangeIcon />
+      </button>
+      <button
+        className="diff-viewer-nav-btn"
+        onClick={goToNextChange}
+        disabled={!fileDiff}
+        title="Next change"
+      >
+        <NextChangeIcon />
+      </button>
+      <button
+        className="diff-viewer-nav-btn"
+        onClick={() => goToNextFile()}
+        disabled={!hasNextFile}
+        title="Next file"
+      >
+        <NextFileIcon />
+      </button>
+    </div>
+  );
+
   return (
     <div className="diff-viewer-overlay">
       <div className="diff-viewer-container">
@@ -554,129 +722,36 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
           </div>
         )}
 
-        {/* Header */}
-        <div className="diff-viewer-header">
-          <div className="diff-viewer-header-row">
-            {/* Mode toggle */}
-            <div className="diff-viewer-mode-toggle">
-              <button
-                className={`diff-viewer-mode-btn ${mode === "comment" ? "active" : ""}`}
-                onClick={() => setMode("comment")}
-                title="Comment mode"
-              >
-                💬
-              </button>
-              <button
-                className={`diff-viewer-mode-btn ${mode === "edit" ? "active" : ""}`}
-                onClick={() => setMode("edit")}
-                title="Edit mode"
-              >
-                ✏️
-              </button>
-            </div>
-
-            {/* Navigation buttons: <<(prev file) <(prev change) >(next change) >>(next file) */}
-            <div className="diff-viewer-nav-buttons">
-              <button
-                className="diff-viewer-nav-btn"
-                onClick={goToPreviousFile}
-                disabled={!hasPrevFile}
-                title="Previous file"
-              >
-                <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
-                  <path d="M11 2L5 8l6 6V2zM4 2v12H2V2h2z" />
-                </svg>
-              </button>
-              <button
-                className="diff-viewer-nav-btn"
-                onClick={goToPreviousChange}
-                disabled={!fileDiff}
-                title="Previous change"
-              >
-                <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
-                  <path d="M10 2L4 8l6 6V2z" />
-                </svg>
-              </button>
-              <button
-                className="diff-viewer-nav-btn"
-                onClick={goToNextChange}
-                disabled={!fileDiff}
-                title="Next change"
-              >
-                <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
-                  <path d="M6 2l6 6-6 6V2z" />
-                </svg>
-              </button>
-              <button
-                className="diff-viewer-nav-btn"
-                onClick={() => goToNextFile()}
-                disabled={!hasNextFile}
-                title="Next file"
-              >
-                <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
-                  <path d="M5 2l6 6-6 6V2zM12 2v12h2V2h-2z" />
-                </svg>
-              </button>
+        {/* Header - different layout for desktop vs mobile */}
+        {isMobile ? (
+          // Mobile header: just selectors 50/50
+          <div className="diff-viewer-header diff-viewer-header-mobile">
+            <div className="diff-viewer-mobile-selectors">
+              {commitSelector}
+              {fileSelector}
             </div>
-
-            {/* Expand/collapse selectors */}
-            <button
-              className="diff-viewer-expand-btn"
-              onClick={() => setSelectorsExpanded(!selectorsExpanded)}
-              title={selectorsExpanded ? "Collapse selectors" : "Expand selectors"}
-            >
-              {selectorsExpanded ? "▲" : "▼"}
-              <span className="diff-viewer-expand-label">
-                {selectedFile ? selectedFile.split("/").pop() : "Select..."}
-              </span>
-            </button>
-
             <button className="diff-viewer-close" onClick={onClose} title="Close (Esc)">
               ×
             </button>
           </div>
-
-          {/* Collapsible selectors */}
-          {selectorsExpanded && (
-            <div className="diff-viewer-selectors">
-              {/* Diff selector */}
-              <select
-                value={selectedDiff || ""}
-                onChange={(e) => setSelectedDiff(e.target.value || null)}
-                className="diff-viewer-select"
-              >
-                <option value="">Choose base...</option>
-                {diffs.map((diff) => {
-                  const stats = `${diff.filesCount} files, +${diff.additions}/-${diff.deletions}`;
-                  return (
-                    <option key={diff.id} value={diff.id}>
-                      {diff.id === "working"
-                        ? `Working Changes (${stats})`
-                        : `${diff.message.slice(0, 40)} (${stats})`}
-                    </option>
-                  );
-                })}
-              </select>
-
-              {/* File selector */}
-              <select
-                value={selectedFile || ""}
-                onChange={(e) => setSelectedFile(e.target.value || null)}
-                className="diff-viewer-select"
-                disabled={files.length === 0}
-              >
-                <option value="">{files.length === 0 ? "No files" : "Choose file..."}</option>
-                {files.map((file) => (
-                  <option key={file.path} value={file.path}>
-                    {getStatusSymbol(file.status)} {file.path}
-                    {file.additions > 0 && ` (+${file.additions})`}
-                    {file.deletions > 0 && ` (-${file.deletions})`}
-                  </option>
-                ))}
-              </select>
+        ) : (
+          // Desktop header: selectors expand, controls on right
+          <div className="diff-viewer-header">
+            <div className="diff-viewer-header-row">
+              <div className="diff-viewer-selectors-row">
+                {commitSelector}
+                {fileSelector}
+              </div>
+              <div className="diff-viewer-controls-row">
+                {navButtons}
+                {modeToggle}
+                <button className="diff-viewer-close" onClick={onClose} title="Close (Esc)">
+                  ×
+                </button>
+              </div>
             </div>
-          )}
-        </div>
+          </div>
+        )}
 
         {/* Error banner */}
         {error && <div className="diff-viewer-error">{error}</div>}
@@ -712,6 +787,51 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
           />
         </div>
 
+        {/* Mobile floating nav buttons at bottom */}
+        {isMobile && (
+          <div className="diff-viewer-mobile-nav">
+            <button
+              className={`diff-viewer-mobile-nav-btn diff-viewer-mobile-mode-btn ${mode === "comment" ? "active" : ""}`}
+              onClick={() => setMode(mode === "comment" ? "edit" : "comment")}
+              title={mode === "comment" ? "Comment mode (tap to switch)" : "Edit mode (tap to switch)"}
+            >
+              {mode === "comment" ? "💬" : "✏️"}
+            </button>
+            <button
+              className="diff-viewer-mobile-nav-btn"
+              onClick={goToPreviousFile}
+              disabled={!hasPrevFile}
+              title="Previous file"
+            >
+              <PrevFileIcon />
+            </button>
+            <button
+              className="diff-viewer-mobile-nav-btn"
+              onClick={goToPreviousChange}
+              disabled={!fileDiff}
+              title="Previous change"
+            >
+              <PrevChangeIcon />
+            </button>
+            <button
+              className="diff-viewer-mobile-nav-btn"
+              onClick={goToNextChange}
+              disabled={!fileDiff}
+              title="Next change"
+            >
+              <NextChangeIcon />
+            </button>
+            <button
+              className="diff-viewer-mobile-nav-btn"
+              onClick={() => goToNextFile()}
+              disabled={!hasNextFile}
+              title="Next file"
+            >
+              <NextFileIcon />
+            </button>
+          </div>
+        )}
+
         {/* Comment dialog */}
         {showCommentDialog && (
           <div className="diff-viewer-comment-dialog">
@@ -726,6 +846,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange }: DiffViewerPro
               <pre className="diff-viewer-selected-text">{showCommentDialog.selectedText}</pre>
             )}
             <textarea
+              ref={commentInputRef}
               value={commentText}
               onChange={(e) => setCommentText(e.target.value)}
               placeholder="Enter your comment..."

ui/src/styles.css 🔗

@@ -2632,17 +2632,45 @@ svg {
   width: 100%;
 }
 
-.diff-viewer-header-left {
+.diff-viewer-selectors-row {
   display: flex;
-  flex-direction: column;
-  gap: 0.125rem;
+  flex: 1;
+  gap: 0.5rem;
+  min-width: 0;
 }
 
-.diff-viewer-title {
-  margin: 0;
-  font-size: 1rem;
-  font-weight: 600;
-  color: var(--text-primary);
+.diff-viewer-selectors-row .diff-viewer-select {
+  flex: 1;
+  min-width: 0;
+  max-width: none;
+}
+
+.diff-viewer-controls-row {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  flex-shrink: 0;
+}
+
+.diff-viewer-header-mobile {
+  flex-direction: row;
+  align-items: center;
+  gap: 0.5rem;
+  padding: 0.375rem 0.5rem;
+}
+
+.diff-viewer-mobile-selectors {
+  display: flex;
+  flex: 1;
+  gap: 0.375rem;
+  min-width: 0;
+}
+
+.diff-viewer-mobile-selectors .diff-viewer-select {
+  flex: 1;
+  min-width: 0;
+  font-size: 0.75rem;
+  padding: 0.375rem;
 }
 
 .diff-viewer-mode-toggle {
@@ -2698,41 +2726,6 @@ svg {
   background: var(--bg-tertiary);
 }
 
-.diff-viewer-expand-btn {
-  display: flex;
-  align-items: center;
-  gap: 0.375rem;
-  padding: 0.25rem 0.5rem;
-  border: 1px solid var(--border-color);
-  background: var(--bg-base);
-  color: var(--text-primary);
-  cursor: pointer;
-  border-radius: 0.25rem;
-  font-size: 0.75rem;
-  flex: 1;
-  min-width: 0;
-  overflow: hidden;
-}
-
-.diff-viewer-expand-btn:hover {
-  background: var(--bg-tertiary);
-}
-
-.diff-viewer-expand-label {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  flex: 1;
-  text-align: left;
-}
-
-.diff-viewer-selectors {
-  display: flex;
-  gap: 0.5rem;
-  width: 100%;
-  padding-top: 0.25rem;
-}
-
 .diff-viewer-select {
   flex: 1;
   min-width: 120px;
@@ -2932,6 +2925,55 @@ svg {
   background: var(--bg-secondary);
 }
 
+/* Mobile floating nav buttons */
+.diff-viewer-mobile-nav {
+  position: absolute;
+  bottom: 1.5rem;
+  left: 50%;
+  transform: translateX(-50%);
+  display: flex;
+  gap: 0.5rem;
+  z-index: 100;
+}
+
+.diff-viewer-mobile-nav-btn {
+  width: 3rem;
+  height: 3rem;
+  border: none;
+  border-radius: 50%;
+  background: var(--bg-base);
+  color: var(--text-primary);
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+}
+
+.diff-viewer-mobile-nav-btn svg {
+  width: 24px;
+  height: 24px;
+}
+
+.diff-viewer-mobile-nav-btn:disabled {
+  opacity: 0.4;
+  cursor: not-allowed;
+}
+
+.diff-viewer-mobile-nav-btn:active:not(:disabled) {
+  background: var(--bg-tertiary);
+  transform: scale(0.95);
+}
+
+.diff-viewer-mobile-mode-btn {
+  font-size: 1.25rem;
+}
+
+.diff-viewer-mobile-mode-btn.active {
+  background: var(--blue-bg);
+  color: var(--blue-text);
+}
+
 /* Monaco comment glyph decoration */
 /* Mobile optimizations for diff viewer */
 @media (max-width: 767px) {
@@ -2941,60 +2983,58 @@ svg {
 
   .diff-viewer-container {
     border-radius: 0;
-    max-height: 100%;
+    width: 100%;
+    max-width: 100%;
     height: 100%;
+    max-height: 100%;
   }
 
-  .diff-viewer-header {
-    padding: 0.375rem 0.5rem;
-    gap: 0.375rem;
-  }
-
-  .diff-viewer-header-row {
-    gap: 0.375rem;
-  }
-
-  .diff-viewer-selectors {
-    flex-direction: column;
-    width: 100%;
+  .diff-viewer-close {
+    width: 1.75rem;
+    height: 1.75rem;
+    font-size: 1.25rem;
+    flex-shrink: 0;
   }
 
-  .diff-viewer-select {
-    max-width: none;
-    width: 100%;
+  /* Monaco editor mobile styles - hide gutters completely */
+  .diff-viewer-editor .monaco-editor .margin {
+    display: none !important;
+    width: 0 !important;
   }
 
-  .diff-viewer-title {
-    font-size: 0.875rem;
+  .diff-viewer-editor .monaco-editor .margin-view-overlays {
+    display: none !important;
+    width: 0 !important;
   }
 
-  .diff-viewer-mode-btn {
-    padding: 0.25rem 0.375rem;
-    font-size: 0.75rem;
+  .diff-viewer-editor .monaco-editor .editor-scrollable {
+    left: 0 !important;
   }
 
-  .diff-viewer-nav-btn {
-    padding: 0.25rem 0.375rem;
-    font-size: 0.75rem;
+  .diff-viewer-editor .monaco-editor .lines-content {
+    margin-left: 0 !important;
   }
 
-  .diff-viewer-expand-btn {
-    font-size: 0.6875rem;
+  /* Ensure diff editor uses full width */
+  .diff-viewer-editor .monaco-diff-editor {
+    width: 100% !important;
   }
 
-  .diff-viewer-close {
-    width: 1.75rem;
-    height: 1.75rem;
-    font-size: 1.25rem;
+  .diff-viewer-editor .monaco-diff-editor .editor.modified {
+    width: 100% !important;
+    left: 0 !important;
   }
 
-  /* Monaco editor mobile styles - hide gutters */
-  .diff-viewer-editor .monaco-editor .margin {
-    display: none !important;
+  /* Comment dialog on top half of screen for mobile (keyboard space) */
+  .diff-viewer-comment-dialog {
+    top: 10%;
+    transform: translate(-50%, 0);
+    max-height: 40vh;
+    overflow-y: auto;
   }
 
-  .diff-viewer-editor .monaco-editor .editor-scrollable {
-    left: 0 !important;
+  .diff-viewer-comment-input {
+    min-height: 60px;
   }
 }