A pile of related fixes around the dashboard's WhatsApp call flow:
- ConversationHeader / Contacts/VoiceCallButton: drop the immediate
setCallActive at initiate time. The call sits in incomingCalls
(callDirection: outbound) until the backend signals real pickup, so
the duration timer never starts pre-pickup. Phone button is disabled
whenever there is an active or incoming call.
- FloatingCallWidget:
* Loop a ringtone (bell.mp3) for inbound ringing only.
* Hide the green Join button for outbound — the agent has nothing to
"join", and clicking it routed through acceptIncomingCall →
prepareInboundAnswer → cleanup() and tore down the live outbound
session before the API 409 ("already accepted by another agent").
* Auto-join watcher skips whatsapp outbound (Twilio's joinConference
flow only).
- useCallSession:
* joinCall short-circuits for outbound calls — defense-in-depth so
no future surface can re-trigger the destroyed-session bug.
* endCall + outbound rejectIncomingCall pass call.callId to
endActiveCall, so terminate fires even if module state was wiped.
- useWhatsappCallSession:
* New recorderArmed flag, reset by cleanup. ontrack only calls
setupRecorder when armed.
* Inbound's acceptIncomingCall arms the recorder before the API
round-trip (agent click = pickup).
* armOutboundRecorder exported for the cable handler when ACCEPTED
arrives.
* endActiveCall accepts a callIdOverride to fall back when the
module's activeCallId was nulled by an earlier cleanup.
- actionCable:
* Split the cable contract: outbound_connected only applies the SDP
answer (tunnel-up signal); outbound_accepted (new) is the real
pickup signal — flips active and arms the recorder.