fix(library): podcasts list newest episode first

A Jellypod podcast listed its episodes alphabetically. The store pinned
SortBy=SortName onto every drill-down, which overrode the order the
channel plugin returns — and since Jellypod prefixes played episodes with
"[Played]", the name sort also clumped every heard episode at the top.

Which order a container's children take is domain knowledge, so it moves
to Rust: the caller names the container (GetItemsOptions.parentKind) and
default_listing_sort answers with the sort. A channel folder is
PremiereDate descending, every other container keeps SortName ascending,
and a caller naming no container still gets no SortBy, so the paths that
rely on the server's own order keep it. An explicit sort always wins.

ChannelFolderItem with is_folder now maps to MediaKind::ChannelFolder
instead of collapsing into Folder — while both were Folder there was
nothing to key the rule on. The offline leg of the cache/server race
applies the same order, so the cached list no longer flashes in name
order before the server's arrives.

TRACES: UR-007 | DR-257 | UT-229, UT-230, UT-231
This commit is contained in:
2026-08-23 18:38:24 +02:00
parent 2ff07bfa49
commit 231ffae626
15 changed files with 285 additions and 16 deletions
+27
View File
@@ -49,6 +49,33 @@ sequenceDiagram
- Background cache updates (planned)
- **Connectivity side-effect**: each server request feeds the `ConnectivityMonitor`, which is the source of truth for the offline/online banner (see [07-connectivity.md](07-connectivity.md)). A server-answered error (401/404/5xx) still counts as *reachable* — only network failures, sustained past a debounce window, flip the app to offline.
### Listing order is decided in Rust
**TRACES**: UR-007 | DR-257
A browse call names the **container** (`GetItemsOptions.parentKind`, the neutral
`MediaKind` the caller already holds) and not a sort field.
`default_listing_sort` in `repository/types.rs` turns that kind into the order:
| Container kind | Order |
|---|---|
| `channelFolder` — one podcast inside a plugin channel | `PremiereDate` descending |
| any other container | `SortName` ascending |
| none given | no `SortBy` — the server's own order stands |
Both legs of the race apply it, so the cached list does not flash in name order
before the server's arrives. An explicit `sortBy` from the caller always wins;
the default only fills the gap.
This is a domain rule, not a display preference, which is why it is not in the
frontend: the store that asks for a podcast's episodes has no business knowing
that podcasts are read newest-first. `MediaKind::ChannelFolder` exists for the
same reason — Jellyfin gives a channel container and an ordinary folder the same
item type (`ChannelFolderItem`), and while both mapped to `Folder` there was
nothing to key the rule on. The defect this prevents: every Jellypod podcast
listed alphabetically, which discarded the release order *and* clumped every
`[Played] …` episode at the top of the list.
## Search Flow (Locally Indexed)
**TRACES**: UR-065 | DR-108 … DR-111, IR-030