fix(collections): sort by addDate and group episodes by show#2868
Conversation
Maintainerr is now the source of truth for collection ordering: both the Plex push and the UI fetch use the same comparator, so what users see in the collection page matches what the media server displays. - deleteSoonest now reads collection_media.addDate (drives the visible "Leaving in X days" overlay) instead of MediaItem.addedAt (the unrelated media-server library date) - Same-day items tie via UTC day bucketing so the title tiebreaker actually fires within a "Leaving Today" group - Title sort groups episodes/seasons under their show via grandparentTitle/parentTitle; movies are unaffected
|
/release-pr |
|
Please test the PR tagged release. |
|
Released to |
|
@00Scooby would be great with some confirmation on this! 🙏🏼 |
- Items missing the sort value (no air date, no rating, no view count, no add date) now sort to the end regardless of asc/desc. Previously they coerced to 0 and silently sorted to the front in ascending order — an item with no air date appeared before one from 1995. - airDate, rating, watchCount, and deleteSoonest collapse onto one helper (compareNumericWithTitleFallback), removing three near-identical case bodies in compareMediaItemsBySort. - airDate now day-buckets like deleteSoonest, so same-day items always reach the title tiebreaker instead of comparing by clock time. - Pin tsBuildInfoFile in apps/server/tsconfig.build.json so nest-cli's type-check pass stops emitting a stray tsconfig.build.tsbuildinfo at the package root.
|
/release-pr |
|
Released to |
- Bucket deleteSoonest by `ceil((addDate - referenceTime) / dayMs)` so items showing the same "Leaving in X days" countdown tie even when their addDates straddle UTC midnight. Falls back to UTC-day bucketing when no reference time is supplied. - Wire `referenceTime = now - deleteAfterDays * dayMs` through the two collection sort paths (applyCollectionSort + paginated fetch). - Replace the show-aware title test data — old data alphabetized identically under either comparator and didn't actually prove the grouping behavior. New data interleaves episode titles across shows and asserts on [show, episode] pairs. - Drop dead `baseUrl` + `paths` from apps/ui/tsconfig.json (unused alias) to clear the TS 6 deprecation surfaced in the IDE.
|
/release-pr |
|
Released to |
Page-loading a collection with `deleteSoonest` was hydrating every row in the collection before slicing — minutes-long blocking page loads on collections of a few hundred items, especially on Jellyfin where `getMetadata` isn't cached at the adapter layer. `deleteSoonest` orders by `addDate + deleteAfterDays`, but `deleteAfterDays` is constant per collection, so the only sort key that matters lives on `collection_media.addDate`. SQL can paginate it directly without touching MediaItem metadata. `applyCollectionSort` (the media-server push) still applies the day-bucketed title tiebreaker via the comparator, so the polished alphabetical-within-day order is what users see when browsing the actual Plex/Jellyfin collection. The Maintainerr UI page may show same-day items in a slightly different order — acceptable because the primary sort key is correct and Maintainerr's DB remains the source of truth driving the next push. Other explicit sorts (airDate / rating / watchCount / title) still go through hydrate-then-paginate because their sort keys live on MediaItem, not on collection_media.
|
/release-pr |
|
Released to |
|
Tested on Jellyfin (Collections sorting) and it works as expected). Nitpick, some flicker when sorting. Might be my browser though. |
|
@enoch85 ... tested |
* docs: sync overlay processing API and add UI test-utils reference (#2866) Documents the force flag on overlay processing endpoints and the 409 gating on DELETE /overlays/reset (#2827), and points contributors at the new TanStack Query test helpers (#2863). * fix(rules): scope exclusion cascade by type, not entry point (#2858) (#2867) Excluding a single episode used to skip every other episode of the same show. The cascade filter keyed off `Exclusion.parent`, but `parent` is the entry point of the exclusion request (set to `data.mediaId`, which from the show overview is always the show id) — not the structural parent. A typed cascade set driven by `mediaServerId` of `type='show'`/`'season'` rows fixes the regression without breaking the legacy null-type fallback. * fix(collections): sort by addDate and group episodes by show (#2868) * fix(collections): sort by addDate and group episodes by show Maintainerr is now the source of truth for collection ordering: both the Plex push and the UI fetch use the same comparator, so what users see in the collection page matches what the media server displays. - deleteSoonest now reads collection_media.addDate (drives the visible "Leaving in X days" overlay) instead of MediaItem.addedAt (the unrelated media-server library date) - Same-day items tie via UTC day bucketing so the title tiebreaker actually fires within a "Leaving Today" group - Title sort groups episodes/seasons under their show via grandparentTitle/parentTitle; movies are unaffected * refactor(sort): pin missing values to end + DRY numeric branches - Items missing the sort value (no air date, no rating, no view count, no add date) now sort to the end regardless of asc/desc. Previously they coerced to 0 and silently sorted to the front in ascending order — an item with no air date appeared before one from 1995. - airDate, rating, watchCount, and deleteSoonest collapse onto one helper (compareNumericWithTitleFallback), removing three near-identical case bodies in compareMediaItemsBySort. - airDate now day-buckets like deleteSoonest, so same-day items always reach the title tiebreaker instead of comparing by clock time. - Pin tsBuildInfoFile in apps/server/tsconfig.build.json so nest-cli's type-check pass stops emitting a stray tsconfig.build.tsbuildinfo at the package root. * refactor(sort): align deleteSoonest buckets with visible daysLeft - Bucket deleteSoonest by `ceil((addDate - referenceTime) / dayMs)` so items showing the same "Leaving in X days" countdown tie even when their addDates straddle UTC midnight. Falls back to UTC-day bucketing when no reference time is supplied. - Wire `referenceTime = now - deleteAfterDays * dayMs` through the two collection sort paths (applyCollectionSort + paginated fetch). - Replace the show-aware title test data — old data alphabetized identically under either comparator and didn't actually prove the grouping behavior. New data interleaves episode titles across shows and asserts on [show, episode] pairs. - Drop dead `baseUrl` + `paths` from apps/ui/tsconfig.json (unused alias) to clear the TS 6 deprecation surfaced in the IDE. * perf(collections): restore SQL fast path for deleteSoonest paging Page-loading a collection with `deleteSoonest` was hydrating every row in the collection before slicing — minutes-long blocking page loads on collections of a few hundred items, especially on Jellyfin where `getMetadata` isn't cached at the adapter layer. `deleteSoonest` orders by `addDate + deleteAfterDays`, but `deleteAfterDays` is constant per collection, so the only sort key that matters lives on `collection_media.addDate`. SQL can paginate it directly without touching MediaItem metadata. `applyCollectionSort` (the media-server push) still applies the day-bucketed title tiebreaker via the comparator, so the polished alphabetical-within-day order is what users see when browsing the actual Plex/Jellyfin collection. The Maintainerr UI page may show same-day items in a slightly different order — acceptable because the primary sort key is correct and Maintainerr's DB remains the source of truth driving the next push. Other explicit sorts (airDate / rating / watchCount / title) still go through hydrate-then-paginate because their sort keys live on MediaItem, not on collection_media. --------- Co-authored-by: enoch85 <mailto@danielhansson.nu>
|
Hey @enoch85, |
|
🎉 This PR is included in version 3.11.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Reported in Discord on
main: with "Delete Soonest" / "Delete Latest" the Plex collection order is wrong, the alphabetical tiebreaker never fires, and TV episodes are interleaved by episode title instead of grouped by show.Maintainerr's DB is the source of truth for ordering.
applyCollectionSortpushes Maintainerr's order to the media server; the UI reads from the same DB. Both must agree on the primary sort key (collection_media.addDate).Key changes
deleteSoonestreadscollection_media.addDatevia adeleteSoonestDatecallback on the comparator options, replacing the previousMediaItem.addedAt(which is the unrelated library-add date).deleteSoonestReferenceTimeoption (now − deleteAfterDays × dayMs) makesceil((addDate − reference) / dayMs)the bucket key, so two items with the same overlay countdown tie even if theiraddDates straddle UTC midnight. Within a tied bucket the show-aware title order is the tiebreaker. Used byapplyCollectionSort.deleteSoonestUI paging stays SQL-paginated — orders bycollection_media.addDatedirectly at the DB level instead of hydrating every row in the collection. Restored after the initial PR removed it; hydrate-then-sort on a 127-item Jellyfin collection blocked the UI page for minutes. Trade-off: same-day items on the UI page may show in a slightly different within-bucket order than the polished Plex/Jellyfin collection (which has the title tiebreak applied byapplyCollectionSort). Primary sort key matches; both views agree on which items leave first.grandparentTitle ?? parentTitle ?? title, thentitle. Movies are unaffected. Shared with the library Overview comparator, so general library title sorts also group episodes under their show — matches Plex's own behavior.Comparator hardening (same surface)
0and silently sorted to the front inascmode.airDate,rating,watchCount, anddeleteSoonestcollapse onto onecompareNumericWithTitleFallbackhelper.airDatenow day-buckets likedeleteSoonest.Other
tsBuildInfoFileinapps/server/tsconfig.build.jsonso nest-cli's type-check pass stops emitting a straytsconfig.build.tsbuildinfoat the package root.baseUrl+@/*paths fromapps/ui/tsconfig.json(cleared a TS 6 deprecation in the IDE).Caveat
Existing collections won't visually re-sort in Plex until membership changes —
applyCollectionSortonly re-runs whennewMedia.length > 0. Editing a rule (touch + save) triggers it. Worth a release-notes line.Test plan
deleteSoonest.asc, items leaving today appear before items leaving in 7+ days, and the page loads in under a second on a collection with 100+ itemsaddDate/ no air date / no rating sort to the end in bothascanddescapps/server/tsconfig.build.tsbuildinfodoes NOT exist afteryarn workspace @maintainerr/server build(onlyapps/server/dist/tsconfig.build.tsbuildinfo)yarn lint,yarn test,yarn build,yarn devall clean