mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
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:
@@ -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
|
||||
---
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
}));
|
||||
@@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user