# Voice-Calls — Debug-Zustand (2026-06-05)

Momentaufnahme beim Live-Debugging mit 2 Geräten (iOS + Android, beide über Metro).
Ziel: Kinderkrankheiten der Call-Funktion per Teile-und-Herrsche fixen.

## Architektur (Ist-Zustand)

Zwei Zustell-Pfade zum Callee — **beide feuern IMMER parallel** (Caller macht beides):

1. **Realtime** `call-ring:<calleeId>` → `useIncomingCalls` → `receiveIncoming()` + `router.push('/call')`.
   Greift nur wenn die App lebt + Realtime subscribed (FG, oder Android kurz nach BG).
2. **Push** `POST /api/calls/ring` → `sendCallRingPush`:
   - **iOS + voipToken** → VoIP-PushKit → `AppDelegate.didReceiveIncomingPush` → `RNCallKeep.reportNewIncomingCall` (CallKit) → JS-Forward via `useCallKeepEvents`.
   - **sonst** (Android, iOS ohne voipToken) → Expo-Push (channel `calls`, `data.type='call'`). Reagiert **nur auf Tap** (`addNotificationResponseReceivedListener`).

Kern-Spannung: im FG entsteht Doppel-UI (in-app `/call` **und** CallKit/ConnectionService).
iOS-BG ist durch Apple auf CallKit festgenagelt (PushKit-Pflicht). Android-BG ist frei wählbar.

## Verhaltens-Matrix

| # | Anruf | Callee-State | Erwartet | Beobachtet | Hypothese (Root Cause) |
|---|-------|--------------|----------|------------|------------------------|
| 1 | Android→iOS | FG | `/call` bleibt, klingelt | `/call` erscheint, **verschwindet nach ~Sek** | VoIP-Push feuert auch im FG → CallKit übernimmt → App→`inactive` → CallKit-`endCall` feuert mit `appState≠active` → `onEnd`-Guard (`appState==='active'`) greift NICHT → `declineCall()` schließt `/call`. **(Logs nötig)** |
| 2 | iOS→Android | FG | korrekt | **korrekt** | — |
| 3 | Android→iOS | BG/locked | CallKit-Incoming (Apple) | **nichts**; nach Auflegen „verpasster Anruf"-Push; Telefon-App zeigt kein ReBreak-Audio | VoIP-Push erreicht Gerät nicht / CallKit nicht reported. Verdacht A: `voipToken=null` in DB → Fallback Expo-Push (im BG silent). Verdacht B: VoIP gesendet, aber APNs-Fehler (env/topic/cert). „Verpasster Anruf"-Push = Chat-DM aus `logCallToChat`. **(Logs nötig)** |
| 4 | iOS→Android | BG (App lebt) | ReBreak `/call` | **Android Telecom-native Incoming-UI** | Realtime im BG → `receiveIncoming` → `displayIncomingCall` → ConnectionService = System-Telecom-UI. „Working as coded", aber Design-Mismatch: gewünscht ist Custom-`/call` (Full-Screen-Intent). |

## Divide & Conquer — Phasen

- **Phase 0 — Diagnose** (non-destruktiv): 4 Szenarien durchspielen + Logs einsammeln
  (`[call-ring] voip=?`, `[voip-push]`, `[VoIP] didReceiveIncomingPush`, `[callkeep] end … appState=`).
  Bestätigt #1 und #3.
- **Phase 1 — iOS FG (#1)**: `/call` autoritativ machen; CallKit im FG unterdrücken / sauber
  programmatisch beenden mit „programmatic-end"-Flag; `onEnd`-Guard reparieren (`inactive` = FG).
- **Phase 2 — iOS BG (#3)**: VoIP-Pfad fixen — voipToken-Registrierung + APNs env/topic/cert
  verifizieren, bis CallKit im BG zuverlässig erscheint.
- **Phase 3 — Android BG (#4)**: Entscheidung Custom-Full-Screen-Intent vs Telecom-UI; ggf. umbauen.
- **Phase 4 — Aufräumen**: Doppel-Push/Doppel-Log, Missed-Call-Handling, einheitlicher Teardown.

## ✅ BESTÄTIGTE Root Cause #3 (2026-06-05, via idevicesyslog + git)

Build 76 wurde aus dem **alten** `with-voip-pushkit-ios.js` kompiliert, wo die PushKit-Registry
eine **lokale Variable** war (`let voipRegistry = PKPushRegistry(queue: nil)`) → deallociert
sofort nach `didFinishLaunching` → eingehende VoIP-Pushes erreichen `didReceiveIncomingPush` nie
→ kein `reportNewIncomingCall` → kein CallKit-Wake. Token-Registrierung klappt trotzdem
(Sync-Callback), daher `voip=1` am Server, aber Pushes verpuffen.

**Beweise (idevicesyslog iPhone Air, Build 76):**
- `[voip] register event fired, token: 33899c2b…` ✅ (Token-Reg ok)
- `[voip-push] sent env=sandbox` am Server ✅ (Push verlässt Server)
- **`[VoIP] didReceiveIncomingPush` / `reportNewIncomingCall` FEHLEN komplett** ❌
- CallKit-Fehler `requesttransaction Code=4` (unknown UUID) beim End-Versuch = Call war CallKit nie bekannt
- „verpasster Anruf"-Push = Chat-DM aus `logCallToChat` beim späteren FG-Realtime-Replay, NICHT der Ring

**Fix:** bereits geschrieben (uncommitted) — Registry als `self.voipRegistry` retainen.
Liegt im `ios/ReBreak/AppDelegate.swift` (Jun 4 20:35), aber **nie in einen Build kompiliert**.
→ Dev-Rebuild (`expo run:ios --device`) kompiliert den Fix. Danach verifizieren dass
`[VoIP] didReceiveIncomingPush` feuert. Offen bleibt sekundärer Verdacht: Doppel-Registry
durch JS `registerVoipToken()` (Library erzeugt 2. unretained Registry) — empirisch nach Rebuild prüfen.

## Apple-Constraint (kein Bug)

Auf iOS **Background/locked** ist die Incoming-UI zwingend CallKit (PushKit verlangt
`reportNewIncomingCall` in derselben Run-Loop, sonst killt iOS die App). Der Custom-`/call`-Screen
kann dort erst NACH „Annehmen" erscheinen — nicht als Klingel-UI.
