Skip to content

fix(collections): sort by addDate and group episodes by show#2868

Merged
enoch85 merged 5 commits into
developmentfrom
fix/2867-delete-soonest-sort-uses-wrong-date
May 11, 2026
Merged

fix(collections): sort by addDate and group episodes by show#2868
enoch85 merged 5 commits into
developmentfrom
fix/2867-delete-soonest-sort-uses-wrong-date

Conversation

@enoch85

@enoch85 enoch85 commented May 10, 2026

Copy link
Copy Markdown
Collaborator

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. applyCollectionSort pushes 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

  • deleteSoonest reads collection_media.addDate via a deleteSoonestDate callback on the comparator options, replacing the previous MediaItem.addedAt (which is the unrelated library-add date).
  • Day buckets align with the visible "Leaving in X days" countdown. New deleteSoonestReferenceTime option (now − deleteAfterDays × dayMs) makes ceil((addDate − reference) / dayMs) the bucket key, so two items with the same overlay countdown tie even if their addDates straddle UTC midnight. Within a tied bucket the show-aware title order is the tiebreaker. Used by applyCollectionSort.
  • deleteSoonest UI paging stays SQL-paginated — orders by collection_media.addDate directly 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 by applyCollectionSort). Primary sort key matches; both views agree on which items leave first.
  • Title sort groups episodes under their show via grandparentTitle ?? parentTitle ?? title, then title. 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)

  • 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 asc mode.
  • airDate, rating, watchCount, and deleteSoonest collapse onto one compareNumericWithTitleFallback helper.
  • airDate now day-buckets like deleteSoonest.

Other

  • Pinned 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.
  • Removed unused baseUrl + @/* paths from apps/ui/tsconfig.json (cleared a TS 6 deprecation in the IDE).

Caveat

Existing collections won't visually re-sort in Plex until membership changes — applyCollectionSort only re-runs when newMedia.length > 0. Editing a rule (touch + save) triggers it. Worth a release-notes line.

Test plan

  • On a movie collection with 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+ items
  • In the actual Plex/Jellyfin collection, items added at 23:00 vs. 01:00 the next morning showing the same overlay countdown sort alphabetically together
  • Episode collection sorted by Title A–Z keeps episodes from the same show contiguous
  • Items with no addDate / no air date / no rating sort to the end in both asc and desc
  • apps/server/tsconfig.build.tsbuildinfo does NOT exist after yarn workspace @maintainerr/server build (only apps/server/dist/tsconfig.build.tsbuildinfo)
  • yarn lint, yarn test, yarn build, yarn dev all clean

enoch85 added 2 commits May 10, 2026 17:15
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
@enoch85

enoch85 commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

/release-pr

@enoch85

enoch85 commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

@timelordx

Please test the PR tagged release.

@github-actions

Copy link
Copy Markdown
Contributor

Released to maintainerr/maintainerr:pr-2868 🚀

@enoch85

enoch85 commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

@00Scooby would be great with some confirmation on this! 🙏🏼

@enoch85 enoch85 added the documentation Improvements or additions to documentation label May 10, 2026
- 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.
@enoch85

enoch85 commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

/release-pr

@github-actions

Copy link
Copy Markdown
Contributor

Released to maintainerr/maintainerr:pr-2868 🚀

- 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.
@enoch85

enoch85 commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

/release-pr

@github-actions

Copy link
Copy Markdown
Contributor

Released to maintainerr/maintainerr:pr-2868 🚀

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.
@enoch85

enoch85 commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

/release-pr

@github-actions

Copy link
Copy Markdown
Contributor

Released to maintainerr/maintainerr:pr-2868 🚀

@enoch85

enoch85 commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

Tested on Jellyfin (Collections sorting) and it works as expected).

Nitpick, some flicker when sorting. Might be my browser though.

@timelordx

Copy link
Copy Markdown

@enoch85 ... tested A-Z, Z-A, and Delete Soonest sorting, and everything seems to be working perfectly. Well done!

@enoch85 enoch85 merged commit 11232fe into development May 11, 2026
13 checks passed
@enoch85 enoch85 deleted the fix/2867-delete-soonest-sort-uses-wrong-date branch May 11, 2026 04:49
maintainerr-automation Bot added a commit that referenced this pull request May 11, 2026
* 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>
@00Scooby

Copy link
Copy Markdown
Contributor

Hey @enoch85,
Sorry I missed the tag earlier (Was sleeping)! Glad to see @timelordx already confirmed it and it's merged. Thanks for jumping on this so quickly, the TV show grouping is a much-needed improvement! 🚀

@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.11.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants