ADR-005: Inter-component communication — Tauri events
ADR-005: Inter-component communication — Tauri events
Status
Accepted
Context
The primer and charter identify an event bus as a v2 concern (scope.md: “Event system / event bus” is out of scope for v1). However, several requirements already imply event-driven communication:
- FR-CORE-1: “the engine emits a ‘playing’ event”
- The queue must advance when a track ends (knowing this requires a notification from the playback engine)
- The library must record playback history
- The search manager must push results to the frontend progressively (ADR-007)
Components in v1 are: Rust backend (plugin runtime, playback engine, library, search manager) and Svelte frontend (rooms, shell, UI).
Options:
- Tauri event system (
app_handle.emit_all/listen): Built-in, bidirectional, no extra dependencies. Events are serialized as JSON. Frontend subscribes withlisten(). - Tauri commands (
invoke): Request-response only. The frontend initiates. Cannot push data from backend to frontend without polling. - Custom event bus (channels/actors in Rust): A Rust-side actor system (e.g.,
tokio::sync::broadcastoractix). Handles internal Rust↔Rust events. Backend→frontend still needs Tauri events. - Full external event bus (Redis pub/sub, NATS): Overkill for a local-first desktop app.
Decision
Use Tauri’s built-in event system for all cross-boundary (Rust ↔ Svelte) communication in v1. Use tokio::sync::broadcast channels for Rust↔Rust communication within the backend.
Architecture:
Plugin Runtime ──tokio::broadcast──→ Search Manager
↓ ↓
Tauri emit("search_result", payload) Tauri emit("search_done")
↓ ↓
Svelte listen("search_result") Svelte listen("search_done")
↓ ↓
Room renders results progressively Room shows "search complete"
Playback Engine ──tokio::broadcast──→ Library (record history)
↓ ↓
Tauri emit("playback_state") Tauri emit("history_updated")
↓ ↓
Mini-player / Shell UI Library UI
Key rules:
- Rust↔Rust events use
tokio::sync::broadcast(orwatchfor state that must always have a value). - Backend→Frontend events use
app_handle.emit_all. - Frontend→Backend events use
app_handle.listen(rare — most frontend→backend isinvoke). - The S3 event bus backlog item (W5) is re-scoped to “replace Tauri events with a formal event bus” when v2 requires cross-process or cross-instance events.
Alternatives considered
- Frontend-polled invoke (frontend calls
get_current_trackevery 100ms): Works but wastes CPU, increases latency, and doesn’t compose with progressive search results. Events are the right model for push-based communication. - Rust actor system (actix): Adds a framework dependency for what is fundamentally point-to-point channel communication.
tokio::sync::broadcastandwatchcover the v1 patterns without a framework. - WebSocket server from Rust: Would decouple frontend from Tauri’s event system. Adds a local WebSocket server, connection management, reconnection logic. None of this is needed when both processes are in the same Tauri webview.
Consequences
Positive:
- Zero additional dependencies for inter-component communication.
- Tauri events are serialized as JSON and delivered asynchronously — fits the FR-CORE-1 “playing event” requirement naturally.
tokio::sync::broadcastgives clean fan-out (one producer, N consumers) for events like search results that multiple components may observe.- The v2 event bus migration has a clear path: keep the event contract, replace the transport.
Negative:
- Tauri events are not persisted. If the frontend isn’t listening (e.g., webview not yet loaded), the event is lost. Mitigation: stateful events use
watchchannels (always have the latest value). - Events are serialized as JSON on every emit. For high-frequency events (audio position updates at 10Hz), this adds serialization overhead. Mitigation: throttle position updates to 1Hz, or use a
watchchannel that the frontend polls lazily. - Tauri’s event system is single-process. Cross-process events (e.g., a future mobile companion) need the v2 event bus.
References
- Scope.md: “Event system / event bus” is v2 (out of scope)
- FR-CORE-1, FR-CORE-4 (functional.md)
- ADR-007 (search manager architecture)