6.3 KiB
description, icon
| description | icon |
|---|---|
| React context providers for service lifecycle and shared state. | layer-group |
Providers
React context providers manage service lifecycle and provide shared state.
Provider chain
The providers wrap the application in a specific order (app/src/App.tsx):
<Sentry.ErrorBoundary>
<Provider store={store}>
<PersistGate persistor={persistor} onBeforeLift={...}>
<UserProvider>
<SocketProvider>
<AIProvider>
<SkillProvider>
<Router>
<AppRoutes />
</Router>
</SkillProvider>
</AIProvider>
</SocketProvider>
</UserProvider>
</PersistGate>
</Provider>
</Sentry.ErrorBoundary>
(Router is HashRouter from react-router-dom.)
Order matters because:
- Redux is outermost for store access.
PersistGaterehydrates persisted slices before children rely on auth.SocketProvideruses the JWT from the store.AIProvider/SkillProviderdepend on socket and store-backed features.- The router supplies navigation to all routes.
SocketProvider (app/src/providers/SocketProvider.tsx)
Manages realtime connectivity: web uses the JS Socket.io client; Tauri bridges to the Rust socket via utils/tauriSocket.ts and reports status back to Redux.
Responsibilities
- Connect when
auth.tokenis available; disconnect when cleared - In Tauri: install listeners once, connect Rust socket, coordinate daemon lifecycle (
useDaemonLifecycle) - Update Redux socket slice / connection status
Implementation
See app/src/providers/SocketProvider.tsx. The file branches on isTauri(): web mode uses socketService directly; Tauri sets up tauriSocket listeners and connectRustSocket / disconnectRustSocket. Do not treat the pseudocode below as the live implementation.
Usage
import { useSocket } from '../providers/SocketProvider';
function MyComponent() {
const { socket, isConnected, emit, on, off } = useSocket();
useEffect(() => {
const handler = (data) => console.log('Received:', data);
on('event-name', handler);
return () => off('event-name', handler);
}, [on, off]);
const sendMessage = () => {
emit('send-message', { text: 'Hello!' });
};
return (
<div>
<span>Status: {isConnected ? 'Connected' : 'Disconnected'}</span>
<button onClick={sendMessage}>Send</button>
</div>
);
}
AIProvider (app/src/providers/AIProvider.tsx)
Initializes memory, sessions, tool registry (including memory + web-search tools), entity manager, LLM / embedding providers, and constitution loading. Exposes useAI() for children. Heavy logic lives under app/src/lib/ai/.
SkillProvider (app/src/providers/SkillProvider.tsx)
On mount (when authenticated), discovers skills from the QuickJS skills engine via Tauri helpers (runtimeDiscoverSkills), syncs manifests into Redux, listens for skill-related Tauri events, and can auto-start configured skills in development.
UserProvider (providers/UserProvider.tsx)
Minimal user context provider (most user state is in Redux).
Responsibilities
- Legacy user context for compatibility
- May be deprecated in favor of Redux
Implementation
interface UserContextValue {
user: User | null;
loading: boolean;
}
export function UserProvider({ children }) {
const user = useAppSelector((state) => state.user.profile);
const loading = useAppSelector((state) => state.user.loading);
return (
<UserContext.Provider value={{ user, loading }}>
{children}
</UserContext.Provider>
);
}
Usage
import { useUserContext } from '../providers/UserProvider';
function Header() {
const { user, loading } = useUserContext();
if (loading) return <Skeleton />;
if (!user) return null;
return <span>Welcome, {user.firstName}</span>;
}
Provider Patterns
Effect-Based Lifecycle
Providers use useEffect to manage service lifecycle:
useEffect(() => {
// Setup on mount or dependency change
service.connect();
// Cleanup on unmount or dependency change
return () => {
service.disconnect();
};
}, [dependencies]);
Redux Integration
Providers read from and dispatch to Redux:
// Read state
const token = useAppSelector((state) => state.auth.token);
// Dispatch actions
const dispatch = useAppDispatch();
dispatch(setStatus({ userId, status: "connected" }));
Parallel initialization
SkillProvider and AIProvider may kick off several async tasks on mount (skill discovery, memory init, constitution load). Prefer reading the source for ordering guarantees rather than assuming parallel Promise.all everywhere.
Session Restoration
Providers restore persisted state on mount:
useEffect(() => {
if (persistedSession) {
service.restoreSession(persistedSession);
}
}, [persistedSession]);
Context vs Redux
| Use Context For | Use Redux For |
|---|---|
| Service instances (socket, client) | Serializable state (status, data) |
| Methods (emit, on, off) | Persisted state (sessions, tokens) |
| Derived values | Complex state logic |
Example:
SocketContextprovidessocketinstance andemitmethod- Redux stores
socketStatusandsocketId
Testing Providers
Mock Provider for Tests
// test-utils.tsx
const mockSocketContext: SocketContextValue = {
socket: null,
isConnected: true,
emit: jest.fn(),
on: jest.fn(),
off: jest.fn()
};
export function TestProviders({ children }) {
return (
<Provider store={testStore}>
<SocketContext.Provider value={mockSocketContext}>
{children}
</SocketContext.Provider>
</Provider>
);
}
Testing Provider Effects
test('SocketProvider connects when token is available', () => {
const store = createTestStore({ auth: { token: 'test-token' } });
render(
<Provider store={store}>
<SocketProvider>
<TestComponent />
</SocketProvider>
</Provider>
);
expect(socketService.connect).toHaveBeenCalledWith('test-token');
});
Previous: Components | Next: Hooks & Utils