Skip to content

Conversation

4gray
Copy link
Owner

@4gray 4gray commented Sep 19, 2025

  • Removed unnecessary imports from header, account-info,
    loading-overlay, navigation-bar, season-container, serial-details, and
    vod-details components.
  • Updated template syntax to use structural directives more efficiently in mpv-player-bar, account-info, navigation-bar, season-container, serial-details, and vod-details components.
  • Enhanced readability and maintainability of the code by organizing imports and using Angular's async pipe and structural directives appropriately.
  • Adjusted TypeScript configuration to use "bundler" for module resolution for better compatibility with modern build tools.

Summary by CodeRabbit

  • New Features
    • Settings: Manage multiple EPG URLs with per-entry refresh/remove and add actions.
    • Details pages: Added more metadata (director, duration/episode time, ratings) for VOD/serials.
  • UI Changes
    • Header menus reorganized with expanded sort/filter and upload options.
    • Improved loading placeholders and template responsiveness across lists and players.
    • Fixed dialog sizing for player dialogs (max width/height).
    • Stalker category view: unified page size option.
  • Breaking Changes
    • PWA offline caching disabled.
  • Style
    • Darker active-sort highlight; input width tweaks.
  • Chores
    • Upgraded Angular to 20.x and TypeScript to 5.9.x.

- Removed unnecessary imports from header, account-info, 
  loading-overlay, navigation-bar, season-container, serial-details, and 
  vod-details components.
- Updated template syntax to use structural directives more efficiently in mpv-player-bar, account-info, navigation-bar, season-container, serial-details, and vod-details components.
- Enhanced readability and maintainability of the code by organizing imports and using Angular's async pipe and structural directives appropriately.
- Adjusted TypeScript configuration to use "bundler" for module resolution for better compatibility with modern build tools.
Copy link

vercel bot commented Sep 19, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
iptvnator Ready Ready Preview Comment Sep 19, 2025 5:46pm

Copy link

coderabbitai bot commented Sep 19, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Expected object, received null at "reviews"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

Project upgrades to Angular 20 and TypeScript 5.9 with config updates. Templates migrate from legacy structural directives to new @if/@for/@defer syntax. Several components drop CommonModule/NgIf imports. Stalker resources refactor from request/loader to params/stream. Bootstrap switches to platformBrowser without service worker. Minor typing/style tweaks included.

Changes

Cohort / File(s) Summary
Tooling & Config
package.json, tsconfig.json, angular.json
Bump Angular to 20.x and TypeScript to 5.9; switch moduleResolution to "bundler"; extend schematics with type/typeSeparator entries.
Bootstrap
src/main.ts
Use platformBrowser() instead of platformBrowserDynamic(); remove Tauri detection and service worker registration.
App Root
src/app/app.component.html, src/app/app.component.ts
Switch *ngIf to @if; change modals type from ModalWindow[] to any[] and drop type import.
Home
src/app/home/home.component.html, src/app/home/home.component.ts
Replace *ngIf with @if; remove NgIf from standalone imports.
Recent Playlists
src/app/home/recent-playlists/recent-playlists.component.html, .../recent-playlists.component.ts
Migrate *ngIf/*ngFor/ng-template to @if/@for; add readonly ghostElements; make searchQuery readonly; drop NgIf/NgFor imports.
Header
src/app/shared/components/header/header.component.html, .../header.component.ts, .../header.component.scss
Rewrite menus/modals with @if/@for; adjust menu structure; remove NgIf import; change .active-sort background color.
Service Types
src/app/services/whats-new.service.ts
Remove ModalWindow return types and related import (commented).
Player Components (imports cleanup)
src/app/player/components/art-player/art-player.component.ts, .../d-player/d-player.component.ts, .../epg-list/epg-item-description/epg-item-description.component.ts, src/app/xtream-tauri/loading-overlay/loading-overlay.component.ts, src/app/xtream/navigation-bar/navigation-bar.component.ts, src/app/xtream/serial-details/serial-details.component.ts, src/app/xtream/vod-details/vod-details.component.ts, src/app/xtream/season-container/season-container.component.ts, src/app/xtream-tauri/account-info/account-info.component.ts
Remove CommonModule/NgIf from standalone imports; no logic changes.
EPG Item Description (template)
src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html
Convert *ngIf blocks to @if; keep content and bindings equivalent.
Multi-EPG
src/app/player/components/multi-epg/multi-epg-container.component.html, .../multi-epg-container.component.ts
Migrate loops/conditions to @for/@if; adjust track strategies; change trackBy to stringify title in key.
Audio Player
src/app/player/components/audio-player/audio-player.component.ts
Switch template conditions to @if; remove NgIf import; make url input required via @input({ required: true }).
Video Player
src/app/player/components/video-player/video-player.component.html, .../video-player.component.scss
Replace defer/ngIf with @defer/@loading/@placeholder/@if; restructure player branches; adjust container positioning (remove top: 0).
Web Player View
src/app/portals/web-player-view/web-player-view.component.html, .../web-player-view.component.ts
Use @if for html5 branch; remove NgIf from imports.
MPV Player Bar
src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html
Convert *ngIf/*ngFor to @if/@for; explicit thumbnail else path.
Settings UI
src/app/settings/settings.component.html
Major template migration to @if/@for; reflow EPG URL management into dynamic list; reorganize sections.
Stalker Resources (API refactor)
src/app/stalker/stalker.store.ts, src/app/stalker/stalker-search/stalker-search.component.ts, src/app/stalker/stalker-favorites/stalker-favorites.component.ts, src/app/stalker/recently-viewed/recently-viewed.component.ts
Replace rxResource request/loader with params/stream; update all internal references and IPC param construction accordingly.
Xtream Views (templates)
src/app/xtream/navigation-bar/navigation-bar.component.html, src/app/xtream/season-container/season-container.component.html, src/app/xtream/serial-details/serial-details.component.html, src/app/xtream/vod-details/vod-details.component.html, src/app/xtream-tauri/account-info/account-info.component.ts
Convert *ngIf/*ngFor/ng-template to @if/@for and @else; minor structure additions for fields and placeholders.
Category Content View
src/app/xtream-tauri/category-content-view/category-content-view.component.ts
Make pageSizeOptions depend on isStalker (stalker → [14], else → [10,25,50,100]).
Xtream Main Container
src/app/xtream/xtream-main-container.component.ts
Add maxWidth and maxHeight to PlayerDialogComponent open calls; formatting tweak.
Player Dialog Style
src/app/xtream-tauri/player-dialog/player-dialog.component.scss
Set .link-input width to 100%; newline at EOF.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as Angular App
  participant Browser as PlatformBrowser
  Note over App,Browser: Bootstrap flow (updated)
  User->>Browser: Load index.html
  Browser->>App: platformBrowser().bootstrapModule(AppModule)
  App-->>Browser: App initialized (no SW registration)
Loading
sequenceDiagram
  autonumber
  participant UI as Component
  participant RxRes as rxResource
  participant Svc as Playlist/IPC Service
  Note over UI,RxRes: Resource flow refactor (request→params, loader→stream)
  UI->>RxRes: params(): { ... }
  alt Stream-based
    RxRes->>Svc: stream(): fetch$(params)
    Svc-->>RxRes: Observable<Data>
    RxRes-->>UI: value()/status()
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

Hoppity hop through templates I go,
Swapping stars for @ifs in a tidy new flow.
Bundler winds whisper, browsers hum bright,
Params feed streams in the Stalker’s night.
With dialogs sized and players anew—
I thump my review stamp: v20, woohoo! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "chore: update to angular v20" is concise and accurately reflects the PR's primary change—upgrading Angular and related tooling/configuration to version 20—so a reviewer scanning history will understand the main intent. It avoids noise and clearly signals a maintenance chore rather than a feature or bugfix.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/update-angular-to-v20

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (8)
src/app/stalker/stalker.store.ts (2)

186-186: Null check order bug: response.js is accessed before verifying response.

Move patchState inside the response check and harden the error path.

-                patchState(store, { totalCount: response.js.total_items });
-
-                if (response) {
+                if (response) {
+                    patchState(store, { totalCount: response.js?.total_items ?? 0 });
                     const newItems = response.js.data.map((item) => ({
                         ...item,
                         cover: item.screenshot_uri,
                     }));
@@
-                } else {
-                    throw new Error(
-                        `Error: ${response.message} (Status: ${response.status})`
-                    );
-                }
+                }
+                else {
+                    throw new Error('STALKER_REQUEST returned no response for content list');
+                }

Also applies to: 216-220


141-146: Error construction dereferences undefined response.

Throw a fixed message (or use optional chaining) when response is falsy.

-                } else {
-                    throw new Error(
-                        `Error: ${response.message} (Status: ${response.status})`
-                    );
-                }
+                } else {
+                    throw new Error('STALKER_REQUEST returned no response for categories');
+                }

Apply the same pattern to the getContentResource error block (lines 217–220).

Also applies to: 217-220

src/app/home/home.component.ts (1)

14-24: Add standalone: true; imports require standalone components.

This component declares an imports array but isn’t marked standalone. In Angular, imports on @component are only valid for standalone components. Add standalone: true to avoid a compile error and ensure the listed imports are honored. Also verify that TranslatePipe is available as a standalone import in your ngx-translate version; if not, import TranslateModule instead. (v18.angular.dev)

Apply this diff:

 @Component({
   selector: 'app-home',
   templateUrl: './home.component.html',
   styleUrls: ['./home.component.scss'],
+  standalone: true,
   imports: [
     HeaderComponent,
     MatProgressBarModule,
     RecentPlaylistsComponent,
     TranslatePipe
   ],
 })
src/app/player/components/audio-player/audio-player.component.ts (1)

107-111: Avoid using ViewChild in ngOnChanges; guard and don’t reassign src imperatively.

@ViewChild may not be ready for the first ngOnChanges. Also, with [src] bound, you don’t need to set it manually.

-  ngOnChanges(changes: SimpleChanges): void {
-      this.audio.nativeElement.src = changes.url.currentValue;
-      this.audio.nativeElement.load();
-      this.play();
-  }
+  ngOnChanges(changes: SimpleChanges): void {
+    if ('url' in changes && this.audio?.nativeElement) {
+      this.audio.nativeElement.load();
+      this.play();
+    }
+  }
src/app/player/components/video-player/video-player.component.html (1)

30-39: Avoid “undefined” in stream URL when epgParams is missing.

Concatenation can produce ...undefined. Default epgParams to empty string.

-                    [options]="{
-                        sources: [
-                            {
-                                src:
-                                    activeChannel?.url +
-                                    activeChannel?.epgParams,
-                                type: 'application/x-mpegURL',
-                            },
-                        ],
-                    }"
+                    [options]="{
+                        sources: [
+                            {
+                                src: (activeChannel?.url ?? '') + (activeChannel?.epgParams ?? ''),
+                                type: 'application/x-mpegURL'
+                            }
+                        ]
+                    }"
src/app/player/components/multi-epg/multi-epg-container.component.ts (1)

60-68: Unsubscribed input subscription can leak.

Use takeUntilDestroyed() for the playlistChannels subscription.

+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
...
     @Input() set playlistChannels(value: Observable<Channel[]>) {
         if (value) {
-            value.subscribe((channels) => {
+            value.pipe(takeUntilDestroyed()).subscribe((channels) => {
                 this._playlistChannels = channels;
                 this.initializeVisibleChannels();
                 this.requestPrograms();
             });
         }
     }
src/app/shared/components/header/header.component.ts (1)

165-173: Rename opedAddPlaylistDialog → openAddPlaylistDialog and update call sites.

Rename the method in src/app/shared/components/header/header.component.ts and update the template bindings below so the click handlers point to the new name.

Locations to update:

  • src/app/shared/components/header/header.component.ts: lines ~165-173 (method).
  • src/app/shared/components/header/header.component.html: lines 192, 200, 208, 217, 227 (click handlers).

Diff (method rename):

-    opedAddPlaylistDialog(type: PlaylistType) {
+    openAddPlaylistDialog(type: PlaylistType) {
         this.dialog.open<AddPlaylistDialogComponent, { type: PlaylistType }>(
             AddPlaylistDialogComponent,
             {
                 width: '600px',
                 data: { type },
             }
         );
     }
src/app/xtream/vod-details/vod-details.component.ts (1)

40-42: Guard against potential null parent route

Accessing this.route.parent.snapshot can throw when parent is null. Use optional chaining.

Apply this diff:

-  private portalId =
-      this.route.snapshot.paramMap.get('id') ??
-      this.route.parent.snapshot.params.id;
+  private portalId =
+      this.route.snapshot.paramMap.get('id') ??
+      this.route.parent?.snapshot?.params?.id;
🧹 Nitpick comments (49)
src/app/xtream-tauri/player-dialog/player-dialog.component.scss (1)

8-12: Guard against overflow with 100% width in dialog inputs

Bumping to 100% can introduce horizontal scroll in some layouts (borders/padding, flex containers). Add safe constraints so it never exceeds the container.

 .link-input {
-    width: 100%;
+    width: 100%;
+    max-width: 100%;
+    min-width: 0; // avoids flex overflow
+    box-sizing: border-box; // ensures borders/padding don’t spill
     padding-top: 5px;
     @include mat.form-field-density(-5);
 }
src/app/xtream/season-container/season-container.component.html (3)

3-11: Use stable track expression and ensure numeric season ordering

  • Tracking by the whole season object is unstable with keyvalue (new entries are new object refs). Track by season.key to avoid DOM churn.
  • If season keys are numeric strings (e.g., "10", "2"), default keyvalue ordering is lexicographic. Provide a numeric comparator to prevent misordered seasons.

Apply this diff:

-  @if (!selectedSeason) {
-    @for (season of seasons | keyvalue; track season) {
+  @if (!selectedSeason) {
+    @for (season of (seasons | keyvalue: seasonNumberCompare); track season.key) {

Add in the component TS:

// In season-container.component.ts
readonly seasonNumberCompare = (a: {key: string|number}, b: {key: string|number}) =>
  Number(a.key) - Number(b.key);

13-15: Make the “Back to seasons” card accessible

mat-card isn’t a button. Add semantic/keyboard affordances or switch to a real button to satisfy a11y.

-    <mat-card class="episode-item" (click)="selectedSeason = undefined">
+    <mat-card class="episode-item" role="button" tabindex="0"
+              (click)="selectedSeason = undefined"
+              (keydown.enter)="selectedSeason = undefined"
+              (keydown.space)="selectedSeason = undefined; $event.preventDefault()">
       <div class="episode-title">< Back to seasons</div>
     </mat-card>

16-30: Track episodes by a stable id and harden null‑safety/a11y on image

  • track episode risks re-renders; track by a stable key (e.g., episode.id or episode_num).
  • Guard optional paths in @if to avoid runtime errors if info is missing.
  • Add alt and loading="lazy" to the image for a11y and perf.
-    @for (episode of seasons[selectedSeason]; track episode) {
+    @for (episode of seasons[selectedSeason]; track episode.id ?? episode.episode_num) {
       <mat-card
         class="episode-item"
         (click)="selectEpisode(episode)"
         >
-        @if (episode.info.movie_image) {
+        @if (episode?.info?.movie_image) {
           <mat-card-content
-            ><img [src]="episode.info.movie_image" class="episode-cover"
-          /></mat-card-content>
+            ><img [src]="episode?.info?.movie_image"
+                  class="episode-cover"
+                  loading="lazy"
+                  [alt]="episode?.title || ('Episode ' + episode?.episode_num)" /></mat-card-content>
         }
         <div class="episode-title">
           {{ episode.episode_num }}. {{ episode.title }}
         </div>
       </mat-card>
     }
src/app/stalker/stalker.store.ts (1)

88-96: Minor: prefer params() returning undefined to idle the resource.

Instead of checking currentPlaylist in the loader, compute params as undefined when playlist is missing; loader won’t run and status stays idle. This matches Angular’s resource docs. (angular.dev)

-            params: () => ({
-                contentType: store.selectedContentType(),
-                action: StalkerPortalActions.GetCategories,
-                currentPlaylist: store.currentPlaylist(),
-            }),
-            loader: async ({ params }) => {
-                if (params.currentPlaylist === undefined) return;
+            params: () => {
+                const cp = store.currentPlaylist();
+                if (!cp) return undefined;
+                return {
+                    contentType: store.selectedContentType(),
+                    action: StalkerPortalActions.GetCategories,
+                    currentPlaylist: cp,
+                };
+            },
+            loader: async ({ params }) => {
src/app/xtream-tauri/category-content-view/category-content-view.component.ts (1)

40-40: Namespace the page-size localStorage key to avoid cross-feature bleed.
category-content-view.component.ts sets 'xtream-page-size' (src/app/xtream-tauri/category-content-view/category-content-view.component.ts:58) and xtream.store.ts reads it for limit (src/app/xtream-tauri/xtream.store.ts:50); use distinct keys per API/mode (e.g., 'xtream:{apiId}:page-size' or 'stalker-page-size').

src/app/player/components/video-player/video-player.component.scss (1)

1-6: Ensure .main-container spans full height: Re-add top: 0 or specify height: 100% / 100vh for the absolute <mat-drawer-container class="main-container">; without it, the container will size to its content and may collapse.

src/app/home/home.component.ts (1)

86-90: Use undefined (or omit) the snack-bar action instead of null.

MatSnackBar.open’s second parameter is an optional string. Passing null can trip strict typing; use undefined or omit the arg. (v12.material.angular.io)

-    this.snackBar.open(message, null, {
+    this.snackBar.open(message, undefined, {
       duration,
     });
src/main.ts (1)

10-14: preserveWhitespaces here has no effect with AOT bootstrap.

With platformBrowser().bootstrapModule, preserveWhitespaces in CompilerOptions only affected JIT. Either remove it or configure whitespace at build time (tsconfig/angular.json) if needed.

 platformBrowser()
-    .bootstrapModule(AppModule, {
-        preserveWhitespaces: false,
-    })
+    .bootstrapModule(AppModule)
     .catch((err) => console.error(err));
src/app/xtream-tauri/account-info/account-info.component.ts (2)

25-139: Localize remaining static strings.

Most of the dialog content is static English. Consider piping these through translate for consistency with the Close button.

Example:

-<h2 mat-dialog-title>Account Information</h2>
+<h2 mat-dialog-title>{{ 'ACCOUNT_INFO.TITLE' | translate }}</h2>

170-195: Remove debug logs and harden date parsing.

Drop console.log calls and parse epoch strings with an explicit radix (or Number) to avoid NaN in edge cases.

-        console.log(playlist);
+        // noop
@@
-            console.log(this.accountInfo);
             if (this.accountInfo) {
-                this.formattedExpDate = new Date(
-                    parseInt(this.accountInfo.user_info.exp_date) * 1000
-                ).toLocaleDateString();
-                this.formattedCreatedDate = new Date(
-                    parseInt(this.accountInfo.user_info.created_at) * 1000
-                ).toLocaleDateString();
+                const exp = Number.parseInt(this.accountInfo.user_info.exp_date, 10);
+                const created = Number.parseInt(this.accountInfo.user_info.created_at, 10);
+                this.formattedExpDate = new Date(exp * 1000).toLocaleDateString();
+                this.formattedCreatedDate = new Date(created * 1000).toLocaleDateString();
             }
src/app/xtream-tauri/loading-overlay/loading-overlay.component.ts (1)

12-20: Prefer determinate when total > 0 (show 0% instead of indeterminate).

Rendering an indeterminate bar when current is 0 can feel jumpy. Gate determinate solely on total() > 0 and compute 0% naturally.

-                @if (current() !== 0 && total() !== 0) {
+                @if (total() > 0) {
                     <mat-progress-bar
                         mode="determinate"
                         [value]="(current() / total()) * 100"
                     />
                     <p>{{ current() }} / {{ total() }}</p>
src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html (3)

1-4: Avoid double async subscriptions; alias once.

Use @if (... | async; as processes) and iterate over processes to prevent re-subscribing and re-evaluating the observable. (angular.dev)

-@if ((activeProcesses$ | async)?.length) {
-  <div class="mpv-player-bar">
-    @for (process of activeProcesses$ | async; track process.id) {
+@if (activeProcesses$ | async; as processes) {
+  @if (processes?.length) {
+  <div class="mpv-player-bar">
+    @for (process of processes; track process.id) {

7-15: Move fallback image logic into a method to avoid direct DOM mutation in templates.

Inline assignment to $event.target.src is brittle. Delegate to a handler.

-            <img
+            <img
               [src]="process.thumbnail"
               [alt]="process.title"
               class="media-thumbnail"
-                        (error)="
-                            $event.target.src =
-                                './assets/images/default-poster.png'
-                        "
+              (error)="onImgError($event)"
             />

Add in the component TS:

onImgError(e: Event) {
  (e.target as HTMLImageElement).src = './assets/images/default-poster.png';
}

41-47: Localize the button title.

For consistency with the rest of the app, consider translating the Close tooltip.

-          title="Close"
+          [title]="'CLOSE' | translate"
src/app/xtream/serial-details/serial-details.component.html (2)

12-14: Add alt/decoding/lazy to cover image for a11y and perf.

-      <img [src]="item.info.cover" />
+      <img [src]="item.info.cover" [attr.alt]="item.info.name || 'cover'" decoding="async" loading="lazy" />

41-46: Prefer semantic containers over label elements.

<label> without a control hurts a11y. Use <p> or <div>.

Also applies to: 48-51, 54-60

src/app/player/components/audio-player/audio-player.component.ts (1)

137-139: Remove console log or gate behind env.

Leftover console.log in production code.

-        console.log(direction);
         this.store.dispatch(setAdjacentChannelAsActive({ direction }));
src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html (1)

10-16: Don’t render empty language row when lang is missing.

Move the <p data-test="lang"> inside the @if (epgProgram.title[0].lang) block.

-    @if (epgProgram.title[0].lang) {
-      <div class="subheading-2">
-        {{ 'EPG.PROGRAM_DIALOG.LANGUAGE' | translate }}
-      </div>
-    }
-    <p data-test="lang">{{ epgProgram.title[0].lang }}</p>
+    @if (epgProgram.title[0].lang) {
+      <div class="subheading-2">
+        {{ 'EPG.PROGRAM_DIALOG.LANGUAGE' | translate }}
+      </div>
+      <p data-test="lang">{{ epgProgram.title[0].lang }}</p>
+    }
src/app/player/components/video-player/video-player.component.html (2)

72-74: Add alt text to illustrative images.

-          <img src="./assets/images/custom-player.png" />
+          <img src="./assets/images/custom-player.png" alt="External player illustration" />

Also applies to: 92-93


21-26: Consider boolean coercion for radio flag.

If radio can be 'true' | 'false' | boolean, coerce once in the controller and use a boolean in the template.

src/app/xtream/navigation-bar/navigation-bar.component.html (2)

15-23: Track function stability.

Using a function call in track can create unnecessary churn. Prefer a stable key: track item.contentType or a unique id.

-      @for (
-        item of contentTypeNavigationItems; track trackByValue($index,
-        item)) {
+      @for (item of contentTypeNavigationItems; track item.contentType) {

93-99: Translate the search placeholder.

Hardcoded placeholder breaks i18n consistency.

-              placeholder="Search by name"
+              [placeholder]="'CHANNELS.SEARCH_BY_NAME' | translate"
package.json (1)

92-103: Align Angular packages between deps and devDeps.

Core Angular libs (@angular/core/common/router/...) are in devDependencies while others are in dependencies. For clarity and to avoid packaging pitfalls (Electron/Tauri), keep Angular runtime libs aligned (same major and group). Consider moving all @angular/* to devDependencies or dependencies consistently.

Also applies to: 37-49

src/app/settings/settings.component.html (9)

43-46: Remove stray matLine attributes from buttons.

matLine is for list items, not mat-icon-button; drop it to avoid unknown-attr noise.

-                <button
-                  matLine
+                <button
                   mat-icon-button
                   color="accent"
...
-                <button
-                  mat-icon-button
-                  matLine
+                <button
+                  mat-icon-button
                   color="accent"

Also applies to: 54-57


57-60: Fix tooltip text for “Remove EPG source”.

Tooltip reuses REFRESH_EPG; use a remove-specific key.

-                  [matTooltip]="'SETTINGS.REFRESH_EPG' | translate"
+                  [matTooltip]="'SETTINGS.REMOVE_EPG_SOURCE' | translate"

Please confirm the i18n key exists (or provide the correct one).


48-51: Harden disabled condition (trim whitespace).

Prevents “Refresh” enabled when input has only spaces.

-                  [disabled]="epgField.value === ''"
+                  [disabled]="!epgField.value?.trim()"

113-115: Remove unsupported “for” on mat-label.

mat-label doesn’t support “for”; rely on form-field semantics or aria-labelledby.

-            <mat-label for="mpvPlayerPath">{{ 'SETTINGS.MPV_PLAYER_PATH' | translate }}</mat-label>
+            <mat-label>{{ 'SETTINGS.MPV_PLAYER_PATH' | translate }}</mat-label>
...
-            <mat-label for="vlcPlayerPath">{{ 'SETTINGS.VLC_PLAYER_PATH' | translate }}</mat-label>
+            <mat-label>{{ 'SETTINGS.VLC_PLAYER_PATH' | translate }}</mat-label>

Also applies to: 136-138


92-99: Use stable, primitive track expressions.

Track by id/value to reduce DOM churn when arrays reallocate.

-            @for (player of players; track player) {
+            @for (player of players; track player.id) {
...
-            @for (streamFormat of streamFormatEnum | keyvalue; track streamFormat) {
+            @for (streamFormat of streamFormatEnum | keyvalue; track streamFormat.value) {
...
-            @for (language of languageEnum | keyvalue; track language) {
+            @for (language of languageEnum | keyvalue; track language.value) {
...
-              @for (theme of themeEnum | keyvalue; track theme) {
+              @for (theme of themeEnum | keyvalue; track theme.value) {

Also applies to: 164-170, 190-198, 218-226


42-52: Add aria-labels to icon buttons.

Improves a11y for screen readers.

-                <button
+                <button
                   mat-icon-button
                   color="accent"
                   [matTooltip]="'SETTINGS.REFRESH_EPG' | translate"
                   type="button"
                   [disabled]="!epgField.value?.trim()"
                   (click)="refreshEpg(epgUrl.value[i])"
+                  [attr.aria-label]="'SETTINGS.REFRESH_EPG' | translate"
                   >
...
-                <button
+                <button
                   mat-icon-button
                   color="accent"
-                  [matTooltip]="'SETTINGS.REFRESH_EPG' | translate"
+                  [matTooltip]="'SETTINGS.REMOVE_EPG_SOURCE' | translate"
                   type="button"
                   (click)="removeEpgSource(i)"
+                  [attr.aria-label]="'SETTINGS.REMOVE_EPG_SOURCE' | translate"
                   >

Also applies to: 53-62


157-159: Placeholders reference VIDEO_PLAYER key; use specific keys for each select.

Avoids confusing UX/i18n.

-          <mat-label>{{ 'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate }}</mat-label>
+          <mat-label>{{ 'SETTINGS.STREAM_FORMAT_PLACEHOLDER' | translate }}</mat-label>
...
-          <mat-label>{{ 'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate }}</mat-label>
+          <mat-label>{{ 'SETTINGS.LANGUAGE_PLACEHOLDER' | translate }}</mat-label>
...
-            <mat-label>{{ 'SETTINGS.VIDEO_PLAYER_PLACEHOLDER' | translate }}</mat-label>
+            <mat-label>{{ 'SETTINGS.THEME_PLACEHOLDER' | translate }}</mat-label>

Please confirm these keys exist (or provide the intended ones).

Also applies to: 183-185, 211-213


277-283: Port input should be numeric with sensible bounds.

Tighten validation to avoid invalid ports.

-                <input
-                  matInput
-                  type="text"
+                <input
+                  matInput
+                  type="number"
+                  inputmode="numeric"
+                  min="1024"
+                  max="65535"
                   id="remoteControlPort"
                   formControlName="remoteControlPort"
                   />

355-357: Open external link safely in a new tab.

Add target and rel to prevent tab-napping.

-            &nbsp;<a href="https://github.com/4gray/iptvnator/releases">{{
+            &nbsp;<a href="https://github.com/4gray/iptvnator/releases" target="_blank" rel="noopener noreferrer">{{
src/app/shared/components/header/header.component.ts (1)

114-117: Avoid bare subscribe; add automatic teardown.

Prevents leaks on component destroy.

+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
...
-        this.sortService.getSortOptions().subscribe((options) => {
+        this.sortService
+            .getSortOptions()
+            .pipe(takeUntilDestroyed())
+            .subscribe((options) => {
             this.currentSortOptions = options;
-        });
+        });
src/app/player/components/multi-epg/multi-epg-container.component.ts (2)

213-221: Date parsing robustness.

new Date(dateStr) is format/locale-dependent; prefer date-fns parse with an explicit pattern and TZ, with a safe fallback.

// Example:
import { parse } from 'date-fns';
// parse '20250101123000 +0000' as 'yyyyMMddHHmmss xx'
const safeParse = (s: string) => parse(s, 'yyyyMMddHHmmss xx', new Date());

Apply in getCachedDate() and when creating start/stop dates.


138-140: Replace console.log with a logger or guard by env.

Noisy logs in production degrade UX.

if (!ngDevMode) { /* skip logs */ }
// or use an injected LoggerService with levels

Also applies to: 167-171, 181-189

src/app/app.component.ts (1)

49-49: Avoid any[] for modals; restore/define a typed shape.

Losing types weakens safety; either import the interface (as type-only) or define a local Modal-like type exposed by WhatsNewService.

-    modals: any[] = [];
+    // If ngx-whats-new exports a type:
+    // import type { ModalWindow } from 'ngx-whats-new';
+    // modals: ModalWindow[] = [];
+    // Otherwise define a minimal local shape:
+    type ModalItem = { title: string; content: string; [k: string]: unknown };
+    modals: ModalItem[] = [];
src/app/portals/web-player-view/web-player-view.component.ts (1)

37-40: Type cast on toSignal is too narrow.

settings emits asynchronously; allow undefined in the Signal type.

-    ) as Signal<Settings>;
+    ) as Signal<Settings | undefined>;
src/app/portals/web-player-view/web-player-view.component.html (1)

3-6: Remove redundant nested @if (player === 'html5').

Outer branch already guards html5; inner check is duplicate.

 } @else if (player === 'html5') {
-  @if (player === 'html5') {
-    <app-html-video-player [channel]="channel" />
-  }
+  <app-html-video-player [channel]="channel" />
 }
src/app/xtream/vod-details/vod-details.component.ts (1)

51-76: One-off read should complete subscription

If getPortalFavorites returns a long-lived stream, this leaks. Take a single emission.

Apply this diff:

-    this.playlistService
-        .getPortalFavorites(this.portalId)
-        .subscribe((favorites) => {
+    this.playlistService
+        .getPortalFavorites(this.portalId)
+        .pipe(take(1))
+        .subscribe((favorites) => {

Add import:

-import { ActivatedRoute } from '@angular/router';
+import { ActivatedRoute } from '@angular/router';
+import { take } from 'rxjs';
src/app/services/whats-new.service.ts (3)

2-2: Restore explicit typing with type-only import (keeps bundler-friendly deep-import avoidance)

Commenting the type import and dropping return types weakens API guarantees.

Apply this diff:

-/* import { ModalWindow } from 'ngx-whats-new/lib/modal-window.interface'; */
+import type { ModalWindow } from 'ngx-whats-new';

If ngx-whats-new does not re-export ModalWindow, define a local minimal type used by your templates instead of deep-importing.


303-305: Add explicit return type

Keep method contract stable.

Apply this diff:

-    getModalsByVersion(version: string) {
+    getModalsByVersion(version: string): ModalWindow[] {
         return this.modals[version] || [];
     }

310-314: Add explicit return type

Same reasoning as above.

Apply this diff:

-    getLatestChanges() {
+    getLatestChanges(): ModalWindow[] {
         const modalsLength = Object.keys(this.modals).length;
         const lastVersion = Object.keys(this.modals)[modalsLength - 1];
         return this.modals[lastVersion];
     }
src/app/xtream/vod-details/vod-details.component.html (2)

13-18: Add alt text for poster image

Improve a11y and LCP hints.

Apply this diff:

-      <img
-        [src]="item.info?.movie_image"
+      <img
+        [src]="item.info?.movie_image"
+        [attr.alt]="item.info?.name || 'Poster'"
         (error)="
             $event.target.src = './assets/images/default-poster.png'
         "
         />

122-122: Deprecated attribute

frameborder is obsolete in HTML5.

Apply this diff:

-        frameborder="0"
src/app/home/recent-playlists/recent-playlists.component.html (1)

4-12: Add an explicit aria-label to the search input

Placeholder text isn’t a label; improve screen-reader support.

Apply this diff:

         <input
             matInput
             #searchQuery
             type="search"
             spellcheck="false"
             autocomplete="off"
             [placeholder]="'HOME.PLAYLISTS.SEARCH_PLAYLISTS' | translate"
+            [attr.aria-label]="'HOME.PLAYLISTS.SEARCH_PLAYLISTS' | translate"
             (input)="onSearchQueryUpdate(searchQuery.value)"
         />
src/app/player/components/multi-epg/multi-epg-container.component.html (1)

34-49: Hard-coded English labels in navigation buttons

Localize these strings for consistency.

Apply this diff:

-      <mat-icon>keyboard_arrow_up</mat-icon> Previous channels
+      <mat-icon>keyboard_arrow_up</mat-icon> {{ 'EPG.PREVIOUS_CHANNELS' | translate }}
...
-      <mat-icon>keyboard_arrow_down</mat-icon> Next channels
+      <mat-icon>keyboard_arrow_down</mat-icon> {{ 'EPG.NEXT_CHANNELS' | translate }}

And likewise replace matTooltip="Previous channels"/"Next channels" with translated keys.

src/app/shared/components/header/header.component.html (2)

73-75: Consider translating the GitHub label

All other items use i18n; keep this consistent if a key exists.

Apply this diff (assuming MENU.GITHUB exists):

-                        <span>GitHub</span>
+                        <span>{{ 'MENU.GITHUB' | translate }}</span>

189-234: Minor naming nit: opedAddPlaylistDialog

Method name has a typo and is used in multiple actions. Consider renaming to openAddPlaylistDialog and updating call sites.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe40a7 and 773faee.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • angular.json (1 hunks)
  • package.json (1 hunks)
  • src/app/app.component.html (1 hunks)
  • src/app/app.component.ts (1 hunks)
  • src/app/home/home.component.html (1 hunks)
  • src/app/home/home.component.ts (2 hunks)
  • src/app/home/recent-playlists/recent-playlists.component.html (1 hunks)
  • src/app/home/recent-playlists/recent-playlists.component.ts (2 hunks)
  • src/app/player/components/art-player/art-player.component.ts (2 hunks)
  • src/app/player/components/audio-player/audio-player.component.ts (2 hunks)
  • src/app/player/components/d-player/d-player.component.ts (2 hunks)
  • src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html (1 hunks)
  • src/app/player/components/epg-list/epg-item-description/epg-item-description.component.ts (2 hunks)
  • src/app/player/components/multi-epg/multi-epg-container.component.html (1 hunks)
  • src/app/player/components/multi-epg/multi-epg-container.component.ts (1 hunks)
  • src/app/player/components/video-player/video-player.component.html (2 hunks)
  • src/app/player/components/video-player/video-player.component.scss (1 hunks)
  • src/app/portals/web-player-view/web-player-view.component.html (1 hunks)
  • src/app/portals/web-player-view/web-player-view.component.ts (2 hunks)
  • src/app/services/whats-new.service.ts (2 hunks)
  • src/app/settings/settings.component.html (1 hunks)
  • src/app/shared/components/header/header.component.html (1 hunks)
  • src/app/shared/components/header/header.component.scss (1 hunks)
  • src/app/shared/components/header/header.component.ts (1 hunks)
  • src/app/shared/components/mpv-player-bar/mpv-player-bar.component.html (1 hunks)
  • src/app/stalker/recently-viewed/recently-viewed.component.ts (1 hunks)
  • src/app/stalker/stalker-favorites/stalker-favorites.component.ts (1 hunks)
  • src/app/stalker/stalker-search/stalker-search.component.ts (2 hunks)
  • src/app/stalker/stalker.store.ts (6 hunks)
  • src/app/xtream-tauri/account-info/account-info.component.ts (2 hunks)
  • src/app/xtream-tauri/category-content-view/category-content-view.component.ts (1 hunks)
  • src/app/xtream-tauri/loading-overlay/loading-overlay.component.ts (1 hunks)
  • src/app/xtream-tauri/player-dialog/player-dialog.component.scss (2 hunks)
  • src/app/xtream/navigation-bar/navigation-bar.component.html (1 hunks)
  • src/app/xtream/navigation-bar/navigation-bar.component.ts (2 hunks)
  • src/app/xtream/season-container/season-container.component.html (1 hunks)
  • src/app/xtream/season-container/season-container.component.ts (2 hunks)
  • src/app/xtream/serial-details/serial-details.component.html (1 hunks)
  • src/app/xtream/serial-details/serial-details.component.ts (2 hunks)
  • src/app/xtream/vod-details/vod-details.component.html (2 hunks)
  • src/app/xtream/vod-details/vod-details.component.ts (2 hunks)
  • src/app/xtream/xtream-main-container.component.ts (3 hunks)
  • src/main.ts (1 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/main.ts (4)
src/environments/environment.dev.ts (1)
  • AppConfig (6-11)
src/environments/environment.prod.ts (1)
  • AppConfig (1-6)
src/environments/environment.web.ts (1)
  • AppConfig (1-6)
src/environments/environment.ts (1)
  • AppConfig (1-6)
src/app/stalker/stalker-search/stalker-search.component.ts (1)
src/app/stalker/stalker-content-types.ts (1)
  • StalkerContentTypes (3-32)
src/app/stalker/stalker.store.ts (2)
shared/ipc-commands.ts (1)
  • STALKER_REQUEST (55-55)
src/app/stalker/stalker-content-types.ts (1)
  • StalkerContentTypes (3-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: e2e-tests (macos-latest)
🔇 Additional comments (29)
src/app/xtream-tauri/player-dialog/player-dialog.component.scss (1)

22-22: EOF newline/hygiene looks good

No functional change; keeping a trailing newline is a good practice.

src/app/stalker/stalker.store.ts (1)

83-148: API shape looks correct for Angular 20 resources (params/loader).

The migration from request→params and loader({params}) aligns with current Angular docs. No issues on the new API surface. (angular.dev)

Also applies to: 149-156

src/app/stalker/stalker-search/stalker-search.component.ts (1)

91-98: API migration to params/loader is correct for Angular 20 resources.

The new shape matches Angular’s resource API. (angular.dev)

src/app/stalker/recently-viewed/recently-viewed.component.ts (1)

47-51: Good migration to rxResource params/stream.

This matches the current rxResource API; using params to trigger refresh via refreshTimestamp is idiomatic.

(angular.dev)

src/app/stalker/stalker-favorites/stalker-favorites.component.ts (1)

47-51: Good migration to rxResource params/stream.

API usage looks correct; refreshTimestamp drives re-subscription.

(angular.dev)

src/app/shared/components/header/header.component.scss (1)

60-62: LGTM!

The styling change from translucent black to solid dark gray for the active sort state aligns with Angular 20's modern control flow syntax migration. The updated color ensures better visual contrast and opaque appearance in the new UI.

tsconfig.json (1)

9-9: Appropriate upgrade to bundler module resolution.

The change from "node" to "bundler" module resolution aligns with the broader TypeScript 5.9 upgrade and improves compatibility with modern build tools. This configuration supports Angular 20's build optimizations.

src/app/player/components/d-player/d-player.component.ts (1)

15-16: LGTM!

The removal of CommonModule import and setting imports to an empty array is consistent with the Angular 20 migration pattern observed across multiple components. Since this component uses an inline template without structural directives, no additional changes are needed.

src/app/player/components/art-player/art-player.component.ts (1)

15-17: LGTM!

The removal of CommonModule import aligns with the new control flow syntax that reduces runtime footprint and improves bundle size. This component's inline template doesn't use structural directives, so the change is safe.

src/app/home/home.component.html (1)

7-9: LGTM! Clean migration to new control flow syntax.

The migration from *ngIf="isLoading" to @if (isLoading) follows Angular 20's new control flow syntax. This provides more ergonomic and intuitive syntax that's closer to JavaScript.

src/app/xtream/season-container/season-container.component.ts (1)

1-10: Confirmed: template uses Angular 20 control-flow (@if/@for)
src/app/xtream/season-container/season-container.component.html contains @if/@for (lines 3, 4, 16, 21) and no *ngIf/*ngFor.

src/app/xtream/navigation-bar/navigation-bar.component.ts (1)

46-58: Confirmed: template updated to Angular 20 control-flow (@if/@for). src/app/xtream/navigation-bar/navigation-bar.component.html uses @if/@for (lines 7,15,49,54,64,76,88 from rg output); no *ngIf/*ngFor were found.

src/main.ts (1)

2-2: Good move: platformBrowser over platformBrowserDynamic.

platformBrowserDynamic is deprecated in favor of platformBrowser; this aligns with Angular’s guidance. If you still rely on JIT in non-CLI contexts, ensure @angular/compiler is present (unlikely here). (angular.dev)

src/app/xtream-tauri/account-info/account-info.component.ts (1)

140-140: styleUrl usage is fine on Angular 17+.

Using styleUrl (singular) is supported since Angular 17; keep as-is. (blog.ninja-squad.com)

Confirm the project is actually building against Angular ≥17 (this PR targets v20), otherwise switch to styleUrls.

src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html (1)

6-10: Confirm the type of epgProgram.title; likely need the first item’s value.

If title is an array, rendering {{ epgProgram.title }} will print [object Object]. Consider title[0]?.value (or model’s actual field).

[suggest_minor_issue]

src/app/player/components/epg-list/epg-item-description/epg-item-description.component.ts (1)

12-13: Imports tidy-up looks good.

Removal of NgIf import aligns with new control flow syntax.

angular.json (1)

21-43: Config LGTM — serviceWorker set to "ngsw-config.json"; local build required to verify.

jq returned "ngsw-config.json"; sandbox build failed with "sh: 1: ng: not found". Run locally (npm run build:prod or ng build -c production) and confirm the build succeeds and the SW config is picked up.

package.json (1)

86-104: Verify builder/test toolchain compatibility with Angular 20

  • Findings — @angular-builders/jest v17.0.0 confirmed; npm ci emitted no peerDependency warnings but showed multiple deprecation notices and 23 vulnerabilities; jq failed to enumerate @angular/* from package.json during verification.
  • Action — Inspect package.json (lines 86–104) to confirm TypeScript (expect TS 5.9) and ESLint plugin versions, run the full test suite/CI, and if tests or peer warnings appear, upgrade or pin @angular-builders/jest / ESLint / TS to releases known to be compatible with Angular 20.
src/app/shared/components/header/header.component.ts (1)

1-1: LGTM: dropping NgIf import aligns with @if blocks.

No issues spotted; AsyncPipe remains available.

src/app/portals/web-player-view/web-player-view.component.ts (1)

25-29: LGTM: imports narrowed to player components.

Matches template’s @if branches.

src/app/xtream/serial-details/serial-details.component.ts (1)

21-25: LGTM: removed NgIf from imports.

Template should now rely on @if blocks.

src/app/app.component.html (1)

3-9: *LGTM: migrated ngIf to @if for ngx-whats-new.

Bindings unchanged; looks correct.

src/app/xtream/vod-details/vod-details.component.ts (2)

24-29: NgIf removal in standalone imports looks correct

Template has migrated to the new control-flow; dropping NgIf from imports is appropriate.


80-83: ID type for removeFromFavorites

removeFromFavoritesClicked is EventEmitter<number>, but fallback (this.item as any)?.id could be non-numeric in stalker mode. Please verify types end-to-end.

src/app/xtream/xtream-main-container.component.ts (3)

111-111: Trailing comma in imports

No behavioral change; fine.


360-364: Dialog bounds for VOD player

Adding maxWidth/maxHeight is a good UX guard on large screens.


390-392: Dialog bounds for episode player

Same here—sensible limits.

src/app/home/recent-playlists/recent-playlists.component.ts (2)

2-2: CommonModule structural directives removal is fine

Template migrated to new control-flow; keeping only AsyncPipe here is correct.


67-70: Readonly BehaviorSubject + skeleton source look good

Marking searchQuery readonly prevents accidental reassignment; ghostElements enables cheap placeholder loops.

Comment on lines +38 to +44
"@angular-builders/jest": "17.0.0",
"@angular/animations": "19.2.0",
"@angular/cdk": "20.2.4",
"@angular/material": "20.2.4",
"@ngrx/component-store": "19.0.1",
"@ngrx/effects": "19.0.1",
"@ngrx/entity": "19.0.1",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Angular version skew: @angular/animations is still v19.

This will violate peer deps with v20 core/material.

-        "@angular/animations": "19.2.0",
+        "@angular/animations": "20.3.1",
📝 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
"@angular-builders/jest": "17.0.0",
"@angular/animations": "19.2.0",
"@angular/cdk": "20.2.4",
"@angular/material": "20.2.4",
"@ngrx/component-store": "19.0.1",
"@ngrx/effects": "19.0.1",
"@ngrx/entity": "19.0.1",
"@angular-builders/jest": "17.0.0",
"@angular/animations": "20.3.1",
"@angular/cdk": "20.2.4",
"@angular/material": "20.2.4",
"@ngrx/component-store": "19.0.1",
"@ngrx/effects": "19.0.1",
"@ngrx/entity": "19.0.1",
🤖 Prompt for AI Agents
In package.json around lines 38 to 44, @angular/animations is pinned to v19
while @angular/cdk and @angular/material are v20, causing Angular version skew
and peer dependency conflicts; update @angular/animations to the matching v20.x
(e.g., 20.2.4) so all Angular packages share the same major/minor version, then
run npm/yarn install (and update the lockfile) and run the test/build to verify
no further peer dep issues.

Comment on lines +141 to +142
"tslib": "2.3.0",
"typescript": "5.9.2",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

tslib too old for TS 5.9 / Angular 20.

Pin to >=2.6.2 to avoid build/runtime issues.

-        "tslib": "2.3.0",
+        "tslib": "^2.6.2",
📝 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
"tslib": "2.3.0",
"typescript": "5.9.2",
"tslib": "^2.6.2",
"typescript": "5.9.2",
🤖 Prompt for AI Agents
In package.json around lines 141-142, the tslib version is pinned too low for
TypeScript 5.9/Angular 20; update the tslib dependency spec to require >=2.6.2
(e.g. change the value to ">=2.6.2" or a compatible semver like "^2.6.2"), save
package.json, run your package manager to update node_modules and regenerate the
lockfile (npm install or yarn install), then run the build/tests to ensure there
are no runtime/build issues.

Comment on lines +11 to +23
[matTooltip]="'EPG.NEXT_DAY' | translate"
mat-icon-button
(click)="switchDay('prev')"
>
<mat-icon>navigate_before</mat-icon>
</button>
<button
[matTooltip]="'EPG.PREVIOUS_DAY' | translate"
mat-icon-button
(click)="switchDay('next')"
<mat-icon>navigate_before</mat-icon>
</button>
<button
[matTooltip]="'EPG.PREVIOUS_DAY' | translate"
mat-icon-button
(click)="switchDay('next')"
>
<mat-icon>navigate_next</mat-icon>
</button>
<div class="today-date">
{{ today | momentDate: 'YYYYMMDD' : 'MMMM Do, dddd' }}
</div>
<button mat-icon-button (click)="zoomIn()" [disabled]="(hourWidth$ | async) >= 800">
<mat-icon>zoom_in</mat-icon>
<mat-icon>navigate_next</mat-icon>
</button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prev/Next day controls are reversed

Tooltip labels don’t match the click handlers. Swap tooltips to align with actions.

Apply this diff:

-  <button
-    [matTooltip]="'EPG.NEXT_DAY' | translate"
+  <button
+    [matTooltip]="'EPG.PREVIOUS_DAY' | translate"
     mat-icon-button
     (click)="switchDay('prev')"
   >
     <mat-icon>navigate_before</mat-icon>
   </button>
   <button
-    [matTooltip]="'EPG.PREVIOUS_DAY' | translate"
+    [matTooltip]="'EPG.NEXT_DAY' | translate"
     mat-icon-button
     (click)="switchDay('next')"
   >
     <mat-icon>navigate_next</mat-icon>
   </button>
📝 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
[matTooltip]="'EPG.NEXT_DAY' | translate"
mat-icon-button
(click)="switchDay('prev')"
>
<mat-icon>navigate_before</mat-icon>
</button>
<button
[matTooltip]="'EPG.PREVIOUS_DAY' | translate"
mat-icon-button
(click)="switchDay('next')"
<mat-icon>navigate_before</mat-icon>
</button>
<button
[matTooltip]="'EPG.PREVIOUS_DAY' | translate"
mat-icon-button
(click)="switchDay('next')"
>
<mat-icon>navigate_next</mat-icon>
</button>
<div class="today-date">
{{ today | momentDate: 'YYYYMMDD' : 'MMMM Do, dddd' }}
</div>
<button mat-icon-button (click)="zoomIn()" [disabled]="(hourWidth$ | async) >= 800">
<mat-icon>zoom_in</mat-icon>
<mat-icon>navigate_next</mat-icon>
</button>
<button
[matTooltip]="'EPG.PREVIOUS_DAY' | translate"
mat-icon-button
(click)="switchDay('prev')"
>
<mat-icon>navigate_before</mat-icon>
</button>
<button
[matTooltip]="'EPG.NEXT_DAY' | translate"
mat-icon-button
(click)="switchDay('next')"
>
<mat-icon>navigate_next</mat-icon>
</button>
🤖 Prompt for AI Agents
In src/app/player/components/multi-epg/multi-epg-container.component.html around
lines 11 to 23, the tooltip labels are reversed relative to the click handlers;
change the first button's [matTooltip] to "'EPG.PREVIOUS_DAY' | translate" and
the second button's [matTooltip] to "'EPG.NEXT_DAY' | translate" so the tooltips
match the (click)="switchDay('prev')" and (click)="switchDay('next')" actions
respectively.

Comment on lines 153 to 155
trackByProgram(_: number, program: EnrichedProgram): string {
return program.start + program.title;
return program.start + program.title.toString();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Possible runtime error: .toString() on undefined/null.

program.title may be absent; .toString() would throw.

-    trackByProgram(_: number, program: EnrichedProgram): string {
-        return program.start + program.title.toString();
-    }
+    trackByProgram(_: number, program: EnrichedProgram): string {
+        return `${program.start}|${program.title ?? ''}`;
+    }

If a stable id exists (e.g., program.id), prefer that.

📝 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
trackByProgram(_: number, program: EnrichedProgram): string {
return program.start + program.title;
return program.start + program.title.toString();
}
trackByProgram(_: number, program: EnrichedProgram): string {
return `${program.start}|${program.title ?? ''}`;
}
🤖 Prompt for AI Agents
In src/app/player/components/multi-epg/multi-epg-container.component.ts around
lines 153 to 155, trackByProgram currently does program.start +
program.title.toString() which can throw if program.title is null/undefined;
update it to return a stable string id: prefer a stable identifier like
program.id (return String(program.id) when present), otherwise safely construct
a string using program.start plus a guarded title (e.g., use program.title ?? ''
or String(program.title)) so the function never calls toString on undefined and
always returns a string.

Comment on lines +149 to +156
params: () => ({
contentType: store.selectedContentType(),
category: store.selectedCategoryId(),
action: StalkerPortalActions.GetOrderedList,
search: store.searchPhrase(),
pageIndex: store.page() + 1,
}),
loader: async ({ request }) => {
loader: async ({ params }) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing reactivity/guard for currentPlaylist in getContentResource.

The loader reads currentPlaylist() but it's not part of params; it won’t rerun when playlist changes and may crash when undefined. Include playlist in params and use it in the loader.

-            params: () => ({
-                contentType: store.selectedContentType(),
-                category: store.selectedCategoryId(),
-                action: StalkerPortalActions.GetOrderedList,
-                search: store.searchPhrase(),
-                pageIndex: store.page() + 1,
-            }),
+            params: () => {
+                const playlist = store.currentPlaylist();
+                const category = store.selectedCategoryId();
+                if (!playlist || !category) return undefined;
+                return {
+                    contentType: store.selectedContentType(),
+                    category,
+                    action: StalkerPortalActions.GetOrderedList,
+                    search: store.searchPhrase(),
+                    pageIndex: store.page() + 1,
+                    currentPlaylist: playlist,
+                };
+            },
@@
-                        url: currentPlaylist().portalUrl,
-                        macAddress: currentPlaylist().macAddress,
+                        url: params.currentPlaylist.portalUrl,
+                        macAddress: params.currentPlaylist.macAddress,

Also applies to: 180-183

🤖 Prompt for AI Agents
In src/app/stalker/stalker.store.ts around lines 149-156 (and similarly
180-183), the loader reads currentPlaylist() but that value is not included in
the params so the loader won’t rerun when the playlist changes and may access
undefined; add currentPlaylist (or currentPlaylistId) to the params function so
the loader is reactive to playlist changes, and update the loader to guard
against undefined (e.g., return early or throw a controlled error if
params.playlist is missing) before using the playlist value.

Comment on lines +224 to 233
params: () => ({
itemId: store.selectedSerialId(),
}),
loader: async ({ request }) => {
loader: async ({ params }) => {
const { portalUrl, macAddress } = store.currentPlaylist();
const params = {
const queryParams = {
action: StalkerContentTypes.series.getContentAction,
type: 'series',
movie_id: request.itemId,
movie_id: params.itemId,
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

serialSeasonsResource: add playlist to params and guard response.

Ensures reactivity to playlist changes and avoids NPEs.

-            params: () => ({
-                itemId: store.selectedSerialId(),
-            }),
-            loader: async ({ params }) => {
-                const { portalUrl, macAddress } = store.currentPlaylist();
+            params: () => {
+                const playlist = store.currentPlaylist();
+                const itemId = store.selectedSerialId();
+                if (!playlist || !itemId) return undefined;
+                return { itemId, currentPlaylist: playlist };
+            },
+            loader: async ({ params }) => {
+                const { portalUrl, macAddress } = params.currentPlaylist;
                 const queryParams = {
                     action: StalkerContentTypes.series.getContentAction,
                     type: 'series',
-                    movie_id: params.itemId,
+                    movie_id: params.itemId,
                 };
                 const response = await dataService.sendIpcEvent(
                     STALKER_REQUEST,
                     {
                         url: portalUrl,
                         macAddress,
                         params: queryParams,
                     }
                 );
-                return sortByNumericValue(response.js.data);
+                if (!response) throw new Error('STALKER_REQUEST returned no response for seasons');
+                return sortByNumericValue(response.js?.data ?? []);

Also applies to: 237-243

🤖 Prompt for AI Agents
In src/app/stalker/stalker.store.ts around lines 224-233 (and also apply same
fix to 237-243), the serialSeasonsResource params only include itemId and the
loader assumes currentPlaylist exists; update params to include the whole
currentPlaylist (or at least portalUrl and macAddress) so the resource reacts to
playlist changes, and in the loader guard the response by checking
currentPlaylist is defined before using its properties and validating the
fetched response (e.g., ensure data exists and has expected shape) before
returning — if missing, return a safe default (empty array/object) or throw a
handled error to avoid NPEs.

Comment on lines 13 to +21
@Component({
selector: 'app-account-info',
imports: [
CommonModule,
MatButton,
MatDialogModule,
MatCardModule,
MatListModule,
TranslateModule,
],
MatButton,
MatDialogModule,
MatCardModule,
MatListModule,
TranslateModule
],
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add standalone: true; required when using imports on components.

Like other updated components, this one uses imports but lacks standalone: true. Add it to avoid compilation errors and to make MatButton/MatDialogModule/etc. available. (v18.angular.dev)

 @Component({
   selector: 'app-account-info',
+  standalone: true,
   imports: [
     MatButton,
     MatDialogModule,
     MatCardModule,
     MatListModule,
     TranslateModule
   ],
📝 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
@Component({
selector: 'app-account-info',
imports: [
CommonModule,
MatButton,
MatDialogModule,
MatCardModule,
MatListModule,
TranslateModule,
],
MatButton,
MatDialogModule,
MatCardModule,
MatListModule,
TranslateModule
],
@Component({
selector: 'app-account-info',
standalone: true,
imports: [
MatButton,
MatDialogModule,
MatCardModule,
MatListModule,
TranslateModule
],
🤖 Prompt for AI Agents
In src/app/xtream-tauri/account-info/account-info.component.ts around lines 13
to 21, the @Component decorator uses the imports array but is missing
standalone: true which is required when declaring component-level imports;
update the @Component metadata to include standalone: true alongside the
existing imports so the MatButton, MatDialogModule, MatCardModule, MatListModule
and TranslateModule are available and the component compiles as a standalone
component.

Comment on lines 5 to 8
@Component({
selector: 'app-loading-overlay',
imports: [MatProgressBarModule, CommonModule],
imports: [MatProgressBarModule],
template: `
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mark component as standalone to use imports.

Same pattern: imports is present but standalone is missing. Add standalone: true. (v18.angular.dev)

 @Component({
   selector: 'app-loading-overlay',
-  imports: [MatProgressBarModule],
+  standalone: true,
+  imports: [MatProgressBarModule],
📝 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
@Component({
selector: 'app-loading-overlay',
imports: [MatProgressBarModule, CommonModule],
imports: [MatProgressBarModule],
template: `
@Component({
selector: 'app-loading-overlay',
standalone: true,
imports: [MatProgressBarModule],
template: `
🤖 Prompt for AI Agents
In src/app/xtream-tauri/loading-overlay/loading-overlay.component.ts around
lines 5 to 8, the @Component decorator includes an imports array but is missing
standalone: true; update the component metadata to add standalone: true (keeping
the existing imports: [MatProgressBarModule]) so the component can use the
provided imports as a standalone component.

Comment on lines +11 to +60
<div class="image">
@if (item.info.cover) {
<img [src]="item.info.cover" />
}
</div>
<div class="details">
<h2>{{ item.info.name }}</h2>
@if (item.info.plot) {
<label>
{{ item.info.plot }}
</label>
}
@if (item.info.releaseDate) {
<label>
<div class="label">{{ 'XTREAM.RELEASE_DATE' | translate }}:</div>
{{ item.info.releaseDate }}
</label>
}
@if (item.info.genre) {
<label>
<div class="label">{{ 'XTREAM.GENRE' | translate }}:</div>
{{ item.info.genre }}
</label>
}
@if (item.info.rating) {
<label>
<div class="label">{{ 'XTREAM.RATING' | translate }}:</div>
{{ item.info.rating }}
</label>
}
@if (item.info.cast) {
<div>
<div class="label">{{ 'XTREAM.CAST' | translate }}:</div>
{{ item.info.cast }}
</div>
}
@if (item.info.director) {
<label>
<div class="label">{{ 'XTREAM.DIRECTOR' | translate }}:</div>
{{ item.info.director }}
</label>
}
@if (item.info.episode_run_time) {
<label>
<div class="label">
{{ 'XTREAM.EPISODE_RUN_TIME' | translate }}:
</div>
<label *ngIf="item.info.director">
<div class="label">{{ 'XTREAM.DIRECTOR' | translate }}:</div>
{{ item.info.director }}
</label>
<label *ngIf="item.info.episode_run_time">
<div class="label">
{{ 'XTREAM.EPISODE_RUN_TIME' | translate }}:
</div>
{{ item.info.episode_run_time }}
</label>
<div class="action-buttons">
<button
*ngIf="!isFavorite; else removeFromFavoritesButton"
mat-stroked-button
color="accent"
(click)="toggleFavorite()"
>
<mat-icon>star_outline</mat-icon>
{{ 'PORTALS.ADD_TO_FAVORITES' | translate }}
</button>
{{ item.info.episode_run_time }}
</label>
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against undefined item/info before dereferencing.

Multiple expressions use item.info.* without a parent guard. Wrap the image/details/actions in a single @if (item?.info) (or as info) block to prevent runtime errors when item or item.info is null.

Apply:

-  <div class="image">
-    @if (item.info.cover) {
+  @if (item?.info) {
+  <div class="image">
+    @if (item.info.cover) {
       <img [src]="item.info.cover" />
     }
   </div>
   <div class="details">
     <h2>{{ item.info.name }}</h2>
     ...
-    </div>
-  </div>
+    </div>
+  </div>
+  }

Also applies to: 82-84

🤖 Prompt for AI Agents
In src/app/xtream/serial-details/serial-details.component.html around lines 11
to 60 (and also apply the same fix at lines 82-84), the template dereferences
item.info properties directly which can throw if item or item.info is undefined;
wrap the sections that access item.info (the image, details and related actions)
in a single parent guard such as @if (item?.info) { ... } or use @if (item?.info
as info) { } and then replace inner references with info.* so all accesses are
safe and the runtime errors are prevented.

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