Skip to content

feat(discover): added availability filter to discover pages#2935

Open
BaLion29 wants to merge 4 commits into
seerr-team:developfrom
BaLion29:feat/discover-availability-filter
Open

feat(discover): added availability filter to discover pages#2935
BaLion29 wants to merge 4 commits into
seerr-team:developfrom
BaLion29:feat/discover-availability-filter

Conversation

@BaLion29

@BaLion29 BaLion29 commented Apr 22, 2026

Copy link
Copy Markdown

Description

Adds a per-user availability filter on the /discover/movies and /discover/tv pages, allowing users to filter content by availability status directly from the filter slideover.

Why?

Currently, the only availability-related control is a global admin toggle (General Settings → Hide Available Media), which applies server-wide and is not designed as a per-session, per-user control.
As a result, regular users cannot filter discovery pages by availability status. In some cases, however, it may be desirable for a user to filter media based on its availability, e.g. when a large portion of the most popular titles is already available and hinders the discovery of new content.

How?

  • The filter is implemented as a URL query parameter (availability) and applied client-side in useDiscover, consistent with the existing hideAvailable pattern.
  • It exposes following three single-choice options: "Show All", "Show Available and Requested only" and "Hide Available and Requested"
  • The only backend change is the addition of the DiscoverAvailabilityFilter enum to server/constants/discover.ts for typing purposes.
  • The added AvailabilitySelector is a simplified version of the RegionSelector, identical in behaviour and appearance.

Known Limitations

  • Depending on the situation, loading media with the filter active may be slower due to a pagination issue that also affects the existing hideAvailable toggle in extreme cases. A clean solution is not known to me beyond potential UI improvements to the loading indicator during scroll.
  • It has yet to be defined how to handle the case that hideAvailable is set to true in the global settings. At the moment, if hideAvailable is true, available media will not be display under "Show All", but it will under "Show Available and Requested only".

Related Issues

How Has This Been Tested?

Tested locally against a Jellyfin instance with a mixed library (available movies, partially available series, pending requests...)

  • Availability filters do work correctly
  • Updates "Active Filters" properly
  • Resets correctly with "Clear Active Filters"
  • Default value "Show All" works and acts as fallback for wrong URL parameter
  • Combination with other filters without complications

Screenshots / Logs

screenshot

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 an "Availability" filter to Discover with three options: Show All, Show Available and Requested only, Hide Available and Requested.
    • Availability selector added to the filter UI and synced with the page query so selections persist in the URL (clearing when "Show All" is chosen).
    • Discover results now respect the selected availability filter.
  • Documentation

    • Added English labels for the new Availability UI.

@BaLion29 BaLion29 requested a review from a team as a code owner April 22, 2026 18:23
@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a new availability filter to Discover: server enum, UI selector, query param wiring, schema support, and post-fetch availability-based filtering in the discover hook.

Changes

Cohort / File(s) Summary
Server Constants
server/constants/discover.ts
Added exported DiscoverAvailabilityFilter enum with ALL, AVAILABLE_OR_REQUESTED, and NOT_AVAILABLE_OR_REQUESTED.
Selector Component
src/components/Selector/AvailabilitySelector.tsx, src/components/Selector/index.tsx
New AvailabilitySelector Listbox component with localized labels and onChange; re-exported from selector index.
Filter UI Integration
src/components/Discover/FilterSlideover/index.tsx
Integrated AvailabilitySelector into FilterSlideover; updates availability query param (clears when ALL).
Filter Schema / Preparation
src/components/Discover/constants.ts
Added optional availability to QueryFilterOptions zod schema and conditionally included in prepareFilterValues.
Discovery Hook Logic
src/hooks/useDiscover.ts
Extracts availability from options (excluded from TMDB query) and applies post-fetch filtering for ALL / AVAILABLE_OR_REQUESTED / NOT_AVAILABLE_OR_REQUESTED based on mediaInfo.status.
Localization
src/i18n/locale/en.json
Added English entries for availability label and three selector option strings.

Sequence Diagram

sequenceDiagram
    participant UI as FilterSlideover UI
    participant Sel as AvailabilitySelector
    participant QP as Query Params
    participant Hook as useDiscover Hook
    participant API as TMDB API
    participant Post as Availability Post-Filter

    UI->>Sel: Render with current availability value
    Note over Sel: User selects an option
    Sel->>UI: onChange(selected filter value)
    UI->>QP: updateQueryParams(availability or clear)
    QP->>Hook: Trigger fetch (availability removed from TMDB params)
    Hook->>API: Fetch TMDB results (tmdbOptions)
    API-->>Hook: Return titles
    Hook->>Post: Apply availability-based filtering
    Post-->>Hook: Return filtered titles
    Hook-->>UI: Render filtered discover items
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Poem

🐰
I hopped through code with a twitch of my nose,
Placed filters where discovery grows.
Show all, or only what's free, or hide the claimed,
Now browse with delight — nothing's misnamed! 🎬

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(discover): added availability filter to discover pages' is clear, concise, and accurately describes the main change—adding an availability filter feature to the discover pages.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/useDiscover.ts (1)

127-165: ⚠️ Potential issue | 🟠 Major

hideAvailable short-circuits the AVAILABLE_OR_REQUESTED availability filter.

When settings.currentSettings.hideAvailable is enabled (the default), AVAILABLE and PARTIALLY_AVAILABLE items are stripped on lines 127-134 before your new availability filter runs. If the user then explicitly selects "Show Available and Requested only", lines 148-156 will never see available items and can only return PROCESSING/PENDING, which contradicts the user's explicit choice.

Skip the implicit hideAvailable pass when the user has set an explicit availability filter.

🐛 Proposed fix
-  if (settings.currentSettings.hideAvailable && hideAvailable) {
+  if (
+    settings.currentSettings.hideAvailable &&
+    hideAvailable &&
+    availability === DiscoverAvailabilityFilter.ALL
+  ) {
     titles = titles.filter(
       (i) =>
         (i.mediaType === 'movie' || i.mediaType === 'tv') &&
         i.mediaInfo?.status !== MediaStatus.AVAILABLE &&
         i.mediaInfo?.status !== MediaStatus.PARTIALLY_AVAILABLE
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useDiscover.ts` around lines 127 - 165, The implicit hideAvailable
filtering (the block that checks settings.currentSettings.hideAvailable &&
hideAvailable) is removing AVAILABLE/PARTIALLY_AVAILABLE items before the
explicit availability filter runs; change the logic so that the hideAvailable
pass is skipped when the user has chosen an explicit availability filter (i.e.,
when availability is set to something other than the "all"/default value).
Concretely, in useDiscover.ts adjust the condition that runs the hideAvailable
filter to also require availability to be the default/all option (so keep the
current checks for settings.currentSettings.hideAvailable and hideAvailable, but
add a check like availability === DiscoverAvailabilityFilter.ALL or availability
== null), leaving the existing availability-based branches
(DiscoverAvailabilityFilter.AVAILABLE_OR_REQUESTED and
NOT_AVAILABLE_OR_REQUESTED) untouched.
🧹 Nitpick comments (2)
src/components/Discover/constants.ts (1)

118-118: Tighten validation to the enum and filter out ALL to avoid phantom active filter count.

Two related refinements:

  1. z.string().optional() accepts any string (e.g. ?availability=foo). Use z.nativeEnum(DiscoverAvailabilityFilter) so invalid values are stripped on parse rather than flowing into filterValues and being counted by countActiveFilters.
  2. When a user lands with ?availability=all (e.g. shared URL), prepareFilterValues currently stores it. It is then counted as an active filter in countActiveFilters (line 279) even though FilterSlideover intentionally clears the param when ALL is selected. Treat ALL as "no filter" on ingest.
♻️ Proposed refactor
+import { DiscoverAvailabilityFilter } from '@server/constants/discover';
 ...
-  availability: z.string().optional(),
+  availability: z.nativeEnum(DiscoverAvailabilityFilter).optional(),
-  if (values.availability) {
+  if (values.availability && values.availability !== DiscoverAvailabilityFilter.ALL) {
     filterValues.availability = values.availability;
   }

Also applies to: 230-232

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Discover/constants.ts` at line 118, Replace the loose
availability schema and ingestion logic so invalid or "ALL" values don't become
active filters: change the Zod schema for availability from
z.string().optional() to z.nativeEnum(DiscoverAvailabilityFilter).optional() so
only enum members survive parsing, and update prepareFilterValues to treat
DiscoverAvailabilityFilter.ALL as equivalent to undefined (i.e., strip it out)
before returning/storing filterValues; this prevents countActiveFilters from
counting phantom/ALL values while leaving other enum handling and
FilterSlideover behavior intact.
src/hooks/useDiscover.ts (1)

67-68: Avoid as any; narrow the options type instead.

Casting options as any silently discards type safety for every caller of useDiscover. Since all known call sites pass objects that are a superset of FilterOptions (see DiscoverMovies/DiscoverTv), constrain O and destructure without the cast.

♻️ Proposed refactor
 const useDiscover = <
   T extends BaseMedia,
   S = Record<string, never>,
-  O = Record<string, unknown>,
+  O extends { availability?: DiscoverAvailabilityFilter | string } = Record<
+    string,
+    unknown
+  >,
 >(
   endpoint: string,
   options?: O,
   { hideAvailable = true, hideBlocklisted = true } = {}
 ): DiscoverResult<T, S> => {
   ...
-  const { availability = DiscoverAvailabilityFilter.ALL, ...tmdbOptions } =
-    options as any;
+  const { availability = DiscoverAvailabilityFilter.ALL, ...tmdbOptions } =
+    options ?? ({} as O);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useDiscover.ts` around lines 67 - 68, The destructure is using
"options as any" which removes type safety; update the generic/parameter so O
extends FilterOptions (or the appropriate FilterOptions union used by
DiscoverMovies/DiscoverTv) and remove the cast, i.e. ensure useDiscover's
signature constrains O to FilterOptions and then destructure directly: const {
availability = DiscoverAvailabilityFilter.ALL, ...tmdbOptions } = options; also
update any related overloads or call sites if needed to satisfy the narrower
generic (referencing useDiscover, the O generic, FilterOptions,
DiscoverAvailabilityFilter, and callers DiscoverMovies/DiscoverTv).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/Discover/FilterSlideover/index.tsx`:
- Line 50: Fix the typo in the user-facing label by changing the value for the
availability key from "Availabilty" to "Availability" in the FilterSlideover
labels object (the availability entry in
src/components/Discover/FilterSlideover/index.tsx), and update the matching key
in the i18n dictionary (the availability entry in en.json) so both the component
label and the locale string read "Availability".

---

Outside diff comments:
In `@src/hooks/useDiscover.ts`:
- Around line 127-165: The implicit hideAvailable filtering (the block that
checks settings.currentSettings.hideAvailable && hideAvailable) is removing
AVAILABLE/PARTIALLY_AVAILABLE items before the explicit availability filter
runs; change the logic so that the hideAvailable pass is skipped when the user
has chosen an explicit availability filter (i.e., when availability is set to
something other than the "all"/default value). Concretely, in useDiscover.ts
adjust the condition that runs the hideAvailable filter to also require
availability to be the default/all option (so keep the current checks for
settings.currentSettings.hideAvailable and hideAvailable, but add a check like
availability === DiscoverAvailabilityFilter.ALL or availability == null),
leaving the existing availability-based branches
(DiscoverAvailabilityFilter.AVAILABLE_OR_REQUESTED and
NOT_AVAILABLE_OR_REQUESTED) untouched.

---

Nitpick comments:
In `@src/components/Discover/constants.ts`:
- Line 118: Replace the loose availability schema and ingestion logic so invalid
or "ALL" values don't become active filters: change the Zod schema for
availability from z.string().optional() to
z.nativeEnum(DiscoverAvailabilityFilter).optional() so only enum members survive
parsing, and update prepareFilterValues to treat DiscoverAvailabilityFilter.ALL
as equivalent to undefined (i.e., strip it out) before returning/storing
filterValues; this prevents countActiveFilters from counting phantom/ALL values
while leaving other enum handling and FilterSlideover behavior intact.

In `@src/hooks/useDiscover.ts`:
- Around line 67-68: The destructure is using "options as any" which removes
type safety; update the generic/parameter so O extends FilterOptions (or the
appropriate FilterOptions union used by DiscoverMovies/DiscoverTv) and remove
the cast, i.e. ensure useDiscover's signature constrains O to FilterOptions and
then destructure directly: const { availability =
DiscoverAvailabilityFilter.ALL, ...tmdbOptions } = options; also update any
related overloads or call sites if needed to satisfy the narrower generic
(referencing useDiscover, the O generic, FilterOptions,
DiscoverAvailabilityFilter, and callers DiscoverMovies/DiscoverTv).
🪄 Autofix (Beta)

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

Run ID: 92a1a1f6-6926-4f4d-b169-a5941aebca6e

📥 Commits

Reviewing files that changed from the base of the PR and between 431ad91 and 1a7fd01.

📒 Files selected for processing (7)
  • server/constants/discover.ts
  • src/components/Discover/FilterSlideover/index.tsx
  • src/components/Discover/constants.ts
  • src/components/Selector/AvailabilitySelector.tsx
  • src/components/Selector/index.tsx
  • src/hooks/useDiscover.ts
  • src/i18n/locale/en.json

voteCount: 'Number of votes between {minValue} and {maxValue}',
status: 'Status',
certification: 'Content Rating',
availability: 'Availabilty',

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.

⚠️ Potential issue | 🟡 Minor

Typo in user-facing label: "Availabilty" → "Availability".

This label renders as the section heading in the filters slideover. The corresponding key value in src/i18n/locale/en.json (line 80) has the same typo and should be fixed together.

✏️ Proposed fix
-  availability: 'Availabilty',
+  availability: 'Availability',
📝 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
availability: 'Availabilty',
availability: 'Availability',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Discover/FilterSlideover/index.tsx` at line 50, Fix the typo
in the user-facing label by changing the value for the availability key from
"Availabilty" to "Availability" in the FilterSlideover labels object (the
availability entry in src/components/Discover/FilterSlideover/index.tsx), and
update the matching key in the i18n dictionary (the availability entry in
en.json) so both the component label and the locale string read "Availability".

BaLion29 and others added 3 commits April 22, 2026 20:37
fixed typo in FilterSlideover messages, from 'Availabilty' to 'Availability'. no influence on rest
of code, just UI
…ity filters won't clash

changed hideAvailable filter behavior to only catch if availability is set to ALL, so that "Show
Available and Requested only" and "Hide Available and Requested" still work when hideAvailable is
set to true
@M0NsTeRRR M0NsTeRRR linked an issue May 29, 2026 that may be closed by this pull request
2 tasks
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.

Per-user availability filter on discovery pages

1 participant