diff --git a/android/src/components/ProjectsBrowser.tsx b/android/src/components/ProjectsBrowser.tsx index bc8c23b..aaa3549 100644 --- a/android/src/components/ProjectsBrowser.tsx +++ b/android/src/components/ProjectsBrowser.tsx @@ -112,6 +112,17 @@ export const ProjectsBrowser: React.FC = ({ visible = true, onClose, onAc return () => unsub(); }, [visible, load]); + // Live-Sync: ein anderer Client (Diagnostic / andere App) hat ein Projekt + // geaendert (verstecken/anlegen/beenden/…) → project_changed ueber RVS → + // Liste neu laden, ohne dass Stefan manuell refreshen muss. + useEffect(() => { + if (!visible) return; + const unsub = rvs.onMessage((msg: any) => { + if (msg?.type === 'project_changed') load(); + }); + return () => unsub(); + }, [visible, load]); + const switchTo = useCallback((id: string) => { // Multi-Threading: Focus-Wechsel ist reine App-lokale UI-Entscheidung. // Brain wird nicht mehr benachrichtigt (kein globaler active_project mehr). @@ -161,11 +172,18 @@ export const ProjectsBrowser: React.FC = ({ visible = true, onClose, onAc ]); }, [load]); + // Nach einer Projekt-Mutation die anderen Clients (Diagnostic, weitere + // App-Instanzen) live aktualisieren — via RVS project_changed. RVS echot + // NICHT an den Sender zurueck, darum laden wir lokal zusaetzlich selbst. + const broadcastProjectsChanged = useCallback(() => { + try { rvs.send('project_changed' as any, { reason: 'app' }); } catch {} + }, []); + const toggleHidden = useCallback((p: Project) => { brainApi.setProjectHidden(p.id, !p.hidden) - .then(() => load()) + .then(() => { broadcastProjectsChanged(); load(); }) .catch(e => Alert.alert('Fehler', String(e?.message || e))); - }, [load]); + }, [load, broadcastProjectsChanged]); const archiveProject = useCallback((p: Project) => { Alert.alert(`"${p.name}" archivieren?`, diff --git a/diagnostic/server.js b/diagnostic/server.js index 7122741..71d5009 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -898,6 +898,11 @@ function connectRVS(forcePlain) { // Mode-Broadcast von der Bridge → an Browser-Clients weiterreichen log("info", "rvs", `Mode-Broadcast: ${msg.payload?.mode} (${msg.payload?.name})`); broadcast({ type: "mode", payload: msg.payload }); + } else if (msg.type === "project_changed") { + // Ein Projekt wurde geaendert (ARIA-Tool, App-Verstecken, …) → an die + // Browser-Tabs weiterreichen, damit die Projektliste live neu laedt + // (bisher wurde das NICHT geforwardet → Diagnostic aktualisierte nie). + broadcast({ type: "project_changed", payload: msg.payload || {} }); } else if (msg.type === "agent_activity") { // Bridge meldet "ARIA denkt/schreibt/tool" oder "idle" — an Browser // weiterreichen, damit der Thinking-Indikator im Chat erscheint. @@ -2168,6 +2173,13 @@ const server = http.createServer((req, res) => { // mehr als eine Minute. const isUpload = /\/attachments(\/upload)?$/.test(targetPath); const timeout = isUpload ? 120000 : 60000; + // Projekt-Mutationen (create/switch/end/archive/patch inkl. hidden) sollen + // alle Clients live aktualisieren. Wir broadcasten nach Erfolg ein + // project_changed an RVS — App + andere Diagnostic-Tabs laden dann neu, + // ohne Seiten-Refresh (spiegelt das bestehende ARIA-project_changed-Event). + const isProjectMutation = + /^\/projects\b/.test(targetPath) && + (req.method === "POST" || req.method === "PATCH" || req.method === "DELETE"); const proxyReq = http.request({ host: "aria-brain", port: 8080, @@ -2178,6 +2190,17 @@ const server = http.createServer((req, res) => { }, (proxyRes) => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); + if (isProjectMutation && proxyRes.statusCode >= 200 && proxyRes.statusCode < 300) { + try { + // An App + Bridge (RVS echot NICHT an den Sender) … + sendToRVS_raw({ type: "project_changed", + payload: { reason: "diagnostic" }, + timestamp: Date.now() }); + // … und an die eigenen Browser-Tabs (die haengen am Diag-Server, nicht + // direkt am RVS, kriegen den RVS-Broadcast also nicht). + broadcast({ type: "project_changed", payload: { reason: "diagnostic" } }); + } catch (_) {} + } }); proxyReq.on("error", (err) => { res.writeHead(503, { "Content-Type": "application/json" });