TRACES: | DR-204
484 ungated `console.*` calls across 63 non-test frontend files shipped to
end users with no way to turn them off. Mechanical substitution, no control
flow, error handling or message semantics changed:
console.log / console.debug -> log.debug
console.info -> log.info
console.warn -> log.warn
console.error -> log.error
Hand-written `"[Scope] …"` prefixes are dropped where the logger's scope
now carries them; scope names that already existed are preserved verbatim
(`[Auth]`, `[VideoPlayer]`, `[PiP]`, …) and inferred from the filename
where a file had none. `src/routes/player/[id]/+page.svelte` keeps its
`NextEpisode` and `AutoPlay` sub-scopes as separate loggers rather than
flattening them into the page scope.
`grep -rn 'console\.' src/` now matches nothing outside the tests and the
facade itself.
An album download put a handful of its tracks on the device while the button
reported the album as downloaded. Two independent gaps, one shared cause.
- `download_album` read its track list from `items WHERE album_id = ?` — the
local catalog cache. Jellyfin does not return `AlbumId` on every listing
endpoint, so tracks cached from one of those sit in `items` with a NULL
`album_id` and are invisible to that query. On the reported database three
whole albums (18, 12 and 9 tracks) had it NULL on every track; a partially
linked album queued only the linked subset.
- The frontend then resolved one stream URL per track from its own list and
paired it with the returned row ids by position. The ids came back in the
backend's `index_number` order over a different set of rows, so a row could
be handed another track's URL and any track past the end of the shorter list
was never started. On Android that loop also stopped wherever the webview was
suspended.
- `album_id` is what `OfflineRepository::get_items` joins a track to its album
on, so a track that did download stayed invisible under its album offline —
the same missing link seen from the other side.
The operation now belongs to Rust end to end:
- `HybridRepository::get_album_tracks` asks the server what the album contains.
Cache-first `get_items` is right for browsing and wrong for deciding what to
download; it errors offline so the caller falls back to the ungated local
catalog, keeping the queue-while-offline flow.
- `queue_album_tracks` writes the album link onto every track it queues, and
creates an `items` row for tracks the cache has never seen.
- Stream URLs resolve here, through the existing reconnect resolver, now scoped
to the rows just queued so one album cannot start every unrelated pending row.
Only the album id crosses the IPC boundary.
- `album_file_names` gives each track its own file. A title repeated inside one
album (deluxe edition, two discs) mapped to one path, so those downloads
overwrote each other.
Re-tapping download on a broken album heals it: missing tracks are queued and
the tracks already on disk get their link.
`download_series`/`download_season` still derive their episode lists from the
cache the same way and want the same treatment.
DR-173, UT-170..172. Rust 673 tests, frontend 975 tests, svelte-check and
check:boundary clean.
Note: this tree is shared with a concurrent session. Only the files above are
committed; docs/traceability.md is left to be regenerated once that work lands.
Add a metered/cellular network detector so downloads honour a "WiFi
only" preference. Android reports network type via NetworkTypeMonitor;
Rust exposes it through download/network.rs and holds the queue pump when
on a metered connection, emitting a queue-wide waitingForNetwork event.
The frontend surfaces this via the networkType service and a
waitingForNetwork store flag.
TRACES: UR-053 | DR-074
Replace the remaining ~155 untyped invoke() calls across stores, services,
components, and routes with the generated commands.* wrappers from
$lib/api/bindings, so every IPC call is compile-time-checked against the
command signatures.
- Register repository_get_subtitle_url and repository_get_video_download_url
in specta_builder() and the invoke_handler; regenerate bindings.ts.
- Source duplicated wire types (AutoplaySettings, CacheConfig, Session,
ConnectivityStatus, audio/video settings, etc.) from bindings.
- Fix two bugs surfaced by the typed wrappers:
- VideoDownloadButton passed an un-awaited Promise as the stream URL.
- setAutoplaySettings omitted the required userId argument.
- Update unit tests asserting the old invoke(name, args) shape.
- Remove the five param-naming guard tests; the compiler and codegen now
enforce what they checked.
svelte-check: 0 errors. vitest: green. cargo test --lib: green.
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).