Add loading indicator to home screen while agents are fetching

useAggregatedAgents now returns { groups, isLoading } where isLoading is true
while the daemon registry loads or while hosts are connecting without a session.
HomeScreen shows an ActivityIndicator during loading, then either the agent
list or empty state. Offline hosts don't block loading—only connecting hosts do.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-11-26 20:07:33 +01:00
co-authored by Claude
parent 69881d3862
commit 0c571f6f43
3 changed files with 79 additions and 14 deletions
+14 -4
View File
@@ -1,9 +1,9 @@
import { View } from "react-native";
import { View, ActivityIndicator } from "react-native";
import { useState, useCallback, useMemo, useRef, useEffect } from "react";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
import ReanimatedAnimated, { useAnimatedStyle } from "react-native-reanimated";
import { StyleSheet } from "react-native-unistyles";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { HomeHeader } from "@/components/headers/home-header";
import { EmptyState } from "@/components/empty-state";
import { AgentList } from "@/components/agent-list";
@@ -13,7 +13,8 @@ import { useLocalSearchParams } from "expo-router";
export default function HomeScreen() {
const insets = useSafeAreaInsets();
const aggregatedAgents = useAggregatedAgents();
const { theme } = useUnistyles();
const { groups: aggregatedAgents, isLoading } = useAggregatedAgents();
const [showCreateModal, setShowCreateModal] = useState(false);
const [showImportModal, setShowImportModal] = useState(false);
const [createModalMounted, setCreateModalMounted] = useState(false);
@@ -106,7 +107,11 @@ export default function HomeScreen() {
{/* Content Area with Keyboard Animation */}
<ReanimatedAnimated.View style={[styles.content, animatedKeyboardStyle]}>
{hasAgents ? (
{isLoading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={theme.colors.mutedForeground} />
</View>
) : hasAgents ? (
<AgentList agentGroups={aggregatedAgents} />
) : (
<EmptyState
@@ -142,4 +147,9 @@ const styles = StyleSheet.create((theme) => ({
content: {
flex: 1,
},
loadingContainer: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
}));
@@ -9,6 +9,11 @@ export interface AggregatedAgentGroup {
agents: Agent[];
}
export interface AggregatedAgentsResult {
groups: AggregatedAgentGroup[];
isLoading: boolean;
}
const sortAgents = (agents: Agent[]): Agent[] => {
return [...agents].sort((left, right) => {
const leftRunning = left.status === "running";
@@ -25,8 +30,8 @@ const sortAgents = (agents: Agent[]): Agent[] => {
});
};
export function useAggregatedAgents(): AggregatedAgentGroup[] {
const { connectionStates } = useDaemonConnections();
export function useAggregatedAgents(): AggregatedAgentsResult {
const { connectionStates, isLoading: registryLoading } = useDaemonConnections();
const sessionDirectory = useSessionDirectory();
return useMemo(() => {
@@ -76,6 +81,19 @@ export function useAggregatedAgents(): AggregatedAgentGroup[] {
return left.serverLabel.localeCompare(right.serverLabel);
});
return aggregatedGroups;
}, [sessionDirectory, connectionStates]);
// Loading if registry is still loading, or if any host is connecting and hasn't
// reported agents yet (idle hosts that haven't connected don't block loading)
let isLoading = registryLoading;
if (!isLoading && connectionStates.size > 0) {
for (const [serverId, record] of connectionStates) {
const hasSession = sessionDirectory.has(serverId);
if (record.status === "connecting" && !hasSession) {
isLoading = true;
break;
}
}
}
return { groups: aggregatedGroups, isLoading };
}, [sessionDirectory, connectionStates, registryLoading]);
}