Casting: restore camelCase serialization on SessionInfo/NowPlayingItem/PlayState
(container rename_all = "camelCase" + per-field PascalCase serde aliases so Jellyfin
PascalCase still deserializes). Bindings and the existing frontend are camelCase
again — casting works with no frontend session changes.
Downloads: migrate DownloadButton.svelte and downloads.ts to the typed
commands.downloadItemAndStart / downloadItem / downloadVideo wrappers (request
objects), matching the bundled backend signatures.
Tooling:
- Enable tauri-specta ErrorHandlingMode::Throw so commands.* return Promise<T>
and throw (drop-in for invoke()).
- Disambiguate specta name collisions: player MediaItem/MediaSource ->
PlayerMediaItem/PlayerMediaSource, auth ServerInfo -> AuthServerInfo.
- Regenerate bindings.ts; svelte-check passes (0 errors).
- Add #[specta::specta] to all 201 #[tauri::command] functions.
- Derive specta::Type on all IPC DTOs (repository/types, settings, player/storage/
download command DTOs, player enums, jellyfin SessionInfo/NowPlayingItem/PlayState,
ThumbnailCacheStats, DownloadInfo, CacheConfig, etc.).
- Replace tauri::generate_handler! with a tauri_specta::Builder + collect_commands!
in lib.rs (exports bindings.ts in debug builds).
Two contract changes required by specta constraints (frontend migration follows):
- specta caps command arity at 10 args: download_item_and_start / download_item /
download_video now take a single request struct (params bundled, body unchanged
via destructuring).
- specta can't parse split serde rename_all: SessionInfo/NowPlayingItem/PlayState
switched to rename_all = "PascalCase" (Jellyfin deserialization preserved; these
now serialize PascalCase to the frontend).
cargo check --lib is clean (0 errors). Frontend migration to bindings.ts is the next step.