impl(web): add /series/<id> page for viewing a media series#78
impl(web): add /series/<id> page for viewing a media series#78andykais-claude wants to merge 3 commits into
Conversation
Moves the media browsing UI components (MediaList, MediaDetails, MediaView, Footer, SearchParams, SearchResults, SearchLink, MediaDetailEntry) and their supporting runes (dimensions, media_selections) out of the route-local browse folder into $lib/components/browse_like and $lib/runes so they can be reused by the upcoming /series/<series_id> route. Introduces a BrowseLikeController base with a BrowseLikeQueryParams contract that shared components rely on. Widens MediaListRune to accept a new 'series_search' paginate type. Co-authored-by: andykais-claude <andykais-claude@users.noreply.github.com>
Brings the /series/<id> route query params in line with the browse route after main's media_type search filter (#71). Maps the image/video/audio dropdown values to the canonical uppercase query.media_type filter now supported by forager.series.search, in addition to the existing 'animated' handling. Co-authored-by: andykais-claude <andykais-claude@users.noreply.github.com>
a512790 to
c7eecf1
Compare
Design review: the
|
Applies the PR #78 design review feedback: - Rename the browse_like/BrowseLike pattern to browsable/Browsable (/components/browsable, BrowsableController). - Hole 1: add BrowsableQueryParams (extends BaseQueryParams) that factors out the duplicated URL parse/serialize/merge and shared query-building helpers; browse + series managers now only implement route-specific bits. The popstate listener is cleaned up once in BaseQueryParams. - Hole 2: replace the any-typed BrowseLikeQueryParams contract with a concrete BrowsableSearchParams interface (generic over the sort union). - Hole 3: remove group_by/mode references from the shared SearchResults. - Hole 5: replace show_series_index/show_series_link boolean props with a single tile_footer snippet supplied per route. - Hole 6: expose series_index via a typed field set from the series-search output type instead of an unsafe double-cast. - Hole 7: extract the duplicated page chrome into a shared BrowsableShell component rendered by thin browse/series +page markers. Also fixes a few incidental pre-existing type issues in touched files (SelectInput options/onchange, MediaListRune constructor args). Co-authored-by: andykais-claude <andykais-claude@users.noreply.github.com>
Summary
Adds a new
/series/<series_id>page to@forager/webfor viewing the contents of a media series, mirroring the/browselayout but driving results fromforager.series.search. Originally a rebase of #68 ontomain; now also incorporates the design-review feedback on the shared-component abstraction.Design review resolution
Addresses the
browsableshared-component review:browse_like/BrowseLiketobrowsable/Browsable($lib/components/browsable/,BrowsableController).BrowsableQueryParams(extends the existingBaseQueryParams) which factors out the duplicatedparse_url/serialize/merge/onMount+popstateand shared query-building helpers (parse_tags,apply_common_filters, media-type mapping). Browse and series managers now implement only route-specific bits (DEFAULTS,URL_PARAM_MAP,execute_search,contextual_query).TagQueryParamsstill extendsBaseQueryParamsdirectly. Thepopstatelistener is now cleaned up once, in the base.any-typedBrowseLikeQueryParamsinterface; introduced a concreteBrowsableSearchParamsinterface (generic over the route's sort union) so the shared components' field bindings are compile-checked.SearchResults) — removed allgroup_by/modereferences from the shared component; the grouped-tile link is now supplied by the browse route via a snippet.show_series_index/show_series_linkwith a singletile_footer?: Snippet<[MediaViewRune]>; browse supplies a "View series"/grouped-link footer, series supplies a#indexfooter.series_indexdouble-cast) —series_indexis now a typed field onMediaViewRune, populated from the typedforager.series.searchresult inMediaListRunerather than cast on read.Header+SearchParams,MediaDetails/MediaView/MediaList,Footer,svelte:window) intoBrowsableShell.svelte, rendered by thinbrowse/series+page.sveltemarkers.Note on Hole 7 (shared shell vs. layout group)
I implemented the shell as a shared component rendered by each thin page rather than the literal
(browsable)/+layout.sveltegroup. Sibling routes under a shared SvelteKit layout keep the same layout instance across client-side navigation, so a controller built in the layout would not rebuild when navigating/browse→/series/<id>(breaking the "View series" link and the queryparamsonMount). Rendering a shared shell from each page dedupes the chrome identically, keeps route-specific snippets fully typed against each concrete controller, preserves theroutes → $libdependency direction, and avoids the layout-persistence pitfall.Testing
deno task --cwd packages/web build— succeeds.deno task --cwd packages/cli ... guiboots with--check(deno's typechecker, which resolves@forager/core) passing.deno run -A npm:svelte-check— 115 errors, down from 125 pre-refactor; all remaining are pre-existing module-resolution / action-typing errors. No new real errors./browserenders the series tile with the View series link (navigates to/series/4);/series/4shows#0/#1/#2labels;?group_by=artistrenders grouped tiles with working group-name links; media-type filter and media viewer work.