Update files

This commit is contained in:
Mohamed Boudra
2026-01-24 22:12:07 +07:00
parent 0d9d95c340
commit 1f91de5cbc
3 changed files with 127 additions and 107 deletions
+120 -102
View File
@@ -14,7 +14,7 @@ import { ScrollView, type ScrollView as ScrollViewType } from "react-native-gest
import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { StyleSheet, useUnistyles } from "react-native-unistyles";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Linking from "expo-linking"; import * as Linking from "expo-linking";
import { ChevronDown, ChevronRight, GitBranch, MoreVertical, RotateCcw } from "lucide-react-native"; import { ChevronDown, ChevronRight, GitBranch, MoreVertical } from "lucide-react-native";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useSessionStore } from "@/stores/session-store"; import { useSessionStore } from "@/stores/session-store";
import { import {
@@ -32,7 +32,6 @@ import { getNowMs, isPerfLoggingEnabled, perfLog } from "@/utils/perf";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuHint,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
@@ -121,6 +120,7 @@ function HighlightedText({ tokens, lineType }: HighlightedTextProps) {
); );
} }
interface DiffFileSectionProps { interface DiffFileSectionProps {
file: ParsedDiffFile; file: ParsedDiffFile;
isExpanded: boolean; isExpanded: boolean;
@@ -824,67 +824,12 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
<Text style={styles.branchLabel} testID="changes-branch" numberOfLines={1}> <Text style={styles.branchLabel} testID="changes-branch" numberOfLines={1}>
{branchLabel} {branchLabel}
</Text> </Text>
{isStatusFetching && ( {isStatusFetching && (
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} /> <ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
)} )}
</View> </View>
{isGit ? ( {isGit ? (
<DropdownMenu> <View style={styles.headerRight}>
<DropdownMenuTrigger
testID="changes-view-selector"
style={styles.viewSelector}
accessibilityRole="button"
accessibilityLabel="Change diff view"
>
<Text style={styles.viewSelectorText}>
{diffMode === "uncommitted" ? "Working" : "Base"}
</Text>
<ChevronDown size={14} color={theme.colors.foregroundMuted} />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" width={180} testID="changes-view-menu">
<DropdownMenuItem
testID="changes-mode-uncommitted"
selected={diffMode === "uncommitted"}
onSelect={() => setDiffMode("uncommitted")}
>
Working
</DropdownMenuItem>
<DropdownMenuItem
testID="changes-mode-base"
selected={diffMode === "base"}
onSelect={() => setDiffMode("base")}
>
Base
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : null}
</View>
{isGit ? (
<View style={styles.toolbarRow} testID="changes-toolbar">
<View style={styles.toolbarLeft}>
{canShowCommit ? (
<Pressable
testID="changes-action-commit"
style={[
styles.secondaryActionButton,
commitDisabled && styles.secondaryActionButtonDisabled,
]}
onPress={() => commitMutation.mutate()}
disabled={commitDisabled}
>
{commitMutation.isPending ? (
<ActivityIndicator size="small" color={theme.colors.foreground} />
) : (
<Text style={styles.secondaryActionText}>Commit…</Text>
)}
</Pressable>
) : (
<View style={styles.toolbarLeftSpacer} />
)}
</View>
<View style={styles.toolbarRight}>
{canShowShip ? ( {canShowShip ? (
<View style={styles.shipSplitButton}> <View style={styles.shipSplitButton}>
<Pressable <Pressable
@@ -917,14 +862,9 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
{shipPrimaryPending ? ( {shipPrimaryPending ? (
<ActivityIndicator size="small" color={theme.colors.foreground} /> <ActivityIndicator size="small" color={theme.colors.foreground} />
) : ( ) : (
<> <Text style={styles.shipPrimaryText}>
<Text style={styles.shipPrimaryText}> {resolvedShipPrimary === "merge" ? "Merge branch" : prActionLabel}
{resolvedShipPrimary === "merge" ? "Merge" : prActionLabel} </Text>
</Text>
<Text style={styles.shipSecondaryText} numberOfLines={1}>
into {baseRefLabel}
</Text>
</>
)} )}
</Pressable> </Pressable>
<DropdownMenu> <DropdownMenu>
@@ -940,19 +880,15 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
<DropdownMenuItem <DropdownMenuItem
testID="changes-ship-merge" testID="changes-ship-merge"
disabled={mergeDisabled} disabled={mergeDisabled}
description={mergeDisabled && hasUncommittedChanges ? "Requires clean working tree" : undefined}
onSelect={() => { onSelect={() => {
void persistShipDefault("merge"); void persistShipDefault("merge");
mergeMutation.mutate(); mergeMutation.mutate();
}} }}
> >
Merge into {baseRefLabel} Merge branch
</DropdownMenuItem> </DropdownMenuItem>
{mergeDisabled && hasUncommittedChanges ? (
<DropdownMenuHint>Requires a clean working tree.</DropdownMenuHint>
) : null}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
testID={hasPullRequest ? "changes-ship-open-pr" : "changes-ship-create-pr"} testID={hasPullRequest ? "changes-ship-open-pr" : "changes-ship-create-pr"}
disabled={prDisabled} disabled={prDisabled}
@@ -965,7 +901,7 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
prMutation.mutate(); prMutation.mutate();
}} }}
> >
{hasPullRequest ? "Open PR" : `Create PR into ${baseRefLabel}`} {hasPullRequest ? "Open PR" : "Create PR"}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -982,35 +918,56 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
<MoreVertical size={16} color={theme.colors.foregroundMuted} /> <MoreVertical size={16} color={theme.colors.foregroundMuted} />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" width={220} testID="changes-overflow-content"> <DropdownMenuContent align="end" width={220} testID="changes-overflow-content">
<DropdownMenuItem {canShowShip ? (
testID="changes-menu-refresh" <>
leading={<RotateCcw size={16} color={theme.colors.foreground} />} {resolvedShipPrimary === "merge" ? (
onSelect={handleRefresh} <DropdownMenuItem
> testID={hasPullRequest ? "changes-menu-open-pr" : "changes-menu-create-pr"}
Refresh disabled={prDisabled}
</DropdownMenuItem> onSelect={() => {
<DropdownMenuSeparator /> void persistShipDefault("pr");
if (hasPullRequest && prStatus?.url) {
void Linking.openURL(prStatus.url);
return;
}
prMutation.mutate();
}}
>
{hasPullRequest ? "Open PR" : "Create PR"}
</DropdownMenuItem>
) : (
<DropdownMenuItem
testID="changes-menu-merge"
disabled={mergeDisabled}
description={mergeDisabled && hasUncommittedChanges ? "Requires clean working tree" : undefined}
onSelect={() => {
void persistShipDefault("merge");
mergeMutation.mutate();
}}
>
Merge branch
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
</>
) : null}
<DropdownMenuItem <DropdownMenuItem
testID="changes-menu-merge-from-base" testID="changes-menu-merge-from-base"
disabled={mergeFromBaseDisabled} disabled={mergeFromBaseDisabled}
description={mergeFromBaseDisabled && hasUncommittedChanges ? "Requires clean working tree" : undefined}
onSelect={() => mergeFromBaseMutation.mutate()} onSelect={() => mergeFromBaseMutation.mutate()}
> >
Merge from {baseRefLabel} Merge from {baseRefLabel}
</DropdownMenuItem> </DropdownMenuItem>
{mergeFromBaseDisabled && hasUncommittedChanges ? ( <DropdownMenuSeparator />
<DropdownMenuHint>Requires a clean working tree.</DropdownMenuHint>
) : null}
<DropdownMenuItem <DropdownMenuItem
testID="changes-menu-push" testID="changes-menu-push"
disabled={pushDisabled} disabled={pushDisabled}
description={pushDisabled && !(gitStatus?.hasRemote ?? false) ? "No remote configured" : undefined}
onSelect={() => pushMutation.mutate()} onSelect={() => pushMutation.mutate()}
> >
Push branch Push branch
</DropdownMenuItem> </DropdownMenuItem>
{pushDisabled && !(gitStatus?.hasRemote ?? false) ? (
<DropdownMenuHint>Remote 'origin' is not configured.</DropdownMenuHint>
) : null}
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
testID="changes-menu-archive" testID="changes-menu-archive"
@@ -1023,6 +980,63 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</View> </View>
) : null}
</View>
{isGit ? (
<View style={styles.toolbarRow} testID="changes-toolbar">
<View style={styles.toolbarLeft}>
{canShowCommit ? (
<Pressable
testID="changes-action-commit"
style={[
styles.secondaryActionButton,
commitDisabled && styles.secondaryActionButtonDisabled,
]}
onPress={() => commitMutation.mutate()}
disabled={commitDisabled}
>
{commitMutation.isPending ? (
<ActivityIndicator size={12} color={theme.colors.foreground} style={styles.buttonSpinner} />
) : (
<Text style={styles.secondaryActionText}>Commit</Text>
)}
</Pressable>
) : (
<View style={styles.toolbarLeftSpacer} />
)}
</View>
<View style={styles.toolbarRight}>
<DropdownMenu>
<DropdownMenuTrigger
testID="changes-view-selector"
style={styles.viewSelector}
accessibilityRole="button"
accessibilityLabel="Change diff view"
>
<Text style={styles.viewSelectorText}>
{diffMode === "uncommitted" ? "Working" : "Base"}
</Text>
<ChevronDown size={14} color={theme.colors.foregroundMuted} />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" width={180} testID="changes-view-menu">
<DropdownMenuItem
testID="changes-mode-uncommitted"
selected={diffMode === "uncommitted"}
onSelect={() => setDiffMode("uncommitted")}
>
Working
</DropdownMenuItem>
<DropdownMenuItem
testID="changes-mode-base"
selected={diffMode === "base"}
onSelect={() => setDiffMode("base")}
>
Base
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</View>
</View> </View>
) : null} ) : null}
@@ -1076,6 +1090,12 @@ const styles = StyleSheet.create((theme) => ({
flex: 1, flex: 1,
minWidth: 0, minWidth: 0,
}, },
headerRight: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[1],
flexShrink: 0,
},
branchLabel: { branchLabel: {
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.sm,
color: theme.colors.foreground, color: theme.colors.foreground,
@@ -1104,7 +1124,9 @@ const styles = StyleSheet.create((theme) => ({
gap: theme.spacing[2], gap: theme.spacing[2],
paddingHorizontal: theme.spacing[3], paddingHorizontal: theme.spacing[3],
paddingTop: theme.spacing[2], paddingTop: theme.spacing[2],
paddingBottom: theme.spacing[1], paddingBottom: theme.spacing[3],
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
}, },
toolbarLeft: { toolbarLeft: {
flex: 1, flex: 1,
@@ -1136,6 +1158,9 @@ const styles = StyleSheet.create((theme) => ({
color: theme.colors.foreground, color: theme.colors.foreground,
fontWeight: theme.fontWeight.medium, fontWeight: theme.fontWeight.medium,
}, },
buttonSpinner: {
height: theme.fontSize.xs * 1.4,
},
shipSplitButton: { shipSplitButton: {
flexDirection: "row", flexDirection: "row",
alignItems: "stretch", alignItems: "stretch",
@@ -1148,7 +1173,7 @@ const styles = StyleSheet.create((theme) => ({
shipPrimaryButton: { shipPrimaryButton: {
paddingHorizontal: theme.spacing[3], paddingHorizontal: theme.spacing[3],
paddingVertical: theme.spacing[2], paddingVertical: theme.spacing[2],
minWidth: 140, justifyContent: "center",
}, },
shipPrimaryButtonDisabled: { shipPrimaryButtonDisabled: {
opacity: 0.6, opacity: 0.6,
@@ -1158,11 +1183,6 @@ const styles = StyleSheet.create((theme) => ({
color: theme.colors.foreground, color: theme.colors.foreground,
fontWeight: theme.fontWeight.medium, fontWeight: theme.fontWeight.medium,
}, },
shipSecondaryText: {
marginTop: 2,
fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted,
},
shipCaretButton: { shipCaretButton: {
width: 36, width: 36,
alignItems: "center", alignItems: "center",
@@ -1373,16 +1393,14 @@ const styles = StyleSheet.create((theme) => ({
color: theme.colors.palette.red[500], color: theme.colors.palette.red[500],
}, },
additions: { additions: {
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.normal, fontWeight: theme.fontWeight.normal,
color: theme.colors.palette.green[400], color: theme.colors.palette.green[400],
fontFamily: Fonts.mono,
}, },
deletions: { deletions: {
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.normal, fontWeight: theme.fontWeight.normal,
color: theme.colors.palette.red[500], color: theme.colors.palette.red[500],
fontFamily: Fonts.mono,
}, },
diffContent: { diffContent: {
borderTopWidth: theme.borderWidth[1], borderTopWidth: theme.borderWidth[1],
+4 -2
View File
@@ -485,11 +485,13 @@ export async function getCheckoutDiff(
throw new Error(`Base ref mismatch: expected ${baseRef}, got ${compare.baseRef}`); throw new Error(`Base ref mismatch: expected ${baseRef}, got ${compare.baseRef}`);
} else { } else {
const normalizedBaseRef = normalizeLocalBranchRefName(baseRef); const normalizedBaseRef = normalizeLocalBranchRefName(baseRef);
const { stdout } = await execAsync(`git diff ${normalizedBaseRef}...HEAD`, { // Diff base ref against working tree (includes uncommitted changes)
const { stdout: trackedDiff } = await execAsync(`git diff ${normalizedBaseRef}`, {
cwd, cwd,
env: READ_ONLY_GIT_ENV, env: READ_ONLY_GIT_ENV,
}); });
diff = stdout; const untrackedDiff = await getUntrackedDiff(cwd);
diff = trackedDiff + untrackedDiff;
} }
} }
+3 -3
View File
@@ -10,8 +10,8 @@ DAEMON_PORT=$(get-port 6767 6768 6769 6770 6771 6772 6773)
METRO_PORT=$(get-port 8081 8082 8083 8084 8085 8086 8087) METRO_PORT=$(get-port 8081 8082 8083 8084 8085 8086 8087)
# Use a temporary PASEO_HOME to avoid conflicts between dev instances # Use a temporary PASEO_HOME to avoid conflicts between dev instances
export PASEO_HOME=$(mktemp -d "${TMPDIR:-/tmp}/paseo-dev.XXXXXX") # export PASEO_HOME=$(mktemp -d "${TMPDIR:-/tmp}/paseo-dev.XXXXXX")
trap "rm -rf '$PASEO_HOME'" EXIT # trap "rm -rf '$PASEO_HOME'" EXIT
# Build CORS origins for this Expo instance # Build CORS origins for this Expo instance
CORS_ORIGINS="http://localhost:${METRO_PORT},http://127.0.0.1:${METRO_PORT}" CORS_ORIGINS="http://localhost:${METRO_PORT},http://127.0.0.1:${METRO_PORT}"
@@ -24,7 +24,7 @@ echo " Paseo Dev"
echo "══════════════════════════════════════════════════════" echo "══════════════════════════════════════════════════════"
echo " Daemon: http://localhost:${DAEMON_PORT}" echo " Daemon: http://localhost:${DAEMON_PORT}"
echo " Metro: http://localhost:${METRO_PORT}" echo " Metro: http://localhost:${METRO_PORT}"
echo " Home: ${PASEO_HOME}" # echo " Home: ${PASEO_HOME}"
echo "══════════════════════════════════════════════════════" echo "══════════════════════════════════════════════════════"
# Export for child processes (overrides .env values) # Export for child processes (overrides .env values)