Commit Graph

201 Commits

Author SHA1 Message Date
duffyduck e9615d987e fix(audio): playbackFinished-Listener feuern erst wenn AudioTrack wirklich durch ist
Race-Condition entdeckt im Log: nach jeder ARIA-Antwort lief
endConversation 5s nach TTS-Start (= "letzter Chunk eingetroffen"),
nicht wenn der AudioTrack-Hardware-Buffer wirklich am Ende war. ARIA
sprach also noch hoerbar, waehrend OpenWakeWord schon re-armte.

Folge: ARIAs eigene Stimme ging direkt nach AudioRecord.startRecording
ins Mikro. Die OpenWakeWord-Sessions von AudioRecord und AudioTrack
sind verschieden → AcousticEchoCanceler kann den Output nicht
subtrahieren (kein gemeinsamer Reference-Stream). Threshold +
Patience-State der Wake-Word-Inferenz wird durch ARIAs konstante
Audio-Eingabe verwirrt, der naechste echte "Computer"-Trigger geht
unter.

Fix: Listener-Fire aus handlePcmChunk(isFinal=true) raus, dafuer in
den schon existierenden PcmPlaybackFinished-Native-Event-Handler
rein. Die Kotlin-Seite emittiert das Event aus dem Writer-Thread-
finally-Block — also genau dann wenn AudioTrack alle Samples
durchgeschrieben hat.

Side-Effect: UI-Konsumenten von onPlaybackFinished sehen den
"finished"-State jetzt 1-2s spaeter (= ehrlicher zur Realitaet,
ist eigentlich eine UX-Verbesserung).
2026-05-30 23:18:53 +02:00
duffyduck ece08f0f2f debug(wake): RVS-Log in endConversation — sichtbar machen ob re-arm greift
Stefan beobachtet dass Wake-Word nach Conversation manchmal nicht
re-armt. endConversation hatte bisher kein RVS-Logging — wir waren
beim Diagnose blind.

Loggt jetzt:
  - 'endConversation called but state=X → noop' (state-Mismatch)
  - 'endConversation called, calling OpenWakeWord.start()' (Eintritt)
  - 'OpenWakeWord.start() OK → state=armed' (Erfolg)
  - 'OpenWakeWord.start() FAIL: ... → state=off' (Native-Fehler)
  - 'fallback: nativeReady=false → state=off' (kein Native-Modul)

Damit sehen wir im naechsten Test welcher Pfad gegriffen hat und ob
das Native-Modul ueberhaupt aufgerufen wurde.
2026-05-30 23:09:11 +02:00
duffyduck 263835ad74 fix(wake): Conversation-Window nur im Foreground, Background → direkt re-armen
Symptom: Wake-Word laeuscht nach erfolgreicher Konversation im
Hintergrund nicht wieder — erst beim App-Vorholen wird's wieder
armed. Grund: nach TTS-Ende laeuft wakeWordService.resume() in
einen setTimeout(800ms) der im Doze stark verzoegert wird. Der
verspaetete Timer findet dann delay > 2800 und ruft endConversation
(re-arm) — aber eben erst beim App-Resume.

Fix: in onPlaybackFinished AppState pruefen:
  active     → resume() wie bisher (Multi-Turn-Conversation-Window)
  background → endConversation() direkt — kein setTimeout, native
               OpenWakeWord.start() greift sofort.

Begruendung fuer das Verhalten:
- Foreground: User ist aktiv, Multi-Turn-Dialog ohne erneutes
  "Computer"-Sagen ist nuetzlich.
- Background: User nutzt das Handy anderweitig, automatisches Mikro-
  Oeffnen ist nicht erwartet und droht durch Doze-Verzoegerung in
  ein Phantom-Trigger-Mismatch zu kippen. Direkt re-armen ist
  robust + erwartungskonform.

Eng verwandt mit dem 0.1.7.0-Fix (kein setTimeout zwischen
wake.detect und Callback) — selbes Doze-Throttling-Pattern, andere
Stelle in der Pipeline.
2026-05-30 23:01:12 +02:00
duffyduck 3d001a1d03 feat(app): manueller Aufnahme-Knopf nutzt jetzt auch Streaming-STT
VoiceButton rewrite — dB/VAD-Pfad endgueltig raus. Knopf ist jetzt nur
noch UI-Trigger:
  - onTapStart   (ChatScreen baut Bubble + startStreamingRecording)
  - onTapStop    (ChatScreen ruft stopStreamingRecording)
  - audioService.onStateChange treibt die Animation (statt internem
    isRecording-Flag)
  - onSilenceDetected-Subscription weg

ChatScreen:
  - handleVoiceRecording (Legacy) → handleVoiceButtonStart +
    handleVoiceButtonStop
  - Bubble wird beim Tap SOFORT gebaut (vorher: erst nach Stop), Text
    landet via audioRequestId-Match im chat-Handler-Update-Pfad
  - noSpeechTimeoutMs=0 (manueller Modus, User kontrolliert via Tap),
    hardCapMs=300_000 (5 Minuten Notbremse)
  - Wake-Word-conversing + manueller Stop = endConversation (User
    will nicht in Multi-Turn-Modus)
  - RecordingResult-Import entfaellt (nicht mehr genutzt)

Damit ist die komplette App-seitige Aufnahme auf Streaming + ML-
Endpointer. Der ganze dB/VAD-Apparat (vadEnabled, vadBaselineSamples,
loadVadSilenceDbOverride, vadTimer, noSpeechTimer, etc.) ist jetzt
nur noch Dead-Code — wird in einem Folge-Commit gemeinsam mit dem
zugehoerigen Settings-Slider abgeraeumt.
2026-05-30 22:31:26 +02:00
duffyduck 3c2e537420 fix(wake): kein Conversation-Window-Resume wenn JS-Thread verspaetet aufwacht
Symptom: User sagt "Naechstes Lied bitte", ARIA spielt Track, Display
geht aus, User holt 10s spaeter die App vor und sieht "Aufnahme laeuft"
— als haette er Wake-Word gesagt. Klassisches Doze-Throttling: nach
TTS-Ende schedulet resume() einen setTimeout(800ms) der den Conversation-
Window-Callback feuert. Im Hintergrund parkt der JS-Thread, der Timer
feuert erst beim App-Resume — gefuehlt ein Phantom-Trigger.

Fix: scheduledAt-Timestamp messen, Delay nach dem setTimeout pruefen.
Wenn der Timer >2.8s ueberfaellig ist (Schwelle = 800ms + 2000ms
Toleranz), JS war im Background → endConversation statt Mikro-oeffnen.

Wenn der User wirklich nachfragen will sagt er einfach nochmal "Computer".
2026-05-30 22:23:13 +02:00
duffyduck 0bf6d49432 fix(app): UI-Fallback wenn Whisper-Bridge nicht antwortet
streamEndpointFired-Latch + neue _fireEndpoint(ev)-Methode konsolidieren
die drei Pfade die den Endpoint-Listener feuern (RVS-stt_endpoint, cancel,
neuer Fallback). Listener feuert pro Session-Cycle maximal einmal.

stopStreamingRecording bekommt einen 3-Sekunden-Watchdog: kommt in dem
Fenster keine echte stt_endpoint-Antwort der Bridge, feuert der
Listener mit text='' (reason=stop:...:no-response) damit ChatScreen
die "wird verarbeitet"-Bubble unstickt + endConversation aufruft.

Greift praktisch in zwei Faellen:
  - Whisper-Bridge laeuft alte/keine Streaming-Version (Stefan Gamebox-
    Restart vergessen) → wir bleiben sonst bis zur 60s-Hardcap haengen
  - User-initiated Stop + Whisper langsam/crashed
2026-05-30 22:09:02 +02:00
duffyduck a68827fb38 fix(updater): parseInt(number) -> Number() — fileSize.size ist schon number 2026-05-30 21:45:17 +02:00
duffyduck be1d2e950a feat(app): Streaming-STT-Pipeline — Phase 1+2 verdrahtet
audio.ts:
  - neue Methoden startStreamingRecording / stopStreamingRecording /
    cancelStreamingRecording mit PcmStreamRecorder als AudioRecord-Source
  - permanenter RVS-Listener fuer stt_partial / stt_endpoint / stt_stream_done,
    Filterung ueber streamRequestId-Match
  - Callbacks onSttEndpoint(SttEndpointEvent) + onSttPartial(text)
  - No-Speech-Watchdog + App-seitiger Hard-Cap (+2s Toleranz gegen Bridge)
  - cancelStreamingRecording feuert onSttEndpoint mit text='' damit
    ChatScreen den No-Speech-Fall behandeln kann (wie frueher
    onSilenceDetected -> stopRecording() -> null)
  - Legacy startRecording / stopRecording / onSilenceDetected unangetastet
    -- VoiceButton (manuelle Aufnahme) nutzt das weiterhin

ChatScreen.tsx:
  - Wake-Callback: startRecording -> startStreamingRecording
  - Bubble wird sofort gebaut, audioRequestId landet via
    stt_endpoint -> chat(sender=stt) im chat-Handler-Update-Pfad wie bisher
  - onSilenceDetected entfernt, ersetzt durch onSttEndpoint:
      text != '' -> log, aria-bridge triggert Brain selbst (Phase-2-Shortcut)
      text == '' -> endConversation (No-Speech-Fall)
  - Barge-In via Wake-Word: ebenfalls auf Streaming umgestellt
  - AppState-resume + toggleWakeWord-off pruefen jetzt isStreamingRecording()
    und nutzen passenden Cancel

Damit: kein dB/VAD mehr im Hot-Path. Whisper hoert auf semantische
Stille (kein neuer Text), Brain bekommt den Text direkt von aria-bridge,
Audio-Roundtrip App->aria->whisper->aria->App entfaellt komplett.
2026-05-30 21:42:02 +02:00
duffyduck 0145179aca fix(wake): kein setTimeout zwischen wake.detect und Callback — JS-Timer im Doze unzuverlaessig
Bridge-Log-Analyse zeigte: setTimeout(200ms) in onWakeDetected feuert im
Hintergrund (Display aus) entweder gar nicht oder erst nach 8+ Sekunden,
auch mit aktivem PARTIAL_WAKE_LOCK + Foreground-Service. Hermes parkt den
JS-Thread sobald er idle ist und wartet auf Native-Wake-Events; die
Bridge-Queue fuer Timer kommt erst dran wenn irgendein Native-Event
(z.B. Audio-Sample) den Thread weckt.

Drei Wake-Events live mitgelesen:
  - Vordergrund:  Timer feuert +209ms (ok)
  - Hintergrund:  Timer feuert +8061ms (wake-callback verspaetet)
  - Hintergrund:  Timer feuert nie (>5 min, gong-Sound bleibt aus)

OpenWakeWord.stop() ist davor awaited → Mikro ist garantiert frei.
Der 200ms-Sicherheitsabstand war Belt-and-Suspenders, jetzt entbehrlich.
Callback wird direkt synchron gefeuert.
2026-05-30 21:00:45 +02:00
duffyduck 98982fea2f feat(app): App-Logs live im Settings → Protokoll → Live Logs Tab anzeigen
Stefan: "wir haben live log + events tab in protokoll einstellungen, da
ist aber nie was drin".

Bisher hoerten Live Logs / Events nur auf RVS-Messages type='log'/'event'
von der Bridge — die Bridge schickt aktuell aber keine solchen Messages
zurueck zur App. Plus: reportAppDebug/Error ging nur an die Bridge in
/shared/logs/app.log, lokal in der App war nichts sichtbar.

Loesung: lokaler DeviceEventEmitter-Bus.

logger.ts:
- APP_LOG_EVENT Konstante exportiert
- reportAppError + reportAppDebug emittieren ZUSAETZLICH zum
  RVS-Send ein lokales DeviceEventEmitter-Event (errors immer,
  debug nur wenn Toggle AN)

SettingsScreen.tsx:
- DeviceEventEmitter.addListener auf APP_LOG_EVENT
- Mappt Log-Entries 1:1 in den 'logs'-State (max 200)
- Cleanup in useEffect-return

Damit sieht Stefan beim Debuggen (Debug-Toggle AN, Live-Logs-Tab
offen) live in der App was passiert — ohne curl gegen Bridge.

APK neu bauen erforderlich.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:44:42 +02:00
duffyduck 356f8b3171 feat(app): Debug-Logs-an-Bridge Toggle (Settings → Protokoll, default aus)
Stefan: "haben wir einen Menupunkt logging? sonst muellen wir uns dicht
wenns funktioniert und wir das logging im moment nicht brauchen"

Stimmt. reportAppDebug() schickt aktuell IMMER an Bridge, auch wenn
gar nicht debuggt wird. Bei armed Wake-Word + Pipeline-Logs sind das
schnell ein Dutzend Eintraege pro Wake-Trigger.

Loesung: separater Settings-Toggle "Debug-Logs an Bridge" mit eigenem
AsyncStorage-Key (aria_debug_logs_to_bridge), Default AUS.

- logger.ts: _debugLogsToBridge flag + isDebugLogsToBridge() /
  setDebugLogsToBridge(). initLogger() laedt den Wert. reportAppDebug()
  prueft das Flag und schickt nur wenn AN.
- SettingsScreen: neuer Toggle direkt unter Verbose-Logging,
  orange (#FF9500) damit er als "Power-User-Option" erkennbar ist,
  mit Erklaerungs-Hinweis dass nur Info-Logs gefiltert werden,
  Crash-Reports (Errors via reportAppError) gehen weiterhin IMMER.

Workflow:
- Default-User: Toggle aus, kein Traffic, kein Disk-Schreiben
- Stefan beim Debuggen: Toggle an, testet die App, schaut Logs via
  curl /api/app-log?lines=N, schaltet wieder aus

APK neu bauen erforderlich.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:41:40 +02:00
duffyduck b4115bb345 debug(wake): mehr Log-Punkte zwischen onWakeDetected-Trigger und Callback-Feuern
Stefan's Test zeigt: 'wake.detect keyword=computer state=armed' kommt
im Background durch (WakeLock greift!), aber 'wake.cb callback fired'
aus ChatScreen fehlt. Heisst: zwischen Detection und Callback-Feuern
geht's irgendwo verloren.

Mehr Logs:
- nach OpenWakeWord.stop(): 'native stop ok' oder 'native stop FAIL msg'
  → klaert ob async stop() haengt
- vor setTimeout: 'state→conversing, wakeCallbacks.length=N, scheduling'
  → klaert ob Liste leer ist (ChatScreen unmounted) und ob wir's
    schedulen
- im setTimeout: 'timeout fired, state=X, cbs=N'
  → klaert ob der Timer in 200ms tatsaechlich feuert (Doze-Throttle?)
- bei barge-path: 'barge path: cbs=N'

Damit sehen wir genau wo's klemmt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:38:14 +02:00
duffyduck d78b668e31 feat(app): reportAppDebug — Live-Debug-Logs an Bridge ohne ADB
Stefan-Anforderung: Background-Wake-Word-Pipeline klappt noch nicht,
ADB nicht zur Hand → Debug via RVS-Log-Pipeline.

Logger:
- reportAppDebug(scope, message) analog zu reportAppError aber
  level=info, kein console.error, fuer Live-Diagnose

Strategische Log-Punkte:
- wakeword.ts: start() emits 'wake.start armed'
- wakeword.ts: onWakeDetected emits 'wake.detect state=X' beim
  Native-Trigger-Empfang
- ChatScreen.tsx wake-callback: 'wake.cb callback fired',
  'wake.cb startRecording=X', 'wake.cb gong played'
- backgroundAudio.ts: 'bg.start slot=X', 'bg.stop service stopped',
  'bg.start.fail msg' wenn Service nicht hochkommt

Abruf live via curl http://172.0.2.33:3001/api/app-log?lines=100

Damit kann Stefan nach APK-Build (mit allen Native-Fixes + Logger)
im Background-Test exakt sehen wo es klemmt:
- Kommt 'wake.detect' im Hintergrund an? (WakeLock-Frage)
- Kommt 'wake.cb callback fired'? (JS-Bridge-Frage)
- Geht 'bg.start slot=wake' durch? (Service-Start-Frage)

APK neu bauen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:27:29 +02:00
duffyduck f2bfd4bbc6 feat(app): Background-GPS als opt-in Settings-Toggle
Stefan-Anforderung: GPS soll auch im Hintergrund liefern (Auto-Szenarien,
Handy-Tasche), aber NUR fuer Power-User die das bewusst aktivieren.
Mama-Tauglichkeit bleibt erhalten — Default AUS, keine Surprise-Permission.

Aenderungen:

AndroidManifest:
- ACCESS_BACKGROUND_LOCATION Permission
- FOREGROUND_SERVICE_LOCATION Permission
- AriaPlaybackService foregroundServiceType erweitert um |location
  (vorher: mediaPlayback|microphone)

backgroundAudio.ts:
- Neuer Slot 'location' zwischen 'wake' und 'background' in der
  Prioritaeten-Liste. Notification zeigt entsprechend.

gpsTracking.ts:
- isBackgroundGpsEnabled() / setBackgroundGpsEnabled() AsyncStorage-Helper
- ensureBackgroundLocationPermission() pruefte ACCESS_BACKGROUND_LOCATION
  und oeffnet Android-Settings wenn fehlend (auf Android 10+ kann das
  NICHT ueber den normalen Permission-Dialog angefordert werden)
- start(): wenn BG-GPS enabled, acquireBackgroundAudio('location') →
  Foreground-Service hochziehen mit type=location
- stop(): releaseBackgroundAudio('location')

SettingsScreen.tsx:
- Neuer Toggle "GPS auch im Hintergrund" direkt unter dem
  GPS-Tracking-Toggle, rot (#FF3B30) statt orange weil's eine stark
  privacy-relevante Einstellung ist
- Erklaerungs-Text zu Android-Settings + Akku-Verbrauch
- Beim Aktivieren: Permission-Check, ggf. Android-Settings oeffnen
- Wenn Tracking bereits laeuft: neustart damit location-Slot greift

APK neu bauen erforderlich.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:19:16 +02:00
duffyduck 9818dc1867 fix(app): Spotify resumed wieder nach TTS — nudgeMediaResume mit TRANSIENT
Stefan-Bug-Report: ARIA liest Nachricht vor, Spotify pausiert korrekt,
ARIA spricht durch — aber Spotify spielt danach NICHT automatisch
weiter. Sollte mit GAIN_TRANSIENT auto-resumen, tut es aber bei
manchen Spotify-Versionen/Geraeten nicht zuverlaessig.

Hintergrund: alte kickReleaseMedia() mit AUDIOFOCUS_GAIN (permanent)
war zu aggressiv (Spotify interpretierte als "user stoppte" =
Auto-Resume kaputt). Wurde entfernt. Jetzt ist das Pendel andersrum
zu weit: ohne Nudge keine Resume.

Sanfter Mittelweg: nudgeMediaResume() mit GAIN_TRANSIENT statt
GAIN-permanent. 100ms hold, abandon. Spotify bekommt Focus-Wechsel-
Hint ohne "user stopped"-Effekt.

audio.ts: nach AudioFocus.release() 50ms warten, dann nudgeMediaResume.
AudioFocusModule.kt: neue Methode + alte kickReleaseMedia bleibt mit
⚠️-Markierung fuer andere Use-Cases.

APK neu bauen erforderlich.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:09:55 +02:00
duffyduck 8359500476 feat(skills): P3 config_schema + P4 Versionierung mit Rollback
P3 — Skill-Configuration
- aria-brain/skills.py: SKILL_CONFIGS_FILE (/shared/config/skill_configs.json)
  als zentrale Werte-Persistenz. _normalize_config_schema validiert die
  Schema-Felder (name/type/label/secret/description/default), CFG_<UPPER_NAME>
  ENV beim run_skill. create_skill + update_skill akzeptieren config_schema.
- agent.py: skill_set_config Brain-Tool fuer ARIA. skill_create/update um
  config_schema-Property erweitert.
- main.py: GET/POST /skills/{name}/config — secret-Werte in Antwort gemaskt.

P4 — Versionierung mit Rollback
- aria-brain/skills.py: archive_current_version archiviert nach
  versions/v_<ts>/ (ohne venv/logs). update_skill ruft das automatisch auf
  bevor strukturelle Aenderungen passieren. list_skill_versions,
  rollback_skill (mit Safety-Snapshot + automatischem venv-Rebuild),
  delete_skill_version.
- agent.py: skill_list_versions, skill_rollback Brain-Tools.
- main.py: GET /skills/{name}/versions, POST /skills/{name}/rollback,
  DELETE /skills/{name}/versions/{version_id}.

UI
- diagnostic/index.html: Skill-Detail um Config-Form (typ-spezifisch,
  Secrets als password-Input mit ***SET***-Hinweis) und Versions-Liste
  mit Rollback-/Delete-Button.
- android SkillBrowser: SkillDetailModal laedt config_schema + versions
  on-mount. Config-Form (TextInput + Switch fuer boolean), Versionen mit
  Rollback-Confirm. brainApi um SkillConfigField/SkillVersion +
  getSkillConfig/setSkillConfig/listSkillVersions/rollbackSkill/
  deleteSkillVersion erweitert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:52:46 +02:00
duffyduck ad87c807de fix(app): App-Reconnect nach Hintergrund — Sticky-Fallback, Zombie-WS, AppState-Hook
Stefan musste seit der HTTPS-Umstellung nach jedem Hintergrund-Rueckkehr
manuell auf "Verbinden" tippen, meist 3x bis es ging. Gleiche Bug-Klasse
wie auf der Bridge davor (Sticky-Fallback), plus zwei App-spezifische
Symptome.

Drei Ursachen:

1. usingTLSFallback klebt: einmal nach onerror auf true gesetzt, blieb
   es bei allen folgenden Reconnects → App versuchte ws://...:443 gegen
   den TLS-only Caddy → HTTP 400 → endlos. Reset war NUR im manuellen
   connect(), nicht in onclose oder scheduleReconnect.
   Fix: in onclose `usingTLSFallback = false` damit der naechste
   Reconnect wieder primary (wss://) probiert.

2. Zombie-WebSocket: Android kann den TCP-Socket im Background still
   killen, der JS-State zeigt aber noch readyState === OPEN. Stefans
   manueller "Verbinden"-Klick rief connect() → "Bereits verbunden"
   No-Op statt sich neu aufzubauen.
   Fix: connect(force=true) optional, bestehendes WS-Objekt wird hart
   geschlossen (mit onclose=null gegen Doppel-Reconnect) bevor neuer
   Aufbau startet.

3. Keine aktive Reconnect-Sequence bei Foreground-Resume: App war
   abhaengig von onclose-Events die bei Zombie-WS nicht zwingend
   feuern.
   Fix: AppState-Listener in App.tsx, bei background → active
   automatischer rvs.connect(true).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:09:30 +02:00
duffyduck 13e87fb083 feat(oauth): ARIA kann Provider selbst registrieren + Custom-Provider in Diagnostic & App
ARIA hat jetzt das META-Tool oauth_register_provider. Wenn Stefan einen
Service nutzen will, der nicht in den (auf Spotify reduzierten) Defaults
ist, kann sie auth_url/token_url/scopes/client_auth selbst eintragen —
ARIA kennt typische OAuth-Endpunkte (Dropbox, Discord, Notion, Slack,
Zoom, Trello, LinkedIn, Reddit, Twitch) aus ihrem Training. Sie traegt
NUR die URLs ein, client_id/secret bleiben Stefans Job (Diagnostic /
App-UI) — bewusste Trennung damit Credentials nicht im Chat-Verlauf
landen.

DEFAULT_PROVIDERS auf Spotify reduziert — Rest war aktuell ungenutzt
und macht den Code unnoetig "groß". ARIA registriert on-demand.

Diagnostic-UI:
- Custom-Provider zeigen auth_url/token_url/scopes als sichtbare Felder
- Defaults verstecken die Felder hinter "Default-URLs ueberschreiben
  (advanced)" damit man die Spotify-URLs nicht versehentlich loescht
- "+ Custom OAuth-Provider hinzufuegen" Button mit Prompts fuer
  Name/URLs/Scopes
- 🗑-Icon bei Custom-Services (Service komplett entfernen)

App-UI (neu fuer unterwegs):
- Settings → Sektion 🔑 "OAuth-Apps" zwischen Skills und Protokoll
- OAuthBrowser-Komponente analog zu Trigger/Skill-Browser:
  Liste mit Status, Tap → Edit-Modal mit client_id/secret +
  Advanced-Toggle fuer URLs. "Autorisieren ↗" oeffnet System-Browser
  via Linking.openURL, redirected zur RVS-Callback-Page,
  Status-Refresh nach 8s.
- "+ Custom"-Button → Full-Screen-Modal fuer Service-Anlage.
- brainApi um listOAuthServices/getOAuthApps/saveOAuthApp/
  deleteOAuthApp/authorizeOAuth/revokeOAuth erweitert.

Workflow ist jetzt: "verbinde mich mit Dropbox" → ARIA registriert
Provider → "trag client_id/secret in Settings ein" → Stefan macht das
in App oder Diagnostic → "Autorisieren ↗" → fertig.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:16:31 +02:00
duffyduck 30c1dd7473 feat(app+brain): App-Bugfixes + Skill-Mgmt-Tools + Voice-Speed persistent + Skill-Browser
App-Bugs:
- Trigger-Liste war leer: brainApi.listTriggers() cast'te {triggers: [...]}
  direkt als Array, t.sort() warf — TriggerBrowser blieb leer. Fix: unwrap.
- GPS-Tracking startete erst bei SettingsScreen-Mount, nicht beim App-Boot.
  Wenn Stefan direkt in den Chat ging, blieb GPS aus. Fix: restoreFromStorage()
  in App.tsx useEffect.
- Text in Chat-Bubbles nicht markierbar / kein Copy-Mechanismus: Bubble jetzt
  Pressable mit onLongPress + neues ⎘-Icon in Status-Row → openBubbleActions().
  Alert-Menu mit "Ganzen Text teilen" + pro extrahierte URL/Mail/Tel eine
  eigene Option. Share.share() — keine neuen Native-Deps noetig.

Brain — Skill-Mgmt:
- ARIA legte beim Skill-Umbau neue Versionen mit Suffix an (Skill-Friedhof),
  weil sie kein Update/Delete-Tool kannte. Zwei neue META_TOOLS in agent.py:
  skill_update (kann entry_code, readme, pip_packages, args, description,
  active patchen — venv wird bei pip_packages-Aenderung rebuilt) + skill_delete.
- skills.py update_skill um entry_code/readme/pip_packages erweitert,
  venv-Rebuild bei pip-Aenderung.

Bridge — Voice-Speed persistent:
- _next_speed_override war pro-Request-Override ohne Persistenz. Bei
  Diagnostic-Chats / Trigger-Replies ohne vorherigen App-Chat fiel der Speed
  auf 1.0 zurueck, ebenso nach Bridge-Restart. Jetzt: _persistent_xtts_speed
  aus voice_config.json (xttsSpeed), wird nach jedem App-chat mit speed
  autopersistiert. TTS-Generation faellt zurueck: per-Request > persistent > 1.0.

App — Feature 6:
- SkillBrowser.tsx: Liste aller Skills, Toggle aktiv/inaktiv, Detail-Modal
  mit Args-Inputs, Ausfuehren mit Live-stdout/stderr, Logs der letzten 20
  Runs, Loeschen. Settings-Sektion "Skills" (🛠️) zwischen Trigger und
  Protokoll. brainApi.listSkills/getSkill/runSkill/updateSkill/deleteSkill/
  getSkillLogs ergaenzt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:24:03 +02:00
duffyduck 2d348aeec7 feat(flux): Modell-Wahl per Diagnostic + raw/switch-Keywords + Download-Hinweis
Diagnostic-Einstellungen fuer FLUX:
- Default-Modell (dev | schnell) — wird via RVS gepusht, flux-bridge
  hot-swappt die Pipeline aus dem HF-Cache (~15-30s)
- Raw-Keyword (Default 'flux') — Pipe-Modus, Brain leitet Stefans Text
  1:1 als prompt durch, kein Rewriting/Beautify
- Switch-Keyword (Default 'fix') — zwingt das ANDERE Modell als Default

Brain-Tool flux_generate um model + raw erweitert, System-Prompt-Block
mit den aktuellen Diagnostic-Settings + Whisper-Toleranz-Hinweis.

Kein eager Bootstrap-Load: flux-bridge wartet auf config oder ersten
Request. Bei erstem HF-Download zeigt Banner "laedt erstmalig runter"
mit Pfeil-Icon, Toast in der App wenn fertig.

FLUX_MODEL aus der .env entfernt (Steuerung jetzt komplett ueber
Diagnostic). HF_TOKEN-Kommentar erklaert warum trotz lokaler Inference
noetig (HF Gate-Mechanismus fuer FLUX.1-dev).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 23:11:22 +02:00
duffyduck 785f5d0805 fix(bridge): grosse File-Re-Downloads zerreissen nicht mehr die WS
Symptom (aus Bridge-Log): bei chat_history_request triggert die App
file_request fuer alle fehlenden Anhaenge. Bei einem 40 MB MP4 wird das
base64-encoded ~53 MB, ueberschreitet das RVS-maxPayload (50 MB).
Server droppt mit Code 1009 'message too big', Bridge crasht im cleanup
mit AttributeError 'NoneType has no call_soon' (websockets-Lib-Bug bei
nested context-manager-cleanup nach abgerissener Verbindung).

Drei Layer:

(1) RVS-Server: maxPayload 50 → 100 MB — deckt ~70 MB binaer ab nach
    base64-inflate. Comment im server.js erklaert den Hintergrund.

(2) Bridge: max_size 50 → 100 MB synchron zum Server. PLUS pre-check
    im file_request-Handler — Dateien > 70 MB werden mit Fehler-Response
    abgewiesen statt blind base64-zu-encoden und die WS zu killen.
    Limit knapp unter Server-Limit damit Bridge proaktiv blockiert.

(3) App: file_response-Handler liest 'error'-Feld aus dem Payload und
    zeigt nen Toast 'Datei X: Datei zu gross fuer Transfer (40 MB,
    Limit 70 MB)'. Statt einfach zu schweigen oder endlos zu retryen.

Crash bei websockets-cleanup ist ein Lib-Bug (NoneType.call_soon) —
nicht direkt fixbar, aber tritt jetzt nicht mehr auf weil Bridge proaktiv
die zu grossen Files ablehnt und die WS nicht mehr abreisst.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:18:52 +02:00
duffyduck 4fe72cc4a8 feat(chat): System-Hints in Bubbles ausblenden (Toggle in Settings)
Bridge fuegt User-Texten Praefixe in eckigen Klammern hinzu damit Brain
Kontext hat — z.B. '[Stefans aktuelle GPS-Position: 53.0, 8.5. Nutze die
nur wenn ...]' oder '[Hinweis: Stefan hat dich gerade unterbrochen...]'.
Die landeten via chat_backup auch in der App-Bubble — Stefan sieht jeden
Hint mit, hat nichts in der UI verloren.

Fix: App-side stripSystemHints() filtert aufeinanderfolgende `[...]`-
Bloecke am Textanfang inkl. Trennleerzeichen. Wird in renderMessage
angewendet, default an (Hints versteckt). Toggle in Settings →
Allgemein → 'Chat-Bubbles' kehrt's um falls Debug gewuenscht.

Brain bekommt weiterhin den vollen Text — Bridge-Side unveraendert.
Live-Toggle: Settings setzt aria_show_hints in AsyncStorage, ChatScreen
re-liest alle 2s (gleicher Mechanismus wie tts_enabled etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:21:12 +02:00
duffyduck 0044e222db fix(phone): Anruf-Erkennung im Hintergrund + bei gesperrtem Display
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>
2026-05-16 15:59:55 +02:00
duffyduck 048d231b60 fix(wake): false-positive nach langer Hintergrund-Pause verwerfen
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>
2026-05-16 15:54:07 +02:00
duffyduck cb0e879118 feat(app): Hintergrund-Modus — App laeuft weiter wenn minimiert
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>
2026-05-16 11:27:01 +02:00
duffyduck ce6f5b551e fix(chat): Gedanken-Stream scrollt jetzt + Suche praeziser
(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>
2026-05-16 11:11:38 +02:00
duffyduck 03edee8881 fix(app): Inbox Scroll-Bug + feat(diagnostic): Trigger-Edit
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>
2026-05-15 22:49:51 +02:00
duffyduck 7093ebaf0b feat(app): Trigger-CRUD-Section in Settings + nested-Scroll-Fix
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>
2026-05-15 22:44:24 +02:00
duffyduck b510ccd93a fix(app): Such-Reihenfolge + About-Escape + GPS-Heartbeat fuer near()
(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>
2026-05-15 22:30:51 +02:00
duffyduck 2cd436f6e9 fix(chat): Such-Sprung praezise via Layout-Cache + Filter
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>
2026-05-15 21:42:08 +02:00
duffyduck 61cf8e3bcc fix(chat): Such-Sprung beim ersten Versuch nach App-Start
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>
2026-05-15 12:10:13 +02:00
duffyduck 635944299e fix(chat): Such-Scroll springt nicht mehr endlos (Retry-Limit + Skip)
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>
2026-05-15 11:58:54 +02:00
duffyduck 93db6a3156 fix(chat): Gedanken-Stream Modal scrollt jetzt
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>
2026-05-15 11:48:39 +02:00
duffyduck 5133f0bc2d fix(chat): User-Bubble →failed bei langsamen ARIA-Antworten
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>
2026-05-15 11:43:04 +02:00
duffyduck 11b205ddaf fix(chat): chat_backup ts auf UNIX-ms umgestellt + Doppelpost-Schutz
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>
2026-05-15 11:26:39 +02:00
duffyduck bf3dc635d9 feat(brain): Live-Tool-Events im Gedanken-Stream
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>
2026-05-15 11:07:39 +02:00
duffyduck 71fc90fcb8 fix(brain): Timeouts 5min → 20min — verkettete Timeouts haben lange
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>
2026-05-15 08:40:26 +02:00
duffyduck 856701fb6f feat(chat): Gedanken-Stream (App + Diagnostic)
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>
2026-05-15 08:31:55 +02:00
duffyduck 8f88cb0030 fix(chat): Doppel-Bubble nach Retry + verwaiste ACK-Timer + docs
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>
2026-05-14 23:46:58 +02:00
duffyduck 5c07aef526 fix(chat): Offline-Bubble verschwand nach Reconnect — clientMsgId-Dedup
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>
2026-05-14 23:14:11 +02:00
duffyduck 205112021b fix(chat): Such-Scroll + Doppel-Send-Hang + Delivery-Handshake
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>
2026-05-14 22:55:44 +02:00
duffyduck 7a22474efd feat(chat): Jump-down-Button + Sprung-an-Text-Anfang + Vision-Issue raus
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>
2026-05-14 22:10:26 +02:00
duffyduck 7e5a4da659 fix(app): Memory-Liste in Settings scrollt jetzt (nestedScrollEnabled)
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>
2026-05-14 17:14:21 +02:00
duffyduck d27fcaf342 fix(chat): Cache leer + Datei-Tap → Auto-Re-Download statt Fehlertoast
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>
2026-05-14 16:14:56 +02:00
duffyduck e74e1eaf70 fix(app): URLSearchParams crasht in Hermes — durch Mini-Query-Builder ersetzt
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>
2026-05-14 16:06:21 +02:00
duffyduck 21a315ca71 feat(debug): App-Crash-Reporting via RVS — Logs in der Diagnostic-UI
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>
2026-05-14 15:42:55 +02:00
duffyduck e88b5f57bf fix(memory): Inbox-Crash auf Android — Modal-Stacking + Alert.prompt
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>
2026-05-14 14:05:42 +02:00
duffyduck ebeacba8b5 fix(chat): Spezial-Bubbles raus aus Chat → Inbox + Emoji-Bug behoben
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>
2026-05-14 13:56:39 +02:00
duffyduck 5c10990cbc feat(memory): Notizen-Inbox + Settings-Editor (Etappen 4+5)
Etappe 4 — 🗂️ Notizen-Inbox-Button neben der Lupe:
- Statusleiste hat jetzt zwei Icons: 🗂️ Inbox + 🔍 Suche
- Tap auf Inbox-Icon oeffnet ein Vollbild-Modal mit MemoryBrowser-
  Komponente. User sieht alle Memories aus der DB, kann suchen,
  filtern, neu anlegen, und in den Detail/Edit-Modus springen.

Etappe 5 — Memory-Editor in App-Settings:
- SETTINGS_SECTIONS um Eintrag 🧠 "Gedächtnis" erweitert
- Sektion rendert MemoryBrowser (selbe Komponente wie Inbox) in
  einer 600px-Box — vom Diagnostic-Gehirn-Tab inspiriert, aber
  fuer's Handy optimiert
- Beide Stellen recyclen MemoryBrowser+MemoryDetailModal aus
  Etappe 2/3 — kein doppelter Code

MemoryBrowser (neue Komponente components/MemoryBrowser.tsx):
- Lazy-Load aller Memories via brainApi.listMemories
- Client-side Filter: Volltext-Suche (Title+Content+Category+Tags),
  Type-Dropdown, Pinned/Cold/Alle-Toggle
- "+ Neu" Knopf mit Alert.prompt fuer Titel, automatisch type=fact,
  oeffnet danach den DetailModal zum Editieren des Contents
- Item-Render mit Pinned-Marker, Anhang-Badge 📎N, Type-Label,
  Category, 2-Zeilen-Content-Preview
- Tap auf Item oeffnet MemoryDetailModal → CRUD weiter dort

Damit sind alle 5 Etappen aus Stefans Wunsch-Trio durch:
- Bubble-Header dynamic (Etappe 1, committed gestern)
- Tap-Modal mit Detail (Etappe 2)
- Edit + Anhang-Upload im Modal (Etappe 3)
- Notizen-Inbox-Button (Etappe 4)
- Memory-Editor in Settings (Etappe 5)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:29:43 +02:00
duffyduck f71936da86 feat(memory): Tap auf Memory-Bubble oeffnet Detail+Edit-Modal in der App (Etappen 2+3)
Stefans naechste Wunsch-Etappe — komplettes Edit eines Memory-Eintrags
aus der App heraus, inkl. Anhang-Upload, ohne Diagnostic-Browser
auszuklappen.

Backend-Fundament (Phase A):
- Brain bekommt GET /memory/get/{id} fuer Einzel-Lookup mit allen Feldern
- RVS ALLOWED_TYPES um brain_request + brain_response erweitert
- Bridge implementiert generischen RVS-Brain-Proxy:
  payload {requestId, method, path, body|bodyBase64, contentType}
  → ruft Brain-HTTP-API → broadcastet brain_response {requestId,
  status, json|text|base64+contentType}. Damit kann die App
  beliebige Brain-Endpoints ueber RVS adressieren — nicht nur Memory.

App-Service (Phase B):
- services/brainApi.ts: Promise-basierter Client. _send() schickt
  brain_request mit requestId, _ensureListener() filtert die passende
  brain_response. Methoden: getMemory, listMemories, searchText,
  searchSemantic, saveMemory, updateMemory, deleteMemory,
  uploadAttachment (Base64), deleteAttachment, getAttachmentBytes.

App-UI (Phasen C+D):
- components/MemoryDetailModal.tsx: Modal mit zwei Modi.
  - Read: Titel, Type, Category, Tags, voller Content, Anhang-Liste
    (Tap = Bild im Vollbild oder Datei-Info), Stift-Icon → Edit.
  - Edit: Titel/Content/Category/Tags/Pinned editierbar, Save via
    brainApi.updateMemory.
  - DocumentPicker + RNFS.readFile(base64) → uploadAttachment(...).
  - Anhang loeschen, kompletter Memory loeschen (mit Alert-confirm).
- ChatScreen: TouchableOpacity-Wrapper um die memorySaved-Bubble,
  Tap setzt memoryDetailId → Modal oeffnet. Hint im Footer
  "tippen für Details" wenn die Bubble eine ID hat.

Etappen 4 (Notizen-Inbox neben Lupe) + 5 (Memory-Editor in App-
Settings) folgen — diese nutzen die gleiche MemoryDetailModal-
Komponente, sind also schnell aufgesetzt sobald 2+3 verifiziert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:26:02 +02:00