Skip to content

feat(search): collapse language/quality variants in global search - #1215

Open
4gray wants to merge 2 commits into
masterfrom
feat/search-variant-grouping
Open

feat(search): collapse language/quality variants in global search#1215
4gray wants to merge 2 commits into
masterfrom
feat/search-variant-grouping

Conversation

@4gray

@4gray 4gray commented Jul 19, 2026

Copy link
Copy Markdown
Owner

What

PR-2 of the tag-extraction track (builds on #1211). Global search for a popular title floods the grid with near-duplicate cards — one per language/quality/provider tag. A new "Group similar titles" toggle (default on, persisted) collapses variants that share a normalized title and content type into a single card with a ×N badge.

Behaviour

  • groupResultsByVariant() keys on normalizeTitleKeys(title).base + type (from feat(matching): strip appended language/quality tags in title normalization #1211), so a movie and a series with the same name never merge; the collapsed card shows the cleanest member title (e.g. The Pitt, not DE| The Pitt).
  • Clicking a multi-variant card expands it inline to reveal every version — the raw titles show the language so the user can pick. Clicking again collapses. A single-variant group opens directly.
  • Composes with the existing "group by playlist" toggle: variants collapse within each playlist section, or globally in the flat list.
  • Fresh searches / cleared searches reset expansion state.

For the Fallout / The Pitt corpora this turns ~100–126 cards into a handful. The upfront language picker stays a follow-up — this change removes the visual flood without a new picker component.

Changes

  • New pure util global-search-grouping.util.ts (+ spec) — grouping logic extracted so the (baselined) component stays thin.
  • ContentCardComponent gains an optional countBadge input (×N pill).
  • SearchResultsComponent: groupSimilar toggle, flatVariantGroups / playlistSections computeds, inline expand/collapse state; template rendered via shared ng-templates for both branches.
  • SEARCH_VIEW.GROUP_SIMILAR_TITLES added to all 18 locales.

Validation

  • Unit: portal-xtream-feature 155/155 (new util spec + 5 component grouping tests), components 105/105.
  • typecheck:ci clean; nx build web clean (AOT templates + all locale JSON); lint clean on touched projects.
  • Feature is Electron-only (global search); the mock catalog uses unique titles, so existing search E2E exercises the ungrouped render path and confirms no regression. Full visual grouping needs a real tagged catalog — covered by unit tests over the corpus-derived normalization.

🤖 Generated with Claude Code

Global search for a popular title returns dozens of near-duplicate
cards — one per language/quality/provider tag ("DE| The Pitt",
"4K-TR - The Pitt (2025) (US)", "|ALB| The Pitt"). A new "Group similar
titles" toggle (default on, persisted) collapses variants sharing a
normalized title and content type into a single card with a "×N" badge.

- groupResultsByVariant() keys on normalizeTitleKeys(title).base + type
  (from PR #1211), so a movie and a series with the same name never
  merge; the collapsed card shows the cleanest member title
- clicking a multi-variant card expands it inline to reveal every
  version (the raw titles show the language); clicking again collapses;
  a single-variant group opens directly
- composes with the existing "group by playlist" toggle: variants
  collapse within each playlist section, or globally in the flat list
- ContentCardComponent gains an optional countBadge input
- SEARCH_VIEW.GROUP_SIMILAR_TITLES added to all 18 locales

The version picker (choose language up front) stays a follow-up; this
change removes the visual flood without a new picker component.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba69d4b75d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


<!-- Variant group: collapsed card (+ expanded members) or a single card -->
<ng-template #variantTpl let-vg>
@if (vg.items.length > 1) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep loading pages when collapsed results do not overflow

When grouping collapses the first 100 results into only a handful of cards—the primary scenario advertised by this change—the results container may not become scrollable. SearchLayoutComponent emits nearEnd only from onSearchContentScroll, so no scroll event occurs and hasMoreGlobalResults() is never consumed; results after the first page remain hidden unless the user disables grouping or expands enough groups to create scrolling. Automatically request another page while the grouped viewport is underfilled, or provide an explicit load-more action.

Useful? React with 👍 / 👎.

Comment on lines +543 to +547
const expanded = new Set(this.expandedVariantKeys());
if (expanded.has(group.key)) {
expanded.delete(group.key);
} else {
expanded.add(group.key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope expansion state to each playlist section

With “group by playlist” enabled, separately computed groups for the same normalized title and type receive the same group.key. Because this shared set stores only that key, expanding a title in one playlist also expands the matching title in every other playlist section. Include the playlist identity in the expansion key for the playlist-grouped rendering path while retaining the current global key for the flat path.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a "Group similar titles" toggle to the global search results that collapses language/quality/provider variants of the same title into a single card with a ×N badge, reducing a flood of near-duplicate cards to a handful while letting the user expand any group inline.

  • groupResultsByVariant() in the new pure utility keys on normalized base + content type, splits buckets with two or more distinct years to avoid merging remakes (e.g. Dune 1984 vs 2021), and picks the cleanest display title (shortest stripped title) and the best-posterred representative independently.
  • SearchResultsComponent gains groupSimilar (default on, localStorage-persisted), expandedVariantKeys, playlistSections/flatVariantGroups computeds, and an autoFillWhileSparse backfill loop (bounded to 5 pages, 24-card threshold) so grouping never leaves the results too sparse to scroll.
  • ContentCardComponent gets an optional countBadge input; all 18 locale JSON files receive the GROUP_SIMILAR_TITLES key.

Confidence Score: 5/5

Safe to merge — all four previously flagged issues are properly resolved and no new defects were found.

All four issues raised in the prior review (year-split for remakes, poster-preferring representative, per-playlist expansion key scoping, and pagination auto-fill) are correctly addressed. The request-version guard in searchGlobal prevents stale appends, the isLoadingMoreGlobalResults flag serializes concurrent auto-fill calls, the null-year bucket gets its own distinct key suffix, and buildGroup tracks displayTitle and representative independently. The unit suite covers corpus scenarios with 155/155 passing tests and build/typecheck are clean.

No files require special attention.

Important Files Changed

Filename Overview
libs/portal/xtream/feature/src/lib/search-results/global-search-grouping.util.ts New pure utility implementing variant grouping with year-aware bucket splitting, shortest-clean-title selection, and poster-preferring representative; all four previously flagged issues fully addressed.
libs/portal/xtream/feature/src/lib/search-results/global-search-grouping.util.spec.ts Comprehensive spec covering collapse, cleanest-title selection, type separation, year-split for remakes, no-year mixing, poster preference, and per-playlist key scoping.
libs/portal/xtream/feature/src/lib/search-results/search-results.component.ts Adds groupSimilar signal, expandedVariantKeys, playlistSections/flatVariantGroups computeds, autoFillWhileSparse pagination backfill, and selectVariantGroup/toggleGroupSimilar methods; request-version guards prevent stale-append races.
libs/portal/xtream/feature/src/lib/search-results/search-results.component.spec.ts New grouping tests added: default-on toggle, flat collapse, expand/collapse toggle, single-variant direct-open, per-playlist key independence, auto-fill backfill, and toggle persistence.
libs/portal/xtream/feature/src/lib/search-results/search-results.component.html Introduces groupSimilar checkbox, shared ng-templates for itemTpl and variantTpl, inline expand/collapse rendering for multi-variant groups, and countBadge wiring; both grouped-by-playlist and flat paths covered.
libs/portal/shared/ui/src/lib/components/content-card/content-card.component.ts Adds optional countBadge input (default 0) with no breaking changes to existing consumers; rendered as a ×N pill only when > 1.

Reviews (2): Last reviewed commit: "fix(search): address variant-grouping re..." | Re-trigger Greptile

Comment thread libs/portal/xtream/feature/src/lib/search-results/global-search-grouping.util.ts Outdated
Comment thread libs/portal/xtream/feature/src/lib/search-results/global-search-grouping.util.ts Outdated
- auto-load further pages when grouping collapses a page into too few
  cards to scroll (the scroll-driven pager would otherwise never fire and
  later results stayed hidden); bounded by card threshold and page count
- scope variant-group keys per playlist so expanding a title in one
  playlist section no longer expands it in every other section
- split a bucket by year when it holds two or more distinct years so
  remakes ("Dune (1984)" vs "Dune (2021)") no longer collapse into one
  card; a single/absent year still keeps variants together
- pick a representative that has a poster, skipping leading posterless
  clones so the collapsed card shows real artwork when any member has it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@4gray

4gray commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

Thanks — addressed all four findings in ab3e2fc:

  • Pagination stalls when collapsed (P1): after a fresh search (and when grouping is toggled on), autoFillWhileSparse() keeps pulling pages while the collapsed top-level card count is below a scroll threshold, bounded by MAX_AUTO_FILL_PAGES. So a page that collapses to a few cards no longer hides the remaining results.
  • Expansion key collision across playlists (P2): groupResultsByVariant now takes a keyPrefix; the playlist path passes \${playlistId}::``, so expanding a title in one section is independent of the same title elsewhere. Flat path keeps the global key.
  • Year-stripped key merges remakes (P2): buckets are split by year when they contain ≥2 distinct years (Dune (1984) vs Dune (2021)), while a single/absent year still keeps variants together — so the common "some variants carry the year" case (The Pitt) stays one card.
  • Representative without a poster (P2): buildGroup now advances the representative past leading posterless clones to the first member with artwork.

New tests cover year-splitting, poster preference, key-prefix scoping, per-playlist expansion independence, and the auto-fill backfill. portal-xtream-feature 161/161, typecheck + lint clean.

@4gray

4gray commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

@greptileai please re-review — commit ab3e2fc addresses all four earlier findings (pagination auto-fill, per-playlist expansion keys, year-split for remakes, poster-preferring representative).

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