# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Plan Mode - When Claude Code is in Plan Mode and produces a final ``, it must also save that finalized plan as a Markdown file in the repo-root `.plans/` directory. - Save only finalized plans. Do not write interim exploration, question turns, or draft revisions to `.plans/`. - Use the filename pattern `YYYY-MM-DD-short-topic.md` such as `.plans/2026-03-12-channel-filtering.md`. - If the intended filename already exists, append a numeric suffix such as `-2`, `-3`, and so on. ## Documentation After Changes - After implementing a meaningful change, Claude Code must assess whether canonical repo docs need updates before considering the task complete. - Meaningful changes include new or changed user-visible behavior, architecture or data-flow changes, non-obvious maintenance workflows, new setup/debugging steps, and new subsystem contracts or boundaries. - Skip doc updates for trivial refactors with unchanged behavior, formatting-only edits, and isolated test-only changes. - Prefer updating an existing authoritative doc before creating a new one: 1. `README.md` for top-level developer or user workflows 2. `docs/architecture/` for architecture, ownership, and behavior contracts 3. the nearest module `README.md` for local usage or behavior - Repo docs are canonical even when they were originally drafted by an LLM. External wiki pages are derivative or synthesis content unless explicitly promoted back into the repo. - The external wiki sync is one-way by default: repo docs -> external wiki `_repo-context/`. - If repo docs changed and `IPTVNATOR_WIKI_VAULT` is configured, run `pnpm wiki:export --mode changed` after the doc update. - The wiki exporter only owns `_repo-context/` in the external vault. It must never overwrite repo docs or maintained wiki pages outside that folder. - Final task summaries should state whether docs were updated, which doc changed, and whether wiki export ran, was skipped, or failed. ## Project Overview IPTVnator is a cross-platform IPTV player application built with Angular and Electron, supporting M3U/M3U8 playlists, Xtream Codes API, and Stalker portals. **Dual Environment Support**: The application is designed to work in both Electron and as a Progressive Web App (PWA). The architecture uses a factory pattern to inject environment-specific services at runtime, ensuring the same codebase works in both contexts. ## Development Commands ### Building and Serving ```bash # Serve the Angular web app only (development mode, baseHref="/") pnpm run serve:frontend # or nx serve web # Serve with PWA configuration (optimized, baseHref="/") pnpm run serve:frontend:pwa # or nx serve web --configuration=pwa # Serve the Electron app (starts both frontend and backend) pnpm run serve:backend # or nx serve electron-backend # Build frontend for Electron (baseHref="./") pnpm run build:frontend # or nx build web # Build frontend for PWA deployment (baseHref="/") pnpm run build:frontend:pwa # or nx build web --configuration=pwa # Build backend (Electron) pnpm run build:backend # or nx build electron-backend # Package the app (creates distributable without installers) pnpm run package:app # or nx run electron-backend:package # Create installers/executables pnpm run make:app # or nx run electron-backend:make ``` ### Electron CDP Debugging - Start Electron in dev mode with: `nx serve electron-backend` - Package-script equivalent: `pnpm run serve:backend` - The workspace is configured to always launch Electron with: `--remote-debugging-port=9222` - Use CDP clients (Chrome DevTools Protocol tools) against: `127.0.0.1:9222` - When the task is Electron automation/debugging, use the `electron` skill For startup tracing or white-screen debugging: ```bash IPTVNATOR_TRACE_STARTUP=1 nx serve electron-backend ``` Useful narrower flags: - `IPTVNATOR_TRACE_IPC=1` traces renderer `window.electron.*` bridge calls - `IPTVNATOR_TRACE_DB=1` traces DB worker requests and DB progress events - `IPTVNATOR_TRACE_SQL=1` traces SQLite statements in both main and worker connections - `IPTVNATOR_TRACE_WINDOW=1` traces BrowserWindow navigation/load lifecycle - `IPTVNATOR_TRACE_RENDERER_CONSOLE=1` mirrors renderer console logs into the Electron terminal For GPU/compositor debugging: ```bash IPTVNATOR_DISABLE_HARDWARE_ACCELERATION=1 nx serve electron-backend ``` If the Nx daemon gets into a bad state before rerunning Electron: ```bash pnpm nx reset ``` Use global `agent-browser` (preferred): ```bash # Verify CDP targets agent-browser --cdp 9222 tab list # Switch to the app tab and inspect interactive elements agent-browser --cdp 9222 tab 1 agent-browser --cdp 9222 snapshot -i -c -d 4 # Capture debug artifacts agent-browser --cdp 9222 screenshot /tmp/iptvnator-cdp.png agent-browser --cdp 9222 trace start /tmp/iptvnator.trace.zip agent-browser --cdp 9222 wait 1500 agent-browser --cdp 9222 trace stop /tmp/iptvnator.trace.zip ``` If `agent-browser` is not in PATH, use: ```bash npx --yes agent-browser --cdp 9222 tab list ``` ### Testing ```bash # Run frontend tests pnpm run test:frontend # or nx test web # Run backend tests pnpm run test:backend # or nx test electron-backend # Run e2e tests (Playwright) nx e2e web-e2e # Run tests with coverage nx test web --configuration=ci ``` ### Linting ```bash # Lint frontend nx lint web # Lint backend nx lint electron-backend ``` ### Documentation And Wiki Export ```bash # Export a full canonical-doc snapshot into the external Obsidian vault pnpm wiki:export --mode full # Export only the latest repo context into the external Obsidian vault pnpm wiki:export --mode changed ``` ## Architecture ### Monorepo Structure (Nx Workspace) This is an Nx monorepo with the following structure: - **apps/web** - Angular application (frontend) - **apps/electron-backend** - Electron main process - **apps/web-e2e** - Playwright end-to-end tests - **libs/** - Shared libraries: - **m3u-state** - NgRx state management for playlists - **services** - Abstract DataService and implementations - **shared/interfaces** - TypeScript interfaces and types - **shared/m3u-utils** - M3U playlist utilities - **ui/components** - Reusable UI components - **ui/pipes** - Angular pipes - **ui/shared-portals** - Portal-related UI components ### Frontend Architecture (Angular) **State Management**: Uses NgRx for playlist state management: - Store configuration in `apps/web/src/app/app.config.ts` - Playlist state, actions, effects, and reducers in `libs/m3u-state/` - Entity adapter pattern for managing playlists collection - Router store integration for route-based state **XtreamStore Architecture** (Signal Store with Feature Composition): The Xtream Codes module uses NgRx Signal Store with a layered architecture: ``` ┌─────────────────────────────────────────────────────────────────┐ │ PRESENTATION LAYER │ │ Components use XtreamStore (facade) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ FACADE LAYER │ │ XtreamStore │ │ (Composes feature stores, unified API) │ └─────────────────────────────────────────────────────────────────┘ │ ┌────────────┬────────────┼────────────┬────────────┐ ▼ ▼ ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ withPortal│ │withContent │ │withSelection│ │ withSearch │ │ withPlayer │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ │ └───────────────────────────┼──────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ DATA SOURCE LAYER │ │ IXtreamDataSource │ │ ┌───────────────────┬───────────────────┐ │ │ ▼ ▼ │ │ ElectronDataSource PwaDataSource │ │ (DB-first + API) (API-only) │ └─────────────────────────────────────────────────────────────────┘ ``` File structure: ``` apps/web/src/app/xtream-electron/ ├── stores/ │ ├── features/ │ │ ├── with-portal.feature.ts # Playlist & portal status │ │ ├── with-content.feature.ts # Categories & streams │ │ ├── with-selection.feature.ts # UI selection & pagination │ │ ├── with-search.feature.ts # Search functionality │ │ ├── with-epg.feature.ts # EPG data │ │ ├── with-player.feature.ts # Stream URLs & player │ │ └── index.ts │ ├── xtream.store.ts # Facade composing all features │ └── index.ts ├── services/ │ ├── xtream-api.service.ts # Xtream Codes API calls │ ├── xtream-url.service.ts # Stream URL construction │ └── index.ts ├── data-sources/ │ ├── xtream-data-source.interface.ts # Abstract interface + types │ ├── electron-xtream-data-source.ts # DB-first implementation │ ├── pwa-xtream-data-source.ts # API-only implementation │ └── index.ts # Factory provider └── with-favorites.feature.ts # Favorites (existing) └── with-recent-items.ts # Recently viewed (existing) ``` Key patterns: - **Feature stores**: Each `with*.feature.ts` uses `signalStoreFeature()` for focused functionality - **Facade pattern**: `XtreamStore` composes all features, maintaining backward compatibility - **Data source abstraction**: `IXtreamDataSource` interface with environment-specific implementations - **Factory injection**: `provideXtreamDataSource()` selects Electron or PWA implementation at runtime Data strategies by environment: | Environment | Strategy | |-------------|----------| | **Electron** | DB-first: Check DB → fetch API if missing → cache to DB | | **PWA** | API-only: Always fetch from API, store in memory | **M3U Playlist Module Architecture**: The M3U playlist module handles traditional M3U/M3U8 playlists with support for 90,000+ channels. ``` ┌─────────────────────────────────────────────────────────────────────┐ │ VIDEO PLAYER PAGE │ │ apps/web/src/app/home/video-player/ │ ├─────────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌──────────────────────┐ ┌────────────────────┐ │ │ │ Sidebar │ │ Video Player │ │ EPG List │ │ │ │ │ │ (ArtPlayer/Video.js)│ │ (Right drawer) │ │ │ │ ┌─────────┐ │ │ │ │ │ │ │ │ │Channel │ │ │ │ │ │ │ │ │ │List │ │ │ │ │ │ │ │ │ │Container│ │ │ │ │ │ │ │ │ └─────────┘ │ │ │ │ │ │ │ └─────────────┘ └──────────────────────┘ └────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ``` **Radio Channel Layout** (when `channel.radio === 'true'`): ``` ┌─────────────────────────────────────────────────────────────────────┐ │ ┌─────────────┐ ┌────────────────────────────────────────────────┐│ │ │ Sidebar │ │ Blurred backdrop (station logo) ││ │ │ │ │ ┌──────────┐ ││ │ │ │ │ │ Artwork │ ← cinematic hero layout ││ │ │ │ │ └──────────┘ ││ │ │ │ │ Station Name ││ │ │ │ │ [LIVE] badge ││ │ │ │ │ ⏮ ▶/⏸ ⏭ ← transport controls ││ │ │ │ │ 🔊 ━━━━━━━━━ ← volume slider ││ │ │ │ │ (no EPG panel) ││ │ └─────────────┘ └────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────────────┘ ``` Key radio behavior: - Detection: `channel.radio === 'true'` (string from M3U `radio` attribute) - The audio player always renders inline — `shouldShowInlinePlayer` is bypassed for radio - EPG panel is conditionally hidden in the template when radio is active - Volume is shared with video player via `localStorage` key `'volume'` - Keyboard: ArrowUp/Down adjusts volume by 5%, M toggles mute - Component: `libs/ui/playback/src/lib/audio-player/audio-player.component.ts` Channel List Component Structure (parent coordinator pattern): ``` libs/ui/components/src/lib/channel-list-container/ ├── channel-list-container.component.ts # Parent - shared state coordinator ├── all-channels-tab/ # Virtual scroll + debounced search ├── groups-tab/ # Expansion panels + infinite scroll ├── favorites-tab/ # CDK drag-drop reordering └── channel-list-item/ # Individual channel display ``` Key patterns: - **EnrichedChannel**: Pre-computed EPG data attached to channels for performance - **Parent coordinator**: Manages shared signals (`channelEpgMap`, `progressTick`, `favoriteIds`) - **Virtual scrolling**: CDK virtual scroll for 90,000+ channel lists - **Infinite scroll**: IntersectionObserver in groups tab loads 50 items at a time - **Global progress tick**: Single 30s interval instead of per-item intervals State management via NgRx (`libs/m3u-state/`): - `PlaylistActions`: loadPlaylists, addPlaylist, removePlaylist, parsePlaylist - `ChannelActions`: setChannels, setActiveChannel, setAdjacentChannelAsActive - `EpgActions`: setActiveEpgProgram, setCurrentEpgProgram, setEpgAvailableFlag - `FavoritesActions`: updateFavorites, setFavorites See `docs/architecture/m3u-playlist-module.md` for complete documentation. **Routing**: Lazy-loaded routes in `apps/web/src/app/app.routes.ts` - Home/playlists overview: `/` - Video player: `/playlists/:id` or `/iptv` - Xtream Codes: `/xtreams/:id` (different routes for Electron vs web) - Stalker portal: `/portals/:id` - Settings: `/settings` **Service Architecture** (Factory Pattern): - Abstract `DataService` class in `libs/services/src/lib/data.service.ts` defines the contract - Two environment-specific implementations: - `ElectronService` (`apps/web/src/app/services/electron.service.ts`) - Uses IPC to communicate with Electron backend - `PwaService` (`apps/web/src/app/services/pwa.service.ts`) - Uses HTTP API and IndexedDB for standalone web version - Factory function `DataFactory()` in `apps/web/src/app/app.config.ts` determines which implementation to inject: ```typescript if (window.electron) { return new ElectronService(); } return new PwaService(); ``` **Data Storage (Environment-Specific)**: - **Electron**: libSQL/SQLite database via Drizzle ORM - Location: `~/.iptvnator/databases/iptvnator.db` - Full-featured relational database with foreign keys and indexes - Supports local file or remote Turso instance via env vars - **PWA (Web)**: IndexedDB via `ngx-indexed-db` - Browser-based NoSQL storage - Same schema structure but implemented in IndexedDB - Limited by browser storage quotas **TypeScript File Size Rule**: Keep TypeScript files under **300 lines**. Hard maximum is **350–400 lines**. - When creating new files, design them to stay within this limit from the start. - When adding a feature to an existing file that would push it past 350 lines, **refactor first**: extract helpers, sub-services, or feature modules before adding the new code. - When you notice a file already exceeds 350 lines, **proactively suggest a refactoring** (or perform it if the change is straightforward) — even if the immediate task is small. Typical split strategies: - Angular components: extract child components, move logic to a dedicated service or store feature - Signal store features: split into smaller `with*` feature functions in separate files - Services: split by responsibility (e.g. separate API, transformation, and state concerns) - Utility files: group by domain and export from a barrel `index.ts` This rule exists to keep the codebase navigable and reviewable. A 150-line file is always preferable to a 500-line file. --- **Angular Coding Standards**: This project uses modern Angular signal-based APIs and patterns. **ALWAYS** use the following: - **Component Queries**: Use `viewChild()`, `viewChildren()`, `contentChild()`, `contentChildren()` instead of `@ViewChild`, `@ViewChildren`, `@ContentChild`, `@ContentChildren` decorators ```typescript // ✅ Correct - Signal-based readonly menu = viewChild.required('menuRef'); readonly items = viewChildren('item'); // ❌ Incorrect - Old decorator syntax @ViewChild('menuRef') menu!: MatMenu; @ViewChildren('item') items!: QueryList; ``` **Important**: When using signals in templates with properties that expect non-signal values, unwrap the signal by calling it: ```html ``` - **Component Inputs/Outputs**: Use `input()` and `output()` functions instead of `@Input()` and `@Output()` decorators ```typescript // ✅ Correct - Signal-based readonly title = input.required(); readonly size = input(10); // with default value readonly clicked = output(); // ❌ Incorrect - Old decorator syntax @Input({ required: true }) title!: string; @Input() size = 10; @Output() clicked = new EventEmitter(); ``` - **Reactive State**: Use signal primitives for reactive state management ```typescript // ✅ Use signal(), computed(), effect(), linkedSignal() readonly count = signal(0); readonly doubled = computed(() => this.count() * 2); constructor() { effect(() => { console.log('Count changed:', this.count()); }); } ``` - **Host Bindings**: Use `@HostBinding()` and `@HostListener()` decorators (these don't have signal equivalents yet) ```typescript @HostBinding('class.active') get isActive() { return this.active(); } @HostListener('click') onClick() { /* ... */ } ``` - **Control Flow**: Use `@if`, `@for`, `@switch` instead of `*ngIf`, `*ngFor`, `*ngSwitch` ```typescript // ✅ Correct - Modern syntax @if (isLoggedIn()) {

Welcome!

} @for (item of items(); track item.id) {
  • {{ item.name }}
  • } // ❌ Incorrect - Old syntax

    Welcome!

  • {{ item.name }}
  • ``` ### Backend Architecture (Electron) **Main Entry**: `apps/electron-backend/src/main.ts` - Bootstraps Electron app and initializes database - Registers event handlers for IPC communication **Database**: - **ORM**: Drizzle ORM with libSQL (local SQLite file or remote Turso) - **Location**: `~/.iptvnator/databases/iptvnator.db` (avoids spaces in path) - **Schema** (`apps/electron-backend/src/app/database/schema.ts`): - `playlists` - Playlist metadata (M3U, Xtream, Stalker) - `categories` - Content categories (live, movies, series) - `content` - Streams/VOD/series items - `favorites` - User favorites - `recentlyViewed` - Watch history - **Connection**: `apps/electron-backend/src/app/database/connection.ts` - Auto-creates tables on init - Supports local file or remote via env vars (`LIBSQL_URL`, `LIBSQL_AUTH_TOKEN`) **IPC Communication**: - **Preload script**: `apps/electron-backend/src/app/api/main.preload.ts` - Exposes `window.electron` API via `contextBridge` - All IPC channels defined here (playlist operations, EPG, database CRUD, external players, etc.) - **Event handlers**: `apps/electron-backend/src/app/events/` - `database.events.ts` - Database CRUD operations - `playlist.events.ts` - Playlist import/update - `epg.events.ts` - EPG fetch and parsing (uses worker) - `xtream.events.ts` - Xtream Codes API - `stalker.events.ts` - Stalker portal API - `player.events.ts` - External player (MPV, VLC) integration - `settings.events.ts` - App settings - `electron.events.ts` - App version, etc. **Workers**: - EPG parsing runs in worker thread: `apps/electron-backend/src/app/workers/epg-parser.worker.ts` ### Key Features **Playlist Support**: - M3U/M3U8 files (local or URL) - Xtream Codes API (`username`, `password`, `serverUrl`) - Stalker portal (`macAddress`, `url`) **Video Players**: - Built-in HTML5 player with HLS.js or Video.js - External players: MPV, VLC (via IPC to Electron backend) **Radio Player**: - Dedicated audio player for channels with `radio="true"` M3U attribute - Cinematic layout: blurred station logo as backdrop, floating artwork card, transport controls - Always uses the built-in inline player — external player settings (MPV/VLC) are ignored for radio - EPG panel is hidden for radio channels (radio streams have no EPG data) - Volume synced with video player via shared `localStorage` key `'volume'` - Keyboard shortcuts: ArrowUp/ArrowDown (volume), M (mute) - Component: `libs/ui/playback/src/lib/audio-player/audio-player.component.ts` **EPG (Electronic Program Guide)**: - XMLTV format support - Background parsing in worker thread - Stored in database for quick lookup **Favorites and Recently Viewed**: - Per-playlist favorites and global favorites - Recently viewed tracks watch history **Internationalization**: - Uses `@ngx-translate` with 16 language files in `apps/web/src/assets/i18n/` ## Development Notes ### Environment Detection and Dual-Mode Architecture The app determines whether it's running in Electron or as a PWA by checking: ```typescript window.electron; // truthy in Electron, undefined in browser ``` **Why Dual Mode?** IPTVnator supports both Electron (desktop app) and PWA (web browser) to provide flexibility: - **Electron**: Full-featured desktop experience with local database, external player support (MPV/VLC), and native file system access - **PWA**: Lightweight web version that runs in any browser without installation **Environment-Specific Behavior**: - `app.config.ts` - `DataFactory()` selects DataService implementation based on environment - `app.routes.ts` - Different routes for Xtream portals (Electron uses Tauri-based routes, PWA uses standard routes) - Storage layer switches automatically: - Electron → libSQL/Drizzle ORM → `~/.iptvnator/databases/iptvnator.db` - PWA → IndexedDB → Browser storage - External player support (MPV/VLC) only available in Electron - File system operations only available in Electron (uploading playlists from disk) **Base Href Configuration**: The app uses different base href values depending on the build target: - **Development & PWA**: `baseHref="/"` (from `index.html`) - Used by: `pnpm run serve:frontend`, `pnpm run build:frontend:pwa` - For web servers with proper routing - **Electron Production**: `baseHref="./"` (overridden in build config) - Used by: `pnpm run build:backend`, `pnpm run make:app` - Required for `file://` protocol in Electron Build configurations in `apps/web/project.json`: - `production`: Electron build with `baseHref="./"` - `pwa`: Web deployment with `baseHref="/"` - `development`: Dev mode with `baseHref="/"` from index.html **Factory Pattern Implementation**: The factory pattern ensures a single codebase works in both environments without conditional checks scattered throughout the application. All environment-specific logic is encapsulated in the service implementations. ### Testing Strategy - **Unit tests**: Jest with `jest-preset-angular` and `ng-mocks` - **E2E tests**: Playwright testing the web app - Backend tests use standard Jest ### Nx Commands Use `nx` CLI for better performance: ```bash nx run : # Example: nx run web:build # Example: nx run electron-backend:serve ``` To run multiple projects: ```bash nx run-many --target=test --all ``` ### Electron Build Process The Electron backend depends on the web app being built first: - `electron-backend:build` depends on `web:build` - Output goes to `dist/apps/electron-backend` (backend) and `dist/apps/web` (frontend) - Packaging combines both into distributable ### Database Migrations No formal migration system yet. Schema changes are applied via raw SQL in `connection.ts` `createTables()` function using `CREATE TABLE IF NOT EXISTS`. ### Common Patterns **IPC Communication**: 1. Define handler in appropriate events file (e.g., `database.events.ts`) 2. Register with `ipcMain.handle()` in the event bootstrap function 3. Expose in preload script via `contextBridge.exposeInMainWorld()` 4. Call from Angular via `window.electron.()` **Adding New Playlist Source**: 1. Add type to `libs/shared/interfaces/src/lib/playlist.interface.ts` 2. Create event handler in `apps/electron-backend/src/app/events/` 3. Add UI in `apps/web/src/app/home/` 4. Update database schema if needed **State Management**: - Use NgRx for global application state (playlists) - Use component stores (`@ngrx/component-store`) for feature-specific state - Use NgRx signals for reactive data streams ## General Guidelines for working with Nx - For navigating/exploring the workspace, invoke the `nx-workspace` skill first - it has patterns for querying projects, targets, and dependencies - When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly - Prefix nx commands with the workspace's package manager (e.g., `pnpm nx build`, `npm exec nx test`) - avoids using globally installed CLI - You have access to the Nx MCP server and its tools, use them to help the user - For Nx plugin best practices, check `node_modules/@nx//PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable. - NEVER guess CLI flags - always check nx_docs or `--help` first when unsure ## Scaffolding & Generators - For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the `nx-generate` skill FIRST before exploring or calling MCP tools ## When to use nx_docs - USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases - DON'T USE for: basic generator syntax (`nx g @nx/react:app`), standard commands, things you already know - The `nx-generate` skill handles generator discovery internally - don't call nx_docs just to look up generator syntax