Files
musicmouse/docs/agent-tasks/06-podcast-kind.md
Martin Bauer 4cb2d085ca Add agent task files for the review's remaining fixes
Authentication (01), one input policy for skip/mute/volume steps (04), the
firmware's interleaved serial frames (05), podcast as an album kind (06), and eight
smaller cleanups (07). Each says why, what was decided, the design, the tests and
when it is done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:51:27 +02:00

4.3 KiB

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.