# Task: make "podcast" a kind of album ## Why `AlbumKind` is `Literal["music", "book"]` (`python-backend/musicmouse/library/sections.py`), and the `Kinderpodcasts` shelf is declared `kind="book"`. So "is this a podcast?" is answered differently in each layer: - web: `album.section === "Kinderpodcasts"` (`web/src/lib/search.ts`, `groupOf`), and everything else goes through `groupOf(album) === "podcasts"` (App, PlayView, BrowseView, MobileBrowse, keyboard.ts, remote.ts, ambience.ts, useTrackDetail); - backend: `SECTIONS[album.section].album_unit == "episode"` (`MusicLibrary.latest_episode`, `tippen/rewards.py`); - typing game: reward kinds `"episode"` and `"podcast_episode"` (`services/web/tippen_api.py`). The shelf's folder name is therefore a de-facto API string the front-end depends on. `todo.md` already asks for a cleaner podcast data model. ## What the owner decided Add `"podcast"` to the album kind. Podcast-ness is then a property of the album, carried in the index and the API, and nobody compares section names. ## Design ### Backend - `AlbumKind = Literal["music", "book", "podcast"]`. `SECTIONS["Kinderpodcasts"]` gets `kind="podcast"`. - `Section.album_unit` stays. It is the scanner's structural fact (one album per file), not a classification. Keep using it in the scanner (`scan_library`) and in `podcast_feeds.find_feed_shows`. Every place that asks "is this album a podcast?" switches to `album.kind == "podcast"`: - `MusicLibrary.latest_episode`; - `tippen/rewards.py` (`_chronological_episodes`, and the `album_unit == "folder"` branches that really mean "not a podcast"); - `tippen_api._animation_kind`; - the `is_podcast` helper, if task 04 added one: change its body, keep its callers. - `scanner.scan_album` derives `series` only for `kind == "book"`. Check that episode albums still get `series = folder.name` (they do, in `scan_episodes`) and that nothing else branches on `"book"` meaning "book or podcast". `grep -rn '"book"' python-backend/musicmouse` and decide per hit. - `FigureConfig.kind` stays `Literal["music", "book"]`. A figurine holding a podcast is not a thing. Type `Config.figure_kinds` accordingly. - `cache._album_from_json` validates the kind: accept `"podcast"`. Bump `_INDEX_VERSION` (with a comment line, like the existing ones), so every cached podcast album is re-scanned with the new kind instead of being read back as `"book"`. - `_ANALYZED_KINDS` stays `("music",)`. - `services/web/schemas.py`: `AlbumOut.kind` and `PlayerStateOut.kind` become the `AlbumKind` literal instead of `str`, so the OpenAPI schema says what the values are. ### Web - `src/api/types.ts`: `AlbumKind = "music" | "book" | "podcast"`. - `src/lib/search.ts`, `groupOf`: map from `kind` alone (`podcast` → `"podcasts"`, `book` → `"audiobooks"`, else `"music"`). No `section` comparison may remain anywhere in `web/src`. - `src/lib/covers.ts`: `isBook` and `aspectOfKind` treat `"book"` specially. Podcasts are drawn like books today *because* they were `kind: "book"`. Keep them looking exactly as they do now: decide explicitly whether `isBook` / `aspectOfKind` include `"podcast"`, and check BrowseView's `mostlyBooks` / `allPodcasts` tiles and the play view. The result must be visually unchanged; compare screenshots of the podcast shelf, a podcast category and the podcast play view before and after. - Anything else that reads `state.kind` or `album.kind`: grep and decide per hit. ### Leave alone The `section` field stays in the API; it is still useful for display and debugging. Don't rename the shelves, the folders or the `Group` names. ## Tests - `tests/test_library.py`: a `Kinderpodcasts` episode scans as `kind == "podcast"`; a cache written at the previous `_INDEX_VERSION` is discarded; `latest_episode` still works. - `tests/test_tippen_rewards.py` / `test_tippen_api.py`: episode rewards still resolve and still animate as `podcast_episode`. - `tests/test_web.py`: `/api/library` reports `"podcast"`. - `web/src/lib/__tests__/search.test.ts`: `groupOf` uses `kind`; an album whose section says `Kinderpodcasts` but whose kind is `music` is music. That proves nothing compares the section any more. ## Done when `scripts/check.sh` passes, `grep -rn "Kinderpodcasts" web/src` finds nothing, the backend's "is it a podcast" checks all read `album.kind`, and the podcast UI looks the same as before.