Symptom: App bekommt im minimierten oder display-gesperrten Zustand
nicht mit ob ein Anruf angefangen oder beendet wurde — TTS spricht
weiter waehrend Telefon klingelt, oder bleibt stumm nach Auflegen.
Zwei Ursachen:
1) Kotlin: TelephonyCallback war auf reactApplicationContext.mainExecutor
registriert. Wenn die Activity pausiert ist (display aus, App im
Hintergrund), wird der mainExecutor verzoegert oder gar nicht
abgearbeitet — Call-State-Events kommen nicht durch.
Fix: eigener Executors.newSingleThreadExecutor() — laeuft unabhaengig
vom UI-Thread solange der App-Prozess lebt (Foreground-Service
garantiert das).
2) TS: TelephonyManager-Listener kann nach laengerer Hintergrund-Zeit
verloren gehen (React-Bridge-Context recreated nach Resume).
Fix: neue refresh()-Methode in phoneCallService, AppState-Resume
ruft sie auf — wenn telephonyAttached=false ist, wird der Native-
Listener neu attached.
Plus: Status-Property telephonyAttached macht in Logs sichtbar ob
Pfad 1 (TelephonyManager) wirklich greift. Pfad 2 (AudioFocus fuer
VoIP) war nie betroffen, der laeuft komplett im Native-Code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Ohr aktiv, App im Hintergrund (jetzt mit Foreground-Service
permanent lebendig), nach laengerer Zeit oeffnet Stefan die App und sie
nimmt schon auf — angeblich Wake-Word getriggert. War aber TV/Husten/
sonstige Hintergrund-Geraeusche waehrend Stefan nicht da war.
Mit dem neuen Hintergrund-Modus laeuft openWakeWord jetzt permanent und
faengt jedes False-Positive im Hintergrund auf. Ohne dieser Fall war
das nicht moeglich weil die JS-Engine pausiert war.
Fix: Heuristik beim AppState-Resume in ChatScreen.tsx
- backgroundDauer wird gemerkt (lastBackgroundAt vs Resume-Zeit)
- Wenn >30s im Hintergrund UND state='conversing' UND letzter Wake-
Trigger juenger als 15s: false-positive — Aufnahme abbrechen + zurueck
zu armed
- Resume-Cooldown 1500 → 3000 ms (Audio-Spikes beim AppState-Switch
haben gelegentlich nach 1.5s noch nicht verklungen)
Neue Methoden:
- wakeword.ts: lastTriggerAt-Tracking + discardIfFreshlyTriggered(maxAge)
- audio.ts: cancelRecording() — bricht recorder ab ohne Result zu
emittieren, loescht die Audio-Datei
Setzt voraus dass Stefan nicht laenger als 30s im Hintergrund mit ARIA
spricht ueber Wake-Word. Falls doch: bei Resume waere die Aufnahme weg
und er muesste nochmal triggern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bisher pausierte Android nach ~30s im Hintergrund die JS-Engine.
WebSocket schlief ein, Trigger-Replies vom Brain kamen nicht durch,
Timer-Erinnerungen feuerten in der App nicht obwohl im Brain
ausgeloest. Nach laengerer Hintergrund-Pause warf Android den
Prozess ganz raus → beim Wiedereroeffnen Cold-Start, sah aus wie Crash.
Loesung: Foreground-Service mit persistenter Notification — die ist
ohnehin schon da fuer TTS/Mic-Aktivitaet (`AriaPlaybackService`).
Wir erweitern das Slot-System um einen `background`-Slot der dauerhaft
aktiv ist (Settings-Toggle, default an). Notification zeigt "ARIA aktiv
— Hintergrund-Modus" wenn nichts spezifisches laeuft, escaliert zu
"ARIA spricht/hoert" bei TTS/Mic. Tap → App.
Drei Dateien:
- services/backgroundAudio.ts: 'background' als 4. Slot (niedrigste
Prio, Fallback-Notification). Bestehende tts/rec/wake unveraendert.
- App.tsx: beim Start `acquireBackgroundAudio('background')` aufrufen
wenn Settings nicht explizit deaktiviert. Plus POST_NOTIFICATIONS-
Permission-Request (Android 13+).
- screens/SettingsScreen.tsx: neuer Toggle in Allgemein-Section.
Plus Hinweis auf Android-Akku-Optimierung-Whitelist falls trotzdem
was klemmt (manche Hersteller-ROMs killen aggressiv).
AndroidManifest unveraendert — foregroundServiceType="mediaPlayback|
microphone" deckt unseren Use-Case ab (ARIA spielt regelmaessig TTS
ab, was den Type rechtfertigt). Service stoppt sich selbst wenn alle
Slots leer sind, das passiert nur wenn der User in Settings den
Hintergrund-Modus deaktiviert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(1) Gedanken-Stream Modal: vorheriger Fix mit onStartShouldSetResponder
war falsch — der View wurde komplett zum Responder, die FlatList drin
bekam null Touch-Events. Jetzt: outer View ohne Touch-Handling, ein
separates TouchableOpacity-Element oberhalb des Sheets nur fuer den
Tap-Outside-Close. Sheet-View ist plain View → FlatList scrollt frei.
(2) Such-Sprung praeziser: drei Verbesserungen
- MAX_SCROLL_RETRIES 3 → 6: bei weiten Spruengen (Bubble #150 von
Position 0) braucht FlatList mehrere Iterationen bis die Items in
der Naehe gemessen sind
- Pre-Scroll-Offset: Fallback fuer unmeasured Items ist jetzt der
dynamische Mittel der bisher gemessenen Items (statt Pauschal-150).
Beim Cold-Start sind nur die untersten 10 gemessen, aber deren
Mittel ist immer noch eine bessere Schaetzung
- Render-Pause nach Pre-Scroll 200 → 350 ms: bei weiten Spruengen
braucht FlatList Zeit die Items zu mounten und onLayout zu feuern
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
App-Inbox-Modal:
- ScrollView der Top-Section ('Aus diesem Chat') nestedScrollEnabled=true
- MemoryBrowser darunter in einen flex:1-Wrapper gepackt damit er den
verbleibenden Platz bekommt — ohne den hat seine FlatList intern
null Hoehe gehabt und Scroll-Gestures verschluckt.
Diagnostic Trigger-Tab:
- ✎ Bearbeiten-Knopf pro Zeile (neben Aktivieren/Deaktivieren/Loeschen)
- Modal hat jetzt einen Edit-Modus: Type+Name disabled, Save-Button
zeigt 'Speichern', Modal-Title 'Trigger bearbeiten — <name>'
- Fuer Timer im Edit-Modus ein zusaetzliches Feld 'Feuert am (ISO, UTC)'
damit man den absoluten Zeitpunkt direkt aendern kann (statt 'in X
Minuten ab jetzt' das nur fuer Create Sinn macht)
- saveTrigger() unterscheidet jetzt zwischen Create-Modus (POST
/triggers/timer|watcher) und Edit-Modus (PATCH /triggers/{name})
- openTriggerEdit(name) fuellt das Modal mit Werten aus dem Cache
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Settings hatte zwei Probleme:
1) Gedächtnis-Liste scrollte nur runter, nicht hoch. Klassisches Android
nested-Scroll-Problem: aeussere ScrollView + innere FlatList mit
fixer height:600 = nur eine Richtung wird respektiert.
Fix: outer ScrollView mit scrollEnabled=false wenn die Section eine
eigene voll-hoch-scrollende Sub-Liste hat (memory/triggers). Plus
dynamische Hoehe via useWindowDimensions (winHeight - 220 statt
hardcoded 600) damit MemoryBrowser sauber den verfuegbaren Platz
nutzt.
2) Trigger waren bisher nur via Diagnostic-Tab editierbar — keine App-
side CRUD. Stefan wollte das.
Neu: TriggerBrowser-Komponente (analog MemoryBrowser-Struktur)
- Liste aller Trigger mit Filter (alle/aktive/inaktive)
- Toggle aktiv/inaktiv via Switch direkt in der Zeile
- Tap oeffnet TriggerEditModal (Nachricht/Condition/fires_at/intervals
editieren, Loeschen-Knopf mit Confirm)
- "+ Neu"-Knopf oeffnet TriggerNewModal mit Type-Switch (Watcher/Timer),
Watcher zeigt Hinweis auf verfuegbare Funktionen + Variablen
- Live Reload-Button, Meta-Info (fire_count, last_fired_at, ...)
brainApi um Trigger-Endpoints erweitert: listTriggers, getTrigger,
createTimer, createWatcher, updateTrigger (patch), deleteTrigger,
getTriggerConditions, getTriggerLogs. Plus Trigger-Type-Definition.
Settings-Liste hat eine neue Section "⏰ Trigger" zwischen Gedaechtnis
und Protokoll.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(1) Such-Treffer jetzt neueste zuerst (analog WhatsApp/Telegram). User
ist visuell unten, der erste Sprung landet meist im Viewport ohne
weiten Pre-Scroll (= weniger Cold-Start-Fail-Risiko). „Naechster"
geht in die Vergangenheit. Plus Pre-Scroll-Wartezeit 80→200 ms damit
FlatList beim ersten Versuch wirklich Zeit zum Rendern hat.
(2) SettingsScreen Ueber-Text: `—` wurde literal gerendert weil
JSX-Text-Knoten keine JS-String-Escapes interpretieren. Fix:
`{'—'}` als JS-Expression-Block.
(3) GPS-Tracking sendete nach der initialen Position nichts mehr wenn
der User stationaer war — `distanceFilter: 30` blockiert
watchPosition-Updates ohne Bewegung. Nach 5 min (NEAR_MAX_AGE_SEC)
verwirft das Brain die Position als veraltet → near()-Watcher feuern
nie. Stefan's DRK-Trigger waren so chronisch tot.
Fix: zusaetzlich zum watchPosition laeuft ein setInterval(60s)
Heartbeat der die zuletzt empfangene Position erneut sendet. Kein
extra GPS-Wakeup — akkufreundlich. Damit bleibt der Brain-State
frisch auch bei stationaerem User; near() funktioniert sobald der
User tatsaechlich im Radius ist.
Anmerkung zu Stefan's konkretem Test: er war 1.5–2 km von den DRK-
Triggern entfernt (Radius je 300 m) — selbst mit frischen GPS-Updates
haetten die nicht gefeuert. Der Heartbeat-Fix ist trotzdem noetig
damit Trigger ueberhaupt eine Chance haben wenn er tatsaechlich dort
vorbeifaehrt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Suche nach 'cessna' sprang zur Oberhausen-Bubble (~15 Bubbles
daneben), egal welcher Versuch.
Zwei Ursachen:
1) searchMatchIds suchte in `messages` (alle Bubbles inkl. Memory/Skill/
Trigger-Spezial-Bubbles), aber gescrollt wird in `invertedMessages`
die diese filtert. Wenn 'cessna' nur in einer Memory-Bubble vorkam,
war die ID in searchMatchIds aber nicht in invertedMessages →
findIndex=-1 → kein Scroll, Pre-Scroll-Offset von voriger Aktion
blieb sichtbar. Fix: searchMatchIds aus chatVisibleMessages.
2) AVG_BUBBLE_HEIGHT=150 als Pauschalschaetzung war zu grob — Voice-
Bubbles sind ~70 px, lange ARIA-Antworten 400+. Pre-Scroll-Offset
landete bei langen Listen weit daneben. Fix: itemHeights-Ref-Map
wird per onLayout in renderMessage gefuettert. Pre-Scroll summiert
echte gemessene Hoehen (Fallback AVG fuer noch nicht gerenderte) —
beim zweiten Such-Versuch lernt der Cache, beim ersten klappt's
schon besser als mit dem Pauschalwert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Suchbegriff direkt nach App-Start eingegeben → springt an
falsche Stelle. Erst beim zweiten Versuch funktioniert es.
Ursache: FlatList rendert per Default nur 10 Items initial.
info.averageItemLength im onScrollToIndexFailed basiert nur auf diesen
10 — bei einem Suchtreffer auf Bubble 150 ist die Schaetzung katastrophal
falsch. Beim zweiten Versuch ist die FlatList „warm gelaufen" und mehr
Items sind gemessen → Schaetzung passt besser.
Drei kombinierte Fixes:
1) Pre-Scroll: vor dem scrollToIndex erst grob mit AVG_BUBBLE_HEIGHT=150
per scrollToOffset(idx*150) in die Naehe springen. FlatList rendert
die Bubbles in der Naehe, dann praezise nachsetzen nach 80ms.
2) initialNumToRender=30 (Default 10) — mehr Items beim Mount gemessen.
3) windowSize=41 (Default 21) — mehr Items im Speicher gehalten, weniger
Layout-Holes beim Weit-Scroll.
Kosten: minimal hoehere Mount-Zeit. Bei 300+ Bubbles im Backup macht
sich der UX-Gewinn lohnt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: Suche zeigt Treffer, springt aber permanent zwischen Bubbles
hin und her in Endlosschleife.
Zwei Ursachen, beide angeschlossen:
1) agent_activity-Handler rief setMessages mit prev.map() — auch wenn
keine sending-Bubble da war. Das erzeugte trotzdem ein neues Array
bei jedem Tool-Event (5-10x pro Brain-Call). invertedMessages neu →
FlatList-Layouts invalidiert mitten in einer aktiven Scroll-Sequenz.
Fix: prev.some() vor map() — wenn nichts zu aendern ist, prev
unveraendert returnen (reference-stable, kein Re-Render).
2) onScrollToIndexFailed retried unbegrenzt. Jeder failed Retry rief
den Handler erneut auf → neuer setTimeout → neuer Versuch → fail →
loop. Vorher waren cascading 3 Retries, dann auf 1 reduziert um
den 3-9-27-Cascade zu fixen, aber EIN ungebremster Retry-Schluss
pro fail bleibt eine Endlos-Schleife wenn Layouts nie stabil
werden. Fix: harter Counter (MAX_SCROLL_RETRIES = 3). Counter wird
bei jedem neuen Search-Hit via clearPendingScrollRetry resettet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der innere TouchableOpacity (eigentlich nur da um Tap-Propagation an
das aeussere close-on-tap-outside-Wrapper zu blocken) hat alle Touch-
Events konsumiert — FlatList bekam nichts ab, kein Scroll moeglich.
Fix: inner durch View ersetzen, mit onStartShouldSetResponder=true
plus onResponderTerminationRequest=false. Das blockt die Propagation
ohne Scrolls der Children zu verschlucken.
Close-on-Tap-outside funktioniert weiter (aeusseres TouchableOpacity
bleibt), das X im Header schliesst auch, Hardware-Back ebenfalls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom: ARIA bearbeitet die Nachricht (im Gedanken-Stream sichtbar),
aber unter der User-Bubble bleibt die Sanduhr stehen und nach ~90 s
springt sie auf ⚠ failed. ARIA-Antwort kommt trotzdem irgendwann durch
— die Bubble war also nie weg, nur visuell schief.
Wurzel: chat_ack vom Bridge kam offenbar in manchen Faellen nicht
verlaesslich an. ACK-Timer (30 s × 3 Retries) lief durch → 'failed'.
Fix: agent_activity = thinking/tool/assistant ist impliziter Beweis,
dass das Brain die Nachricht bekommen und angefangen hat zu arbeiten.
Beim ersten non-idle Event:
- alle laufenden ACK-Timer cancelen
- alle 'sending'-User-Bubbles auf 'sent' (✓) setzen
ARIA-Reply markiert dann wie gehabt 'delivered' (✓✓). Damit kann keine
Bubble mehr auf failed gehen waehrend Brain noch laeuft.
Plus: ACK_TIMEOUT_MS 30 → 60 s als Backup-Reserve fuer den Fall dass
weder ACK noch agent_activity ankommt (sehr unwahrscheinlich, aber
billig).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug-1: _append_chat_backup nutzte asyncio.get_event_loop().time() —
das ist Container-Monotonic (bei Restart wieder 0), NICHT UNIX-Zeit.
Bridge schrieb so Eintraege mit ts wie 394M (=6.5 min Uptime), App-side
generiert User-Bubbles mit Date.now() = 1.778e12. Beim Sortieren in
der App: Server-Bubbles landeten alle als "uralt" (kleine ts) ueber den
lokalen Bubbles und teilweise unter dem 500er-Cap raus — Symptom:
"alles nach Hello Kitty fehlt in der App".
Fix: _append_chat_backup nutzt jetzt time.time() * 1000 (UNIX-ms).
Bug-2: doppelte User-Bubble nach App-Hintergrund/Restart mit Retry-Knopf.
Race-Fix von vorhin (text+timestamp-Heuristik, 5-Min-Fenster) griff
nicht weil bei kaputten Server-ts (394M) und lokalen UNIX-ms (1.778e12)
das Diff 1.7 Billionen ms war → Fenster nie zutreffend → lokale Bubble
blieb als Duplikat.
Fix: Text-Match alleine reicht — wenn der Server irgendwo eine
textgleiche User-Bubble hat, ist es dieselbe Nachricht. Greift jetzt
unabhaengig von ts-Konsistenz.
Plus: tools/migrate_chat_backup_ts.py — repariert vorhandene jsonl
(284 von 299 Eintraege auf der VM hatten Container-Uptime-ts). Datei-
Reihenfolge bleibt erhalten (war eh chronologisch), ts werden ab File-
Mtime rueckwaerts 60s-Schritten vergeben. Idempotent, .bak-Backup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Proxy-Patch hookt Claude-CLI `assistant`-Events: bei jedem tool_use-
Block (Bash, Read, Edit, Grep, ...) wird per HTTP-POST an die Bridge
gemeldet. Bridge spiegelt das als `agent_activity tool=<name>` an die
RVS-Clients. App- und Diagnostic-Gedanken-Stream zeigen damit live mit
was ARIA gerade macht — vorher kam pro Brain-Call nur EIN „💭 denkt"
am Anfang und EIN „✓ fertig" am Ende.
Drei neue Bausteine:
- proxy-patches/routes.js: kompletter Replacement der npm-Version mit
`_attachToolHook(subprocess)` — feuert pro tool_use-Block ein HTTP-
POST an http://aria-bridge:8090/internal/agent-activity (URL via
ARIA_TOOL_HOOK_URL Env-Variable ueberschreibbar). Fire-and-forget,
fail-open — Brain-Call bricht NICHT ab wenn Bridge mal nicht da ist.
- docker-compose.yml: vierter cp-Schritt im proxy-Service kopiert
routes.js ueber die npm-Version (analog zu openai-to-cli + cli-to-
openai).
- bridge/aria_bridge.py: neuer `/internal/agent-activity`-Endpoint im
bestehenden _serve_internal_http. Plus _emit_activity hat jetzt
force=True-Param damit wiederholte gleiche Tool-Aufrufe (3x Bash in
Folge) als drei Eintraege im Stream sichtbar bleiben.
App + Diagnostic: pushThought-Dedup laesst tool-Events durch (3x Bash
hintereinander gibt 3 Eintraege im Gedanken-Stream).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multi-Tool-Sessions chronisch gekappt
Live-Diagnose auf der VM: drei verkettete 5-Min-Timeouts feuern bei
jedem laengeren Brain-Call exakt gleichzeitig:
06:16:02 Brain → Proxy /v1/chat/completions
06:20:53 Bridge kappt (4m51s, urlopen timeout=300)
06:21:02 Brain bekommt HTTP 500 vom Proxy ('timed out after 300000ms')
Stefan's Karten-Rekonstruktion (curl gegen Nominatim/OSRM + viele Bash-
Tool-Calls + DB-Inserts) braucht locker 8–15 Min — alle Brain-Calls
ueber 5 Min sind reihenweise mit 'Brain-Fehler: timed out' verreckt,
auch wenn die Arbeit zu 80% durch war.
Drei Stellen patchen:
- bridge/aria_bridge.py: urlopen 300 → 1200 (20 Min)
- aria-brain/proxy_client.py: PROXY_TIMEOUT_SEC default 300 → 1200
- docker-compose.yml: dritter sed-Patch im proxy-Service
setzt DEFAULT_TIMEOUT im claude-max-api-proxy von 300000 auf 1200000
Plus App-Watchdog: 180s → 1260s (21 Min, knapp ueber Brain-Timeout)
damit der lokale Stuck-Watchdog nicht waehrend legitimer langer
Sessions feuert. Echte Verbindungsabbrueche kappen vorher per WS-
Disconnect.
UX-Tradeoff bewusst akzeptiert: User sieht jetzt bis zu 20 Min nur
'ARIA denkt...' ohne Zwischen-Updates. Echte Loesung waere Streaming
oder async-Job-API (siehe Etappe B/C im Vorschlag) — das ist groesseres
Refactoring, hier reicht erst mal der Quick-Fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persistentes chronologisches Log was ARIA intern macht — gefuettert aus
agent_activity-Events (thinking/tool/assistant/idle). Bleibt zwischen
Denk-Phasen stehen, neue Eintraege kommen unten dran, lange Pausen
werden mit Trennlinie + Minuten-Hint sichtbar gemacht.
App (ChatScreen.tsx):
- 💭-Icon in der Statusleiste neben 🗂️ und 🔍, zeigt Eintrags-Anzahl
- Bottom-Sheet (60% Hoehe) mit chronologischer Liste, Tap auf Hintergrund
schliesst, 🗑-Confirm zum Leeren
- Persistierung in AsyncStorage (aria_thought_stream, capped 500)
- Dedup gegen direkt aufeinanderfolgende identische Events
Diagnostic (index.html):
- 💭 Gedanken-Button im Chat-Test-Header neben „Vollbild"
- Zentrales Modal (720px x 70vh), Live-Update wenn neue Eintraege kommen
(autoscroll ans Ende), 🗑 Leeren-Button mit Confirm
- Persistierung in localStorage, gleiche cap/dedup-Logik wie App
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Race nach Etappe-3-Reconnect-Fix: lokale failed-Bubble (mit clientMsgId)
und Server-Backup-Eintrag (ohne clientMsgId, aus alter Bridge-Version)
landeten beide im Merge → User sah Doppelpost: einmal ueber der
ARIA-Antwort (Server), einmal mit Retry-Knopf darunter (lokal). Plus
ACK-Timer konnte weiterlaufen obwohl die Bubble schon delivered war —
Retry pushte den Status zurueck auf sending und nach 30 s auf failed.
App:
- chat_history_response-Merge faellt zusaetzlich auf text+timestamp-
Heuristik im 5-Min-Fenster zurueck wenn die Server-Bubble keine
clientMsgId hat → lokale Kopie wird verworfen, kein Doppelpost
- messagesRef + dispatchWithAck prueft vor Send/Retry ob die Bubble
bereits delivered ist → kein verspaetetes failed mehr
- ARIA-Reply cleart ALLE laufenden ACK-Timer (Bridge hat unsere
Messages ja offensichtlich verarbeitet)
Docs:
- issue.md: neuer Block 'Chat-Stabilitaet' mit den drei Etappen +
beiden Race-Fixes; AsyncStorage-Race-Punkt aus 'Offen' abgehakt
- README.md: Chat-Such-Zeile aktualisiert (highlight statt filter),
Jump-to-Bottom + Delivery-Status-Bubbles dokumentiert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Race-Bug nach Etappe 3: Beim Reconnect schickt die App parallel
chat_history_request und (via flushQueuedMessages) die offline gestaute
Nachricht. Die history_response kam an bevor die Bridge die Bubble in
chat_backup.jsonl geschrieben hatte → Server-Liste ohne unsere Bubble →
Merge ersetzte den lokalen Stand → Bubble weg (im Diagnostic war sie
gleich danach drin).
Bridge: _append_chat_backup nimmt clientMsgId mit auf. send_to_core
reicht sie als kwarg durch (chat- und audio-Pfad).
App: chat_history_response-Merge dedupt per clientMsgId. Lokale User-
Bubbles deren clientMsgId der Server noch nicht kennt bleiben erhalten
(localOnly-Filter erweitert). Server-User-Bubbles mit clientMsgId
kriegen deliveryStatus='delivered' damit das ✓✓ auch nach Reload sichtbar
bleibt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei Etappen Chat-Fixes:
Etappe 1 — Such-Scroll permanent springen weg:
- invertedMessages raus aus dem useEffect-Deps; neue ARIA-Nachrichten triggern den Scroll-Effect nicht mehr. Aktueller Snapshot via Ref.
- onScrollToIndexFailed: statt 3 cascading Retries (120/320/600ms) nur noch EINE Retry nach 300ms. Cascading-Retries waren der Endlos-Cascade-Bug (jeder Failed-Retry triggerte 3 weitere).
Etappe 2 — AsyncStorage-Race + Stuck-Thinking:
- Init-Load merged statt overwrite — Nachrichten die zwischen Mount und Load-Done reinkommen werden nicht mehr verschluckt.
- Stuck-Thinking-Watchdog: 180s ohne agent_activity-Update → Auto-Reset auf idle + Timeout-Bubble. Gegen "App haengt auf 'ARIA denkt'".
Etappe 3 — Delivery-Handshake (WhatsApp-Style):
- Pro User-Bubble: clientMsgId + deliveryStatus (queued/sending/sent/delivered/failed).
- Offline-Queue: Send waehrend disconnected → 'queued' → flush bei Reconnect.
- Bridge sendet chat_ack zurueck → Bubble auf 'sent' (✓).
- ARIA-Reply → alle vorigen User-Bubbles 'delivered' (✓✓).
- ACK-Timeout 30s, bis zu 3 Retries, danach 'failed' (rotes Tap-fuer-Retry).
- Bridge: LRU-Idempotenz (200 cmids) verhindert Doppelte beim Retry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei kleine UX-Fixes im Chat:
1. Jump-Down-Button (↓): Bei inverted FlatList erscheint rechts ueber
der Eingabe ein blauer FAB, sobald man mehr als 250px von der
neuesten Nachricht weg gescrollt ist. Tap → scrollToOffset(0)
animated → wieder unten. Auto-hide wenn man unten ist.
2. Such-Sprung landet jetzt am TEXT-ANFANG der Treffer-Bubble:
viewPosition 0.5 (Mitte) → 0 (Item-Top am Viewport-Top). Plus
Retry-Folge (180/420/800ms) gegen Layout-Race bei langen Listen.
Vorher musste man oft nochmal hoch scrollen um den Anfang zu sehen.
onScrollToIndexFailed-Fallback genauso mit viewPosition 0.
3. issue.md: "Bilder: Claude Vision direkt nutzen" raus aus den
offenen Punkten — ist durch Stufe E (Memory-Anhaenge, Read-Tool
multi-modal) längst geloest. ARIA sieht Bilder echt.
Folge-Etappen: Such-Sprung-Resilienz war Teil davon (mehrere Retries
abgedeckt). Naechste Brocken: Doppel-Send-Haenger, AsyncStorage-Race,
Offline-Queue mit Idempotenz.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: Memory-Liste in Settings → Gedaechtnis-Sektion laesst sich
nicht scrollen. Klassisches FlatList-in-ScrollView-Problem auf
Android: die aeussere ScrollView (Settings-Screen-Container) faengt
alle Gesten ab, die innere FlatList (MemoryBrowser) bleibt regungslos.
Fix:
- MemoryBrowser FlatList bekommt nestedScrollEnabled={true}
- SettingsScreen-aeussere-ScrollView ebenfalls nestedScrollEnabled
- Plus keyboardShouldPersistTaps="handled" damit Taps auf Filter-
Buttons nicht von der Tastatur weggefangen werden
In der Inbox-Modal-Nutzung ist's egal — dort hat MemoryBrowser
flex:1 und der Container ist kein ScrollView.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: nach App-Cache-Leeren in Settings tippt er auf eine Datei im
Chat oder im memorySaved-Anhang → "Oeffnen fehlgeschlagen" Toast,
aber kein Re-Download. Datei hing als Geister-uri im State (RNFS-Cache
weg, State-attachment.uri zeigt noch auf den entfernten Pfad).
Fix in beiden Tap-Handlern (item.attachments im normalen Chat +
memorySaved.attachments via localUri):
1. RNFS.exists(localPath) pruefen
2. Wenn ja → openFileWithIntent
3. Wenn nein:
- State bereinigen (uri/localUri auf undefined setzen, UI zeigt
dann "tippen zum Laden")
- Toast "Cache leer — lade nach..."
- file_request via RVS triggern
- autoOpenPaths-Marker setzen, sodass file_response → Datei
speichern + automatisch oeffnen
Bilder-Branch hatte schon onError-Handler (Image-Komponente meldet
self) — der ist unveraendert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inbox-Crash gefunden via App-Crash-Reporter (commit 21a315c):
"URLSearchParams.set is not implemented"
at MemoryBrowser → brainApi.listMemories
React Native's Hermes-Polyfill kennt zwar new URLSearchParams() aber
nicht die .set()-Methode darauf. Pickup-Bug — auf iOS / aelteren
Versionen geht's, Stefan's Android-Build crasht.
Fix: kleine _qs()-Helper im brainApi.ts der einen Query-String aus
einem flachen Object baut, ohne URLSearchParams:
_qs({q:'cessna', k:5, type:'fact'}) → "?q=cessna&k=5&type=fact"
Plus: undefined/null/empty Werte werden ausgelassen — saubererer als
URLSearchParams.set wo man manuell prefilten muss.
ErrorBoundary aus 21a315c hat den Crash sauber abgefangen, statt der
App-Tot war ne Error-Box im Inbox-Modal mit der vollen Stack-Trace.
Stefan konnte den Log via tools/fetch-app-logs.sh holen ohne ADB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan ist unterwegs, ADB-Zugriff nicht moeglich. Loesung: die App
loggt ihre eigenen Crashes via RVS, Bridge sammelt sie in
/shared/logs/app.log, Diagnostic-Server liefert sie als JSON.
Damit braucht's keinen ADB mehr — Crashes sind sofort vom Browser
(oder Claude per curl) lesbar.
Komponenten:
1. App components/ErrorBoundary.tsx
- React-ErrorBoundary fuer kritische Sections
- componentDidCatch → reportAppError (RVS-Send)
- UI zeigt Error-Box statt White-Screen + Reset-Button
2. App services/logger.ts
- reportAppError(scope, message, stack) → rvs.send('app_log', ...)
- installGlobalCrashReporter() haengt sich an ErrorUtils.setGlobalHandler
UND HermesInternal.enablePromiseRejectionTracker — fangt sowohl
ungefangene Errors als auch unhandled Promise-Rejections
- Konsole bleibt parallel aktiv (damit ADB im Dev-Build weiter
was sieht)
3. App App.tsx: installGlobalCrashReporter() im useEffect zusammen
mit initLogger.
4. App ChatScreen.tsx:
- Inbox-Modal mit ErrorBoundary umschlossen (scope: InboxModal,
onReset schliesst Modal)
- MemoryDetailModal mit ErrorBoundary umschlossen
- DetailModal wird nur noch konditional gerendert (memoryDetailId
!= null) statt immer visible-toggle — vermeidet potentielles
Modal-Stacking-Problem
5. RVS server.js: ALLOWED_TYPES += "app_log"
6. Bridge aria_bridge.py:
- elif msg_type == "app_log": haengt eine Zeile an
/shared/logs/app.log (JSONL, jedes Item {ts, platform, level,
scope, message, stack})
- Plus log.info Hinweis fuer das normale Bridge-Log
7. Diagnostic server.js:
- GET /api/app-log[?limit=N] → letzte N Eintraege als JSON
- POST /api/app-log/clear → log-Datei loeschen
Workflow zum Debuggen des Inbox-Crashes:
Stefan rebuilded App → drueckt Inbox → ErrorBoundary fangt den
Crash (oder Global-Handler bei ungefangenem Error) → reportAppError
→ RVS → Bridge schreibt nach /shared/logs/app.log → Stefan
oder Claude rufen GET /api/app-log auf → sehen Stacktrace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stefan: App crasht beim Tap auf Inbox-Button. Zwei Ursachen:
1. Modal-in-Modal-Stacking (Inbox-Modal enthielt MemoryBrowser, der
wiederum ein MemoryDetailModal gerendered hat). Android Modal hat
damit Probleme — der Native-Layer mag nur eine Modal-Instance
gleichzeitig zuverlaessig.
2. MemoryBrowser nutzte Alert.prompt fuer "Neue Memory anlegen" —
das ist iOS-only, Android wirft eine Warnung oder crasht.
Fix:
- MemoryBrowser bekommt optionalen onOpenMemory-Callback. Wenn der
Parent diesen liefert, mounted MemoryBrowser KEIN eigenes
DetailModal mehr. ChatScreen mountet das DetailModal nur einmal
auf seiner Ebene; Inbox-Modal schliesst sich beim Tap und delegiert
die ID an memoryDetailId-State. Damit ist immer maximal ein Modal
aktiv.
- Alert.prompt durch eigenes kleines Dialog-Modal ersetzt: TextInput
fuer Titel, Anlegen/Abbrechen-Buttons. Cross-platform stabil.
SettingsScreen-Nutzung von MemoryBrowser bleibt unveraendert (kein
Callback → eingebautes DetailModal, aber dort kein Modal-Stacking
weil Settings kein Modal ist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zwei Bugs aus Stefans Screenshot:
1. memorySaved/triggerCreated/skillCreated bleiben permanent unten im
Chat-Verlauf statt mit den anderen Bubbles zu scrollen — sieht aus
wie Werbe-Bumper. Fix: chatVisibleMessages-Filter raus aus
FlatList-Source, diese Bubbles werden im Chat ueberhaupt nicht mehr
gerendert.
Stefans urspruengliche Idee war ja "trigger und gedächtnis bubble
in ein extra modal fenster" — genau das ist die Inbox jetzt.
2. Inbox-Emoji 🗂️ wurde als Literal "🗂️"-Text
gerendert. Letztes Edit hat es ohne JSX-String-Literal-Schutz
eingefuegt. Fix: {'🗂️'} statt direktes Emoji-Token.
Modal-Header analog.
Inbox-Modal erweitert:
- Neue Section "AUS DIESEM CHAT" oben: kompakte Liste der Spezial-
Bubbles aus messages (chronologisch neueste oben). Memory-Eintraege
oeffnen MemoryDetailModal (mit Tap auf den Pfeil). Trigger/Skills
zeigen nur Title+Meta — keine Edit-UI, dafuer gibt's die jeweiligen
Tabs im Diagnostic.
- Darunter wie bisher der volle MemoryBrowser mit allen DB-Memories.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>