mediatracker TODO.md
8.4 kB · 39 lines
Markdown
at main

TODO #

Open tasks and decisions. The skills in .claude/skills/ describe how things are; what is still open goes here, and an entry is deleted when it is done (then the skill is updated).

Watch button: a new episode during a rewatch #

  • Where: nextToWatch in TvShowDetailPage, useMarkEpisodeWatched; skill mediatracker-detail-pages ("Watch button on the detail pages").
  • Problem: the button points at the episode after the latest logged watch, so a newly released episode doesn't show up in the middle of a rewatch - it is only reached at the end.
  • To decide: should an unseen new episode win over the rewatch (the button then points at the first never-watched episode after the latest watch)? Does backfilling an old episode, which moves "the latest watch" back, need a rule of its own?

Anime: provider order per section and episode-number translation #

  • Where: skill mediatracker-anime ("Known gaps" of "Anime enrichment (AniDB)", "Not done" of "Title resolution").
  • Open: the other direction of the idea - provider order per section and translating episode numbers between sources (the season/offset from the Fribb mapping is how to do it) - and measuring the share of MAL ids the mapping can resolve on real data.

Plugin system (third-party plugins, first step a "core" plugin) #

  • Goal: plugins written by third parties add sources, enrichments, review sources, jobs, controllers and settings; the frontend lists them and turns a plugin or single components on and off and configures them; install/remove is started from the frontend and takes a restart (as in Jellyfin).
  • Done: MetadataSource is an open struct (no enum); ISourceRegistry + AddMetadataSource(source, owner) register sources and fail the start on a collision; the mock backend has a source (Acme) announced like a plugin's; checked in the frontend against the mock: the source choice of the search, its results, its detail page and the source priorities show it without a frontend change (filters not looked at). The anime mapping logic keeps naming Tmdb/Tvdb/Mal on purpose (it is about those sources).
  • Decided:
    • Plugin id = GUID (like Jellyfin), written once by the developer into the manifest and as a constant in the plugin; never changes. No domain/NSID (a developer must not need one). The host checks the format (not empty, not all zeros) and refuses a second plugin with the same id; it can't tell a copied id from a new version (a catalog or signatures would be needed, later). The sample manifest must show a freshly generated id and say "do not copy" (Jellyfin's template ships a real example GUID in build.yaml, and its README only lists tools to generate one).
    • Internal names come from the id, automatically: PostgreSQL schema plugin_<32 hex>, settings path Plugins:<id>:, cache key prefix, data directory, routes /plugins/<id>/. Logs and the UI use the manifest's name, never the GUID. The assigned names are stored at first load (table of plugin id -> schema/paths), so a later rule change or rename orphans nothing.
    • Source keys are not prefixed: short, chosen by the plugin in its manifest (trakt), lowercase letters/digits (see MetadataSource.IsValidName: raise the 32 limit toward the lexicon's 64 if needed), checked for collisions at load (built-ins reserved). They are data in users' records and so a namespace shared between instances; two plugins with one key for different services can't be told apart by the host (later: a list of known keys like the lexicon's knownValues).
  • Manifest: implemented as plugin.json (see skill mediatracker-plugins). Still open: Jellyfin's meta.json and repository manifest.json schemas were not read, so a catalog format (a list of installable plugins with download address, checksum, targetAbi-like compatibility) is undecided.
  • Open:
    • Plugin cache and database, what is left: the schema is separate by convention only (one database user). A plugin can no longer ask for another plugin's cache through the API (the cache is the handle that AddCache returns), but with reflection or direct SQL it still can.
    • Dependencies between plugins are not modeled (a plugin cannot say it needs another one).
    • Install from an address or a catalog (parked, not needed yet; only worth it once someone distributes plugins): a package can only be uploaded; a list of installable plugins (download address, checksum, compatibility) and fetching it is undecided (the backend must not fetch addresses it is only told about: SSRF).
    • Frontend: the pages of plugins are generic (list, a page and sidebar entry per plugin, component settings and switches); UI that needs a plugin's own code (a custom page, a widget on a detail page) is not possible: that needs frontend modules loaded at run time (module federation or dynamic ES modules, with typed texts) - only once third-party plugins exist.
  • Steps: (1) done, see skill mediatracker-plugins: contract, manifest, loader, the core plugin, /admin/plugins (on/off from the next start); (2) done as generic pages (see skill mediatracker-plugins): a page and a sidebar entry per plugin and the settings/switch of each component, all from GET /admin/plugins; what stays open is UI that only a plugin's own code can draw (custom pages: runtime-loaded frontend modules, only once third-party plugins exist); (3) done (upload a zip, remove, take back, restart; see skill mediatracker-plugins); (4) done (cache category and database schema per plugin; see skill mediatracker-plugins); (5) catalog/signatures: parked, no need for it so far.

Mock backend, mock Studio and Storybook: what is still open #

  • Where: skills mediatracker-testing (mock hosts, fakes) and mediatracker-frontend-conventions (Storybook).
  • C# of the mock work: the fakes have unit tests (Mediatracker.Testing.Tests) and both mock hosts start in tests on a real PostgreSQL (Mediatracker.Api.Mock.Tests, Mediatracker.FeedStudio.Mock.Tests: login, library with the odd titles, failing source, images, seeded feeds, publishing); still without tests of their own: MockDatabase, MockWorld, MockServices beyond what those host tests exercise.
  • Mock data: faults are per title or, with PUT /mock/faults, for every call of the source (not per request path, not random); only TMDB (and Acme) as sources; the mock Studio has no feeds with odd names, and the embedded Studio login in the mocked Mediatracker is not set up; admin pages show real services (Tap, AniDB) as down or off.
  • Storybook: about 100 components of the Mediatracker app and most of the Studio's have no story; components that ask an API can have parameters.api (done for ReviewsCollapsible and EpisodeCard), the Studio's FeedForm, RulePanel, PublishPanel and TemplateDialog too; still open: MediaSection, SeasonCollapsible, FeedListSection, DatasourceEditor; most stories were only checked by npm run check:stories (renders, overflow), not looked at; no interaction or screenshot tests, check:stories is in no pipeline.
  • Frontend walk-through with the odd titles: the Mediatracker's pages (home, library, calendar, search, discover, lists, feeds, detail pages of the odd and failing titles, settings, admin) were walked at 375/768/1280 px (dark at 375) with npm run walk:pages: no page gets wider than the window; what is left are small overflows in loading skeletons (a few px) and the id mappings page at 768 px (cards' texts 56/47, 63/47); with npm run walk:interactions the collapsibles of the detail pages (seasons with the odd episodes, media, alternative titles) with their episode cards expanded and the dialogs behind the action buttons (watch history, add to list, rating, move to another entry) were opened at 375 and 1280 px: nothing overflows (the only finding is a toast's icon, a library's own element). Not done: menus, forms filled in, the Log watch / mark several dialogs on a show with episodes. The Studio's feed pages, tokens, accounts, datasources and identity were walked the same way, but not its rule panel, template dialog, datasource editor with data, nor the publish flow.
  • Not tried in the IDEs: the Rider and WebStorm run configurations were written as files, not opened (the ids in the Compose all ones follow the pattern of the existing multi-launch file).