From fed3cb9f1899827266d8aba2b646cbfb4946684c Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 11 Jul 2026 17:11:51 +0200 Subject: [PATCH] fix(projects): Live-Sync verstecken zwischen App und Diagnostic (ohne Refresh) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kern-Bug: der Diagnostic-Server reichte ein von RVS empfangenes project_changed NIE an die Browser weiter — der Handler in index.html war toter Code, der Diagnostic aktualisierte die Projektliste also nie live. - server.js: RVS-Handler forwardet project_changed jetzt an die Browser-Tabs; der /api/brain-Proxy broadcastet project_changed (RVS + lokal) nach jeder erfolgreichen Projekt-Mutation (create/switch/end/archive/PATCH inkl. hidden). - App ProjectsBrowser: sendet project_changed nach dem Verstecken/Sichtbar- machen und laedt bei eingehendem project_changed selbst neu. Damit: verstecken in der App -> sofort im Diagnostic weg (und umgekehrt), ohne Seiten-Refresh. Diagnostic-Hide funktionierte schon (Daten/Proxy/Logik korrekt) — versteckte sind per "Versteckte anzeigen (N)"-Toggle sichtbar. Co-Authored-By: Claude Opus 4.8 --- android/src/components/ProjectsBrowser.tsx | 22 +++++++++++++++++++-- diagnostic/server.js | 23 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) 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" });