refactor: remove old git-diff and file-explorer screens

- Delete standalone /git-diff and /file-explorer routes
- Update agent menu to open explorer sidebar instead of navigating
- Update file path clicks in chat to open sidebar
- Fix unnecessary re-renders in sidebar panes by using agents.has() instead of agents.get()

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Mohamed Boudra
2026-01-09 11:05:20 +07:00
parent 3ba778eea7
commit 6f278cb6a6
8 changed files with 21 additions and 2251 deletions

View File

@@ -1,7 +1,7 @@
---
id: 5f407d28
title: Remove old git diff and file browser screens - keep only file explorer
status: open
status: done
deps: []
created: 2026-01-08T16:21:35.362Z
---

View File

@@ -270,8 +270,6 @@ export default function RootLayout() {
<Stack.Screen name="agent/[serverId]/[agentId]" />
<Stack.Screen name="settings" />
<Stack.Screen name="audio-test" />
<Stack.Screen name="git-diff" />
<Stack.Screen name="file-explorer" />
</Stack>
</AppWithSidebar>
</HorizontalScrollProvider>

View File

@@ -192,7 +192,7 @@ function AgentScreenContent({
addImagesRef.current = addImages;
}, []);
const { isOpen: isExplorerOpen, toggle: toggleExplorer, open: openExplorer, close: closeExplorer } = useExplorerSidebarStore();
const { isOpen: isExplorerOpen, toggle: toggleExplorer, open: openExplorer, close: closeExplorer, setActiveTab: setExplorerTab } = useExplorerSidebarStore();
const {
translateX: explorerTranslateX,
backdropOpacity: explorerBackdropOpacity,
@@ -580,29 +580,15 @@ function AgentScreenContent({
const handleViewChanges = useCallback(() => {
handleCloseMenu();
if (resolvedAgentId) {
router.push({
pathname: "/git-diff",
params: {
agentId: resolvedAgentId,
serverId: serverId,
},
});
}
}, [resolvedAgentId, serverId, router, handleCloseMenu]);
setExplorerTab("changes");
openExplorer();
}, [handleCloseMenu, setExplorerTab, openExplorer]);
const handleBrowseFiles = useCallback(() => {
handleCloseMenu();
if (resolvedAgentId) {
router.push({
pathname: "/file-explorer",
params: {
agentId: resolvedAgentId,
serverId: serverId,
},
});
}
}, [handleCloseMenu, resolvedAgentId, serverId, router]);
setExplorerTab("files");
openExplorer();
}, [handleCloseMenu, setExplorerTab, openExplorer]);
const handleRefreshAgent = useCallback(() => {
if (!resolvedAgentId || !refreshAgent) {

File diff suppressed because it is too large Load Diff

View File

@@ -1,399 +0,0 @@
import { useEffect, useRef, useState } from "react";
import { View, Text, ScrollView, ActivityIndicator } from "react-native";
import { useLocalSearchParams } from "expo-router";
import { StyleSheet } from "react-native-unistyles";
import { BackHeader } from "@/components/headers/back-header";
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
import { formatConnectionStatus } from "@/utils/daemons";
import { useSessionStore } from "@/stores/session-store";
interface ParsedDiffFile {
path: string;
lines: Array<{
type: "add" | "remove" | "context" | "header";
content: string;
}>;
}
function parseDiff(diffText: string): ParsedDiffFile[] {
if (!diffText || diffText.trim().length === 0) {
return [];
}
const files: ParsedDiffFile[] = [];
const sections = diffText.split(/^diff --git /m).filter(Boolean);
for (const section of sections) {
const lines = section.split("\n");
const firstLine = lines[0];
const pathMatch = firstLine.match(/a\/(.*?) b\//);
const path = pathMatch ? pathMatch[1] : "unknown";
const parsedLines: ParsedDiffFile["lines"] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith("+++") || line.startsWith("---") || line.startsWith("@@") || line.startsWith("index ")) {
parsedLines.push({ type: "header", content: line });
} else if (line.startsWith("+")) {
parsedLines.push({ type: "add", content: line });
} else if (line.startsWith("-")) {
parsedLines.push({ type: "remove", content: line });
} else {
parsedLines.push({ type: "context", content: line });
}
}
files.push({ path, lines: parsedLines });
}
return files;
}
export default function GitDiffScreen() {
const { agentId, serverId } = useLocalSearchParams<{ agentId: string; serverId?: string }>();
const resolvedServerId = typeof serverId === "string" ? serverId : undefined;
const { connectionStates } = useDaemonConnections();
const session = useSessionStore((state) => resolvedServerId ? state.sessions[resolvedServerId] : undefined);
const connectionServerId = resolvedServerId ?? null;
const connection = connectionServerId ? connectionStates.get(connectionServerId) : null;
const serverLabel = connection?.daemon.label ?? connectionServerId ?? session?.serverId ?? "Current host";
const connectionStatus = connection?.status ?? "idle";
const connectionStatusLabel = formatConnectionStatus(connectionStatus);
const lastError = connection?.lastError ?? null;
if (!session) {
return (
<SessionUnavailableState
serverLabel={serverLabel}
connectionStatus={connectionStatus}
connectionStatusLabel={connectionStatusLabel}
lastError={lastError}
/>
);
}
return (
<GitDiffContent
serverId={session.serverId}
agentId={agentId}
/>
);
}
function GitDiffContent({
serverId,
agentId,
}: {
serverId: string;
agentId?: string;
}) {
const [isLoading, setIsLoading] = useState(true);
const hasRequestedRef = useRef<string | null>(null);
const agent = useSessionStore((state) =>
agentId && serverId ? state.sessions[serverId]?.agents?.get(agentId) : undefined
);
const diffText = useSessionStore((state) =>
agentId && serverId ? state.sessions[serverId]?.gitDiffs?.get(agentId) : undefined
);
const requestGitDiff = useSessionStore((state) =>
serverId ? state.sessions[serverId]?.methods?.requestGitDiff : undefined
);
useEffect(() => {
if (!agentId || !requestGitDiff) {
setIsLoading(false);
return;
}
// Prevent duplicate requests for the same agentId
if (hasRequestedRef.current === agentId) {
return;
}
hasRequestedRef.current = agentId;
setIsLoading(true);
requestGitDiff(agentId);
const timeout = setTimeout(() => {
setIsLoading(false);
}, 5000);
return () => clearTimeout(timeout);
}, [agentId, requestGitDiff]);
useEffect(() => {
if (diffText !== undefined) {
setIsLoading(false);
}
}, [diffText]);
if (!agent) {
return (
<View style={styles.container}>
<BackHeader title="Changes" />
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Agent not found</Text>
</View>
</View>
);
}
const isError = diffText?.startsWith("Error:");
const parsedFiles = isError || !diffText ? [] : parseDiff(diffText);
const hasChanges = parsedFiles.length > 0;
return (
<View style={styles.container}>
<BackHeader title="Changes" />
<ScrollView style={styles.scrollView} contentContainerStyle={styles.contentContainer}>
{isLoading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Loading changes...</Text>
</View>
) : isError ? (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{diffText}</Text>
</View>
) : !hasChanges ? (
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>No changes</Text>
</View>
) : (
parsedFiles.map((file, fileIndex) => (
<View key={fileIndex} style={styles.fileSection}>
<View style={styles.fileHeader}>
<Text style={styles.filePath}>{file.path}</Text>
</View>
<View style={styles.diffContent}>
<View style={styles.diffLinesContainer}>
{file.lines.map((line, lineIndex) => (
<View
key={lineIndex}
style={[
styles.diffLineContainer,
line.type === "add" && styles.addLineContainer,
line.type === "remove" && styles.removeLineContainer,
line.type === "header" && styles.headerLineContainer,
line.type === "context" && styles.contextLineContainer,
]}
>
<Text
style={[
styles.diffLineText,
line.type === "add" && styles.addLineText,
line.type === "remove" && styles.removeLineText,
line.type === "header" && styles.headerLineText,
line.type === "context" && styles.contextLineText,
]}
>
{line.content}
</Text>
</View>
))}
</View>
</View>
</View>
))
)}
</ScrollView>
</View>
);
}
function SessionUnavailableState({
serverLabel,
connectionStatus,
connectionStatusLabel,
lastError,
}: {
serverLabel: string;
connectionStatus: ConnectionStatus;
connectionStatusLabel: string;
lastError: string | null;
}) {
const isConnecting = connectionStatus === "connecting";
return (
<View style={styles.container}>
<BackHeader title="Changes" />
<View style={styles.sessionStateContainer}>
{isConnecting ? (
<>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Connecting to {serverLabel}...</Text>
<Text style={styles.statusText}>We will show changes once this session is online.</Text>
</>
) : (
<>
<Text style={styles.offlineTitle}>
{serverLabel} is currently {connectionStatusLabel.toLowerCase()}.
</Text>
<Text style={styles.offlineDescription}>
We'll reconnect automatically and show changes once the host is back online. No action needed.
</Text>
{lastError ? <Text style={styles.offlineDetails}>{lastError}</Text> : null}
</>
)}
</View>
</View>
);
}
const styles = StyleSheet.create((theme) => ({
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
sessionStateContainer: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingTop: theme.spacing[16],
paddingHorizontal: theme.spacing[6],
gap: theme.spacing[3],
},
scrollView: {
flex: 1,
},
contentContainer: {
padding: theme.spacing[4],
paddingBottom: theme.spacing[8],
},
loadingContainer: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingTop: theme.spacing[16],
gap: theme.spacing[4],
},
loadingText: {
fontSize: theme.fontSize.base,
color: theme.colors.mutedForeground,
},
errorContainer: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingTop: theme.spacing[16],
paddingHorizontal: theme.spacing[6],
},
errorText: {
fontSize: theme.fontSize.base,
color: theme.colors.destructive,
textAlign: "center",
},
statusText: {
marginTop: theme.spacing[3],
textAlign: "center",
fontSize: theme.fontSize.sm,
color: theme.colors.mutedForeground,
},
errorDetails: {
marginTop: theme.spacing[2],
textAlign: "center",
fontSize: theme.fontSize.xs,
color: theme.colors.mutedForeground,
},
offlineTitle: {
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.semibold,
color: theme.colors.foreground,
textAlign: "center",
},
offlineDescription: {
fontSize: theme.fontSize.sm,
color: theme.colors.mutedForeground,
textAlign: "center",
},
offlineDetails: {
fontSize: theme.fontSize.xs,
color: theme.colors.mutedForeground,
textAlign: "center",
},
emptyContainer: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingTop: theme.spacing[16],
},
emptyText: {
fontSize: theme.fontSize.lg,
color: theme.colors.mutedForeground,
},
fileSection: {
marginBottom: theme.spacing[6],
borderRadius: theme.borderRadius.lg,
overflow: "hidden",
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
width: "100%",
},
fileHeader: {
backgroundColor: theme.colors.muted,
padding: theme.spacing[3],
borderBottomWidth: theme.borderWidth[1],
borderBottomColor: theme.colors.border,
},
filePath: {
fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.semibold,
color: theme.colors.foreground,
fontFamily: "monospace",
},
diffContent: {
backgroundColor: theme.colors.card,
},
diffLinesContainer: {
width: "100%",
},
diffLineContainer: {
width: "100%",
paddingHorizontal: theme.spacing[3],
paddingVertical: theme.spacing[1],
flexDirection: "row",
alignItems: "flex-start",
},
diffLineText: {
fontSize: theme.fontSize.xs,
fontFamily: "monospace",
color: theme.colors.foreground,
flexShrink: 1,
flexWrap: "wrap",
width: "100%",
},
addLineContainer: {
backgroundColor: theme.colors.palette.green[900],
},
addLineText: {
color: theme.colors.palette.green[200],
},
removeLineContainer: {
backgroundColor: theme.colors.palette.red[900],
},
removeLineText: {
color: theme.colors.palette.red[200],
},
headerLineContainer: {
backgroundColor: theme.colors.muted,
},
headerLineText: {
color: theme.colors.mutedForeground,
},
contextLineContainer: {
backgroundColor: theme.colors.card,
},
contextLineText: {
color: theme.colors.mutedForeground,
},
}));

View File

@@ -17,7 +17,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import Animated, { FadeIn, FadeOut, cancelAnimation, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming } from "react-native-reanimated";
import { ChevronDown } from "lucide-react-native";
import { useRouter } from "expo-router";
import { useExplorerSidebarStore } from "@/stores/explorer-sidebar-store";
import {
AssistantMessage,
UserMessage,
@@ -72,7 +72,7 @@ export function AgentStreamView({
const isProgrammaticScrollRef = useRef(false);
const isNearBottomRef = useRef(true);
const isUserScrollingRef = useRef(false);
const router = useRouter();
const { open: openExplorer, setActiveTab: setExplorerTab } = useExplorerSidebarStore();
// Get serverId (fallback to agent's serverId if not provided)
const resolvedServerId = serverId ?? agent.serverId ?? "";
@@ -134,29 +134,16 @@ export function AgentStreamView({
requestFilePreviewOrInert(agentId, normalized.file);
}
router.push({
pathname: "/file-explorer",
params: {
agentId,
path: normalized.directory,
serverId: resolvedServerId,
...(normalized.file ? { file: normalized.file } : {}),
...(target.lineStart !== undefined
? { lineStart: String(target.lineStart) }
: {}),
...(target.lineEnd !== undefined
? { lineEnd: String(target.lineEnd) }
: {}),
},
});
setExplorerTab("files");
openExplorer();
},
[
agent.cwd,
agentId,
requestDirectoryListingOrInert,
requestFilePreviewOrInert,
resolvedServerId,
router,
setExplorerTab,
openExplorer,
]
);

View File

@@ -70,10 +70,10 @@ export function FileExplorerPane({
const { connectionStates } = useDaemonConnections();
const daemonProfile = connectionStates.get(serverId)?.daemon;
const agent = useSessionStore((state) =>
const agentExists = useSessionStore((state) =>
agentId && state.sessions[serverId]
? state.sessions[serverId]?.agents.get(agentId)
: undefined
? state.sessions[serverId]?.agents.has(agentId)
: false
);
const explorerState = useSessionStore((state) =>
@@ -763,7 +763,7 @@ export function FileExplorerPane({
setThumbnailLoadingMap({});
}, [activePath, viewMode]);
if (!agent) {
if (!agentExists) {
return (
<View style={styles.centerState}>
<Text style={styles.errorText}>Agent not found</Text>

View File

@@ -218,11 +218,11 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
agentId,
});
const agent = useSessionStore((state) =>
state.sessions[serverId]?.agents?.get(agentId)
const agentExists = useSessionStore((state) =>
state.sessions[serverId]?.agents?.has(agentId) ?? false
);
if (!agent) {
if (!agentExists) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Agent not found</Text>