# Type Synchronization & Thread Safety

## PlayerState (Rust <-> TypeScript)

Rust:

    #![allow(unused)]
    fn main() {
    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<MediaItem>, error: String },
    }
    }

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

    #![allow(unused)]
    fn main() {
    // 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<String>,
        // ...
    }
    }

    // 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: "..." }`

    // 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

    #![allow(unused)]
    fn main() {
    // Shared state wrapped in Arc<Mutex<>>
    pub struct PlayerController {
        backend: Arc<Mutex<Box<dyn PlayerBackend>>>,
        queue: Arc<Mutex<QueueManager>>,
        // ...
    }

    // Tauri state wrapper
    pub struct PlayerStateWrapper(pub Mutex<PlayerController>);

    // Command handler pattern
    #[tauri::command]
    pub fn player_play(state: State<PlayerStateWrapper>) -> Result<PlayerStatus, String> {
        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:

    const { subscribe, update } = writable<PlayerStore>(initialState);

    // Atomic updates
    function setPlaying(media: MediaItem, position: number, duration: number) {
      update(state => ({
        ...state,
        state: { kind: "playing", media, position, duration }
      }));
    }