From 48ff4b9cc56c144f8339a4c98354cba07ad73701 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:52:00 +0200 Subject: [PATCH] fix(portal): remove redundant catalog type badges (#1217) Remove redundant LIVE, VOD, and SERIES badges from homogeneous Xtream and Stalker catalog grids while preserving mixed-content badges and type-driven grid behavior. --- ...20-hide-homogeneous-catalog-type-badges.md | 243 ++++++++++++++++++ ...-homogeneous-catalog-type-badges-design.md | 53 ++++ .../grid-list/grid-list.component.scss | 29 --- .../grid-list/grid-list.component.spec.ts | 19 +- .../grid-list/grid-list.component.ts | 10 - 5 files changed, 311 insertions(+), 43 deletions(-) create mode 100644 docs/superpowers/plans/2026-07-20-hide-homogeneous-catalog-type-badges.md create mode 100644 docs/superpowers/specs/2026-07-20-hide-homogeneous-catalog-type-badges-design.md diff --git a/docs/superpowers/plans/2026-07-20-hide-homogeneous-catalog-type-badges.md b/docs/superpowers/plans/2026-07-20-hide-homogeneous-catalog-type-badges.md new file mode 100644 index 000000000..19a13d667 --- /dev/null +++ b/docs/superpowers/plans/2026-07-20-hide-homogeneous-catalog-type-badges.md @@ -0,0 +1,243 @@ +# Hide Homogeneous Catalog Type Badges Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Remove redundant `LIVE`, `VOD`, and `SERIES` badges from homogeneous Stalker and Xtream catalog grids while preserving type badges in mixed-content cards. + +**Architecture:** Remove the type-badge presentation from the shared `GridListComponent`, whose current consumers are homogeneous catalog grids. Keep its `type` input because artwork placeholders and live-title normalization still depend on it; do not modify `ContentCardComponent`, which owns badges for mixed-content surfaces. + +**Tech Stack:** Angular standalone components, Angular signal inputs, SCSS, Jest through Nx, Playwright E2E through Nx. + +--- + +### Task 0: Bootstrap the Nx workspace + +**Files:** +- Verify only: `package.json` +- Verify only: `pnpm-lock.yaml` + +- [ ] **Step 1: Install the locked workspace dependencies** + +Run: + +```bash +pnpm install --frozen-lockfile +``` + +Expected: exit 0 without changing `pnpm-lock.yaml`. + +- [ ] **Step 2: Verify Nx project discovery** + +Run: + +```bash +pnpm nx show projects +``` + +Expected: exit 0 and output containing `portal-shared-ui`, +`portal-catalog-feature`, and `web-e2e`. + +### Task 1: Remove the redundant grid-list badge + +**Files:** +- Modify: `libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.spec.ts` +- Modify: `libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts` +- Modify: `libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.scss` + +- [ ] **Step 1: Write the failing regression test** + +In `grid-list.component.spec.ts`, change the existing live-logo test so it only +asserts logo-card rendering, then add this parameterized test inside +`describe('GridListComponent', ...)`: + +```typescript +it.each(['live', 'vod', 'series'] as const)( + 'does not render a redundant %s type badge in homogeneous grids', + (type) => { + fixture.componentRef.setInput('items', [ + { + name: 'Catalog item', + stream_icon: 'catalog-item.png', + }, + ]); + fixture.componentRef.setInput('type', type); + + fixture.detectChanges(); + + expect( + fixture.debugElement.query(By.css('.type-badge')) + ).toBeNull(); + } +); +``` + +The updated live-logo test must retain these assertions: + +```typescript +expect(card.nativeElement.classList).toContain('grid-card--logo'); +expect(image.nativeElement.getAttribute('src')).toBe('channel-logo.png'); +``` + +It must no longer query or assert `.type-badge`. + +- [ ] **Step 2: Run the focused test to verify RED** + +Run: + +```bash +pnpm nx test portal-shared-ui --testPathPattern=grid-list.component.spec.ts --runInBand +``` + +Expected: FAIL for `live`, `vod`, and `series` because +`GridListComponent` still renders `.type-badge`. + +- [ ] **Step 3: Remove the badge markup** + +In the inline template in `grid-list.component.ts`, remove only this block: + +```html +@if (type()) { +
+ {{ type() }} +
+} +``` + +Keep the `type` input and every remaining use of `type()` unchanged. + +- [ ] **Step 4: Remove the now-unused grid badge styles** + +In `grid-list.component.scss`, delete the entire `.type-badge` rule, including +its `.live`, `.movie`, and `.series` variants: + +```scss +.type-badge { + position: absolute; + top: 6px; + left: 6px; + z-index: 1; + padding: 3px 6px; + border-radius: 5px; + font-size: 0.58rem; + font-weight: 700; + line-height: 1; + text-transform: uppercase; + letter-spacing: 0.04em; + background: rgba(0, 0, 0, 0.85); + color: #fff; + + &.live { + color: #ef5350; + } + + &.movie { + color: #42a5f5; + } + + &.series { + color: #66bb6a; + } +} +``` + +Do not change the separate content-card badge mixin or +`content-card.component.*`. + +- [ ] **Step 5: Run the focused test to verify GREEN** + +Run: + +```bash +pnpm nx test portal-shared-ui --testPathPattern=grid-list.component.spec.ts --runInBand +``` + +Expected: PASS, including the three type-badge regression cases and the +existing placeholder/title behavior. + +- [ ] **Step 6: Run affected unit and lint targets** + +Run: + +```bash +pnpm nx test portal-shared-ui +pnpm nx test portal-catalog-feature +pnpm nx lint portal-shared-ui +``` + +Expected: all commands exit 0 with no failed tests or lint errors. + +- [ ] **Step 7: Verify mixed-content badge ownership is untouched** + +Run: + +```bash +git diff -- libs/portal/shared/ui/src/lib/components/content-card libs/portal/xtream/feature/src/lib/search-results libs/portal/stalker/feature/src/lib/stalker-search +``` + +Expected: no output. `ContentCardComponent` and both mixed search surfaces +remain unchanged. + +- [ ] **Step 8: Commit the implementation** + +```bash +git add \ + libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.spec.ts \ + libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts \ + libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.scss +git commit -m "fix(portal): remove redundant catalog type badges" +``` + +Expected: one commit containing only the grid-list behavior and regression +coverage. + +### Task 2: Validate the portal workflows + +**Files:** +- Verify only: `apps/web-e2e/src/xtream.e2e.ts` +- Verify only: `apps/web-e2e/src/stalker.e2e.ts` +- Verify only: `docs/architecture/iptvnator-ui-guidelines.md` + +- [ ] **Step 1: Run the Xtream portal E2E target** + +Run: + +```bash +pnpm nx run web-e2e:e2e-ci--src/xtream.e2e.ts +``` + +Expected: PASS for the Xtream category and content-list workflows. + +- [ ] **Step 2: Run the Stalker portal E2E target** + +Run: + +```bash +pnpm nx run web-e2e:e2e-ci--src/stalker.e2e.ts +``` + +Expected: PASS for the Stalker category and content-list workflows. + +- [ ] **Step 3: Perform the final diff and documentation-impact check** + +Run: + +```bash +git diff HEAD^ --check +git show --stat --oneline HEAD +git status --short +``` + +Expected: + +- `git diff --check` exits 0; +- the implementation commit contains only the three grid-list files; +- the worktree contains only this implementation plan if it has not been + committed separately; +- no canonical documentation update is needed because + `docs/architecture/iptvnator-ui-guidelines.md` already directs contributors + not to add redundant badges or secondary selection systems. diff --git a/docs/superpowers/specs/2026-07-20-hide-homogeneous-catalog-type-badges-design.md b/docs/superpowers/specs/2026-07-20-hide-homogeneous-catalog-type-badges-design.md new file mode 100644 index 000000000..b02705a9a --- /dev/null +++ b/docs/superpowers/specs/2026-07-20-hide-homogeneous-catalog-type-badges-design.md @@ -0,0 +1,53 @@ +# Hide Type Badges in Homogeneous Portal Catalogs + +## Context + +The shared `GridListComponent` renders catalog items after the user has already +selected Live TV, VOD, or Series in the portal rail. Every item in these grids +has the same content type, so the `LIVE`, `VOD`, and `SERIES` badges repeat +information that is already established by the surrounding navigation. + +Mixed-content surfaces such as portal search use `ContentCardComponent`, where +the type badge remains useful for distinguishing results. + +## Design + +Remove the type-badge markup and its dedicated styles from +`GridListComponent`. + +Keep the component's `type` input. It still controls provider-neutral behavior +that is unrelated to the badge: + +- choosing artwork placeholder icons; +- limiting country-prefix stripping to live content; +- selecting poster/logo presentation behavior. + +Do not change `ContentCardComponent` or any search, favorites, recently added, +or dashboard surface. Their badges remain unchanged. + +## Affected Portals and Views + +Because Xtream and Stalker both route their category pages through the shared +portal catalog view, removing the badge from `GridListComponent` covers: + +- Xtream Live TV, VOD, and Series homogeneous grids; +- Stalker Live TV, VOD, and Series homogeneous grids; +- the Xtream Live TV all-items grid. + +## Testing + +Update the focused `GridListComponent` tests to prove that no `.type-badge` is +rendered for `live`, `vod`, or `series`, while the `type` input continues to +drive existing title and placeholder behavior. + +Run the affected shared portal UI test target and the closest catalog feature +test target. Since this is a visible portal workflow change, run the closest +Xtream and Stalker Playwright coverage when available; otherwise perform the +strongest available targeted validation and document any skipped UI automation. + +## Documentation Impact + +The canonical UI guideline already says not to add redundant badges or a +second selection system. No canonical documentation change is required beyond +this design record because no route, architecture boundary, setup workflow, or +subsystem contract changes. diff --git a/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.scss b/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.scss index 4c5c04304..aead84cdc 100644 --- a/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.scss +++ b/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.scss @@ -106,35 +106,6 @@ mat-card:hover { } } -// ─── Type badge ────────────────────────────────────────────────────────────── -.type-badge { - position: absolute; - top: 6px; - left: 6px; - z-index: 1; - padding: 3px 6px; - border-radius: 5px; - font-size: 0.58rem; - font-weight: 700; - line-height: 1; - text-transform: uppercase; - letter-spacing: 0.04em; - background: rgba(0, 0, 0, 0.85); - color: #fff; - - &.live { - color: #ef5350; - } - - &.movie { - color: #42a5f5; - } - - &.series { - color: #66bb6a; - } -} - // ─── Rating badge ───────────────────────────────────────────────────────────── .rating { position: absolute; diff --git a/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.spec.ts b/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.spec.ts index 20ccfcb16..4bd36a133 100644 --- a/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.spec.ts +++ b/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.spec.ts @@ -59,7 +59,7 @@ describe('GridListComponent', () => { fixture = TestBed.createComponent(GridListComponent); }); - it('renders live logo cards with stream icons and a live badge', () => { + it('renders live logo cards with stream icons', () => { fixture.componentRef.setInput('items', [ { name: 'Live Channel', @@ -73,16 +73,27 @@ describe('GridListComponent', () => { const card = fixture.debugElement.query(By.css('mat-card')); const image = fixture.debugElement.query(By.css('.stream-icon')); - const badge = fixture.debugElement.query(By.css('.type-badge')); expect(card.nativeElement.classList).toContain('grid-card--logo'); expect(image.nativeElement.getAttribute('src')).toBe( 'channel-logo.png' ); - expect(badge.nativeElement.classList).toContain('live'); - expect(badge.nativeElement.textContent.trim()).toBe('live'); }); + it.each(['live', 'vod', 'series'] as const)( + 'does not render a redundant %s type badge in homogeneous grids', + (type) => { + fixture.componentRef.setInput('items', [ + { name: 'Catalog item', stream_icon: 'catalog-item.png' }, + ]); + fixture.componentRef.setInput('type', type); + fixture.detectChanges(); + expect( + fixture.debugElement.query(By.css('.type-badge')) + ).toBeNull(); + } + ); + it('renders the live placeholder for logo cards without artwork', () => { fixture.componentRef.setInput('items', [ { diff --git a/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts b/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts index 1b1d59565..d4be4c1a7 100644 --- a/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts +++ b/libs/portal/shared/ui/src/lib/components/grid-list/grid-list.component.ts @@ -106,16 +106,6 @@ function normalizeArtworkUrl(value: string | undefined): string | undefined { > @let poster = resolvePoster(i);
- @if (type()) { -
- {{ type() }} -
- } @if (poster && !hasArtworkFailed(poster)) {