fix+feat(projects): Spinner-Bug, Back-Button, kollabierbare Chat-Bloecke, File-Filter

Drei Stefan-Bugs aus dem ersten Deploy-Test plus die fehlenden Polish-
Features fuer die Projekt-Funktion.

Fixes:
- ProjectsBrowser-Spinner-Hang: useRef-Pattern statt useCallback([onActive
  Changed]) — Parent uebergibt inline-arrow-Callbacks, neue Identitaet
  jedes Render → useCallback recomputes → useEffect refeuert → infinite
  Spinner. Fix: Ref-Bridge fuer Callbacks, useCallback mit empty deps.
- ChatScreen Banner: zusaetzlicher × Hauptchat-Button rechts (sichtbar
  nur wenn Projekt aktiv) — ein Tap und zurueck zum Hauptthread, ohne
  Modal-Umweg.

Features:
- Brain ChatOut.project_id: aktive Projekt-ID NACH dem Turn (kann
  durch project_enter/exit-Tools waehrend Turn gewechselt sein). Bridge
  liest sie aus dem /chat-Response und haengt sie an jeden ARIA-Chat-
  Broadcast als payload.projectId.
- App: ChatMessage.projectId-Feld. User-Bubbles werden mit aktiver
  Projekt-ID getaggt vor dem Senden (auch im RVS-Payload). ARIA-Bubbles
  kriegen die ID vom Bridge.
- App: Chat-Verlauf rendert aufeinanderfolgende Project-Messages als
  einklappbaren Block mit Header (▶/▼ + Projekt-Name + Count). Auto-
  Collapse beim Projekt-Wechsel (altes ein, neues aus), Default beim
  ersten Render: alle inaktiven Projekte eingeklappt.
- File-Manager Project-Tagging:
  - diagnostic/server.js: Manifest /shared/config/file_projects.json
    + /api/files-list returnt projectId pro Datei + neuer Endpoint
    /api/files-set-project.
  - bridge/aria_bridge.py: nach App-Upload Auto-Tag mit aktivem Projekt
    (Brain-Status-Query, best-effort fail-silent).
  - App SettingsScreen: scrollbare Projekt-Pill-Reihe als Filter, default
    auf aktives Projekt wenn vorhanden, sonst "Alle Projekte".
  - Diagnostic: zweites Dropdown im Files-Tab, baut Projekt-Optionen
    dynamisch aus /api/brain/projects/list.

Bewusst nicht drin (Folgeschritt):
- Per-File "Projekt zuweisen"-Action (Long-Press / Right-Click)
- Filter-Sync zwischen ChatScreen-Banner und SettingsScreen-Filter

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 21:55:02 +02:00
parent 1baa1a7a08
commit 1fb512c2fd
7 changed files with 359 additions and 22 deletions
+11 -5
View File
@@ -12,7 +12,7 @@
* SettingsScreen.tsx in der Section 'projects'.
*/
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
Alert,
@@ -60,17 +60,23 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
const [editName, setEditName] = useState('');
const [editDesc, setEditDesc] = useState('');
// Refs damit useCallback NICHT bei jeder Re-Render des Parents neu erzeugt
// wird (parent uebergibt oft inline-arrow-Callbacks, neue Identity jedes
// Render → useCallback re-runs → useEffect refeuert → infinite spinner).
const onActiveChangedRef = useRef(onActiveChanged);
useEffect(() => { onActiveChangedRef.current = onActiveChanged; }, [onActiveChanged]);
const load = useCallback(() => {
setLoading(true); setErr(null);
brainApi.getProjectStatus()
.then(status => {
setProjects(status.projects || []);
setActiveId(status.active_id || '');
onActiveChanged?.(status.active);
onActiveChangedRef.current?.(status.active);
})
.catch(e => setErr(String(e?.message || e)))
.finally(() => setLoading(false));
}, [onActiveChanged]);
}, []);
useEffect(() => { if (visible) load(); }, [visible, load]);
@@ -85,10 +91,10 @@ export const ProjectsBrowser: React.FC<Props> = ({ visible = true, onClose, onAc
brainApi.switchProject(id)
.then(status => {
setActiveId(status.active_id || '');
onActiveChanged?.(status.active);
onActiveChangedRef.current?.(status.active);
})
.catch(e => Alert.alert('Fehler', String(e?.message || e)));
}, [onActiveChanged]);
}, []);
const createProject = useCallback(() => {
const name = newName.trim();