Previously: incoming calls silently popped an "Accept/Reject"
panel. Easy to miss — no audible cue, no system-level alert if
the app was backgrounded. Now the incoming-call path triggers
both a synthesized ring tone and a system notification banner.
## Ring tone (desktop/src/main.ts)
New `Ringer` class using Web Audio API directly — no external
asset files, no new npm dep. Synthesizes a classic NANP two-tone
cadence (440Hz + 480Hz sine mix, 2s tone + 4s silence, looped)
through an envelope-gated gain node that ramps on/off to avoid
clicks. Audible on every Tauri-supported platform because
WebView carries Web Audio.
- `start()` — lazily creates AudioContext on first use
(platforms that require a user gesture for AudioContext
creation still work because the incoming-call event is
user-adjacent from the webview's perspective), starts
setInterval(6000) loop.
- `stop()` — clears the timer AND disconnects any active
oscillators so there's no tail audio.
- Active-nodes array is swept every cycle so it doesn't grow
unbounded across long rings.
Hooked into signal-event handlers:
- `"incoming"` → `ringer.start()` + notifyIncomingCall
- `"answered"`, `"setup"`, `"hangup"` → `ringer.stop()`
- Accept/Reject button click handlers → `ringer.stop()` as
the first thing they do (before any await)
## System notification (desktop/src-tauri + main.ts)
Added `tauri-plugin-notification = "2"` to the Tauri app and
registered in the builder. Capabilities updated with the four
notification permissions.
Frontend calls the plugin commands via the generic `invoke`
instead of adding `@tauri-apps/plugin-notification` as a JS
dep — Tauri plugins expose `plugin:notification|notify` etc.
directly. Flow:
1. `is_permission_granted` — check cached
2. If not granted → `request_permission` (Android prompts the
user once, cached thereafter)
3. `notify` with title="Incoming call", body="From <alias>"
All wrapped in try/catch with console.debug fallback — plugin
missing or permission denied is non-fatal, the visible panel +
ring tone still alert the user.
## Known gaps (deferred)
- Android native system ringtone (RingtoneManager) + full-
screen intent for lockscreen-visible ringer. Requires
platform-specific Java/Kotlin glue in the Tauri Android
shell — bigger lift.
- Desktop window flash / taskbar attention-seek on incoming
call when app is backgrounded.
- Vibration pattern on Android.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>