fix(offline): one server-only rule for both library views

Two defects with one cause: "server only" was a private $derived inside
MediaCard.

The list view (what LibraryGrid renders when the stored view preference
is list) had no notion of it at all, so a library browsed as a list
offline showed every revealed item as an ordinary tappable row that plays
nothing, with no way to queue it.

And the rule asked the downloads store whether *this item id* was
downloaded — but only a playable leaf (Audio, Movie, Episode) ever has a
download row. An album's tracks carry them, the album does not, so a
fully downloaded album greyed itself out and offered to queue what was
already on the device.

The rule moves to the pure $lib/utils/serverOnly and both views call it.
The container half is answered by the backend rather than guessed at:
get_download_disk_usage().sizes already carries container subtotals
beside leaf sizes (DR-085), so deviceContentIds is membership in a
Rust-computed map, not a frontend list of which item types are
containers. That map was loaded only by the Downloads page, so the shell
primes it at startup and re-reads it whenever the offline gate settles.
Queueing is shared too, since the list view had no copy to diverge from.

TRACES: UR-052, UR-055 | DR-292 | UT-257, UT-258
This commit is contained in:
2026-09-22 21:29:38 -04:00
parent a90de67c54
commit bb7d5dc01a
10 changed files with 468 additions and 36 deletions
+16
View File
@@ -112,6 +112,22 @@ function createDownloadedCatalogStore() {
export const downloadedCatalog = createDownloadedCatalogStore();
/**
* Every item id the device holds bytes for — playable leaves *and* the
* containers above them, as the backend's disk-usage map reports them.
*
* This is what stops the offline browse greying out a fully downloaded album:
* only a leaf (Audio, Movie, Episode) ever has a download row of its own, so
* asking the downloads store about an album id always answered "no". Which ids
* are containers, and which children roll up into them, stays a Rust question
* (`get_download_disk_usage`); the frontend only reads membership.
*
* Refreshed with the rest of the catalog — see `downloadedCatalog.refresh()`.
*
* TRACES: UR-052, UR-056 | DR-292
*/
export const deviceContentIds = derived(downloadedCatalog, ($c) => new Set(Object.keys($c.sizes)));
export const downloadedLibraries = derived(downloadedCatalog, ($c) => $c.libraries);
export const downloadedDeviceTotal = derived(downloadedCatalog, ($c) => $c.deviceTotalBytes);
export const downloadedItemCount = derived(downloadedCatalog, ($c) => $c.itemCount);