C4 Level 2 — Container diagram
C4 Level 2 — Container diagram
Diagram
C4Container
title Container Diagram — Rymflux v1
Person(listener, "Listener", "End user consuming audio")
System_Boundary(rymflux, "Rymflux Desktop App") {
Container(svelte_shell, "Shell UI", "Svelte + TypeScript", "Navigation, Home/Lobby, mini-player, room registry")
Container(music_room, "Music Room", "Svelte + TypeScript", "Browse albums/artists/genres, search, player expansion, queue view")
Container(backend, "Rust Backend", "Rust (Tauri)", "Single process hosting all core services")
ContainerDb(sqlite, "Library Database", "SQLite", "library_items, content_resolution, playlists, playback_history")
}
System_Ext(plugin_proc, "Plugin Processes", "External executables (Python, Go, etc.)")
System_Ext(audio_dev, "Audio Output", "OS audio device")
Rel(listener, svelte_shell, "Interacts with", "GUI")
Rel(svelte_shell, music_room, "Contains", "Svelte component tree")
Rel(svelte_shell, backend, "Commands + events", "Tauri IPC bridge")
Rel(backend, plugin_proc, "stdin/stdout JSON-RPC", "Launch on demand, idle timeout")
Rel(backend, audio_dev, "Decoded PCM buffers", "phonic (cpal) output stream")
Rel(backend, sqlite, "Reads/writes", "rusqlite")
Backend services (within the Rust process)
These are logical modules within the single Rust process, not separate deployable units:
┌────────────────────────────────────────────────────┐
│ Rust Backend │
│ │
│ ┌────────────────┐ ┌────────────────────┐ │
│ │ Plugin │ │ Search │ │
│ │ Runtime │ │ Manager │ │
│ │ │ │ │ │
│ │ • Discovery │ │ • Dispatch │ │
│ │ • Lifecycle │ │ • Timeout │ │
│ │ • IPC bridge │ │ • Merge/dedup │ │
│ │ • stderr capt. │ │ • Config │ │
│ └───────┬─────────┘ └─────┬──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────┐ ┌────────────────────┐ │
│ │ Playback │ │ Library │ │
│ │ Engine │ │ Service │ │
│ │ │ │ │ │
│ │ • phonic │ │ • rusqlite │ │
│ │ • symphonia(w) │ │ • Favorites │ │
│ │ • rubato(w) │ │ • Playlists │ │
│ │ • cpal(w) │ │ │ │
│ │ • Queue control │ │ • History │ │
│ └────────────────┘ └─────┬──────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ SQLite │ │
│ │ (file) │ │
│ └────────────────┘ │
└────────────────────────────────────────────────────┘
Service responsibilities
| Service | Responsibility | Key dependencies |
|---|---|---|
| Plugin Runtime | Scan plugins directory on startup, parse manifests, launch plugin processes on first request, supervise lifecycle (timeout, crash recovery, idle termination), capture stderr | std::process::Command, tokio::io for stdin/stdout |
| Search Manager | Accept start_search command, dispatch JSON-RPC search requests to all plugins with search capability, collect results with per-plugin timeout, merge/deduplicate, emit search_result and search_done events |
Plugin Runtime (to send requests), tokio::select!, SearchConfig |
| Playback Engine | Accept URL or file path, play via phonic (symphonia decode + rubato resample + cpal output), manage PlaybackHandle, emit playback_state events from status channel |
phonic, tokio::sync::mpsc for playback status |
| Library Service | CRUD for favorites, playlists, history. Content resolution lookup (content UUID to (plugin_id, foreign_id)). Deduplication on first save | rusqlite, content resolution table |
Container interaction patterns
Search flow
Listener types query
|
v
Svelte Shell invoke("start_search", { query: "..." })
|
v
Rust: Search Manager
| tokio::select! across N plugin processes
+-- Plugin A responds -> emit("search_result", { plugin: "A", results: [...] })
+-- Plugin B times out -> emit("search_error", { plugin: "B", error: "timeout" })
+-- All done -> emit("search_done", { total: 42 })
|
v
Svelte Shell / Music Room
+-- listen("search_result") -> append + deduplicate results
+-- listen("search_error") -> show per-plugin notification
+-- listen("search_done") -> show "N results from M plugins"
Playback flow
Listener presses play on a track in Music Room
|
v
Music Room resolve content UUID -> invoke("resolve_content", { id: "..." })
|
v
Rust: Library Service -> content_resolution table -> (plugin_id, foreign_id)
|
v
Rust: Plugin Runtime -> send JSON-RPC stream request to plugin
|
v
Plugin returns {"url": "https://...", "content_type": "audio/mpeg"}
|
v
Rust: Playback Engine -> phonic plays (symphonia decodes -> rubato resamples -> cpal outputs)
|
v
Rust emits "playback_state" -> Svelte mini-player updates
Startup flow
App launches (Tauri)
|
v
Rust: Plugin Runtime -> scan ~/.rymflux/plugins/
|
+-- Parse each plugin.toml
+-- Validate manifest
+-- Register valid plugins
|
v
Rust: Library Service -> open SQLite, run migrations
|
v
Rust: emit("platform_ready", { plugins: [...] })
|
v
Svelte Shell -> render Home, populate room list
(w)throughout the backend diagram = wrapped by phonic.symphonia,rubato, andcpalare consumed via thephoniccrate, not linked directly.
Technology decisions
| Component | Technology | Rationale |
|---|---|---|
| Desktop shell | Tauri v2 | Cross-platform, small binary, Rust backend |
| Frontend UI | Svelte 5 + TypeScript | Reactive, compiled, minimal overhead |
| Audio engine | phonic | Wraps cpal + symphonia + rubato. Built-in playback status events, sample-accurate scheduling, DSP effects |
| Database | rusqlite | Embeddable, relational, well-tested |
| Async runtime | tokio | Required by Tauri, supports async process supervision |
| Plugin IPC | JSON-RPC 2.0 over stdin/stdout | Language-agnostic, zero networking, well-understood |
| HTTP client | reqwest | Fetch stream URLs, battle-tested |