feat(app): add zustand store for session state management

This commit is contained in:
Mohamed Boudra
2025-11-27 21:36:03 +01:00
parent bf58e81ce8
commit 08dc2ed937
7 changed files with 142 additions and 121 deletions
@@ -1,9 +1,6 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { createContext, useCallback, useContext, useEffect, useState } from "react";
import type { ReactNode } from "react";
import { useDaemonRegistry, type DaemonProfile } from "./daemon-registry-context";
import type { SessionContextValue } from "./session-context";
export type SessionSnapshot = SessionContextValue;
export type ConnectionState =
| { status: "idle"; lastError: null; lastOnlineAt: string | null; sessionReady: false }
@@ -29,9 +26,6 @@ interface DaemonConnectionsContextValue {
connectionStates: Map<string, DaemonConnectionRecord>;
isLoading: boolean;
updateConnectionStatus: (daemonId: string, update: ConnectionStateUpdate) => void;
sessionSnapshots: Map<string, SessionSnapshot>;
updateSessionSnapshot: (daemonId: string, snapshot: SessionSnapshot | null) => void;
clearSessionSnapshot: (daemonId: string) => void;
}
const DaemonConnectionsContext = createContext<DaemonConnectionsContextValue | null>(null);
@@ -116,7 +110,6 @@ export function useDaemonConnections(): DaemonConnectionsContextValue {
export function DaemonConnectionsProvider({ children }: { children: ReactNode }) {
const { daemons, isLoading: registryLoading } = useDaemonRegistry();
const [connectionStates, setConnectionStates] = useState<Map<string, DaemonConnectionRecord>>(new Map());
const [sessionSnapshotRegistry, setSessionSnapshotRegistry] = useState<Map<string, SessionSnapshot>>(new Map());
// Ensure connection states stay in sync with registry entries
useEffect(() => {
@@ -133,24 +126,6 @@ export function DaemonConnectionsProvider({ children }: { children: ReactNode })
});
}, [daemons]);
useEffect(() => {
setSessionSnapshotRegistry((prev) => {
const validDaemonIds = new Set(daemons.map((daemon) => daemon.id));
let changed = false;
const next = new Map<string, SessionSnapshot>();
for (const [daemonId, snapshot] of prev.entries()) {
if (!validDaemonIds.has(daemonId)) {
changed = true;
continue;
}
next.set(daemonId, snapshot);
}
return changed ? next : prev;
});
}, [daemons]);
const updateConnectionStatus = useCallback(
(daemonId: string, update: ConnectionStateUpdate) => {
@@ -177,38 +152,10 @@ export function DaemonConnectionsProvider({ children }: { children: ReactNode })
[]
);
const updateSessionSnapshot = useCallback((serverId: string, snapshot: SessionSnapshot | null) => {
setSessionSnapshotRegistry((prev) => {
const next = new Map(prev);
if (snapshot === null) {
next.delete(serverId);
} else {
next.set(serverId, snapshot);
}
return next;
});
}, []);
const clearSessionSnapshot = useCallback((serverId: string) => {
setSessionSnapshotRegistry((prev) => {
if (!prev.has(serverId)) {
return prev;
}
const next = new Map(prev);
next.delete(serverId);
return next;
});
}, []);
const sessionSnapshots = useMemo(() => new Map(sessionSnapshotRegistry), [sessionSnapshotRegistry]);
const value: DaemonConnectionsContextValue = {
connectionStates,
isLoading: registryLoading,
updateConnectionStatus,
sessionSnapshots,
updateSessionSnapshot,
clearSessionSnapshot,
};
return (
+11 -11
View File
@@ -30,6 +30,7 @@ import type {
import { ScrollView } from "react-native";
import * as FileSystem from 'expo-file-system';
import { useDaemonConnections } from "./daemon-connections-context";
import { useSessionStore } from "@/stores/session-store";
const derivePendingPermissionKey = (agentId: string, request: AgentPermissionRequest) => {
const fallbackId =
@@ -396,9 +397,10 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
const wsIsConnected = ws.isConnected;
const {
updateConnectionStatus,
updateSessionSnapshot,
clearSessionSnapshot,
} = useDaemonConnections();
const setSession = useSessionStore((state) => state.setSession);
const updateSession = useSessionStore((state) => state.updateSession);
const clearSession = useSessionStore((state) => state.clearSession);
useEffect(() => {
if (ws.isConnected) {
@@ -1843,21 +1845,19 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
);
useEffect(() => {
updateSessionSnapshot(serverId, value);
}, [serverId, updateSessionSnapshot, value]);
setSession(serverId, { ...value });
}, [serverId, setSession]);
useEffect(() => {
if (wsIsConnected) {
return;
}
clearSessionSnapshot(serverId);
}, [clearSessionSnapshot, serverId, wsIsConnected]);
const payload = { ...value };
updateSession(serverId, payload);
}, [serverId, updateSession, value]);
useEffect(() => {
return () => {
clearSessionSnapshot(serverId);
clearSession(serverId);
};
}, [serverId, clearSessionSnapshot]);
}, [clearSession, serverId]);
return (
<SessionContext.Provider value={value}>