fix(explore): render Plex Discover home shelves

Plex Explore showed only the Watchlist row. `/hubs/sections/watchlist`
answers with placeholder hubs — every entry carries `placeholder: true`,
`size: 0` and no `Metadata` — so `fetchHubs` mapped each one to an empty
page and dropped all of them. That is true no matter what the profile has
watchlisted; the shelves never rendered.

Read `/hubs/sections/home` instead, the section Plex's own web client
renders on its Home > Trending tab, and hydrate each placeholder from its
own key (six at a time). `directory` shelves list browse categories and
`clip` shelves list trailers, neither of which becomes a catalog item, so
they are skipped before spending a request. A shelf that fails degrades to
the ones that succeeded; a pass where every shelf failed still throws.

Discover ignores container offsets on hub keys and truncates with `limit`
instead, so a hub is one page: View All takes the whole shelf in a single
request rather than replaying page one, and hub requests drop `Media` and
`Image` elements the catalog layer never reads.
This commit is contained in:
edde746
2026-07-28 05:26:37 +02:00
parent 53535e1678
commit 82d6c5d555
6 changed files with 243 additions and 109 deletions
+5 -5
View File
@@ -109,8 +109,8 @@ class _FakeHubSource extends _FakeSource implements CatalogHubSource {
if (returnEmptyHubs) return const [];
return [
CatalogHub(
id: 'because-watchlisted',
title: 'Because You Watchlisted Inception',
id: 'trending-plex',
title: 'Trending on Plex',
page: CatalogPage(items: [_hubItem('Initial Recommendation')], hasMore: true),
),
];
@@ -197,8 +197,8 @@ void main() {
expect(explore.rowHubs, hasLength(2));
final providerHub = explore.rowHubs.last;
expect(providerHub.row, isNull);
expect(providerHub.providerHubId, 'because-watchlisted');
expect(providerHub.hub.title, 'Because You Watchlisted Inception');
expect(providerHub.providerHubId, 'trending-plex');
expect(providerHub.hub.title, 'Trending on Plex');
expect(providerHub.hub.items.single.title, 'Initial Recommendation');
expect(providerHub.hub.more, isTrue);
@@ -243,7 +243,7 @@ void main() {
expect(explore.rowHubs, isEmpty);
});
test('mutation retries watchlist-derived hubs after a partial refresh failure', () async {
test('mutation retries provider hubs after a partial refresh failure', () async {
final source = _FakeHubSource(CatalogSourceId.plex);
addTearDown(source.dispose);
sources.setActive(source);