diff --git a/src/App.tsx b/src/App.tsx
index 3c382de..8d4e573 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -4,6 +4,7 @@ import { Header } from './components/Header';
import { Sidebar } from './components/Sidebar';
import { Chat } from './components/Chat';
import { LoginScreen } from './components/LoginScreen';
+import { ConnectionBanner } from './components/ConnectionBanner';
export default function App() {
const {
@@ -38,6 +39,7 @@ export default function App() {
/>
setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} onLogout={logout} />
+
diff --git a/src/components/ConnectionBanner.tsx b/src/components/ConnectionBanner.tsx
new file mode 100644
index 0000000..34f6a09
--- /dev/null
+++ b/src/components/ConnectionBanner.tsx
@@ -0,0 +1,72 @@
+import { useState, useEffect, useRef } from 'react';
+import { Wifi, Loader2 } from 'lucide-react';
+import type { ConnectionStatus } from '../types';
+import { useT } from '../hooks/useLocale';
+
+interface Props {
+ status: ConnectionStatus;
+}
+
+type BannerState = 'hidden' | 'reconnecting' | 'reconnected';
+
+export function ConnectionBanner({ status }: Props) {
+ const t = useT();
+ const [banner, setBanner] = useState('hidden');
+ const prevStatus = useRef(status);
+ const dismissTimer = useRef | null>(null);
+
+ useEffect(() => {
+ const prev = prevStatus.current;
+ prevStatus.current = status;
+
+ if (dismissTimer.current) {
+ clearTimeout(dismissTimer.current);
+ dismissTimer.current = null;
+ }
+
+ if (status === 'disconnected' || status === 'connecting') {
+ // Only show reconnecting if we were previously connected (not initial load)
+ if (prev === 'connected') {
+ setBanner('reconnecting');
+ }
+ } else if (status === 'connected' && prev !== 'connected') {
+ // Just reconnected — flash success only if we were showing the banner
+ if (banner === 'reconnecting' || prev === 'disconnected' || prev === 'connecting') {
+ setBanner('reconnected');
+ dismissTimer.current = setTimeout(() => setBanner('hidden'), 3000);
+ }
+ }
+
+ return () => {
+ if (dismissTimer.current) clearTimeout(dismissTimer.current);
+ };
+ }, [status]);
+
+ if (banner === 'hidden') return null;
+
+ const isReconnecting = banner === 'reconnecting';
+
+ return (
+
+ {isReconnecting ? (
+ <>
+
+ {t('connection.reconnecting')}
+ >
+ ) : (
+ <>
+
+ {t('connection.reconnected')}
+ >
+ )}
+
+ );
+}
diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts
index e5bd6e9..495e9cf 100644
--- a/src/lib/i18n.ts
+++ b/src/lib/i18n.ts
@@ -49,6 +49,10 @@ const en = {
// Tool call
'tool.result': 'Result',
+ // Connection banner
+ 'connection.reconnecting': 'Connection lost — reconnecting…',
+ 'connection.reconnected': 'Reconnected!',
+
// Timestamps
'time.yesterday': 'Yesterday',
} as const;
@@ -89,6 +93,9 @@ const fr: Record = {
'tool.result': 'Résultat',
+ 'connection.reconnecting': 'Connexion perdue — reconnexion…',
+ 'connection.reconnected': 'Reconnecté !',
+
'time.yesterday': 'Hier',
};