GET /api/library/browse extended to ALL media kinds: Episode/MusicVideo/Song/OtherVideo/Image/RemoteStream added to LibraryBrowseMediaType with hydrators mirroring the existing four; optional Subtitle field gives per-kind context ("Show - S1E2", "Artist - Album", …). The null/all-kinds browse now spans all 10 kinds (behavior change — makes ChannelBuilder's untyped library browse surface episodes/songs within a library).
New GET /api/search?query= — grouped multi-kind results (stable 10-group contract, reuses the browse hydration via per-kind dispatch, mirrors Blazor Search.razor's type:{kind} AND (query)); 422 on empty query. state:FileNotFound passes through for trash.
New DELETE /api/media-items (body {ids}) → DeleteItemsFromDatabase; 422 on empty, 204 on success. Security + error-contract tests included.
MediaBrowseScreen (/app/media) — one screen, in-screen kind switcher for all 9 top-level kinds (deviation from Blazor's 9 nav rows, noted), debounced per-kind search, 100/page paging.
SearchScreen (/app/search) — grouped sections + counts + "See all" into browse; TopBar search box now actually works.
- MediaBrowseScreen (/app/media): one parameterized screen for all 9 top-level
kinds with an in-screen kind switcher, per-kind search box, 100/page paging
- SearchScreen (/app/search): grouped per-kind results with counts + 'See all';
TopBar search input now navigates here
- TrashScreen (/app/trash): state:FileNotFound results, multi-select + Empty Trash
via DELETE /api/media-items and /api/maintenance/empty_trash (confirm dialogs)
- Shared media/mediaKinds (icon/label maps, hueOf, duration helpers) + MediaPosterCard;
ChannelBuilder and CollectionsScreen now reuse the single-source maps (their
exhaustive Record<LibraryBrowseMediaType> had to cover the 6 new kinds anyway)
- api: search.ts, mediaItems.ts, maintenance.ts (+ URL-assert tests)
- One consolidated 'Browse' nav entry with an in-screen kind switcher instead of
9 per-kind nav rows (deviation from Blazor's per-kind Media links)
- Nav: Media group gains Browse/Search/Trash; Settings Classic-UI help trimmed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/library/browse without mediaType now spans all 10 media kinds
(was 4: Movie/TelevisionShow/TelevisionSeason/Artist), which flooded two
pickers with episodes/songs/etc. mixed in with the intended top-level
items. Fan both consumers out per-kind and merge, restoring pre-#168
scoping without touching the API:
- ChannelBuilder's library browse tab now fans out Movie/TelevisionShow/
TelevisionSeason/Artist per page (mirrors the existing loadCollections
pattern) and sums per-kind totalCount so paging/load-more still works.
- CollectionsScreen's add-items search now fans out over the same 4
addable kinds instead of filtering one unscoped page client-side.
Extended App.test.tsx and CollectionsScreen.test.tsx to assert the
per-kind requests and updated a few fixtures that relied on the old
unscoped browse call returning collection-kind items under the library
tab.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements the main slice of #141 (SPA parity: media browsing, search & trash) + the core of API gap #161.
Backend (#161)
GET /api/library/browseextended to ALL media kinds:Episode/MusicVideo/Song/OtherVideo/Image/RemoteStreamadded toLibraryBrowseMediaTypewith hydrators mirroring the existing four; optionalSubtitlefield gives per-kind context ("Show - S1E2", "Artist - Album", …). Thenull/all-kinds browse now spans all 10 kinds (behavior change — makes ChannelBuilder's untyped library browse surface episodes/songs within a library).GET /api/search?query=— grouped multi-kind results (stable 10-group contract, reuses the browse hydration via per-kind dispatch, mirrors Blazor Search.razor'stype:{kind} AND (query)); 422 on empty query.state:FileNotFoundpasses through for trash.DELETE /api/media-items(body{ids}) →DeleteItemsFromDatabase; 422 on empty, 204 on success. Security + error-contract tests included.Frontend (#141)
MediaBrowseScreen(/app/media) — one screen, in-screen kind switcher for all 9 top-level kinds (deviation from Blazor's 9 nav rows, noted), debounced per-kind search, 100/page paging.SearchScreen(/app/search) — grouped sections + counts + "See all" into browse; TopBar search box now actually works.TrashScreen(/app/trash) — FileNotFound grid, multi-select + delete, Empty Trash (existing endpoint), confirm dialogs.Deferred (stay open in #141/#161): media detail endpoints/pages, GetMediaItemInfo, image folder browser, remote-stream detail.
Verification: build clean; ErsatzTV.Tests 608 green; Core.Tests 493 (+1 skipped); web typecheck/vitest 209 (20 files)/build/eslint clean. Live E2E on empty scratch DB: all new-kind browses + search groups + 422s + delete 204 + empty_trash verified; SPA routes serve.
Refs #141, #161.
🤖 Generated with Claude Code
- Extend /api/library/browse to episodes, music videos, songs, other videos, images and remote streams (new LibraryBrowseMediaType values + hydrators); add optional Subtitle to LibraryBrowseItemResponseModel for leaf-item context - Add GET /api/search: grouped per-kind results reusing the browse query/shape; empty query -> 422 - Add DELETE /api/media-items: body { ids }, empty -> 422, success -> 204 - Tests: SearchController, MediaItemsController, security + OpenAPI contract entries - Regenerate openapi v1.json + web v1.d.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>