MediaCards paged handlers: 1-based paging + delete-or-keep (count drift fixed in #690/#758) #832
Open
opened 2026-08-26 08:20:35 +02:00 by timothy
·
1 comment
No Branch/Tag Specified
main
renovate/meziantou.analyzer-3.x
release/v26.15.0-notes
fix/830-add-items-error-surface
renovate/lucene.net
renovate/cliwrap-3.x
issue-806-guard-populations
renovate/dotnet-monorepo
scratch/767b-poisoned
scratch/767b-control
release/v26.14.0-notes
release/v26.14.0
renovate/sqlitepclraw.bundle_e_sqlite3-3.x
docs/510-skill-logo-bug-policy
fix/510-watermark-resolution-policy
fix/629-verdict-classifier-falseopens
fix/609-decisions-edit-token-scope
issue-135-clear-to-none
release/v26.12.0-notes
fix/409b-lastscan-api-parity
fix/401-updatechannel-mirror-422
fix/327-playlist-rename-validation
fix/410-scancancel-log-level
fix/409-447-librariesscreen-neverscanned
fix/338-zap-exit-code
fix/367-plex-budget-message
fix/310-debom-legacy-cs
ci/604-lane-rebalance
feat/388-design-mirror
feat/247-test-ownership
feat/247-primary-action
feat/357-player-owned-playback
feat/357-jellyfin-plugin-poc
fix/289-mcp-hardening
issue58-mcp
feat/244-channels-extract
ci/auto-bump-prod-compose
feat/multi-rerun-collections-api
feat/collections-api
feat/quick-wins
feat/185-docs-part2
feat/140-collections-screen
feat/146-channel-edit
feat/147-classic-ui-link
issue22-renovate-dashboard
feat/91-cutover
feat/63-composite-create
feat/65-library-browse
feat/85-epg
feat/86-schedule-editor
feat/109-dashboard-data
feat/99-session-tracking
fix/dockerfile-node-tag
feat/59-spa-foundation
docs/59-ui-redesign-brief
feat/102-json-guide
feat/111-schedule-durations
feat/104-artwork-upload
feat/103-media-sources-api
feat/playouts-read-api
feat/108-health-api
feat/105-picker-list-endpoints
issue-97-channel-state-api
issue42-jellyfin-musicvideos
issue46-rest-api-error-contract
dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
qsv-improvements
hdr-vulkan-cuda-test
v26.15.0
v26.14.0
v26.13.0
v26.12.0
v26.11.0
v26.10.0
v26.9.0
v26.8.0
v26.7.0
blazor-final
v26.6.0
v26.5.0
v26.4.0
v26.3.1
v26.3.0
v26.2.0
v26.1.1
v26.1.0
v25.9.0
v25.8.0
v25.7.1
v25.7.0
v25.6.0
v25.5.0
v25.4.0
v25.3.1
v25.3.0
v25.2.0
v25.1.0
v0.8.8-beta
v0.8.7-beta
v0.8.6-beta
v0.8.5-beta
v0.8.4-beta
v0.8.3-beta
v0.8.2-beta
v0.8.1-beta
v0.8.0-beta
v0.7.9-beta
v0.7.8-beta
v0.7.7-beta
v0.7.6-beta
v0.7.5-beta
v0.7.4-beta
v0.7.3-beta
v0.7.2-beta
v0.7.1-beta
v0.7.0-beta
v0.6.9-beta
v0.6.8-beta
v0.6.7-beta
v0.6.6-beta
v0.6.5-beta
v0.6.4-beta
v0.6.3-beta
v0.6.2-beta
v0.6.1-beta
v0.6.0-beta
v0.5.8-beta
v0.5.7-beta
v0.5.6-beta
v0.5.5-beta
v0.5.4-beta
v0.5.3-beta
v0.5.2-beta
v0.5.1-beta
v0.5.0-beta
v0.4.5-alpha
v0.4.4-alpha
v0.4.3-alpha
v0.4.2-alpha
v0.4.1-alpha
v0.4.0-alpha
v0.3.8-alpha
v0.3.7-alpha
develop
v0.3.6-alpha
v0.3.5-alpha
v0.3.4-alpha
v0.3.3-alpha
v0.3.2-alpha
v0.3.1-alpha
v0.3.0-alpha
v0.2.5-alpha
v0.2.4-alpha
v0.2.3-alpha
v0.2.2-alpha
v0.2.1-alpha
v0.2.0-alpha
v0.1.5-alpha
v0.1.4-alpha
v0.1.3-alpha
v0.1.2-alpha
v0.1.1-alpha
v0.1.0-alpha
v0.0.62-alpha
v0.0.61-alpha
v0.0.60-alpha
v0.0.59-alpha
v0.0.58-alpha
v0.0.57-alpha
v0.0.56-alpha
v0.0.55-alpha
v0.0.54-alpha
v0.0.53-alpha
v0.0.52-alpha
v0.0.51-alpha
v0.0.50-alpha
v0.0.49-prealpha
v0.0.48-prealpha
v0.0.47-prealpha
v0.0.46-prealpha
v0.0.45-prealpha
v0.0.44-prealpha
v0.0.43-prealpha
v0.0.42-prealpha
v0.0.41-prealpha
v0.0.40-prealpha
v0.0.39-prealpha
v0.0.38-prealpha
v0.0.37-prealpha
v0.0.36-prealpha
v0.0.35-prealpha
v0.0.34-prealpha
v0.0.33-prealpha
v0.0.32-prealpha
v0.0.31-prealpha
v0.0.30-prealpha
v0.0.29-prealpha
v0.0.28-prealpha
v0.0.27-prealpha
v0.0.26-prealpha
v0.0.25-prealpha
v0.0.24-prealpha
v0.0.23-prealpha
v0.0.22-prealpha
v0.0.21-prealpha
v0.0.20-prealpha
v0.0.19-prealpha
v0.0.18-prealpha
v0.0.17-prealpha
v0.0.16-prealpha
v0.0.15-prealpha
v0.0.14-prealpha
v0.0.13-prealpha
v0.0.12-prealpha
v0.0.11-prealpha
v0.0.10-prealpha
v0.0.9-prealpha
v0.0.8-prealpha
v0.0.7-prealpha
v0.0.6-prealpha
v0.0.5-prealpha
v0.0.4-prealpha
v0.0.3-prealpha
v0.0.2-prealpha
v0.0.1-prealpha
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: timothy/ersatztv#832
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the cold review of PR for #690/#758, while deriving the paged-handler population by SHAPE
rather than by the
GetPaged*Handlername. These three are the same mechanism as #690/#758 — thecount and the page are computed from different predicates — but they sit in
MediaCards, where thecount and the page are two separate repository methods rather than two expressions in one handler,
so the #690/#758 fix (one
IQueryable, count it, page it) does not directly apply.They were deliberately not fixed in that PR and are recorded here instead of being left
unrecorded, per
an issue's file list is not the population.The three sites
GetTelevisionSeasonCardsHandler:26,35TelevisionRepository.GetSeasonCount=Seasons.CountAsync(s => s.ShowId == showId)(TelevisionRepository.cs:134)GetPagedSeasons(:142) pagesSeasons.Where(s => showIds.Contains(s.ShowId))over every show sharing the same Title+YearGetTelevisionEpisodeCardsHandler:38,47Episodes.CountAsync(e => e.SeasonId == id)(:179)EpisodeMetadata.Filter(em => em.Episode.SeasonId == id)(:187)GetMusicVideoCardsHandler:33,36SELECT COUNT(*) FROM MusicVideo WHERE ArtistId = @ArtistId(MusicVideoRepository.cs:171)MusicVideoMetadata(:179)Why this is not urgent
All three are currently unreachable.
grep 'new GetTelevisionSeasonCards(\|new GetTelevisionEpisodeCards(\|new GetMusicVideoCards('over the git-tracked
*.csset returns zero hits outside their ownQueries/folders — they areleftovers from the removed Blazor UI (
blazor.ui-removed). Nothing in the SPA, the controllers orErsatzTV.Mcpreaches them. So this is dead code carrying a latent defect, not a live bug.They also use 1-based
pageNumber(Skip((pageNumber - 1) * pageSize)), against the repo-wide0-based contract in
api.paging-zero-based— a second reason not to fold them into a fix PRtargeting live handlers.
Options
library browsing is served by
GetLibraryBrowseItems, which counts correctly.the page derive from, and move to 0-based paging.
Decide which before doing either; do not "fix" dead code into permanence by reflex.
Done-when
pageNumberis 0-based perapi.paging-zero-basedapi.paged-count-matches-page-query's deferred-sites paragraph updated to record the outcomeScope narrowed — the count/page drift is FIXED, not deferred. The PR for #690/#758 fixes all
three sites after both cold reviews independently rated the deferral MEDIUM, and after checking the
fact that decided it: each of the six repository methods has exactly one caller (the dead handler),
so there was no blast radius.
Fixed there:
GetSeasonCountexpands to the same Title+Year show setGetPagedSeasonspages.GetEpisodeCount/GetMusicVideoCountcount the metadata table their pages are taken from.ErsatzTV.Tests/Application/Paging/MediaCardsCountMatchesPageTests.cs, with a mutationproof (restoring all three pre-fix counts reddens all four tests).
What is still open here
pageNumber. All three pageSkip((pageNumber - 1) * pageSize), against therepo-wide 0-based contract in
api.paging-zero-based. Deliberately not changed alongside acount-correctness fix.
Queries/folder — leftovers from
blazor.ui-removed. If nothing intends to revive media-card browsing,deleting them (and the six repository methods, which have no other caller) is likely better than
migrating their paging.
the reason "count the table the page reads" is necessary but not sufficient here.
GetPagedEpisodes's chain walksEpisode -> Season -> Show -> ShowMetadata, and a missingShowrow drops every row, so the page can be narrower than even the corrected metadata count. Not
closed, not claimed closed. If these handlers are kept, this needs its own answer; if they are
deleted, it dies with them.
Done-when (revised)
pageNumberis 0-based perapi.paging-zero-based, and the include-chain narrowingin (3) is either fixed or recorded as accepted with its reason
api.paged-count-matches-page-query's MediaCards paragraph updated to record the outcomeMediaCards paged handlers count a different predicate than they page (same shape as #690/#758, dead code)to MediaCards paged handlers: 1-based paging + delete-or-keep (count drift fixed in #690/#758)