feat(app): add maestro tests, download progress, and task hierarchy
Add Maestro E2E test flows for app launch and sidebar interactions. Implement download progress tracking with speed/ETA display in file explorer. Extend task CLI with parent-child hierarchy, body content from stdin, and ancestor context in task show. Fix explorer sidebar gesture to allow horizontal scrolling while preserving close swipe. Replace hardcoded monospace font with Fonts.mono constant. Fix RefreshControl to only show spinner on manual pull-to-refresh.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { View, Text, Pressable } from 'react-native';
|
||||
import { StyleSheet } from 'react-native-unistyles';
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import type { AgentActivity, GroupedTextMessage, MergedToolCall, SessionUpdate } from '@/types/agent-activity';
|
||||
|
||||
interface AgentActivityItemProps {
|
||||
@@ -346,7 +347,7 @@ const stylesheet = StyleSheet.create((theme) => ({
|
||||
code: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: Fonts.mono,
|
||||
backgroundColor: theme.colors.muted,
|
||||
padding: theme.spacing[2],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import Markdown from "react-native-markdown-display";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import Animated, { FadeIn, FadeOut, cancelAnimation, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming } from "react-native-reanimated";
|
||||
import { ChevronDown } from "lucide-react-native";
|
||||
import { useExplorerSidebarStore } from "@/stores/explorer-sidebar-store";
|
||||
@@ -1160,7 +1161,7 @@ const permissionStyles = StyleSheet.create((theme) => ({
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
metadataValue: {
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
diffSection: {
|
||||
@@ -1174,7 +1175,7 @@ const permissionStyles = StyleSheet.create((theme) => ({
|
||||
borderWidth: theme.borderWidth[1],
|
||||
},
|
||||
fileBadgeText: {
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
diffWrapper: {
|
||||
@@ -1183,7 +1184,7 @@ const permissionStyles = StyleSheet.create((theme) => ({
|
||||
overflow: "hidden",
|
||||
},
|
||||
rawContentText: {
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.sm,
|
||||
lineHeight: 20,
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { useEffect } from "react";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
|
||||
export interface Artifact {
|
||||
id: string;
|
||||
@@ -120,7 +121,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
codeText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
},
|
||||
metadataContainer: {
|
||||
backgroundColor: theme.colors.card,
|
||||
@@ -148,7 +149,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
flex: 1,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import type { DiffLine } from "@/utils/tool-call-parsers";
|
||||
|
||||
interface DiffViewerProps {
|
||||
@@ -80,7 +81,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
paddingVertical: theme.spacing[1],
|
||||
},
|
||||
lineText: {
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
|
||||
@@ -66,8 +66,10 @@ export function ExplorerSidebar({ serverId, agentId }: ExplorerSidebarProps) {
|
||||
Gesture.Pan()
|
||||
.withRef(closeGestureRef)
|
||||
.enabled(isMobile && isOpen)
|
||||
// Only activate after 15px horizontal movement (creates deadzone for taps)
|
||||
.activeOffsetX([-15, 15])
|
||||
// Only activate on rightward swipe (positive X), fail on leftward or vertical
|
||||
// This allows ScrollViews using waitFor to scroll left normally
|
||||
.activeOffsetX(15)
|
||||
.failOffsetX(-10)
|
||||
.failOffsetY([-10, 10])
|
||||
.onStart(() => {
|
||||
isGesturing.value = true;
|
||||
|
||||
@@ -18,8 +18,10 @@ import {
|
||||
useWindowDimensions,
|
||||
} from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import { File as FSFile, Paths } from "expo-file-system";
|
||||
import * as LegacyFileSystem from "expo-file-system/legacy";
|
||||
import * as Sharing from "expo-sharing";
|
||||
import {
|
||||
BottomSheetModal,
|
||||
@@ -169,7 +171,16 @@ export function FileExplorerPane({
|
||||
status: "downloading" | "complete" | "error";
|
||||
fileName: string;
|
||||
message?: string;
|
||||
progress?: {
|
||||
percent: number;
|
||||
bytesWritten: number;
|
||||
totalBytes: number;
|
||||
speed: number;
|
||||
eta: number;
|
||||
};
|
||||
} | null>(null);
|
||||
const downloadStartTimeRef = useRef<number>(0);
|
||||
const lastProgressRef = useRef<{ bytes: number; time: number } | null>(null);
|
||||
const downloadToastTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const agentIdRef = useRef(agentId);
|
||||
const viewModeRef = useRef(viewMode);
|
||||
@@ -467,21 +478,59 @@ export function FileExplorerPane({
|
||||
return;
|
||||
}
|
||||
|
||||
downloadStartTimeRef.current = Date.now();
|
||||
lastProgressRef.current = null;
|
||||
showDownloadToast({ status: "downloading", fileName: displayName });
|
||||
|
||||
const targetFile = resolveDownloadTargetFile(fileName);
|
||||
const downloadedFile = await FSFile.downloadFileAsync(
|
||||
const downloadResumable = LegacyFileSystem.createDownloadResumable(
|
||||
downloadUrl,
|
||||
targetFile,
|
||||
targetFile.uri,
|
||||
downloadTarget.authHeader
|
||||
? { headers: { Authorization: downloadTarget.authHeader } }
|
||||
: undefined
|
||||
: undefined,
|
||||
(data) => {
|
||||
const now = Date.now();
|
||||
const { totalBytesWritten, totalBytesExpectedToWrite } = data;
|
||||
|
||||
if (totalBytesExpectedToWrite <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const percent = totalBytesWritten / totalBytesExpectedToWrite;
|
||||
const elapsed = (now - downloadStartTimeRef.current) / 1000;
|
||||
const speed = elapsed > 0 ? totalBytesWritten / elapsed : 0;
|
||||
const remaining = totalBytesExpectedToWrite - totalBytesWritten;
|
||||
const eta = speed > 0 ? remaining / speed : 0;
|
||||
|
||||
lastProgressRef.current = { bytes: totalBytesWritten, time: now };
|
||||
|
||||
setDownloadToast((prev) =>
|
||||
prev?.status === "downloading"
|
||||
? {
|
||||
...prev,
|
||||
progress: {
|
||||
percent,
|
||||
bytesWritten: totalBytesWritten,
|
||||
totalBytes: totalBytesExpectedToWrite,
|
||||
speed,
|
||||
eta,
|
||||
},
|
||||
}
|
||||
: prev
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const result = await downloadResumable.downloadAsync();
|
||||
if (!result) {
|
||||
throw new Error("Download was cancelled.");
|
||||
}
|
||||
|
||||
showDownloadToast({ status: "complete", fileName: displayName });
|
||||
|
||||
if (await Sharing.isAvailableAsync()) {
|
||||
await Sharing.shareAsync(downloadedFile.uri, {
|
||||
await Sharing.shareAsync(result.uri, {
|
||||
mimeType: tokenResponse.mimeType ?? undefined,
|
||||
dialogTitle: fileName ? `Share ${fileName}` : "Share file",
|
||||
});
|
||||
@@ -947,11 +996,23 @@ export function FileExplorerPane({
|
||||
</Text>
|
||||
<Text style={styles.downloadToastStatus}>
|
||||
{downloadToast.status === "downloading"
|
||||
? "Downloading..."
|
||||
? downloadToast.progress
|
||||
? `${Math.round(downloadToast.progress.percent * 100)}% · ${formatSpeed(downloadToast.progress.speed)} · ${formatEta(downloadToast.progress.eta)}`
|
||||
: "Starting..."
|
||||
: downloadToast.status === "complete"
|
||||
? "Download complete"
|
||||
: downloadToast.message ?? "Download failed"}
|
||||
</Text>
|
||||
{downloadToast.status === "downloading" && downloadToast.progress && (
|
||||
<View style={styles.downloadProgressBar}>
|
||||
<View
|
||||
style={[
|
||||
styles.downloadProgressFill,
|
||||
{ width: `${Math.round(downloadToast.progress.percent * 100)}%` },
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{downloadToast.status !== "downloading" && (
|
||||
<Pressable
|
||||
@@ -983,6 +1044,28 @@ function formatFileSize({ size }: { size: number }): string {
|
||||
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function formatSpeed(bytesPerSecond: number): string {
|
||||
if (bytesPerSecond < 1024) {
|
||||
return `${Math.round(bytesPerSecond)} B/s`;
|
||||
}
|
||||
if (bytesPerSecond < 1024 * 1024) {
|
||||
return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
|
||||
}
|
||||
return `${(bytesPerSecond / (1024 * 1024)).toFixed(1)} MB/s`;
|
||||
}
|
||||
|
||||
function formatEta(seconds: number): string {
|
||||
if (seconds < 1) {
|
||||
return "< 1s";
|
||||
}
|
||||
if (seconds < 60) {
|
||||
return `${Math.round(seconds)}s`;
|
||||
}
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.round(seconds % 60);
|
||||
return `${mins}m ${secs}s`;
|
||||
}
|
||||
|
||||
function formatModifiedTime({ value }: { value: string }): string {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
@@ -1241,7 +1324,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flex: 1,
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.mutedForeground,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
},
|
||||
backButton: {
|
||||
padding: theme.spacing[1],
|
||||
@@ -1379,7 +1462,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
codeText: {
|
||||
color: theme.colors.foreground,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.sm,
|
||||
flexShrink: 0,
|
||||
},
|
||||
@@ -1510,6 +1593,18 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
downloadProgressBar: {
|
||||
height: 3,
|
||||
backgroundColor: theme.colors.muted,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
marginTop: theme.spacing[1],
|
||||
overflow: "hidden",
|
||||
},
|
||||
downloadProgressFill: {
|
||||
height: "100%",
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
},
|
||||
downloadToastDismiss: {
|
||||
padding: theme.spacing[1],
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useEffect, useId } from "react";
|
||||
import { useState, useCallback, useEffect, useId, useRef } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
type NativeSyntheticEvent,
|
||||
type NativeScrollEvent,
|
||||
} from "react-native";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { ScrollView, type ScrollView as ScrollViewType } from "react-native-gesture-handler";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { ChevronRight } from "lucide-react-native";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
type HighlightToken,
|
||||
} from "@/hooks/use-highlighted-diff-query";
|
||||
import { useHorizontalScrollOptional } from "@/contexts/horizontal-scroll-context";
|
||||
import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
|
||||
type HighlightStyle = NonNullable<HighlightToken["style"]>;
|
||||
|
||||
@@ -120,8 +122,19 @@ function DiffFileSection({ file, defaultExpanded = true, testID }: DiffFileSecti
|
||||
const { theme } = useUnistyles();
|
||||
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
||||
const [scrollViewWidth, setScrollViewWidth] = useState(0);
|
||||
const [isAtLeftEdge, setIsAtLeftEdge] = useState(true);
|
||||
const horizontalScroll = useHorizontalScrollOptional();
|
||||
const scrollId = useId();
|
||||
const scrollViewRef = useRef<ScrollViewType>(null);
|
||||
|
||||
// Get the close gesture ref from animation context (may not be available outside sidebar)
|
||||
let closeGestureRef: React.MutableRefObject<any> | undefined;
|
||||
try {
|
||||
const animation = useExplorerSidebarAnimation();
|
||||
closeGestureRef = animation.closeGestureRef;
|
||||
} catch {
|
||||
// Not inside ExplorerSidebarAnimationProvider, which is fine
|
||||
}
|
||||
|
||||
const toggleExpanded = useCallback(() => {
|
||||
setIsExpanded((prev) => !prev);
|
||||
@@ -139,9 +152,12 @@ function DiffFileSection({ file, defaultExpanded = true, testID }: DiffFileSecti
|
||||
|
||||
const handleScroll = useCallback(
|
||||
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (!horizontalScroll) return;
|
||||
const offsetX = event.nativeEvent.contentOffset.x;
|
||||
horizontalScroll.registerScrollOffset(scrollId, offsetX);
|
||||
// Track if we're at the left edge (with small threshold for float precision)
|
||||
setIsAtLeftEdge(offsetX <= 1);
|
||||
if (horizontalScroll) {
|
||||
horizontalScroll.registerScrollOffset(scrollId, offsetX);
|
||||
}
|
||||
},
|
||||
[horizontalScroll, scrollId]
|
||||
);
|
||||
@@ -183,6 +199,7 @@ function DiffFileSection({ file, defaultExpanded = true, testID }: DiffFileSecti
|
||||
</Pressable>
|
||||
{isExpanded && (
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator
|
||||
@@ -192,6 +209,11 @@ function DiffFileSection({ file, defaultExpanded = true, testID }: DiffFileSecti
|
||||
onScroll={handleScroll}
|
||||
scrollEventThrottle={16}
|
||||
onLayout={(e) => setScrollViewWidth(e.nativeEvent.layout.width)}
|
||||
// When at left edge, wait for close gesture to fail before scrolling.
|
||||
// The close gesture fails quickly on leftward swipes (failOffsetX=-10),
|
||||
// so scrolling left works normally. On rightward swipes, close gesture
|
||||
// activates and closes the sidebar.
|
||||
waitFor={isAtLeftEdge && closeGestureRef?.current ? closeGestureRef : undefined}
|
||||
>
|
||||
<View style={[styles.linesContainer, scrollViewWidth > 0 && { minWidth: scrollViewWidth }]}>
|
||||
{file.hunks.map((hunk, hunkIndex) =>
|
||||
@@ -217,6 +239,20 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
||||
serverId,
|
||||
agentId,
|
||||
});
|
||||
// Track user-initiated refresh to avoid iOS RefreshControl animation on background fetches
|
||||
const [isManualRefresh, setIsManualRefresh] = useState(false);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
setIsManualRefresh(true);
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
// Reset manual refresh flag when fetch completes
|
||||
useEffect(() => {
|
||||
if (!isFetching && isManualRefresh) {
|
||||
setIsManualRefresh(false);
|
||||
}
|
||||
}, [isFetching, isManualRefresh]);
|
||||
|
||||
const agentExists = useSessionStore((state) =>
|
||||
state.sessions[serverId]?.agents?.has(agentId) ?? false
|
||||
@@ -239,8 +275,8 @@ export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) {
|
||||
testID="git-diff-scroll"
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isFetching && !isLoading}
|
||||
onRefresh={refresh}
|
||||
refreshing={isManualRefresh && isFetching}
|
||||
onRefresh={handleRefresh}
|
||||
tintColor={theme.colors.mutedForeground}
|
||||
colors={[theme.colors.primary]}
|
||||
/>
|
||||
@@ -354,7 +390,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.foreground,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
flex: 1,
|
||||
},
|
||||
newBadge: {
|
||||
@@ -373,13 +409,13 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.palette.green[400],
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
},
|
||||
deletions: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.palette.red[500],
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
},
|
||||
diffContent: {
|
||||
borderTopWidth: theme.borderWidth[1],
|
||||
@@ -398,7 +434,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
diffLineText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
addLineContainer: {
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { baseColors, theme } from "@/styles/theme";
|
||||
import { createMarkdownStyles, createCompactMarkdownStyles } from "@/styles/markdown-styles";
|
||||
import { Colors } from "@/constants/theme";
|
||||
import { Colors, Fonts } from "@/constants/theme";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import type { TodoEntry, ThoughtStatus } from "@/types/stream";
|
||||
import type { CommandDetails, EditEntry, ReadEntry } from "@/utils/tool-call-parsers";
|
||||
@@ -162,7 +162,7 @@ export const assistantMessageStylesheet = StyleSheet.create((theme) => ({
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
paddingVertical: 2,
|
||||
borderRadius: theme.borderRadius.sm,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: 13,
|
||||
},
|
||||
// Used in custom markdownRules for path chip styling
|
||||
@@ -176,7 +176,7 @@ export const assistantMessageStylesheet = StyleSheet.create((theme) => ({
|
||||
},
|
||||
pathChipText: {
|
||||
color: theme.colors.secondaryForeground,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: 13,
|
||||
},
|
||||
}));
|
||||
@@ -554,7 +554,7 @@ const activityLogStylesheet = StyleSheet.create((theme) => ({
|
||||
metadataText: {
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
lineHeight: 16,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useCallback, useMemo, useState, useEffect } from "react";
|
||||
import { View, Pressable, Text, Platform } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Animated, {
|
||||
@@ -37,6 +37,21 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
isGesturing,
|
||||
} = useSidebarAnimation();
|
||||
|
||||
// Track user-initiated refresh to avoid showing spinner on background revalidation
|
||||
const [isManualRefresh, setIsManualRefresh] = useState(false);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
setIsManualRefresh(true);
|
||||
refreshAll();
|
||||
}, [refreshAll]);
|
||||
|
||||
// Reset manual refresh flag when revalidation completes
|
||||
useEffect(() => {
|
||||
if (!isRevalidating && isManualRefresh) {
|
||||
setIsManualRefresh(false);
|
||||
}
|
||||
}, [isRevalidating, isManualRefresh]);
|
||||
|
||||
const isMobile =
|
||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||
|
||||
@@ -198,8 +213,8 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
{/* Middle: scrollable agent list */}
|
||||
<AgentList
|
||||
agents={limitedAgents}
|
||||
isRefreshing={isRevalidating}
|
||||
onRefresh={refreshAll}
|
||||
isRefreshing={isManualRefresh && isRevalidating}
|
||||
onRefresh={handleRefresh}
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={handleAgentSelectMobile}
|
||||
listFooterComponent={viewMoreButton}
|
||||
@@ -246,8 +261,8 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
{/* Middle: scrollable agent list */}
|
||||
<AgentList
|
||||
agents={limitedAgents}
|
||||
isRefreshing={isRevalidating}
|
||||
onRefresh={refreshAll}
|
||||
isRefreshing={isManualRefresh && isRevalidating}
|
||||
onRefresh={handleRefresh}
|
||||
selectedAgentId={selectedAgentId}
|
||||
listFooterComponent={viewMoreButton}
|
||||
/>
|
||||
|
||||
@@ -8,6 +8,7 @@ import React, {
|
||||
} from "react";
|
||||
import { View, Text, Pressable, ScrollView } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import {
|
||||
BottomSheetModal,
|
||||
BottomSheetScrollView,
|
||||
@@ -765,7 +766,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
fileBadgeText: {
|
||||
color: theme.colors.foreground,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
diffContainer: {
|
||||
@@ -786,7 +787,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
padding: theme.spacing[2],
|
||||
},
|
||||
scrollText: {
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foreground,
|
||||
lineHeight: 18,
|
||||
@@ -824,7 +825,7 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
metaValue: {
|
||||
color: theme.colors.foreground,
|
||||
fontFamily: "monospace",
|
||||
fontFamily: Fonts.mono,
|
||||
fontSize: theme.fontSize.xs,
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user