Skip to content

feat(discover): filter movie discovery by minimum release - #3384

Open
demigodmode wants to merge 1 commit into
seerr-team:developfrom
demigodmode:feat-digital-release-filter
Open

feat(discover): filter movie discovery by minimum release#3384
demigodmode wants to merge 1 commit into
seerr-team:developfrom
demigodmode:feat-digital-release-filter

Conversation

@demigodmode

@demigodmode demigodmode commented Aug 12, 2026

Copy link
Copy Markdown

Description

Adds a "Minimum Release" filter to the movie discover filters (part of #579). You pick a release stage from the dropdown and it shows movies that have reached at least that stage. Picking Digital also covers Physical and TV, since those are all effectively "available at home."

The fiddly bit is that TMDB's with_release_type does nothing on its own. It only filters when it's paired with a release_date range, and it works off release_date rather than the primary_release_date field the existing Release Date filter uses. So when a Minimum Release is selected, the query switches its date range over to release_date and defaults the "To" date to today if you haven't set one. With nothing selected, the query is unchanged and the normal Release Date filter behaves exactly as before.

Keeping this to movie discover for a first pass. Related to #579 but not closing it, since that issue also covers things like release-status badges on movie pages.

AI disclosure: I originally wrote the first version of this filter myself. For this iteration I had some AI assistance to help rework it, mainly the cumulative release-type logic and to code-review the TMDB query behavior and the diff. The review caught a timezone bug I missed and an unrelated translation change, both fixed. I've reviewed and tested all the changes myself and understand how they work.

How Has This Been Tested?

Ran it locally against a Jellyfin instance:

  • The dropdown shows up in the movie filters, and the "To" date auto-fills to today when a level is picked.
  • Results change by level. Checked against TMDB's discover endpoint directly: digital-and-up (with_release_type=4|5|6 + release_date.lte=<today>) returns ~314k vs ~1.16M unfiltered, and narrows as the floor goes up (theatrical ~1.05M, TV ~105k). No region needed.
  • With no Minimum Release selected, the existing Release Date filter still uses primary_release_date and behaves the same.
  • tsc (client + server) and eslint pass.

Screenshots / Logs (if applicable)

Screenshot 2026-08-12 152506 Screenshot 2026-08-12 152354

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy)
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required)

Summary by CodeRabbit

  • New Features
    • Added release-type filtering to movie discovery.
    • Filter movies by digital, physical, limited theatrical, theatrical, or TV release.
    • Release-type filtering supports cumulative matching and automatically sets an end date when needed.
  • Documentation
    • Documented supported release types and filtering behavior.
  • Localization
    • Added English labels for the new release-type filter options.

Adds a Minimum Release filter to movie discover. Picking a level matches that release type or later,
so Digital also covers physical and TV. with_release_type is a no-op on TMDB unless paired with a
release_date range, so when it's set the range switches to release_date and the "To" date defaults
to today.

re seerr-team#579
@demigodmode
demigodmode requested a review from a team as a code owner August 12, 2026 21:54
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Movie discovery now supports release type filtering. The UI validates and submits release types 26, the server forwards them to TMDB, and TMDB applies cumulative release-type matching with release_date bounds.

Changes

Movie release type filtering

Layer / File(s) Summary
Filter contract and UI selector
src/components/Discover/constants.ts, src/components/Discover/FilterSlideover/index.tsx, src/i18n/locale/en.json
The Discover filters validate release types 26. The movie-only selector updates releaseType and localized labels.
Validated release type request flow
server/routes/discover.ts, server/api/themoviedb/index.ts
The route validates and forwards releaseType. The TMDB client expands the selected type through type 6 and uses release_date bounds with with_release_type.
Movie discovery API contract
seerr-api.yml
The API documentation defines the optional releaseType parameter, accepted values, and cumulative matching behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: 🟡 Moderate · up to 9d7d6

The new movie discovery filter currently has an unlabeled selector for assistive technologies, and direct API requests can return unfiltered results when only a minimum release stage is provided. These bounded accessibility and correctness issues should be fixed before merging.

Sequence Diagram(s)

sequenceDiagram
  participant DiscoverFilter
  participant DiscoverRoute
  participant TheMovieDb
  DiscoverFilter->>DiscoverRoute: Submit releaseType filter
  DiscoverRoute->>TheMovieDb: Forward validated releaseType
  TheMovieDb->>TheMovieDb: Expand release types through type 6
  TheMovieDb->>TheMovieDb: Build release_date bounds and with_release_type
Loading

Suggested reviewers: fallenbagel, 0xsysr3ll

Poem

A rabbit hops through filters bright,
And picks a release type just right.
The dates now stretch, the types align,
TMDB follows every sign.
Cumulative hops complete the flight!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a minimum release filter to movie discovery.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@seerr-api.yml`:
- Around line 5604-5610: Ensure the API applies or documents the UI’s
primaryReleaseDateLte=today default when releaseType is provided. Update the
releaseType schema/handling near its description so direct requests with only
releaseType still include the appropriate release-date bound, or clearly
document that callers must supply primaryReleaseDateLte.

In `@src/components/Discover/FilterSlideover/index.tsx`:
- Around line 143-149: Replace the visible label span in the release-type
selector block with a label element and associate it using
htmlFor="releaseType", preserving the existing message text and select id.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e2ec2276-f0a8-4206-bf4a-90f752d6fd59

📥 Commits

Reviewing files that changed from the base of the PR and between 39ff48c and 9d7d6cb.

📒 Files selected for processing (6)
  • seerr-api.yml
  • server/api/themoviedb/index.ts
  • server/routes/discover.ts
  • src/components/Discover/FilterSlideover/index.tsx
  • src/components/Discover/constants.ts
  • src/i18n/locale/en.json

Comment thread seerr-api.yml
Comment on lines +5604 to +5610
description: >-
Minimum release availability for movie discovery. The selected type
and everything above it are matched (e.g. 4 = digital also matches 5
= physical and 6 = TV, sent to TMDB as with_release_type=4|5|6).
When set, results are filtered on release_date instead of
primary_release_date. 2 = limited theatrical, 3 = theatrical,
4 = digital, 5 = physical, 6 = TV

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -u

for file in \
  "src/components/Discover/FilterSlideover/index.tsx" \
  "server/routes/discover.ts" \
  "server/api/themoviedb/index.ts"; do
  printf '\n== %s ==\n' "$file"
  rg -n -C 10 \
    'releaseType|primaryReleaseDateGte|primaryReleaseDateLte|release_date|defaultFutureDate|new Date' \
    "$file" || true
done

Repository: seerr-team/seerr

Length of output: 14899


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

route = Path("server/routes/discover.ts").read_text()
client = Path("server/api/themoviedb/index.ts").read_text()
ui = Path("src/components/Discover/FilterSlideover/index.tsx").read_text()

assert "releaseType: query.releaseType" in route
assert "primaryReleaseDateLte: query.primaryReleaseDateLte" in route
assert "primaryReleaseDateGte: query.primaryReleaseDateGte" in route
assert "with_release_type: releaseTypeFilter" in client
assert "if (value && !currentFilters[dateLte])" in ui
assert "[dateLte]: today" in ui

def tmdb_dates(gte=None, lte=None):
    # Mirrors the date-bound logic in getDiscoverMovies.
    past = "1900-01-01"
    future = "<defaultFutureDate>"
    return (
        past if not gte and lte else gte,
        future if not lte and gte else lte,
    )

print("route forwards releaseType and both date fields without adding a default")
print("no dates ->", tmdb_dates())
print("gte only ->", tmdb_dates("2025-01-01"))
print("lte only ->", tmdb_dates(lte="2025-01-01"))
print("UI adds today's dateLte only when the user selects releaseType")
PY

Repository: seerr-team/seerr

Length of output: 406


Apply or document the primaryReleaseDateLte default for releaseType.

The UI sets primaryReleaseDateLte to today, but direct API requests do not. Requests with only releaseType send no release-date bounds, so TMDB does not apply the release-type filter.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@seerr-api.yml` around lines 5604 - 5610, Ensure the API applies or documents
the UI’s primaryReleaseDateLte=today default when releaseType is provided.
Update the releaseType schema/handling near its description so direct requests
with only releaseType still include the appropriate release-date bound, or
clearly document that callers must supply primaryReleaseDateLte.

Comment on lines +143 to +149
<span className="text-lg font-semibold">
{intl.formatMessage(messages.minimumRelease)}
</span>
<select
id="releaseType"
name="releaseType"
value={currentFilters.releaseType ?? ''}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Associate the selector with its visible label.

Line 143 renders the control label as a span. The select on lines 146-149 therefore has no accessible name. Use a label with htmlFor="releaseType".

Proposed fix
-            <span className="text-lg font-semibold">
+            <label
+              htmlFor="releaseType"
+              className="text-lg font-semibold"
+            >
               {intl.formatMessage(messages.minimumRelease)}
-            </span>
+            </label>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span className="text-lg font-semibold">
{intl.formatMessage(messages.minimumRelease)}
</span>
<select
id="releaseType"
name="releaseType"
value={currentFilters.releaseType ?? ''}
<label
htmlFor="releaseType"
className="text-lg font-semibold"
>
{intl.formatMessage(messages.minimumRelease)}
</label>
<select
id="releaseType"
name="releaseType"
value={currentFilters.releaseType ?? ''}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Discover/FilterSlideover/index.tsx` around lines 143 - 149,
Replace the visible label span in the release-type selector block with a label
element and associate it using htmlFor="releaseType", preserving the existing
message text and select id.

@SoulofAkuma SoulofAkuma left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, just my 2 cents. I am not a code owner, so we probably need an actual code owner to review this as well.

keywords?: string;
excludeKeywords?: string;
sortBy?: SortOptions;
releaseType?: string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
releaseType?: string;
minimumReleaseType?: string;

voteAverageLte,
voteCountGte,
voteCountLte,
releaseType,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
releaseType,
minimumReleaseType,

Comment on lines +629 to +637
const releaseTypeFilter = releaseType
? Array.from(
{ length: 6 - Number(releaseType) + 1 },
(_, i) => Number(releaseType) + i
).join('|')
: undefined;
const releaseDateField = releaseType
? 'release_date'
: 'primary_release_date';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const releaseTypeFilter = releaseType
? Array.from(
{ length: 6 - Number(releaseType) + 1 },
(_, i) => Number(releaseType) + i
).join('|')
: undefined;
const releaseDateField = releaseType
? 'release_date'
: 'primary_release_date';
const releaseTypeFilter = minimumReleaseType
? Array.from(
{ length: 6 - Number(minimumReleaseType) + 1 },
(_, i) => Number(minimumReleaseType) + i
).join('|')
: undefined;
const releaseDateField = minimumReleaseType
? 'release_date'
: 'primary_release_date';

Comment thread seerr-api.yml
type: string
example: 2023-01-01
- in: query
name: releaseType

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: releaseType
name: minimumReleaseType

Comment on lines +171 to +184
<option value="">
{intl.formatMessage(messages.anyRelease)}
</option>
<option value="2">
{intl.formatMessage(messages.limitedTheatrical)}
</option>
<option value="3">
{intl.formatMessage(messages.theatrical)}
</option>
<option value="4">{intl.formatMessage(messages.digital)}</option>
<option value="5">{intl.formatMessage(messages.physical)}</option>
<option value="6">
{intl.formatMessage(messages.tvRelease)}
</option>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Premiere release type is missing. Is that intentional?

[dateLte]: today,
});
} else {
updateQueryParams('releaseType', value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
updateQueryParams('releaseType', value);
updateQueryParams('minimumReleaseType', value);


const dateGte =
type === 'movie' ? 'primaryReleaseDateGte' : 'firstAirDateGte';
const dateLte =

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what would be the right place to change the primaryReleaseDate to releaseDate. Here or in the function that does the TMDB API call.

inputClassName="pr-1 sm:pr-4 text-base leading-5"
/>
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest adding a warning message under the date picker here if the type is a movie, a minimumReleaseType other than Any has been selected and both the to and from dates are empty. That warning message should indicate that this will cause the selected minimum release to have no effect.

sortBy: z.string().optional(),
primaryReleaseDateGte: z.string().optional(),
primaryReleaseDateLte: z.string().optional(),
releaseType: z.enum(['2', '3', '4', '5', '6']).optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
releaseType: z.enum(['2', '3', '4', '5', '6']).optional(),
minimumReleaseType: z.enum(['2', '3', '4', '5', '6']).optional(),

Comment on lines +142 to +144
if (values.releaseType) {
filterValues.releaseType = values.releaseType;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (values.releaseType) {
filterValues.releaseType = values.releaseType;
}
if (values.minimumReleaseType) {
filterValues.minimumReleaseType = values.minimumReleaseType;
}

@SoulofAkuma

Copy link
Copy Markdown

It'd probably be great if this can also have some testing. There is already a discover cypress suite where you might be able to add a test case or two (at least validating that selecting a certain value leads to the correct TMDB query being made)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants