Final cold review of 34eee753b: the commit's own changes were confirmed correct,
but it flagged a real pre-existing bug in the exact block I had just edited, and
I was adding the first-ever tests for that method without covering it.
`Playouts/Mapper.GetDisplayTitle`'s Song arm interpolated `{s}` -- the
`case Song s` ENTITY -- into its chapter-title branch instead of `{t}`, the
composed title. Song has no ToString() override, so a chaptered song rendered as
the literal "ErsatzTV.Core.Domain.Song (Chapter 3)" in the playout guide,
troubleshooting, media-item info and channel states. The sibling MusicVideo and
OtherVideo arms are correct only because they happen to name their lambda `s`.
Pre-existing on main; fixed here because it is one token inside the block this
branch already touches. Two tests pin it; reverting renders the type name.
Also: completed the guard on that arm (`Optional(s.SongMetadata).Flatten()`, the
other half of the sibling pattern I claimed to have copied), added the new
mechanism to the record's `mechanics:`, added the symptom tokens a future session
would actually search for (ArgumentNullException, Artists, primitive collection,
chaptered song) to `signals:`, restored the remedy sentence an earlier trim
dropped, and trimmed to 59 prose lines for margin under the 60-line ceiling.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cold review of 572737a29. Findings taken; two are corrections to my own claims.
CORRECTION: "a regression this branch INTRODUCED" was wrong, and I verified the
reviewer's counter-claim against origin/main before accepting it. That handler
already included SongMetadata AND already routed Song there, so
GET /api/v1/playlists/{id}/items was ALREADY a live 500 for a null-Artists song.
This branch only made the same throw reachable on a second path. The record said
so twice; both are fixed, because a wrong explanation outlives a wrong line.
SWEEP: fixing one site left the mirror standing -- Playouts/Mapper.GetDisplayTitle
had the identical unguarded join on a path that also eager-loads SongMetadata, so
it too was live, feeding the playout guide, troubleshooting, media-item info and
channel states. Guarded, with a unit test; reverting it reproduces
ArgumentNullException. LibraryBrowseItemMapper already wrote `Artists ?? []`, so
the nullability was known in-tree and these sites were simply unswept. Filed #691
for the remaining SongVideoGenerator dereferences on the playback path.
Also: documented that the shared matrix is the RERUN predicate used as a superset
for playlists (the playlist write path rejects RemoteStream today); noted the
third, inert consumer ReplacePlaylistItemsHandler; added the new mechanisms to the
record's `mechanics:` field; and trimmed the record under the 60-line prose
ceiling -- it was tipping the corpus p90 above the ceiling and reddening the
calibration test in scripts/tests, which passes again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
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>
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>