# Type Synchronization & Thread Safety ## PlayerState (Rust <-> TypeScript) **Rust:** ```rust pub enum PlayerState { Idle, Loading { media: MediaItem }, Playing { media: MediaItem, position: f64, duration: f64 }, Paused { media: MediaItem, position: f64, duration: f64 }, Seeking { media: MediaItem, target: f64 }, Error { media: Option, error: String }, } ``` **TypeScript:** ```typescript type PlayerState = | { kind: "idle" } | { kind: "loading"; media: MediaItem } | { kind: "playing"; media: MediaItem; position: number; duration: number } | { kind: "paused"; media: MediaItem; position: number; duration: number } | { kind: "seeking"; media: MediaItem; target: number } | { kind: "error"; media: MediaItem | null; error: string }; ``` ## MediaItem Serialization ```rust // Rust (serde serialization) #[derive(Serialize, Deserialize)] pub struct MediaItem { pub id: String, pub title: String, #[serde(skip_serializing_if = "Option::is_none")] pub artist: Option, // ... } ``` ```typescript // TypeScript interface MediaItem { id: string; title: string; artist?: string; // ... } ``` ## Tauri v2 IPC Parameter Naming Convention **CRITICAL**: Tauri v2's `#[tauri::command]` macro automatically converts snake_case Rust parameter names to camelCase for the frontend. All `invoke()` calls must use camelCase for top-level parameters. **Rule**: Rust `fn cmd(repository_handle: String)` -> Frontend sends `{ repositoryHandle: "..." }` ```typescript // CORRECT - Tauri v2 auto-converts snake_case -> camelCase await invoke("player_play_tracks", { repositoryHandle: "handle-123", // Rust: repository_handle request: { trackIds: ["id1"], startIndex: 0 } }); await invoke("remote_send_command", { sessionId: "session-123", // Rust: session_id command: "PlayPause" }); await invoke("pin_item", { itemId: "item-123" // Rust: item_id }); // WRONG - snake_case causes "invalid args request" error on Android await invoke("player_play_tracks", { repository_handle: "handle-123", // Will fail! }); ``` **Parameter Name Mapping (Rust -> Frontend)**: | Rust Parameter | Frontend Parameter | Used By | |----------------|-------------------|----| | `repository_handle` | `repositoryHandle` | `player_play_tracks`, `player_add_track_by_id`, `player_play_album_track` | | `session_id` | `sessionId` | `remote_send_command`, `remote_play_on_session`, `remote_session_seek` | | `item_id` | `itemId` | `pin_item`, `unpin_item` | | `current_item_id` | `currentItemId` | `playback_mode_transfer_to_local` | | `position_ticks` | `positionTicks` | `playback_mode_transfer_to_local`, `remote_session_seek` | | `item_ids` | `itemIds` | `remote_play_on_session` | | `start_index` | `startIndex` | `remote_play_on_session` | **Nested struct fields** use `#[serde(rename_all = "camelCase")]` separately - this is serde deserialization, not the command macro. Both layers convert independently. **Test Coverage**: Integration tests in `src/lib/utils/tauriIntegration.test.ts` validate all invoke calls use correct camelCase parameter names. ## Rust Backend Thread Safety ```rust // Shared state wrapped in Arc> pub struct PlayerController { backend: Arc>>, queue: Arc>, // ... } // Tauri state wrapper pub struct PlayerStateWrapper(pub Mutex); // Command handler pattern #[tauri::command] pub fn player_play(state: State) -> Result { let mut controller = state.0.lock().unwrap(); // Acquire lock controller.play()?; // Operate Ok(get_player_status(&controller)) // Lock released } ``` ## Frontend Stores Svelte stores are inherently reactive and thread-safe for UI updates: ```typescript const { subscribe, update } = writable(initialState); // Atomic updates function setPlaying(media: MediaItem, position: number, duration: number) { update(state => ({ ...state, state: { kind: "playing", media, position, duration } })); } ```