Skip to content

feat(discover): add setting to hide requested media from discover pages#3264

Closed
andersennl wants to merge 2 commits into
seerr-team:developfrom
andersennl:feat/hide-requested-media
Closed

feat(discover): add setting to hide requested media from discover pages#3264
andersennl wants to merge 2 commits into
seerr-team:developfrom
andersennl:feat/hide-requested-media

Conversation

@andersennl

@andersennl andersennl commented Jul 19, 2026

Copy link
Copy Markdown

Description

This PR adds a new "Hide Requested Media" setting (default: off) to Settings → General. When enabled, movies and series with at least one pending or approved request are hidden from the Discover home page, sliders, and the Recommended/Similar sections on detail pages. This includes partially requested series (e.g. only some seasons requested) and 4K-only requests. Search results are intentionally unaffected, so hidden items can still be found by title.

It is an alternative to #1855 that implements the review feedback given there. Credit to the original author for the settings plumbing, which this PR reuses largely unchanged.

Semantics

Request state Discover visibility
At least one PENDING or APPROVED request (any season, standard or 4K) Hidden
Only DECLINED, FAILED, or COMPLETED requests Visible
No requests (incl. media only monitored in Radarr/Sonarr) Visible

FAILED stays visible on purpose so failed requests remain discoverable and can be retried. PARTIALLY_AVAILABLE series are hidden only while a season request is actually active — hasActiveRequest is the source of truth, not the media status.

Implementation notes

Unlike #1855, no request entities are loaded or serialized (see the review concern about leftJoinAndSelect('media.requests'), which would leak request metadata to every user, multiply rows against the existing watchlist join, and bloat the payload):

  • When the setting is enabled, Media.getRelatedMedia() runs one additional grouped query per batch that selects only the IDs of media with active requests, and exposes a transient mediaInfo.hasActiveRequest: boolean. No request, requester, or season data leaves the server.
  • When the setting is disabled, no extra query is executed at all.
  • No N+1 queries, no join multiplication, payload grows by one boolean per locally known media item.
  • No database migration required.

Client-side changes:

  • Visibility filtering is centralized in a new pure helper (src/utils/mediaFilters.ts), used by both useDiscover and MediaSlider. This also fixes a pre-existing bug where the hideAvailable/hideBlocklisted filters dropped person and collection results from mixed sliders.
  • useDiscover now fetches a bounded number of extra pages (max 5, mirroring the existing MediaSlider behavior) when client-side filters empty out entire pages, so lists no longer appear exhausted while the server still has results. The isEmpty shortcut was removed from isReachingEnd for the same reason.

How Has This Been Tested?

  • 10 new integration tests in server/test/entity/Media.test.ts (node:test with the real SQLite test DB) covering: pending/approved → flagged; declined/failed/completed → not flagged; 4K-only requests; partially available series with and without an active season request; batch behavior; no extra computation when the setting is disabled; and that the serialized result contains no requests/requestedBy/modifiedBy data.
    • Note: the test lives under server/test/entity/ rather than next to the entity because the TypeORM configs glob server/entity/**/*.ts as entity classes (and the server build ships dist/entity/**/*.js), so a colocated test file would be loaded as an entity.
  • pnpm lint, pnpm typecheck, pnpm test (131/131), and pnpm build all pass on this branch.
  • Manually verified on a self-hosted instance (Docker, SQLite): toggling the setting via UI and API, hidden/visible behavior for all request states above via the discover and search endpoints, and that search results and person/collection cards remain visible.

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy): implementation was AI-assisted; design, review, and testing were done by me.
  • 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) — not required

Summary by CodeRabbit

  • New Features

    • Added a “Hide Requested Media” setting for Discover pages.
    • Movies and series with pending or approved requests are hidden, including partially requested and 4K requests.
    • Media with declined, failed, or completed requests remains visible.
    • Search results are unaffected.
    • Added automatic pagination handling to fill listings when filtering removes results.
  • Documentation

    • Documented the new setting and its default disabled state.

Adds a hideRequested setting (default off). When enabled, movies and
series with at least one pending or approved request - including
partially requested series and 4K requests - are hidden from discover
pages, sliders, and recommended/similar listings. Search results are
not affected.

Unlike the upstream proposal, no request objects are loaded or
serialized: when the setting is enabled, getRelatedMedia runs a single
grouped query per batch selecting only the IDs of media with active
requests and exposes a transient mediaInfo.hasActiveRequest boolean.
With the setting disabled no extra query is executed.

Client-side visibility filtering is centralized in a shared helper so
person and collection results are never dropped, and useDiscover now
fetches a bounded number of extra pages when client-side filters empty
out entire pages.
@andersennl
andersennl requested a review from a team as a code owner July 19, 2026 21:36
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9f3744ad-b232-43fd-a5d7-f668b26f6a77

📥 Commits

Reviewing files that changed from the base of the PR and between 2d152a0 and bd10d1b.

📒 Files selected for processing (1)
  • src/hooks/useDiscover.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/useDiscover.ts

📝 Walkthrough

Walkthrough

Adds a disabled-by-default “Hide Requested Media” setting, exposes active-request metadata, and filters requested movies and series from discover-related views while retaining them in search results.

Changes

Hide Requested Media

Layer / File(s) Summary
Settings contract and controls
server/lib/settings/index.ts, server/interfaces/api/settingsInterfaces.ts, seerr-api.yml, src/components/Settings/SettingsMain/index.tsx, src/context/SettingsContext.tsx, src/pages/_app.tsx, src/i18n/locale/en.json, docs/using-seerr/settings/general.md
Adds hideRequested to settings defaults, public responses, API schemas, application state, the settings form, localization, and documentation.
Active-request resolution and validation
server/entity/Media.ts, server/test/entity/Media.test.ts, seerr-api.yml
Computes hasActiveRequest for related media from pending or approved requests and tests regular, 4K, partial-season, batch, inactive-status, and serialization cases.
Discover visibility filtering
src/utils/mediaFilters.ts, src/hooks/useDiscover.ts, src/components/MediaSlider/index.tsx, src/components/Search/index.tsx
Centralizes visibility rules, applies requested-media filtering to discover results, preserves search results, and fetches additional pages when filtering reduces page contents.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SettingsMain
  participant SettingsAPI
  participant SettingsContext
  participant useDiscover
  participant filterVisibleTitles
  SettingsMain->>SettingsAPI: save hideRequested
  SettingsAPI-->>SettingsContext: return public settings
  SettingsContext->>useDiscover: provide current settings
  useDiscover->>filterVisibleTitles: filter discovered titles
  filterVisibleTitles-->>useDiscover: return visible titles
Loading

Possibly related PRs

Suggested reviewers: gauthier-th, fallenbagel, m0nsterrr

Poem

I’m a rabbit who filters the queue,
Hiding requests that are pending and new.
Approved ones vanish from view,
Search still knows where they grew—
Hop, hop, the settings now do!

🚥 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 summarizes the main change: a new setting to hide requested media from 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.

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.

🧹 Nitpick comments (1)
src/hooks/useDiscover.ts (1)

135-156: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Backfill window can briefly show an empty state, and effect deps are ineffective.

Two related nits in this block:

  • isEmpty (Lines 135-136) doesn't account for the pending backfill in progress: right after a page finishes loading but before the useEffect below bumps size, titles can be thin enough that isEmpty becomes true for a render, momentarily flashing a "no results" state — the exact scenario this backfill logic is meant to avoid. Consider folding the backfill condition into isEmpty/isLoadingMore (e.g., treat "would trigger a backfill fetch" as still-loading).
  • The useEffect dependency array includes titles (Line 156), but titles is a new array reference every render (from .reduce/.filter), so it provides no real change-detection value — the effect re-evaluates on every render regardless. Prefer titles.length to make the intent explicit and avoid redundant re-invocation.
💡 Possible adjustment
-  const isEmpty =
-    !isLoadingInitialData && !isLoadingMore && titles?.length === 0;
+  const willBackfill =
+    !!data && titles.length < 20 && size < 5 && !isReachingEnd;
+  const isEmpty =
+    !isLoadingInitialData &&
+    !isLoadingMore &&
+    !willBackfill &&
+    titles?.length === 0;
   const isReachingEnd =
     (!!data && (data[data.length - 1]?.results.length ?? 0) < 20) ||
     (!!data && (data[data.length - 1]?.totalResults ?? 0) <= size * 20) ||
     (!!data && (data[data.length - 1]?.totalResults ?? 0) < 41);

   useEffect(() => {
-    if (
-      !!data &&
-      titles.length < 20 &&
-      size < 5 &&
-      !isReachingEnd &&
-      !isLoadingMore &&
-      !isValidating
-    ) {
+    if (willBackfill && !isLoadingMore && !isValidating) {
       setSize(size + 1);
     }
-  }, [data, titles, size, isReachingEnd, isLoadingMore, isValidating, setSize]);
+  }, [willBackfill, isLoadingMore, isValidating, size, setSize]);
🤖 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/hooks/useDiscover.ts` around lines 135 - 156, Update the empty-state and
backfill logic around isEmpty and the useEffect: treat a pending backfill fetch
as loading so isEmpty cannot briefly become true before setSize runs, while
preserving normal empty-state behavior. Replace the useEffect dependency on the
derived titles array with titles.length, and update references as needed so the
dependency list tracks the value that controls backfill decisions.
🤖 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.

Nitpick comments:
In `@src/hooks/useDiscover.ts`:
- Around line 135-156: Update the empty-state and backfill logic around isEmpty
and the useEffect: treat a pending backfill fetch as loading so isEmpty cannot
briefly become true before setSize runs, while preserving normal empty-state
behavior. Replace the useEffect dependency on the derived titles array with
titles.length, and update references as needed so the dependency list tracks the
value that controls backfill decisions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 11de00f4-f1fa-4d67-9dbe-63bf212d7676

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae70d0 and 2d152a0.

📒 Files selected for processing (14)
  • docs/using-seerr/settings/general.md
  • seerr-api.yml
  • server/entity/Media.ts
  • server/interfaces/api/settingsInterfaces.ts
  • server/lib/settings/index.ts
  • server/test/entity/Media.test.ts
  • src/components/MediaSlider/index.tsx
  • src/components/Search/index.tsx
  • src/components/Settings/SettingsMain/index.tsx
  • src/context/SettingsContext.tsx
  • src/hooks/useDiscover.ts
  • src/i18n/locale/en.json
  • src/pages/_app.tsx
  • src/utils/mediaFilters.ts

Fold the backfill condition into isEmpty so a fully filtered page does
not briefly render the no-results state before the bounded refetch
kicks in, and depend on titles.length instead of the per-render titles
array reference in the effect.
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.

1 participant