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:
@@ -1109,6 +1109,11 @@
|
||||
<option value="aria">Von ARIA (aria_*)</option>
|
||||
<option value="user">Vom Benutzer</option>
|
||||
</select>
|
||||
<select id="files-filter-project" onchange="renderFilesList()" style="background:#080810;color:#E0E0F0;border:1px solid #1E1E2E;padding:6px;border-radius:4px;font-family:inherit;font-size:11px;">
|
||||
<option value="__all__">Alle Projekte</option>
|
||||
<option value="">💬 Hauptchat</option>
|
||||
<!-- Project options werden dynamisch via loadFiles() befuellt -->
|
||||
</select>
|
||||
</div>
|
||||
<div id="files-info" style="margin-top:6px;font-size:10px;color:#8888AA;"></div>
|
||||
</div>
|
||||
@@ -4209,6 +4214,37 @@
|
||||
const d = await r.json();
|
||||
if (!d.ok) throw new Error(d.error || 'Unbekannter Fehler');
|
||||
filesCache = d.files || [];
|
||||
// Projekt-Filter-Optionen aktualisieren — Liste aller bekannten projectIds
|
||||
// aus den Dateien + Namen via brain api.
|
||||
const pidsInFiles = new Set(filesCache.map(f => f.projectId).filter(Boolean));
|
||||
try {
|
||||
const pr = await fetch('/api/brain/projects/list?include_archived=true');
|
||||
const pdata = await pr.json();
|
||||
const projects = pdata?.projects || [];
|
||||
const sel = document.getElementById('files-filter-project');
|
||||
if (sel) {
|
||||
const current = sel.value;
|
||||
// Bestehende Options ab Index 2 (nach __all__ und Hauptchat) entfernen
|
||||
while (sel.options.length > 2) sel.remove(2);
|
||||
for (const p of projects) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.id;
|
||||
opt.textContent = `📁 ${p.name}`;
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
// Auch IDs aus Files die nicht in projects sind (gelöschte Projekte)
|
||||
const knownIds = new Set(projects.map(p => p.id));
|
||||
for (const pid of pidsInFiles) {
|
||||
if (!knownIds.has(pid)) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = pid;
|
||||
opt.textContent = `📁 ${pid} (gelöscht?)`;
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
}
|
||||
sel.value = current || '__all__';
|
||||
}
|
||||
} catch {}
|
||||
// Selection bereinigen — nicht mehr existierende Pfade raus
|
||||
const existing = new Set(filesCache.map(f => f.path));
|
||||
for (const p of [...filesSelected]) if (!existing.has(p)) filesSelected.delete(p);
|
||||
@@ -4221,9 +4257,11 @@
|
||||
function getVisibleFiles() {
|
||||
const q = (document.getElementById('files-search').value || '').toLowerCase();
|
||||
const filter = document.getElementById('files-filter').value;
|
||||
const pidFilter = document.getElementById('files-filter-project')?.value || '__all__';
|
||||
let files = filesCache.slice();
|
||||
if (filter === 'aria') files = files.filter(f => f.fromAria);
|
||||
else if (filter === 'user') files = files.filter(f => !f.fromAria);
|
||||
if (pidFilter !== '__all__') files = files.filter(f => (f.projectId || '') === pidFilter);
|
||||
if (q) files = files.filter(f => f.name.toLowerCase().includes(q));
|
||||
return files;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user