Commit Graph
3 Commits
Author SHA1 Message Date
timothyandClaude Opus 5 572737a29e fix(671): review round 2 -- guard Song.Artists, cover the second consumer
Cold independent review of 017ef988d. Three findings taken, one filed out.

The important one is a regression this branch INTRODUCED. `SongMetadata.Artists`
is a nullable EF primitive collection (JSON in one column, not a navigation)
that FallbackMetadataProvider leaves unassigned when a song's tags fail to read,
and `string.Join` throws ArgumentNullException on a null sequence. The rerun
list previously did not load SongMetadata at all, so the throw was unreachable
there; adding the include promoted it to a live 500 that would have failed the
whole page. Confirmed by reverting the guard: ArgumentNullException, parameter
'values'. The file header claiming every member was guarded was false.
The empty case is filtered too, so an artist-less song loses its bare " - ".

Second: `GetPlaylistItemsHandler` had no handler-level test at all (its
controller tests stub the mediator), so the RemoteStream include added last
round was discharged by inspection -- the same method that produced #671. It
now runs the same 13-type matrix via a shared SelectionSeedData; removing the
include fails that matrix.

Third: dropped the dead `(i as Season).SeasonMetadata` include leg -- the Season
projection reads Show.ShowMetadata and the scalar SeasonNumber, never
SeasonMetadata.

Filed #690 for the pre-existing, out-of-scope finding: the paged TotalCount
ignores the search query, so the SPA renders empty pages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:33:52 +02:00
timothyandClaude Opus 5 017ef988d0 fix(671): review round 1 -- pin exact names, complete the playlist include
Cross-family (Codex) adversarial review of 8523088ce. All three findings taken:

- The name assertion only checked "not a placeholder", so it could not see a
  missing NESTED include leg: dropping Episode -> Season -> Show still renders
  "s00e04 - Selected episode", which contains no placeholder marker and passed.
  Now every type pins its whole expected string; re-removing that leg fails, as
  verified before restoring it.
- Widening the shared switch with a RemoteStream arm put `GetPlaylistItemsHandler`
  one include short -- it loaded metadata for the other nine types, so playlist
  RemoteStream names alone would have degraded to "???".
- `?? 0` rendered an unloaded Season as "s00", which conventionally means
  Specials and so fabricated plausible-looking real data; it now renders "s??".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:00:08 +02:00
timothyandClaude Opus 5 8523088ceb fix(671): resolve rerun-collection selections through one shared include chain
The paged list handler eager-loaded nothing, so `ProjectToViewModel` read four
unloaded navigations and every row of every collection type projected a null
selection. Because the selected id and the display name are read off the SAME
navigation, this dropped the id too -- the harm is not an unlabelled badge but
an editor that round-trips a null and clears the user's stored selection.

The by-id handler loaded metadata for only four of the ten selectable media
types: Song/OtherVideo/Image/RemoteStream returned a null-ish selection and
Episode/MusicVideo threw an NRE that surfaced as a 500.

Fixed at the boundary rather than per call site:

- `RerunCollectionQueryExtensions.IncludeSelectionDetails()` is now the single
  include chain, called by both handlers, joining the existing
  `ProgramScheduleItemQueryExtensions.IncludeScheduleItemDetails()` precedent
  (#229). Artwork legs are deliberately omitted -- this projection reads only
  ids and titles.
- The media-item switch was duplicated verbatim for RerunCollection and
  PlaylistItem; both now call one `ProjectMediaItemToViewModel`, which handles
  `RemoteStream` (via a new `ProjectToNamedViewModel`, since the existing
  `ProjectToViewModel(RemoteStream)` returns an unrelated type) and never falls
  through to null -- an unknown subtype keeps its id and takes a conspicuous
  name, because throwing would fail a whole paged GET over one bad row.
- Every metadata navigation in `MediaItems.Mapper` is now read through
  `Optional(...).Flatten()`, so an un-included nav degrades to "???" instead of
  being a latent 500 for whichever caller loads least.

Tests enumerate all 13 supported CollectionTypes for both handlers, with the
matrix derived from `IsSupportedSelectionType` so a newly-supported type joins
it automatically, plus a completeness guard on the set. Each mechanism was
removed in turn and confirmed red first.

fixes #671

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:48:24 +02:00