From ad1e3ada181f04cbce4a11f0eecd3310ee351429 Mon Sep 17 00:00:00 2001 From: Philip Zeyliger Date: Sat, 7 Feb 2026 20:45:21 -0800 Subject: [PATCH] shelley/ui: pass git worktree to diff viewer, add dir chooser Prompt: Sometimes the diff view can get confused about what dir you're in. In the case that someone is clicking on a "diff" link that the agent reports after a git change, that should always work, since we can include the git repo dir in the hyperlink. We can also add a "dir" chooser (just a folder icon) that lets you navigate to a different git directory from the top bar. We can re-use the directory picker, probably, but we should filter to only show folders, not files, since git repositories are always folders. Three changes: 1. When clicking a 'diff' link on a git commit message, the worktree path from the git state is now passed through to the DiffViewer, so it always opens in the correct directory regardless of the conversation's cwd. 2. Added a folder icon button to the DiffViewer header (both mobile and desktop) that opens a DirectoryPickerModal, allowing users to switch to a different git repository directory. 3. Added a 'foldersOnly' prop to DirectoryPickerModal that filters out non-directory entries, since git repositories are always directories. Co-authored-by: Shelley --- ui/src/components/ChatInterface.tsx | 8 +++- ui/src/components/DiffViewer.tsx | 47 +++++++++++++++++++++- ui/src/components/DirectoryPickerModal.tsx | 3 ++ ui/src/components/Message.tsx | 6 +-- ui/src/styles.css | 20 +++++++++ 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/ui/src/components/ChatInterface.tsx b/ui/src/components/ChatInterface.tsx index f2b5c1d9b1ae602eeb165c332376e27b42971877..30ba8375787b9080d04b0e144fd5a0aeef85a8d2 100644 --- a/ui/src/components/ChatInterface.tsx +++ b/ui/src/components/ChatInterface.tsx @@ -591,6 +591,7 @@ function ChatInterface({ const [diffViewerInitialCommit, setDiffViewerInitialCommit] = useState( undefined, ); + const [diffViewerCwd, setDiffViewerCwd] = useState(undefined); const [diffCommentText, setDiffCommentText] = useState(""); const [agentWorking, setAgentWorking] = useState(false); const [cancelling, setCancelling] = useState(false); @@ -1350,8 +1351,9 @@ function ChatInterface({ { + onOpenDiffViewer={(commit, cwd) => { setDiffViewerInitialCommit(commit); + setDiffViewerCwd(cwd); setShowDiffViewer(true); }} onCommentTextChange={setDiffCommentText} @@ -1825,14 +1827,16 @@ function ChatInterface({ {/* Diff Viewer */} { setShowDiffViewer(false); setDiffViewerInitialCommit(undefined); + setDiffViewerCwd(undefined); }} onCommentTextChange={setDiffCommentText} initialCommit={diffViewerInitialCommit} + onCwdChange={setDiffViewerCwd} /> {/* Version Checker Modal */} diff --git a/ui/src/components/DiffViewer.tsx b/ui/src/components/DiffViewer.tsx index 357705b92717be84b9e1f93a121b4b6068ebdd4e..5b9f91db6b42af6aa8b00aaf90ce8067282c244d 100644 --- a/ui/src/components/DiffViewer.tsx +++ b/ui/src/components/DiffViewer.tsx @@ -3,6 +3,7 @@ import type * as Monaco from "monaco-editor"; import { api } from "../services/api"; import { isDarkModeActive } from "../services/theme"; import { GitDiffInfo, GitFileInfo, GitFileDiff } from "../types"; +import DirectoryPickerModal from "./DirectoryPickerModal"; interface DiffViewerProps { cwd: string; @@ -10,6 +11,7 @@ interface DiffViewerProps { onClose: () => void; onCommentTextChange: (text: string) => void; initialCommit?: string; // If set, select this commit when opening + onCwdChange?: (cwd: string) => void; // Called when user picks a different git directory } // Icon components for cleaner JSX @@ -79,9 +81,17 @@ function loadMonaco(): Promise { type ViewMode = "comment" | "edit"; -function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }: DiffViewerProps) { +function DiffViewer({ + cwd, + isOpen, + onClose, + onCommentTextChange, + initialCommit, + onCwdChange, +}: DiffViewerProps) { const [diffs, setDiffs] = useState([]); const [gitRoot, setGitRoot] = useState(null); + const [showDirPicker, setShowDirPicker] = useState(false); const [selectedDiff, setSelectedDiff] = useState(null); const [files, setFiles] = useState([]); const [selectedFile, setSelectedFile] = useState(null); @@ -904,6 +914,27 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit } ); + const dirButton = ( + + ); + return (
@@ -929,6 +960,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit } {commitSelector} {fileSelector}
+ {dirButton} @@ -944,6 +976,7 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
{navButtons} {modeToggle} + {dirButton} @@ -1072,6 +1105,18 @@ function DiffViewer({ cwd, isOpen, onClose, onCommentTextChange, initialCommit }
)}
+ + {/* Directory picker for changing git directory */} + setShowDirPicker(false)} + onSelect={(path) => { + onCwdChange?.(path); + setShowDirPicker(false); + }} + initialPath={cwd} + foldersOnly + /> ); } diff --git a/ui/src/components/DirectoryPickerModal.tsx b/ui/src/components/DirectoryPickerModal.tsx index eb6210c70939d1ad340386370beacc4c43c1cbd4..1e255e3cc4c9f7e6a611d54c78a7b62a16fb4fcb 100644 --- a/ui/src/components/DirectoryPickerModal.tsx +++ b/ui/src/components/DirectoryPickerModal.tsx @@ -20,6 +20,7 @@ interface DirectoryPickerModalProps { onClose: () => void; onSelect: (path: string) => void; initialPath?: string; + foldersOnly?: boolean; // If true, only show directories (hide files) } function DirectoryPickerModal({ @@ -27,6 +28,7 @@ function DirectoryPickerModal({ onClose, onSelect, initialPath, + foldersOnly, }: DirectoryPickerModalProps) { const [inputPath, setInputPath] = useState(() => { if (!initialPath) return ""; @@ -173,6 +175,7 @@ function DirectoryPickerModal({ // Filter entries based on prefix (case-insensitive) const filteredEntries = displayDir?.entries.filter((entry) => { + if (foldersOnly && !entry.is_dir) return false; if (!filterPrefix) return true; return entry.name.toLowerCase().startsWith(filterPrefix.toLowerCase()); }) || []; diff --git a/ui/src/components/Message.tsx b/ui/src/components/Message.tsx index 08c359852b5d50db8192b5fe5a1955ac0ac7de45..c5038c0c7a98bd8cf24b6cffd617f61b308d53a3 100644 --- a/ui/src/components/Message.tsx +++ b/ui/src/components/Message.tsx @@ -28,7 +28,7 @@ interface ToolDisplay { interface MessageProps { message: MessageType; - onOpenDiffViewer?: (commit: string) => void; + onOpenDiffViewer?: (commit: string, cwd?: string) => void; onCommentTextChange?: (text: string) => void; } @@ -72,7 +72,7 @@ function GitInfoMessage({ onOpenDiffViewer, }: { message: MessageType; - onOpenDiffViewer?: (commit: string) => void; + onOpenDiffViewer?: (commit: string, cwd?: string) => void; }) { const [copied, setCopied] = useState(false); @@ -111,7 +111,7 @@ function GitInfoMessage({ const handleDiffClick = () => { if (commitHash && onOpenDiffViewer) { - onOpenDiffViewer(commitHash); + onOpenDiffViewer(commitHash, worktree || undefined); } }; diff --git a/ui/src/styles.css b/ui/src/styles.css index dfa8106c5949f17122e397e9ba30488dc6f47baf..ede174c8fd18d51e4563daf0f9c49d64651043a5 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -3502,6 +3502,26 @@ svg { font-size: 0.875rem; } +.diff-viewer-dir-btn { + width: 2rem; + height: 2rem; + border: none; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + border-radius: 0.25rem; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 0; +} + +.diff-viewer-dir-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + .diff-viewer-close { width: 2rem; height: 2rem;