shelley/ui: add keyboard shortcuts for diff view navigation

Philip Zeyliger created

Prompt: In a new worktree, add keyboard shortcuts for the diff view, when in comment mode. "." should go to next change. ">" to next file. Similar for "," and "<". Obviously these should only happen when in comment mode and not when a comment is open for editing.

In comment mode (when not editing a comment), add shortcuts:
- . (period) - go to next change
- , (comma) - go to previous change
- > (shift+period) - go to next file
- < (shift+comma) - go to previous file

These shortcuts only work when:
1. In comment mode (not edit mode)
2. The comment dialog is closed

Also update button tooltips to show the keyboard shortcuts.

Use capture phase on the keydown listener to intercept events before
Monaco editor handles them.

Change summary

ui/src/components/DiffViewer.tsx | 55 +++++++++++++++++++++++++++------
1 file changed, 44 insertions(+), 11 deletions(-)

Detailed changes

ui/src/components/DiffViewer.tsx 🔗

@@ -596,6 +596,28 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
         saveImmediately();
         return;
       }
+
+      // Comment mode navigation shortcuts (only when comment dialog is closed)
+      if (mode === "comment" && !showCommentDialog) {
+        if (e.key === ".") {
+          e.preventDefault();
+          goToNextChange();
+          return;
+        } else if (e.key === ",") {
+          e.preventDefault();
+          goToPreviousChange();
+          return;
+        } else if (e.key === ">") {
+          e.preventDefault();
+          goToNextFile();
+          return;
+        } else if (e.key === "<") {
+          e.preventDefault();
+          goToPreviousFile();
+          return;
+        }
+      }
+
       if (!e.ctrlKey) return;
       if (e.key === "j") {
         e.preventDefault();
@@ -606,9 +628,20 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
       }
     };
 
-    window.addEventListener("keydown", handleKeyDown);
-    return () => window.removeEventListener("keydown", handleKeyDown);
-  }, [isOpen, goToNextFile, goToPreviousFile, showCommentDialog, onClose, saveImmediately]);
+    // Use capture phase to intercept events before Monaco editor handles them
+    window.addEventListener("keydown", handleKeyDown, true);
+    return () => window.removeEventListener("keydown", handleKeyDown, true);
+  }, [
+    isOpen,
+    goToNextFile,
+    goToPreviousFile,
+    goToNextChange,
+    goToPreviousChange,
+    showCommentDialog,
+    onClose,
+    saveImmediately,
+    mode,
+  ]);
 
   if (!isOpen) return null;
 
@@ -693,7 +726,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
         className="diff-viewer-nav-btn"
         onClick={goToPreviousFile}
         disabled={!hasPrevFile}
-        title="Previous file"
+        title="Previous file (<)"
       >
         <PrevFileIcon />
       </button>
@@ -701,7 +734,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
         className="diff-viewer-nav-btn"
         onClick={goToPreviousChange}
         disabled={!fileDiff}
-        title="Previous change"
+        title="Previous change (,)"
       >
         <PrevChangeIcon />
       </button>
@@ -709,7 +742,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
         className="diff-viewer-nav-btn"
         onClick={goToNextChange}
         disabled={!fileDiff}
-        title="Next change"
+        title="Next change (.)"
       >
         <NextChangeIcon />
       </button>
@@ -717,7 +750,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
         className="diff-viewer-nav-btn"
         onClick={() => goToNextFile()}
         disabled={!hasNextFile}
-        title="Next file"
+        title="Next file (>)"
       >
         <NextFileIcon />
       </button>
@@ -817,7 +850,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
               className="diff-viewer-mobile-nav-btn"
               onClick={goToPreviousFile}
               disabled={!hasPrevFile}
-              title="Previous file"
+              title="Previous file (<)"
             >
               <PrevFileIcon />
             </button>
@@ -825,7 +858,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
               className="diff-viewer-mobile-nav-btn"
               onClick={goToPreviousChange}
               disabled={!fileDiff}
-              title="Previous change"
+              title="Previous change (,)"
             >
               <PrevChangeIcon />
             </button>
@@ -833,7 +866,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
               className="diff-viewer-mobile-nav-btn"
               onClick={goToNextChange}
               disabled={!fileDiff}
-              title="Next change"
+              title="Next change (.)"
             >
               <NextChangeIcon />
             </button>
@@ -841,7 +874,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
               className="diff-viewer-mobile-nav-btn"
               onClick={() => goToNextFile()}
               disabled={!hasNextFile}
-              title="Next file"
+              title="Next file (>)"
             >
               <NextFileIcon />
             </button>