docs(agents): compact root guidance and preserve task-specific knowledge (#1645)

* docs(agents): compact root guidance and preserve task-specific knowledge

* fix(agents): parse guidance navigation with Markdown tokens

* fix(agents): validate generic literal repository paths

* fix(agents): distinguish code symbols and shortcut images

* fix(agents): recognize SCSS filename literals

* fix(agents): handle fenced imports and encoded paths

* fix(agents): parse prose and rendered HTML anchors

* fix(agents): validate rendered HTML navigation

* fix(agents): use GitHub-compatible heading slugs

* fix(agents): require standalone top-level Claude import

* fix(agents): exclude HTML-contained guidance imports

* fix(agents): handle image fragments and quoted imports

* fix(agents): validate visible HTML and image source sets

* fix(agents): recognize package scopes and route source work

* fix(agents): parse JSONC and constrain package exemptions

* fix(agents): decode link entities and allow package subpaths

* fix(agents): route source work and check extensionless files

* fix(agents): support package versions and source fragments

* fix(agents): accept qualified package prose

* fix(agents): retain rendered context for Markdown references

* fix(agents): validate visible headings and spaced paths

* fix(agents): validate media and hyphenated literal paths

* fix(agents): decode full HTML entities and media assets

* fix(agents): recognize possessive package mentions

* fix(agents): validate extensionless imports and version comparators

* fix(agents): retain visible backticks and explicit path punctuation

* fix(agents): validate image-map navigation targets

* fix(agents): count all Markdown line endings in budgets

* fix(agents): delimit package prose at Unicode punctuation

* fix(agents): normalize punctuation for extensionless imports

* fix(agents): preserve filenames across prose punctuation

* fix(agents): validate iframe document references

* fix(agents): inspect document suffix before URL fragments

* fix(agents): unify Markdown suffix and encoded import guards

* fix(agents): handle wildcard versions and alternate documents

* fix(agents): validate document formats and trim HTML URLs

* fix(agents): cover document families and guidance basenames

* fix(agents): require files for media references

* fix(agents): preserve block boundaries and validate embeds

* fix(agents): normalize internal HTML URL whitespace

* fix(agents): reject empty media and ignore URL at-signs

* fix(agents): validate srcdoc references and empty srcset

* fix(agents): honor HTML bases and preserve adjacent imports

* fix(agents): convert base file URLs to native paths

* fix(agents): preserve imports after bare URL punctuation

* fix(agents): exclude opaque URI prose from import scans

* fix(agents): keep import tokens outside URI scheme matches

* fix(agents): restrict opaque URI exemptions to parsed links

* fix(agents): handle opening prose delimiters

* fix(agents): scan nested imports and share document suffixes

* fix(agents): reject pathless media and direct file URLs

* fix(agents): reject file bases and preserve quoted URL boundaries

* fix(agents): distinguish URL quotes and cover guidance variants

* fix(agents): validate SVG images and conventional guides

* fix(agents): handle declared package names handles and SVG use

* fix(agents): normalize closing punctuation on federated handles

* fix(agents): normalize Unicode punctuation on handles

* fix(agents): normalize possessive federated handles

* fix(agents): separate parenthetical prose from handles

* fix(agents): exclude www autolinks from import scanning

* ci: allow manual CodeQL validation of PR branches

* fix(agents): reject nonportable Windows drive links
This commit is contained in:
4gray authored and GitHub committed 2026-09-21 18:07:14 +02:00
1 parent b02d79805b
commit faad8fd8fd
24 files changed
+4178 -3256

No files matched your search

+3
View File
@@ -147,6 +147,9 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Validate agent guidance
run: pnpm run agents:validate
- name: Validate Nx dependency version policy
run: pnpm run deps:nx:validate
+1
View File
@@ -6,6 +6,7 @@
name: "CodeQL"
on:
workflow_dispatch:
push:
branches: [master]
pull_request:
@@ -0,0 +1,67 @@
# Agent guidance reorganization — issue #1643
Approved implementation plan, 2026-09-20.
## Outcome
One source of common instructions: AGENTS.md (at most 200 lines / 16 KiB).
CLAUDE.md imports @AGENTS.md and contains only Claude-specific guidance
(at most 30 lines / 2 KiB). Do not increase Codex loading limits. No runtime
or public API changes.
## Knowledge preservation
Inventory both original files at the starting commit in
`docs/maintenance/agent-guidance-migration.md`. Record source section and line
ranges, destination document and heading, and whether each contract was moved,
merged with an existing equivalent, or corrected with evidence. Split long
player sections into individual contracts. Preserve exceptions, commands,
rationale and platform constraints. Do not create a required monolithic archive.
## Destinations
Use existing authoritative docs first: Nx boundaries for structure/dependencies;
validation-map for tests/lint; release-pipeline and release skills for releases;
sqlite-db-worker and the database README for IPC/migrations; m3u-playlist-module
for M3U/XMLTV/startup/source health; Xtream/Stalker compatibility docs for portals;
player-controls-contract for web controls/radio/sleep; embedded-mpv-native for
native runtime/packaging; UI guidelines, detail navigation and remote control for
navigation; PWA/host connectivity/security docs for networking; existing download,
TMDB, multi-source, workspace and backup docs for their domains; website README
for website policy.
Create docs/development/agent-workflow.md for documentation/skill maintenance and
Angular conventions, and docs/development/electron-debugging.md for CDP/tracing.
Add a developer navigation link in README.md.
## Root guidance and navigation
Retain project purpose, essential commands, .nvmrc/frozen install/Nx bootstrap,
scoped imports and boundaries, migration safety, credential redaction, regression
coverage, release-note/doc requirements, protected Markdown formatting and plan
storage. Preserve the Nx-managed block/markers, conditional on available tools.
Replace mandatory root-file updates with updates to each subsystem's canonical
doc. Root instructions hold only universal rules and a compact topic routing table.
Create docs/maintenance/agent-context-map.md with topics, code paths, docs and
skills. Read affected contracts only; cross-domain work reads each relevant one.
Update existing skills rather than proliferating copies; preserve byte-identical
release mirrors. No mass nested instructions in this change.
## Tooling
Extend repository-skills (no new Nx project) with agents:validate and node:test
coverage. Check UTF-8 bytes/line budgets, one standalone @AGENTS.md import in
CLAUDE.md and no other root imports, local navigation/map/migration links and
anchors, and literal repository paths without treating globs/commands as paths.
Add an unconditional CI validation step and correct Nx test inputs/lint commands.
## Acceptance
Tests cover exact/over budgets, UTF-8, LF/CRLF, missing/duplicate/extra imports,
missing local files and anchors. Run frozen install, Nx discovery, repository-skills
test/lint, agents:validate, skills:validate, release:notes:validate, git diff --check
and workflow validation. Audit every source block to a destination, with no
unresolved or lost unique contract. Walk navigation for XMLTV, Xtream, MPV,
migrations and releases. App unit/E2E is unnecessary (no runtime changes); no
release note for docs/tooling validation. Do not run whole-file Prettier on docs,
AGENTS.md or CLAUDE.md.
+115 -1219
View File
File diff suppressed because it is too large. Load diff
+8 -2032
View File
File diff suppressed because it is too large. Load diff
+8
View File
@@ -389,3 +389,11 @@ The name **"IPTVnator"** and the IPTVnator logo are unregistered trademarks of t
[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
## Developer and agent documentation
Start with the [task context map](docs/maintenance/agent-context-map.md) to find
the authoritative contract and validation for your area. Common agent rules are
in [AGENTS.md](AGENTS.md); Claude Code imports that same file. Development and
documentation-maintenance conventions live in the
[agent workflow](docs/development/agent-workflow.md).
+41
View File
@@ -1906,3 +1906,44 @@ deletes with follow-up cleanup warnings. The UI does not resurrect a deleted
row after a cleanup failure. Downloaded files are not removed. The dialog's
confirmation covers deletion of the source and associated favorites, history
and playback positions; no deletion happens on merely opening the dialog.
## Opening playlists from the operating system
Electron only: a `.m3u`/`.m3u8` path passed
on the command line, opened through a file association, or delivered by macOS'
`open-file` event is normalized to an absolute path in the main process
(`apps/electron-backend/src/app/services/playlist-open-request.ts`) and queued there. The renderer
(`apps/web/src/app/services/playlist-open-request.service.ts`) subscribes to the
`OPEN_FILE` push **before** calling `announcePlaylistOpenListener`, which is
what makes the main process flush. `OPEN_FILE` is the only way out of the
queue, and a request stays there until the renderer confirms receipt via
`acknowledgePlaylistOpenRequest` — `webContents.send()` returns before the
listener runs, and a reload or dead render process keeps the `WebContents`
alive, so a successful push is not proof of delivery. Anything unacknowledged
is replayed to the next renderer that announces itself. The renderer
imports them on a single promise chain so a burst arrives in a deterministic
order. `addPlaylist$` in `libs/m3u-state` uses `concatMap` (not `switchMap`)
for the same reason: each action carries a different playlist, so a newer add
must never cancel an older one's write, EPG fetch and navigation. The import
itself reuses the normal file path
(`updatePlaylistFromFilePath` → `PlaylistActions.addPlaylist`), so persistence,
playlist-scoped EPG, and the navigation to the new playlist all behave exactly
like a dialog import.
The OS-level registration that makes those paths reachable is
`fileAssociations` in `electron-builder.json` — one entry per extension, each
with its own `mimeType`. Electron Builder derives all three platform
registrations from it: macOS `CFBundleDocumentTypes` (which is what makes
`open-file` fire from Finder), the NSIS registry entries, and, on Linux, the
desktop entry's `MimeType` plus `/usr/share/mime/packages/iptvnator.xml` for
deb/rpm/pacman. Two traps: it assigns the derived `MimeType` _after_ spreading
`linux.desktop.entry`, so declaring `MimeType` there is silently overwritten and
must not be used; and it appends `%U` to `Exec`, so Linux file managers hand
over percent-encoded `file://` URIs rather than paths —
`createPlaylistOpenRequest` decodes them before the extension check. `%U` is
also the _plural_ exec code, so a multi-file selection arrives as one launch
with one argument per file; `extractPlaylistOpenRequestsFromArgv` returns all
of them and `enqueueAll` queues the batch, because stopping at the first match
would silently drop the rest of the selection. Adding an exec code to
`linux.executableArgs` would suppress the `%U` but also pass that code to the
app as a real argument, so it is not an option.
@@ -130,6 +130,22 @@ patched prefilter/matcher wiring and version pin, stress-tests the false-positiv
chunk shape, and preserves ordinary and comment-bearing asset and worker
`new URL(..., import.meta.url)` matches.
## Electron Builder signing patch
`app-builder-lib` 26.15.7 is patched in
`patches/app-builder-lib@26.15.7.patch` with the upstream backport
electron-userland/electron-builder#10172. For macOS signing,
`security set-key-partition-list -k` must receive the temporary keychain's own
password rather than the `.p12` import password. macOS runner images since
`macos-26-arm64` 20260831 verify that password; the old argument caused
`SecKeychainUnlock: The user name or passphrase you entered is not correct`.
Keep the patch until electron-builder resolves a fixed app-builder-lib (26.16.1+).
Run `pnpm run deps:electron-builder:test` after related dependency updates; it
also rejects a mismatch between the patched and installed version.
Native addon builds additionally require the root `node-gyp` devDependency;
see [runtime staging](embedded-mpv-native.md#runtime-staging) before removing it.
## Placement Decision
- `apps/` owns runtime applications, development servers, E2E applications,
@@ -7,6 +7,9 @@ Embedded MPV rendering and native-view bounds behavior remain documented in
## Current status
The shared-controls preference checkbox is visible only when HTML5, Video.js
or ArtPlayer is selected in Settings → Playback.
The shared-controls foundation supports four runtime consumers and includes:
- the `PlayerController` contract, default state, and capability presets;
@@ -1533,3 +1536,36 @@ replacement, track-list lifecycle and stable IDs, caption preference and
explicit-off behavior, MPEG-TS live/VOD handling and duration projection,
volume preservation/authority, stale ArtPlayer `customType` callbacks, and
collaborator teardown. Persistent/background player ownership has not landed.
## Radio and display sleep
### Radio audio player
M3U `radio="true"` entries use `AudioPlayerComponent` under
`libs/ui/playback/src/lib/audio-player/`. The player always renders inline and
uses HTML5 `<audio>` regardless of the configured video player. Radio bypasses
`shouldShowInlinePlayer`'s external-player gate and hides the EPG ribbon and panel
toggle. The station artwork, blurred logo background and glass controls form the
radio layout; title/group scrolling is CSS-only. It supports play/pause, mute,
and volume, including the volume keys in 5% steps. Volume shares the video
players' `volume` localStorage key. The template, SCSS and TypeScript component
live together; routing/integration stays in the M3U player template.
### Display sleep during playback
`PlaybackKeepAwakeService` in the web app watches `<video>` using document-level
capture listeners because media events do not bubble. Release listeners also
attach to the tracked element: Chromium's pause after DOM removal never reaches
the document. A playing video holds a display-sleep lock only while the document
is visible or that video is in picture-in-picture, which survives minimization.
Electron uses main-process `powerSaveBlocker` through
`window.electron.setPlaybackKeepAwake`. The renderer vote clears on reload,
main-frame non-same-document navigation, crash (`render-process-gone`) or
destruction; Angular navigation does not itself clear it. The PWA uses Screen
Wake Lock. Browser auto-release clears its sentinel; the next media, visibility
or PiP synchronization can request another lock. If state changes during a
pending request, rejection triggers one queued re-evaluation rather than losing
that update. Radio `<audio>` deliberately never blocks display sleep.
Embedded MPV owns a separate blocker in `EmbeddedMpvNativeService`, and external
MPV/VLC inhibit their own screensaver.
+22
View File
@@ -277,3 +277,25 @@ For manual Docker smoke testing, run the Xtream and Stalker mock servers plus a
small M3U fixture, then verify in the browser that M3U, Xtream, and Stalker can
add sources, play an item, toggle favorites, populate global favorites,
populate recently viewed, and appear on the dashboard rails.
## Service factory and build bases
`DataService` in `libs/services/src/lib/data.service.ts` is the renderer service
contract. `DataFactory()` in `apps/web/src/app/app.config.ts` chooses
`ElectronService` for the desktop bridge and `PwaService` for browser HTTP and
IndexedDB work. This environment-level selection is not evidence for an
individual capability: Xtream data-source selection requires its complete
SQLite bridge, and feature visibility follows `RuntimeCapabilitiesService`.
The same workspace route tree is used in both runtimes.
Desktop relational data uses the canonical schema/connection in
`libs/shared/database`; the SQLite path is `~/.iptvnator/databases/iptvnator.db`.
Desktop Chromium settings/storage still exist alongside SQLite. PWA data uses
browser IndexedDB (with browser quotas); its structure is not the SQL schema.
Browser-selected file uploads remain possible even though native filesystem
access is Electron-only.
Web development and PWA use `baseHref="/"`; packaged Electron frontend uses
`baseHref="./"` so file URLs resolve. In `apps/web/project.json`, `production`
is the Electron frontend build, `pwa` is the web build, and `development` uses
the index base. Do not ship the Electron frontend build as a PWA deployment.
+38
View File
@@ -11,6 +11,16 @@ pnpm nx show projects --withTarget lint
pnpm nx show projects --withTarget e2e
```
## Manual CI Runs
When a PR event does not start checks for the current head, dispatch CI and E2E
with `gh workflow run ci.yml --ref <branch>` and
`gh workflow run e2e-tests.yaml --ref <branch>`. CodeQL also supports
`gh workflow run codeql-analysis.yml --ref <branch>`; this analyzes the selected
branch commit instead of the PR merge commit. Verify each run's head SHA before
using its result as evidence. Docker validation can use
`gh workflow run docker.yml --ref <branch> -f push=false`.
## Unit And Type Checks
| Area | Command |
@@ -152,3 +162,31 @@ are gated by:
```bash
IPTVNATOR_TRACE_PLAYER=1 pnpm run serve:backend
```
## Test impact and completion
Before finishing a feature, bug fix, data-flow or UI workflow change, identify
the affected projects and choose unit, integration, E2E, build, lint and manual
checks. Bug fixes normally include regression coverage that fails before the
fix. If automation is impractical, explain why and report the strongest manual
validation. Update fixtures, mocks, routes and E2E flows when behavior changes.
Prefer extending the closest existing suite to introducing a parallel suite.
Run targeted unit checks first, then affected E2E for workflows, routing,
persistence, playback, portals, settings or import flows. Electron IPC, SQLite,
packaged runtime, external players, native files and Electron-only routes require
Electron E2E where available, otherwise CDP/manual verification using the
[debugging guide](../development/electron-debugging.md). Prefer atomized E2E
targets before broad suites. Final reports name tests changed, commands/results,
and skipped validation with reasons. Docs-only changes need Markdown validation,
not app unit/E2E. Tooling validation still requires its own focused tests.
## Agent guidance checks
`pnpm run agents:validate` checks root instruction budgets/imports and guidance
navigation links/anchors. `pnpm run skills:validate` checks skill frontmatter,
length, paths and release mirrors. Both tooling suites run through
`pnpm nx test repository-skills`; syntax checks use
`pnpm nx lint repository-skills`. The CI guidance check runs regardless of the
Nx affected set. Semantic preservation of moved contracts is a review task;
link validation alone cannot prove it.
+10
View File
@@ -327,3 +327,13 @@ Intentionally out of scope:
2. Freeform widget grid with collision management.
3. External data rails such as RSS, sports, or news adapters.
4. Per-user A/B variants of rail ordering.
## Source subscription expiry
Source cards show a passive subscription-expiry chip: amber within seven days,
error-toned once expired. Account details stay behind the Account info menu.
`DashboardSourceExpiryService` in `libs/workspace/dashboard/data-access` reads
Xtream expiry from cached `PortalStatusService.checkPortalStatusDetails()`
(`exp_date`). Stalker uses the persisted `stalkerAccountInfo` snapshot from the
playlist payload, not the metadata row; each source therefore needs one memoized
full-playlist read. The chip is not a separate account-refresh request.
@@ -373,3 +373,31 @@ It reuses the canonical timeshift resolver and original timestamps, preserves
playback headers and does not change playback. See
[Download Manager](download-manager.md#xtream-archive-downloads) for identity,
restart, expiry and transport-completion limits.
## Store composition and catalog windowing
`XtreamStore` is the public facade built with `signalStore()`, composing
`signalStoreFeature()` features for portal, content, selection, search, EPG,
player, favorites, recent and playback positions. Most features live under
`libs/portal/xtream/data-access/src/lib/stores/features/`; favorites and recent
items live directly under the data-access library’s `src/lib/`.
Routed components consume that facade; features delegate persistence/networking
to `IXtreamDataSource`, selected through `provideXtreamDataSource()`. Complete
SQLite capability uses database-first cache reads and API fill; the PWA source
uses API requests and session memory. This does not move screen orchestration
into shared utility projects.
Catalog lazy loading: catalog grids scroll infinitely instead of paging.
`withSelection` keeps a `visibleCount` render window over the in-memory
catalog plus bounded per-selection scroll snapshots for detail/tab
round-trips; the shared `InfiniteScrollDirective`
(`libs/portal/shared/ui`) measures container overflow to auto-fill tall
viewports (terminating on lack of container growth, not on a load count)
and fires `loadMore` near the bottom. The search layout routes its results
container through the same directive (`nearEnd*` inputs). Stalker feeds the
same contract from server-paged appends: portal pages accumulate into one
deduplicated list, `hasMoreContent` derives from accumulated length vs
`total_items`, a failed append keeps loaded pages and offers a tail retry,
and the facade maps page 0 to the skeleton and later pages to the tail
spinner. These catalog/search surfaces use incremental loading instead of
page buttons.
+223
View File
@@ -0,0 +1,223 @@
# Agent development workflow
Common startup rules live in [AGENTS.md](../../AGENTS.md). This document holds
procedures and conventions to read when they apply, not extra startup imports.
## Maintaining canonical knowledge
After meaningful changes, assess documentation before declaring completion.
Meaningful changes include user-visible behavior, architecture/data flow,
maintenance/setup/debugging workflows, and subsystem contracts. Formatting,
behavior-preserving refactors and isolated test changes need no doc update.
Prefer the existing authoritative architecture doc or nearest module README;
README.md owns top-level user/developer entry points. Update stale routes,
paths, commands and contracts you encounter, or explicitly flag unresolved
claims in the final summary. Repo docs remain canonical regardless of authorship.
Keep AGENTS.md limited to repository-wide constraints and task routing. Do not
add feature histories, method lists, schema inventories or troubleshooting
procedures to it. CLAUDE.md imports AGENTS.md; do not mirror common prose by hand.
Use [the context map](../maintenance/agent-context-map.md) for the owning document
and relevant skill. Read multiple contracts for cross-domain work. Add a new
canonical document only when no existing owner fits; link it from the map.
When relocating knowledge, compare both sources and the destination, retain
unique exceptions and rationale, and record corrections with code evidence.
The [2026-09 migration ledger](../maintenance/agent-guidance-migration.md) records
the initial move; it is an audit artifact, not required reading for feature work.
Later normal edits maintain the canonical docs, not duplicate historical prose.
Run `pnpm run agents:validate` after guidance changes. It checks line/byte budgets,
root imports and local navigation links/anchors, including migration destinations.
Line budgets count LF, CRLF and standalone CR endings consistently.
Markdown navigation is parsed with the already-declared `marked` dependency;
undefined explicit references (including shortcut images) are errors, and code examples are excluded.
Backticked concrete paths in root guidance and the context map are checked from
the repository root, including unknown top-level directories and filenames.
Write generic filenames as prose; commands, templates, globs, URLs, package
aliases and dotted code symbols are excluded. Bare dotted names with conventional
file suffixes (such as .md, .json or .ts) are treated as filenames. Document formats
share the suffix set used by package-import guards, including PDF and AsciiDoc. Use a `./`
prefix or Markdown link for other ambiguous filenames that resemble code symbols.
Explicit relative literals denote paths, including spaces, filesystem punctuation and hyphenated words.
Put executable command examples in fenced code when their syntax also looks like a path.
Multi-part dotfiles are path candidates too.
Conventional extensionless filenames such as Dockerfile, Makefile and LICENSE
are also path candidates; use an explicit `./` prefix for other extensionless files.
Link paths and fragments are decoded separately so encoded filename delimiters
stay in the filename. Fenced and indented examples
do not count as root guidance imports or satisfy the required Claude import.
The required Claude import must be an unformatted standalone line in a top-level
paragraph; headings, quotes and list items do not satisfy it.
The parsed HTML tree also verifies that this paragraph is outside HTML containers,
including templates split across Markdown tokens. Generated HTML is inspected
in memory only; it is never executed or emitted.
Heading anchors decode HTML character references in text and use `github-slugger`
for GitHub-compatible character filtering and duplicate suffixes.
Only headings present outside inert HTML containers contribute slugs or duplicate counters.
Explicit HTML anchors use `parse5`, excluding comments, scripts, styles and template contents.
Rendered HTML anchor and image-map area hrefs and image sources use the same local-reference checks
as Markdown links, including decoded attributes and fragment validation.
URL attributes remove ASCII tabs/newlines throughout and discard surrounding
ASCII control/space characters before resolution.
Iframe/embed sources and object data attributes are document references and retain Markdown-target anchor checks.
Inline iframe srcdoc documents are traversed too, with their own fragment anchors.
The first active HTML base href sets reference resolution, including nested srcdoc bases.
Resolved file URLs use native filesystem conversion, including Windows drive paths.
Explicit srcset attributes must contain at least one parsed candidate.
Image and media references require a nonempty path that resolves to a file, not a directory.
Direct file URLs, Windows drive paths and HTML bases using either form are rejected; use portable repository-relative paths.
Image references check file existence without interpreting image fragments as
Markdown headings; document links keep anchor checks even when sharing a target.
SVG image/use hrefs (including xlink), HTML image-input, video, audio, source and track `src` assets and video posters use the same
existence checks as images. Entity decoding uses full HTML text/attribute rules,
including references whose semicolon may be omitted.
Inline guidance imports are rejected after punctuation as well as whitespace.
At-signs inside external URIs (including www autolinks and explicit opaque autolinks such as mailto) are excluded
per HTML text node, preserving adjacent imports. A colon directly before an import
does not make that import a URI. Opaque schemes are excluded only in parsed links
whose visible text equals their URI, so colon-labeled prose remains checked.
A closing bracket or matching enclosing quote followed by punctuation and an at-sign terminates a bare URL exclusion.
Extensionless inline candidates are also imports when they resolve to repository files,
checking the full filename before prefixes at ASCII/Unicode prose separators,
including opening parentheses, brackets and braces. Each at-sign candidate is
checked independently, including imports nested next to a package mention.
An at-sign inside a word (for example, foo@INSTRUCTIONS or an email address)
is not an import boundary, including within parenthetical prose.
Declared scoped dependencies, scope wildcards and matching TypeScript path aliases
are recognized as package/alias mentions. Traversal and document-file imports are
rejected before those exemptions, including document paths with fragments or queries.
All recognized Markdown extensions share the document-import guard; reStructuredText
and AsciiDoc, PDF, Word, OpenDocument, RTF, Org and TeX documents are also excluded
from package exemptions. Recognized extensionless guidance names (including AGENTS,
CLAUDE, INSTRUCTIONS, README, CONTRIBUTING and SECURITY, case-insensitively) are excluded in package subpaths too. URL-encoded
paths do not receive package exemptions. TypeScript configuration is parsed as JSONC.
Declared packages also permit safe subpaths; exact aliases stay exact.
Federated handles in the @user@host form are prose, not imports; trailing closing
ASCII/Unicode punctuation and possessive apostrophe-s suffixes are ignored. Opening
delimiters separate adjacent prose; nested imports remain checked. Extra at-signs do not qualify for that exemption.
Exact declared packages remain exempt after version normalization.
Declared package mentions may include a version (including semver comparators and wildcard ranges) or dist-tag qualifier.
Qualifier handling includes unscoped names; terminal sentence punctuation is
removed before matching a declared package, as are straight/curly apostrophe possessives.
Unicode punctuation and ASCII opening delimiters, commas, semicolons, colons, question/exclamation marks
separate package mentions from adjacent prose.
Markdown destinations decode HTML entities before URI parsing, matching rendered links.
Heading-anchor lookup is limited to Markdown targets. Source-file line fragments,
PDF page fragments and other non-Markdown fragments retain file-existence checks.
The import scan separates HTML block/table elements and includes visible text and literal backticks; it excludes parsed code nodes and non-rendered containers.
Navigation uses the parsed rendered tree too. Temporary in-memory markers retain
definition, unresolved-reference and literal-path metadata, so Markdown inside
inert templates is excluded consistently with raw HTML navigation.
Image source sets use `parse-srcset` to check each candidate URL. Root-relative
literals never suppress source-relative definition checks.
The Nx test hash includes `marked`, `parse5`, `github-slugger`, `parse-srcset` and `typescript`
so dependency changes invalidate parser coverage.
It cannot prove semantic equivalence; review changed contracts as well.
## Protected Markdown edits
Never run whole-file `prettier --write` on AGENTS.md, CLAUDE.md or docs/**.
Upstream formatting is not uniformly Prettier-clean: whole-file writes can
corrupt nested list indentation or change a literal continuation into a bullet.
Format only intended new lines. If accidental formatting occurred, reconstruct
from the pre-edit version and reapply only intended changes; preserve unrelated
user edits. Use the merge-base version only if it actually represents that
pre-edit state. Review the diff rather than blindly restoring an older branch.
## Plans and completion reports
Save only finalized plans in `.plans/YYYY-MM-DD-short-topic.md`; use numeric
suffixes for collisions. Do not save drafts or questions there. If an active
mode forbids writes, save the approved plan on entering execution.
Completion reports list changed docs, tests added/updated, commands and results,
skipped validation with reasons, and release-note status. A docs-only task needs
Markdown/link validation, not app unit/E2E tests. Tooling changes need their own tests.
## Repository skills
Repository skills live under `.codex/skills/`. Descriptions are trigger-only,
begin with `Use when`, and each skill is at most 500 words. Frontmatter owns
trigger descriptions; avoid copying them into navigation prose. Skills provide
workflow and links to authoritative contracts, not a second contract copy.
`release-cut` and `release-notes` have byte-identical `.claude/skills/` mirrors.
Run `pnpm run skills:validate` after changing a committed skill or a literal
path it documents. New guidance tooling belongs to the existing
`repository-skills` Nx project; no new project is needed for another validator.
## Angular conventions
Use signal-based queries (`viewChild`, `viewChildren`, `contentChild`,
`contentChildren`) and inputs/outputs (`input`, `output`). For required queries,
use `viewChild.required`. Unwrap signals when passing values in templates:
```typescript
readonly menu = viewChild.required<MatMenu>('menuRef');
readonly title = input.required<string>();
readonly size = input<number>(10);
readonly clicked = output<void>();
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);
```
```html
<button [matMenuTriggerFor]="menu()">Open Menu</button>
```
Use `signal`, `computed`, `effect` and `linkedSignal` for reactive state. Existing
host bindings/listeners use `@HostBinding` and `@HostListener`; this relocation
does not change that convention. Prefer `@if`, `@for` (with a stable `track`),
and `@switch` over the legacy structural directives. A signal is a function;
passing `menu` instead of `menu()` to Material supplies the wrong value.
## Adding behavior across layers
For IPC, define the handler in the appropriate Electron events module,
register it in the event bootstrap, expose a typed preload method, and consume
it through the renderer service. Keep channel contracts typed in
`ElectronBridgeApi`; use the database worker contract for heavy database work.
See [Electron security](../architecture/electron-security.md) and
[DB worker ownership](../architecture/sqlite-db-worker.md).
For a playlist source, extend the shared playlist type, add the backend event
handler, add the import UI under `libs/playlist/import/feature`, and update
state actions/effects. Preserve runtime capabilities and migration behavior.
NgRx owns M3U global state; portal/feature state uses composed NgRx Signal Store;
component-local state uses Angular signals. Avoid expanding large classes:
extract components/services or `with*` store features before exceeding limits;
shared types belong in their own contract modules. The hard production limit
is 400 (not a variable 350–400); target under 300. See
[Nx file-size policy](../architecture/nx-workspace-boundaries.md#typescript-file-size).
## Build and serve commands
Use local `pnpm nx` for underlying project targets. Package scripts are the
supported entry points for composed tasks:
| Task | Command |
| --- | --- |
| Web development | `pnpm run serve:frontend` |
| PWA development | `pnpm nx serve web --configuration=pwa` |
| Electron development | `pnpm run serve:backend` |
| Electron frontend | `pnpm run build:frontend` |
| PWA frontend | `pnpm run build:frontend:pwa` |
| Electron backend | `pnpm run build:backend` |
| Package without installers | `pnpm run package:app` |
| Create installers | `pnpm run make:app` |
Electron backend build depends on the web build; outputs live under
`dist/apps/electron-backend` and `dist/apps/web` and packaging combines them.
Use [the validation map](../architecture/validation-map.md) for test/lint tasks
and [the release pipeline](../architecture/release-pipeline.md) for packaging.
## Shared search text folding
Use `foldSearchText` from `libs/shared/interfaces/src/lib/search-text-fold.util.ts`
on both sides of every in-memory search comparison. Channel lists, catalog and
category filters, command palette, sources and downloads must share this fold;
row filtering and count/queue sources must not diverge. It lowercases without a
locale, normalizes to NFC, then strips remaining combining marks. Composing first
keeps canonical spellings equivalent while preserving accents; the leftover dot
in Turkish dotted İ is stripped so it matches plain i. Do not substitute
`toLocaleLowerCase`. Electron content search uses the same fold and adds explicit
Turkish-locale İ variants to LIKE/GLOB queries because SQLite LIKE folds only ASCII.
+79
View File
@@ -0,0 +1,79 @@
# Electron debugging and tracing
Use this procedure for Electron/CDP tasks. Read the available `electron` skill
when automating the desktop app. These commands assume a bootstrapped worktree.
## Start and attach
- Start the Electron development app with: `pnpm nx serve electron-backend`
- Package-script equivalent: `pnpm run serve:backend`
- Electron is configured to start with: `--remote-debugging-port=9222`
- Connect Chrome DevTools Protocol tools to: `127.0.0.1:9222`
- For Electron automation/debugging tasks, use the `electron` skill
- Do not auto-open DevTools during normal CDP automation. In development, DevTools is opt-in via `ELECTRON_OPEN_DEVTOOLS=1`.
- If DevTools is open, `agent-browser --cdp 9222 ...` may attach to the DevTools page instead of the IPTVnator window. Symptoms: `tab list` shows `about:blank`, snapshots are empty, and screenshots are black.
- If that happens, inspect targets with `curl http://127.0.0.1:9222/json/list` and connect directly to the IPTVnator page websocket from the `webSocketDebuggerUrl` field.
- The app holds a single-instance lock (`acquireSingleInstanceLock` in `apps/electron-backend/src/app/services/single-instance.ts`): a second launch against the same `userData` quits immediately and focuses the running window. To attach a second CDP-enabled instance to the same profile, set `IPTVNATOR_ALLOW_MULTIPLE_INSTANCES=1` — knowing that only one of the two processes will own the renderer's IndexedDB, so settings written by the other are lost. Before focusing, the guard forwards the second launch's argv to `onSecondInstance`, which is how a playlist path handed to an already-running app reaches the open queue.
### Trace / Debug Startup
- Full startup tracing:
```bash
IPTVNATOR_TRACE_STARTUP=1 pnpm nx serve electron-backend
```
- Narrower trace flags:
- `IPTVNATOR_TRACE_IPC=1` traces renderer `window.electron.*` bridge calls
- `IPTVNATOR_TRACE_DB=1` traces DB worker requests and request-scoped DB events
- `IPTVNATOR_TRACE_SQL=1` traces SQLite statements in the main process and DB worker
- `IPTVNATOR_TRACE_WINDOW=1` traces BrowserWindow lifecycle and unresponsive events
- `IPTVNATOR_TRACE_PLAYER=1` traces external-player activity and bounded Embedded MPV runtime-probe stderr
- `IPTVNATOR_TRACE_RENDERER_CONSOLE=1` mirrors renderer console output into the Electron terminal
- `IPTVNATOR_PERF_CAPTURE=1` enables development/test-only, redacted M3U and Xtream preload IPC request/completion markers plus count-only M3U acquire/parse/normalize, Xtream main network/JSON-transform/success-response-ready/cancel-dispatch, and renderer store phase capture; renderer wrappers emit only while the benchmark installs its Symbol hook, benchmark tooling sets the flag explicitly, and production launches must leave it unset
- `IPTVNATOR_PERF_WORKER_PROFILING=1` enables development/test-only, request-scoped worker receive/work/response-post timestamps, thread CPU, event-loop utilization/delay, count-only playlist serialization/SQLite write/read/deserialization plus Xtream category/content/cache-clear/delete/in-source-search phase events, profiling-only worker cancel-receipt acknowledgements, valid-sample-counted isolate peak memory, and the database worker's idle-only one-shot post-GC heap probe; overlapping database requests are explicitly invalidated instead of misattributed, the performance benchmark sets the flag automatically, and production launches must leave it unset
- Settings, portal request/response, and trace payloads must use
`@iptvnator/shared/logging` or the redacting portal logger before reaching
`console.*`; never log raw credentials while debugging.
- If local Nx state gets weird before a rerun:
```bash
pnpm nx reset
```
### agent-browser (global install)
```bash
agent-browser --cdp 9222 tab list
agent-browser --cdp 9222 tab 1
agent-browser --cdp 9222 snapshot -i -c -d 4
agent-browser --cdp 9222 screenshot /tmp/iptvnator-cdp.png
```
### Fallback
```bash
npx --yes agent-browser --cdp 9222 tab list
```
### DevTools Workaround
```bash
ELECTRON_OPEN_DEVTOOLS=1 pnpm nx serve electron-backend
curl http://127.0.0.1:9222/json/list
agent-browser connect ws://127.0.0.1:9222/devtools/page/<iptvnator-page-id>
agent-browser screenshot /tmp/iptvnator-cdp.png
```
## Main-process ownership
The entry point is `apps/electron-backend/src/main.ts`; it bootstraps the database,
registers events and creates the main window. The preload is
`apps/electron-backend/src/app/api/main.preload.ts`, with handlers under
`apps/electron-backend/src/app/events/`. The window follows the saved startup mode
(normal/maximized/fullscreen); `--fullscreen` overrides a single launch. Use
[workspace shell](../architecture/workspace-shell.md) for window behavior,
[DB worker](../architecture/sqlite-db-worker.md) for worker ownership and
[Electron security](../architecture/electron-security.md) for bridge boundaries.
+59
View File
@@ -0,0 +1,59 @@
# Agent context map
Read the row for your task before editing. For a cross-domain change, read each
affected contract. This is navigation, not a request to load every linked file.
Common constraints remain in [AGENTS.md](../../AGENTS.md); Claude imports it.
Repository skill frontmatter defines triggers. If your client does not discover
a listed skill automatically, read its SKILL.md directly. Optional global tools
are not prerequisites for reading repository contracts.
## Development and maintenance
| Area / code ownership | Canonical documents | Repository skill |
| --- | --- | --- |
| Bootstrap, project placement, dependencies, aliases and lint configuration; root Nx config and project-local project.json files | [Nx boundaries](../architecture/nx-workspace-boundaries.md), [security overrides](../architecture/dependency-security-overrides.md) | [Nx architecture](../../.codex/skills/iptvnator-nx-architecture/SKILL.md) |
| Angular conventions; docs and skills maintenance | [Agent workflow](../development/agent-workflow.md) | Use the area's skill below |
| Unit, E2E, lint and coverage; `tools/coverage` | [Validation map](../architecture/validation-map.md) | Use the area's validation section |
| Electron entry/events/preload and CDP; `apps/electron-backend` | [Debugging and trace flags](../development/electron-debugging.md), [Electron security](../architecture/electron-security.md) | Use the available global electron skill for automation |
| Releases, notes, screenshots, native assets, Linux manager metadata; `tools/release` | [Release pipeline](../architecture/release-pipeline.md), [note format](../../.changes/README.md) | [Release notes](../../.codex/skills/release-notes/SKILL.md), [release cut](../../.codex/skills/release-cut/SKILL.md) |
## Data, sources and networking
| Area / code ownership | Required contracts | Repository skill |
| --- | --- | --- |
| SQLite schema/startup; `libs/shared/database`; Electron DB events/workers/operations | [DB worker](../architecture/sqlite-db-worker.md), [migration ownership and tests](../../libs/shared/database/README.md) | [SQLite worker](../../.codex/skills/iptvnator-sqlite-db-worker/SKILL.md) |
| M3U import/state/player, XMLTV, source lifecycle, startup readiness and OS file opening; `libs/m3u-state`, `libs/playlist`, `libs/epg` | [M3U module](../architecture/m3u-playlist-module.md), [adding sources across layers](../development/agent-workflow.md#adding-behavior-across-layers) | Read both contracts when adding a source |
| Xtream API/store/data sources and routed views; `libs/portal/xtream` | [Xtream compatibility](../architecture/xtream-portal-compatibility.md), [category management](../architecture/category-management.md), [detail navigation](../architecture/portal-detail-navigation.md) | [Xtream](../../.codex/skills/xtream-electron/SKILL.md) |
| Stalker/Ministra protocol, identity, sessions and routed views; `libs/portal/stalker` | [Stalker portal](../architecture/stalker-portal.md), [Stalker EPG](../architecture/stalker-epg.md) for EPG work, [store API baseline](../architecture/stalker-store-api-baseline.md) for store API changes | [Stalker](../../.codex/skills/stalker-portal/SKILL.md) |
| Browser runtime, HTTP proxies, redirects and backend networking; `apps/web-backend`, `libs/shared/host-health` | [PWA/self-hosting](../architecture/pwa-self-hosted.md), [connectivity guard](../architecture/host-connectivity-guard.md), [Electron security](../architecture/electron-security.md) for desktop boundary changes | Read the affected runtime contract |
| Source health and selective cleanup; portal shared data access | [Desktop source health](../architecture/m3u-playlist-module.md#desktop-source-health), [subscription expiry](../architecture/workspace-dashboard.md#source-subscription-expiry) | Read the affected provider skill |
| Backup and restore; playlist persistence | [Backup/restore](../architecture/playlist-backup-restore.md), [database migrations](../../libs/shared/database/README.md) | Read the affected persistence skill |
## Playback, navigation and UI
| Area / code ownership | Required contracts | Repository skill |
| --- | --- | --- |
| Web engines, controls, tracks, PiP, radio and display sleep; `libs/ui/playback` | [Player controls](../architecture/player-controls-contract.md), [inline playback/diagnostics/recovery](../architecture/embedded-inline-playback.md) | Read the contract directly |
| Embedded MPV, platform engines, addon, pinned runtime and packaging; Electron native services, `tools/embedded-mpv` | [Native MPV](../architecture/embedded-mpv-native.md), [runtime build and licensing](../../tools/embedded-mpv/README.md) | Read the contract directly |
| Live panels, keyboard focus, grid/layout conventions; shared UI and portal views | [UI guidelines](../architecture/iptvnator-ui-guidelines.md), [detail navigation](../architecture/portal-detail-navigation.md) | [UI design](../../.codex/skills/iptvnator-ui-design/SKILL.md), [theme/style](../../.codex/skills/iptvnator-theme-style/SKILL.md) |
| Workspace routes, title bar, switcher, collections and dashboard; `libs/workspace` | [Workspace shell](../architecture/workspace-shell.md), [dashboard](../architecture/workspace-dashboard.md), [collection/detail navigation](../architecture/portal-detail-navigation.md) | UI/theme skills for visible changes |
| Remote control, playback queue, channel return and shortcuts; `libs/ui/remote-control`, `apps/remote-control-web` | [Remote control](../architecture/remote-control.md) | Provider skill when queue ownership changes |
| Downloads, offline details, catch-up and file availability; `libs/portal/downloads` | [Download manager](../architecture/download-manager.md), provider contract for URL resolution | Read the affected provider skill |
| VOD source discovery, factual metadata and failover; `libs/portal/shared/data-access` | [VOD multi-source](../architecture/vod-multi-source.md) | [Xtream](../../.codex/skills/xtream-electron/SKILL.md) |
| TMDB enrichment, artwork, actors and recommendations; `libs/services/src/lib/tmdb` | [TMDB contracts](../architecture/tmdb-metadata-enrichment.md), [dashboard](../architecture/workspace-dashboard.md) | UI skill for rendering changes |
| Timezones, catch-up formatting and EPG display offsets | [Date handling](../architecture/date-handling.md), [Xtream compatibility](../architecture/xtream-portal-compatibility.md), [M3U EPG](../architecture/m3u-playlist-module.md) | Affected provider skill |
| Website, blog and download pages; `apps/website` | [Website README](../../apps/website/README.md) | Use an available website skill |
| Mock servers and fictional release fixtures; `apps/stalker-mock-server`, `apps/xtream-mock-server` | [Stalker mock](../architecture/stalker-mock-server.md), [Xtream mock](../architecture/xtream-mock-server.md), [release screenshot contract](../architecture/release-pipeline.md) | Release-cut for release captures |
## Maintenance rules
Keep unique behavior contracts in the authoritative document, procedures in a
skill or development guide, and universal constraints in AGENTS.md. Update this
map when ownership or a canonical destination changes. Preserve release skill
mirrors; do not create extra copies of other contracts for individual agents.
Use normal Markdown links for document destinations so `pnpm run agents:validate`
can check them. Do not add root imports for the linked documents.
The [migration ledger](agent-guidance-migration.md) explains how the original
root instructions were accounted for. It is historical audit evidence, not
another source of current runtime policy or required task context.
@@ -0,0 +1,802 @@
# Agent guidance migration ledger
Issue #1643, 2026-09-20. Immutable source commit: `d4df0fd81a71f0bf196ceac2b45efe3697feb973`.
Original files: [AGENTS.md](https://github.com/4gray/iptvnator/blob/d4df0fd81a71f0bf196ceac2b45efe3697feb973/AGENTS.md)
(1,217 lines, 88,565 bytes) and [CLAUDE.md](https://github.com/4gray/iptvnator/blob/d4df0fd81a71f0bf196ceac2b45efe3697feb973/CLAUDE.md)
(2,022 lines, 231,858 bytes). Table coordinates refer to these immutable files,
not the shortened roots. This is audit evidence; it is not a required startup document.
## Method and coverage
Every non-empty source block was inventoried. Headings, standalone labels,
separator lines and Nx markers are structural; their contents are represented
below. Lists are split at each top-level item; code fences stay intact. The
17,847-character CLAUDE.md line 1315 is split into sentence-level entries so
individual constraints do not disappear behind one row. Repeated entries from
the two agents intentionally retain separate source references.
716 content entries are accounted for below; there are no unassigned
source blocks. "Existing contract" means the canonical document already carries
the behavior and was reviewed instead of copying another summary. "Added" and
"Moved" identify knowledge incorporated during this change. Destination sections
are entry points into the owning contract; adjacent subheadings cover supporting
exceptions and examples. The [context map](agent-context-map.md) is the live
navigation surface; this ledger records the one-time relocation.
## Corrections and consolidation decisions
- Root growth/mirroring requirements are replaced by one source of common rules
and topic-specific maintenance in [agent workflow](../development/agent-workflow.md#maintaining-canonical-knowledge).
Root limits do not apply to canonical reference docs; those load on demand.
- Stalker static URL wording was oversimplified. Missing flag evidence still
requires minting a link (with the directly playable radio exception), as
specified in [playback link resolution](../architecture/stalker-portal.md#playback-link-resolution)
and implemented by the existing Stalker link-semantics utilities. Keep the
authoritative decision table, not the old "otherwise static" shorthand.
- Generic Electron detection is not an individual capability gate. Desktop also
has Chromium settings storage, PWA supports browser-selected uploads, and SQL
and IndexedDB schemas are not identical. Preserve the existing DataFactory
boundary with these corrections in [service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases).
- Browser wake-lock auto-release clears the sentinel; the next media/visibility/
PiP sync reacquires. Preserve actual renderer behavior, not an implication of
immediate unconditional reacquisition; see [display sleep](../architecture/player-controls-contract.md#display-sleep-during-playback).
- Full-file formatting recovery must preserve unrelated user edits. Replace the
unconditional merge-base overwrite recipe with [safe reconstruction](../development/agent-workflow.md#protected-markdown-edits).
- The TypeScript hard limit is 400, not a variable 350–400; target under 300.
HostBinding/HostListener conventions are retained, not silently modernized.
- Nx tools and global skills are optional. Retain discovery fallbacks and the
managed markers in [AGENTS.md](../../AGENTS.md#general-guidelines-for-working-with-nx).
- Build/serve examples use local pnpm Nx or package scripts. Packaging uses
`package:app` (`make --prepackageOnly`), not the obsolete `electron-backend:package`
target suggested in the old alternative command.
- Version-specific signing patch rationale was missing from canonical docs and
now lives in [the dependency contract](../architecture/nx-workspace-boundaries.md#electron-builder-signing-patch).
- Exhaustive trees, diagrams and code examples are consolidated into the owning
architecture/maintenance sections; they do not become a second project map.
Existing snapshot inventories are navigation aids, not instructions to duplicate
every new project in the root. Plan saving respects active no-write modes.
## Block inventory
| Original source | Section and contract / example | Canonical destination | Disposition |
| --- | --- | --- | --- |
| AGENTS.md:3–3 | AGENTS.md — This file provides guidance to coding agents working in this repository. | [Plans and completion reports](../development/agent-workflow.md#plans-and-completion-reports) | Moved / consolidated |
| AGENTS.md:7–7 | Plan Mode — When an agent is in Plan Mode and produces a final &lt;proposed_plan&gt;, it must also save that finalized plan… | [Plans and completion reports](../development/agent-workflow.md#plans-and-completion-reports) | Moved / consolidated |
| AGENTS.md:8–8 | Plan Mode — Save only finalized plans. Do not write interim exploration, questions, or draft revisions to .plans/. | [Plans and completion reports](../development/agent-workflow.md#plans-and-completion-reports) | Moved / consolidated |
| AGENTS.md:9–9 | Plan Mode — Use the filename pattern YYYY-MM-DD-short-topic.md such as .plans/2026-03-12-channel-filtering.md. | [Plans and completion reports](../development/agent-workflow.md#plans-and-completion-reports) | Moved / consolidated |
| AGENTS.md:10–10 | Plan Mode — If the intended filename already exists, append a numeric suffix such as -2, -3, and so on. | [Plans and completion reports](../development/agent-workflow.md#plans-and-completion-reports) | Moved / consolidated |
| AGENTS.md:14–14 | Agent Bootstrap — In a fresh worktree, run pnpm install --frozen-lockfile before relying on Nx project discovery, lint,… | [Fresh Worktree Bootstrap and Discovery](../architecture/nx-workspace-boundaries.md#fresh-worktree-bootstrap-and-discovery) | Existing contract |
| AGENTS.md:15–15 | Agent Bootstrap — Re-run the install whenever the checkout moves — git pull, git reset --hard, a rebase, or a worktree… | [Fresh Worktree Bootstrap and Discovery](../architecture/nx-workspace-boundaries.md#fresh-worktree-bootstrap-and-discovery) | Existing contract |
| AGENTS.md:16–16 | Agent Bootstrap — Never run prettier --write on CLAUDE.md, AGENTS.md or docs/. These files are not Prettier-clean upstream,… | [Protected Markdown edits](../development/agent-workflow.md#protected-markdown-edits) | Corrected safe restore |
| AGENTS.md:17–17 | Agent Bootstrap — After dependencies are installed, verify workspace discovery with pnpm nx show projects. | [Fresh Worktree Bootstrap and Discovery](../architecture/nx-workspace-boundaries.md#fresh-worktree-bootstrap-and-discovery) | Existing contract |
| AGENTS.md:18–18 | Agent Bootstrap — Use scoped path aliases from tsconfig.base.json such as @iptvnator/services, @iptvnator/shared/interfaces,… | [Import Aliases and Public APIs](../architecture/nx-workspace-boundaries.md#import-aliases-and-public-apis) | Existing contract |
| AGENTS.md:19–19 | Agent Bootstrap — Every Nx project should keep scope:, domain:, and type: tags in project.json so… | [Project Tags](../architecture/nx-workspace-boundaries.md#project-tags) | Existing contract |
| AGENTS.md:20–20 | Agent Bootstrap — See docs/architecture/nx-workspace-boundaries.md for the current Nx tag and alias policy. | [Import Aliases and Public APIs](../architecture/nx-workspace-boundaries.md#import-aliases-and-public-apis) | Existing contract |
| AGENTS.md:21–22 | Agent Bootstrap — Keep nx and every official @nx/ package on the same exact version; run pnpm run deps:nx:validate after… | [Nx Dependency Updates](../architecture/nx-workspace-boundaries.md#nx-dependency-updates) | Existing contract |
| AGENTS.md:23–24 | Agent Bootstrap — Use the Node version in .nvmrc for development and CI. Angular 22 requires Node ^22.22.3 &#124;&#124; ^24.15.0 and… | [Angular 22 Toolchain Compatibility](../architecture/nx-workspace-boundaries.md#angular-22-toolchain-compatibility) | Existing contract |
| AGENTS.md:25–29 | Agent Bootstrap — Vite 8.1.5, resolved through Angular's build tooling, retains upstream precise matchers and adds bounded… | [Vite Dev-Server Patch](../architecture/nx-workspace-boundaries.md#vite-dev-server-patch) | Existing contract |
| AGENTS.md:30–40 | Agent Bootstrap — app-builder-lib 26.15.7 (electron-builder's macOS signing) is patched in… | [Electron Builder signing patch](../architecture/nx-workspace-boundaries.md#electron-builder-signing-patch) | Added missing detail |
| AGENTS.md:41–47 | Agent Bootstrap — node-gyp is a declared root devDependency because apps/electron-backend/build-embedded-mpv.js resolves it… | [Packaging State](../architecture/embedded-mpv-native.md#packaging-state) | Existing contract |
| AGENTS.md:48–52 | Agent Bootstrap — nx-electron@22.0.0 uses a local Nx 23 export-path patch and an explicit webpack-node-externals package… | [Angular 22 Toolchain Compatibility](../architecture/nx-workspace-boundaries.md#angular-22-toolchain-compatibility) | Existing contract |
| AGENTS.md:53–59 | Agent Bootstrap — A directory holding files consumed by other projects must be an Nx project. Nx builds its graph from… | [Shared Stylesheets and Cache Inputs](../architecture/nx-workspace-boundaries.md#shared-stylesheets-and-cache-inputs) | Existing contract |
| AGENTS.md:60–63 | Agent Bootstrap — Update Nx with pnpm nx migrate nx@&lt;target&gt; --skipInstall, regenerate the lockfile, run generated… | [Nx Dependency Updates](../architecture/nx-workspace-boundaries.md#nx-dependency-updates) | Existing contract |
| AGENTS.md:64–64 | Agent Bootstrap — ESLint enforces max-lines on TypeScript files: production code targets under 300 with a hard maximum of… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| AGENTS.md:65–65 | Agent Bootstrap — Project lint targets that shell out to eslint must quote the glob, e.g. eslint "apps/&lt;project&gt;//.ts". An… | [Command-Based Lint Targets](../architecture/nx-workspace-boundaries.md#command-based-lint-targets) | Existing contract |
| AGENTS.md:66–66 | Agent Bootstrap — Repository-specific skills live under .codex/skills/. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:67–68 | Agent Bootstrap — Frontmatter descriptions are trigger-only and begin with Use when; keep each skill at or below 500 words. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:69–70 | Agent Bootstrap — Run pnpm run skills:validate after editing a committed skill or a literal path it documents. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:71–72 | Agent Bootstrap — Keep .codex and .claude copies of release-notes and release-cut byte-identical. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:76–76 | Documentation After Changes — After implementing a meaningful change, agents must assess whether canonical repo docs need updates before… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:77–77 | Documentation After Changes — Meaningful changes include new or changed user-visible behavior, architecture or data-flow changes,… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:78–78 | Documentation After Changes — Skip doc updates for trivial refactors with unchanged behavior, formatting-only edits, and isolated… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:79–82 | Documentation After Changes — Prefer updating an existing authoritative doc before creating a new one: 1. README.md for top-level… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:83–83 | Documentation After Changes — Keep the root CLAUDE.md and this file up to date. They are living documents: whenever a change touches… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:84–84 | Documentation After Changes — When adding a new feature area, check whether the Architecture or Key Features sections of CLAUDE.md… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:85–85 | Documentation After Changes — Do not let CLAUDE.md or AGENTS.md drift: a stale path or route in these files poisons the context of every… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:86–86 | Documentation After Changes — Repo docs are canonical even when they were originally drafted by an LLM. | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:87–87 | Documentation After Changes — Final task summaries should state whether docs were updated and which doc changed. | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| AGENTS.md:91–91 | Release Notes For User-Visible Changes — Any change a user could notice — new behavior, changed behavior, bug fix, performance win, breaking change… | [File](../../.changes/README.md#file) | Existing contract |
| AGENTS.md:92–92 | Release Notes For User-Visible Changes — Name it &lt;area&gt;-&lt;short-slug&gt;.md; area matches the conventional-commit scope. There is no version field —… | [File](../../.changes/README.md#file) | Existing contract |
| AGENTS.md:93–93 | Release Notes For User-Visible Changes — Write the body for a user, not a reviewer: "the player now remembers volume between episodes", not "hoist… | [Writing the body](../../.changes/README.md#writing-the-body) | Existing contract |
| AGENTS.md:94–94 | Release Notes For User-Visible Changes — type: internal records invisible maintenance. Internal notes stay collapsed in CHANGELOG.md, are omitted… | [Writing the body](../../.changes/README.md#writing-the-body) | Existing contract |
| AGENTS.md:95–95 | Release Notes For User-Visible Changes — highlight: &lt;short headline&gt; (max 60 characters, rejected on type: internal) marks a note as one of the… | [Writing the body](../../.changes/README.md#writing-the-body) | Existing contract |
| AGENTS.md:96–96 | Release Notes For User-Visible Changes — Skip the note for test-only changes, docs, CI/workflow plumbing, and pure refactors with no behavior… | [When a note is not needed](../../.changes/README.md#when-a-note-is-not-needed) | Existing contract |
| AGENTS.md:97–97 | Release Notes For User-Visible Changes — CI enforces this: the "Release note gate" job in .github/workflows/ci.yml fails PRs that change runtime… | [Surfaces built from one set of notes](../architecture/release-pipeline.md#surfaces-built-from-one-set-of-notes) | Existing contract |
| AGENTS.md:98–98 | Release Notes For User-Visible Changes — The release-notes skill covers writing notes; the release-cut skill covers the full release sequence.… | [Surfaces built from one set of notes](../architecture/release-pipeline.md#surfaces-built-from-one-set-of-notes) | Existing contract |
| AGENTS.md:99–99 | Release Notes For User-Visible Changes — Validate before finishing: pnpm run release:notes:validate. | [Commands](../../.changes/README.md#commands) | Existing contract |
| AGENTS.md:100–100 | Release Notes For User-Visible Changes — Announcement drafts and highlight cards are built from the same notes: pnpm --silent run… | [Writing the body](../../.changes/README.md#writing-the-body) | Existing contract |
| AGENTS.md:101–101 | Release Notes For User-Visible Changes — Pushes to master and v can publish Docker images. A v tag build creates a draft GitHub release. | [Two phases](../architecture/release-pipeline.md#two-phases) | Existing contract |
| AGENTS.md:102–102 | Release Notes For User-Visible Changes — pnpm run release:verify:draft waits for that tag build (polling until the run is indexed, then gh run… | [Draft verification](../architecture/release-pipeline.md#draft-verification) | Existing contract |
| AGENTS.md:103–103 | Release Notes For User-Visible Changes — Publishing the GitHub release verifies its Snap assets and automatically uploads them to edge;… | [After verification](../architecture/release-pipeline.md#after-verification) | Existing contract |
| AGENTS.md:104–104 | Release Notes For User-Visible Changes — Release-post screenshots come only from the release capture script running against the mock servers. Never… | [Screenshots](../../.changes/README.md#screenshots) | Existing contract |
| AGENTS.md:105–105 | Release Notes For User-Visible Changes — Final task summaries should state whether a release note was added or why it was skipped. | [Plans and completion reports](../development/agent-workflow.md#plans-and-completion-reports) | Moved / consolidated |
| AGENTS.md:109–114 | AppImage Manager Metadata — AppManager full-download discovery uses appImage.desktop.entry URL fields. Electron Builder generates the… | [Surfaces built from one set of notes](../architecture/release-pipeline.md#surfaces-built-from-one-set-of-notes) | Existing contract |
| AGENTS.md:118–118 | Upgrade And Migration Compatibility — Users may skip releases. The application must apply all required migrations in dependency order when… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| AGENTS.md:119–119 | Upgrade And Migration Compatibility — Preserve migration paths for existing persisted data. Do not make deleting a database/profile or… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| AGENTS.md:120–120 | Upgrade And Migration Compatibility — Create required tables first, add missing columns before dependent indexes/triggers/queries, and make… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| AGENTS.md:121–121 | Upgrade And Migration Compatibility — For persistence changes, test real SQLite initialization with representative historical schemas and data,… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| AGENTS.md:122–122 | Upgrade And Migration Compatibility — See libs/shared/database/README.md for SQLite migration ownership and validation guidance. | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| AGENTS.md:126–126 | Regression Prevention And Test Updates — Before the final summary for any feature, behavior change, bug fix, data-flow change, Electron… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| AGENTS.md:127–127 | Regression Prevention And Test Updates — Bug fixes must normally include regression coverage that fails on the old behavior and passes with the… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| AGENTS.md:128–128 | Regression Prevention And Test Updates — Feature work and behavior changes must update existing tests when assertions, fixtures, mocks, routes, or… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| AGENTS.md:129–133 | Regression Prevention And Test Updates — Default validation ladder: 1. Run targeted unit tests for directly affected projects with pnpm nx test… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| AGENTS.md:134–134 | Regression Prevention And Test Updates — Electron-specific changes affecting IPC, SQLite, packaged runtime, external players, native file access,… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| AGENTS.md:135–135 | Regression Prevention And Test Updates — Final task summaries must list tests added or updated, validation commands run with results, and any… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| AGENTS.md:139–146 | Legacy Desktop Profile Migration — electron-profile-bootstrap.ts selects the known v0.19 electron-backend profile before eager main-process… | [Desktop upgrades from legacy profiles](../architecture/m3u-playlist-module.md#desktop-upgrades-from-legacy-profiles) | Existing contract |
| AGENTS.md:148–154 | Legacy Desktop Profile Migration — Startup shows AppStartupStatusComponent until the initial route and source inventory are ready, including… | [Desktop upgrades from legacy profiles](../architecture/m3u-playlist-module.md#desktop-upgrades-from-legacy-profiles) | Existing contract |
| AGENTS.md:158–158 | Electron Debugging (CDP) — Start the Electron development app with: nx serve electron-backend | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:159–159 | Electron Debugging (CDP) — Package-script equivalent: pnpm run serve:backend | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:160–160 | Electron Debugging (CDP) — Electron is configured to start with: --remote-debugging-port=9222 | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:161–161 | Electron Debugging (CDP) — Connect Chrome DevTools Protocol tools to: 127.0.0.1:9222 | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:162–162 | Electron Debugging (CDP) — For Electron automation/debugging tasks, use the electron skill | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:163–163 | Electron Debugging (CDP) — Do not auto-open DevTools during normal CDP automation. In development, DevTools is opt-in via… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:164–164 | Electron Debugging (CDP) — If DevTools is open, agent-browser --cdp 9222 ... may attach to the DevTools page instead of the IPTVnator… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:165–165 | Electron Debugging (CDP) — If that happens, inspect targets with curl http://127.0.0.1:9222/json/list and connect directly to the… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:166–166 | Electron Debugging (CDP) — The app holds a single-instance lock (acquireSingleInstanceLock in… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:170–170 | Trace / Debug Startup — Full startup tracing: | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:172–174 | Trace / Debug Startup — bash IPTVNATOR_TRACE_STARTUP=1 nx serve electron-backend | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:176–184 | Trace / Debug Startup — Narrower trace flags: - IPTVNATOR_TRACE_IPC=1 traces renderer window.electron. bridge calls -… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:186–188 | Trace / Debug Startup — Settings, portal request/response, and trace payloads must use @iptvnator/shared/logging or the redacting… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:190–190 | Trace / Debug Startup — If local Nx state gets weird before a rerun: | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:192–194 | Trace / Debug Startup — bash pnpm nx reset | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:198–203 | agent-browser (global install) — bash agent-browser --cdp 9222 tab list agent-browser --cdp 9222 tab 1 agent-browser --cdp 9222 snapshot -i… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:207–209 | Fallback — bash npx --yes agent-browser --cdp 9222 tab list | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:213–218 | DevTools Workaround — bash ELECTRON_OPEN_DEVTOOLS=1 nx serve electron-backend curl http://127.0.0.1:9222/json/list agent-browser… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| AGENTS.md:222–226 | Xtream Category Management — The Electron Live TV, Movies, and Series category dialog applies Select/Deselect to search results while a… | [Behavior Notes](../architecture/category-management.md#behavior-notes) | Existing contract |
| AGENTS.md:230–234 | XMLTV Response Compression — Electron decodes HTTP compression before the gzip file layer. For .gz/gzip metadata plus HTTP gzip, a… | [XMLTV response compression](../architecture/m3u-playlist-module.md#xmltv-response-compression) | Existing contract |
| AGENTS.md:238–256 | XMLTV Source Removal — Saving Settings → EPG reconciles cached XMLTV with committed global URLs and all enabled M3U playlist… | [XMLTV source lifecycle](../architecture/m3u-playlist-module.md#xmltv-source-lifecycle) | Existing contract |
| AGENTS.md:260–269 | Web Backend Provider Redirects — All four provider proxy routes use ValidatedHttpClient: automatic redirects are disabled, the initial URL… | [Web Backend](../architecture/pwa-self-hosted.md#web-backend) | Existing contract |
| AGENTS.md:273–278 | Portal Connectivity Preference — Half-open trial slots follow the complete request lifetime with no elapsed-time expiry. All four… | [Desktop preference and account feedback](../architecture/host-connectivity-guard.md#desktop-preference-and-account-feedback) | Existing contract |
| AGENTS.md:279–286 | Portal Connectivity Preference — Desktop Settings &gt; General &gt; Portal connections exposes default-on Settings.portalConnectivityGuard. Only… | [Desktop preference and account feedback](../architecture/host-connectivity-guard.md#desktop-preference-and-account-feedback) | Existing contract |
| AGENTS.md:287–289 | Portal Connectivity Preference — Both account-info dialogs explain guard refusals with localized paused-request copy and Retry now; Stalker… | [Desktop preference and account feedback](../architecture/host-connectivity-guard.md#desktop-preference-and-account-feedback) | Existing contract |
| AGENTS.md:293–299 | Live Channel Return — Xtream and Stalker (including radio) capture displayed playback order on explicit selection. Remote… | [Live channel return and playback order](../architecture/remote-control.md#live-channel-return-and-playback-order) | Existing contract |
| AGENTS.md:303–309 | Stalker Live Search — ITV sidebar and fullscreen searches independently filter the complete selected category; only All Items… | [Full ITV Channel List Cache](../architecture/stalker-portal.md#full-itv-channel-list-cache) | Existing contract |
| AGENTS.md:313–339 | Live TV Panel Levels — Portal live layouts (Xtream live, Stalker itv/radio) fold their panels from the outside in, in three… | [Collapsible Live Sidebar](../architecture/iptvnator-ui-guidelines.md#collapsible-live-sidebar) | Existing contract |
| AGENTS.md:343–358 | Channel and Detail Keyboard Scrolling — Channel scroll owners use ChannelScrollFocusDirective; pointer selection focuses the viewport, native… | [Detail Scroll and Focus](../architecture/portal-detail-navigation.md#detail-scroll-and-focus) | Existing contract |
| AGENTS.md:362–370 | Xtream Connection Test — Add/Edit source Test HTTPS and HTTP discloses plaintext credential use before the click and can replace an… | [Explicit protocol discovery](../architecture/xtream-portal-compatibility.md#explicit-protocol-discovery) | Existing contract |
| AGENTS.md:374–382 | Xtream Live Auto Format — The routed Xtream live host supplies liveAutoTsUrl only for Auto with explicit HLS+TS account evidence,… | [Initial Auto HLS failure](../architecture/xtream-portal-compatibility.md#initial-auto-hls-failure) | Existing contract |
| AGENTS.md:386–402 | Xtream Catch-Up Server Timezone — The {Y-m-d:H-M} segment of a timeshift URL is read by the panel in ITS timezone (server_info.timezone),… | [Catch-Up Playback URLs](../architecture/xtream-portal-compatibility.md#catch-up-playback-urls) | Existing contract |
| AGENTS.md:406–406 | Radio / Audio Player — M3U playlists can contain radio channels identified by the radio="true" attribute on #EXTINF lines. When a… | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:408–408 | Radio / Audio Player — The dedicated AudioPlayerComponent (libs/ui/playback/src/lib/audio-player/) renders instead of a video player | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:409–409 | Radio / Audio Player — The audio player always uses the built-in inline player — external player settings (MPV/VLC) are ignored | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:410–410 | Radio / Audio Player — The EPG panel is hidden (radio streams have no EPG data) | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:411–411 | Radio / Audio Player — The layout uses a cinematic hero pattern: the station logo is blurred as a full-area backdrop with a… | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:412–412 | Radio / Audio Player — Volume is shared with the video player via localStorage key 'volume' | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:413–413 | Radio / Audio Player — Keyboard shortcuts: ArrowUp/ArrowDown (volume +/-5%), M (mute toggle) | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:414–414 | Radio / Audio Player — Radio detection in the video player template: activeChannel.radio === 'true' — this is a string… | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:416–416 | Radio / Audio Player — Key files: | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:418–418 | Radio / Audio Player — libs/ui/playback/src/lib/audio-player/audio-player.component.ts — the audio player component | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:419–419 | Radio / Audio Player — libs/ui/playback/src/lib/audio-player/audio-player.component.scss — cinematic hero styling | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:420–420 | Radio / Audio Player — libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html — template conditionals… | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:421–421 | Radio / Audio Player — libs/shared/interfaces/src/lib/channel.interface.ts — radio: string field on Channel interface | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Existing contract |
| AGENTS.md:425–434 | M3U Playback Mode — isLikelyM3uVod in libs/shared/m3u-utils recognizes video-file extensions and exact /movie/, /movies/,… | [M3U Playback Mode](../architecture/m3u-playlist-module.md#m3u-playback-mode) | Existing contract |
| AGENTS.md:438–440 | M3U URL User-Agent — PlaylistsService.getPlaylist() joins the per-playlist mutation queue so a route opened during refresh… | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| AGENTS.md:441–445 | M3U URL User-Agent — The URL import form accepts an optional User-Agent and stores it as Playlist.userAgent. Electron sends it… | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| AGENTS.md:446–448 | M3U URL User-Agent — Reuse the existing source editor and channel-over-playlist playback header precedence. Contract:… | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| AGENTS.md:452–466 | Shared Player Controls — Stream info popover: an info button in the top-right corner of the shared controls overlay shows live… | [Stream info popover](../architecture/player-controls-contract.md#stream-info-popover) | Existing contract |
| AGENTS.md:468–473 | Shared Player Controls — The Embedded MPV native-view dock follows app theme tokens as a solid app surface, including Material… | [Player And EPG Theme Boundaries](../architecture/iptvnator-ui-guidelines.md#player-and-epg-theme-boundaries) | Existing contract |
| AGENTS.md:475–478 | Shared Player Controls — libs/ui/playback/src/lib/player-controls/ contains the additive, engine-neutral PlayerController contract,… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| AGENTS.md:479–494 | Shared Player Controls — The subtitle menu carries capability-gated advanced subtitle support (#1408): external subtitle file… | [Advanced subtitle support](../architecture/player-controls-contract.md#advanced-subtitle-support) | Existing contract |
| AGENTS.md:495–501 | Shared Player Controls — In fullscreen, app-player-controls shows a pointer-transparent media-title overlay at the top while… | [Fullscreen media title](../architecture/player-controls-contract.md#fullscreen-media-title) | Existing contract |
| AGENTS.md:502–524 | Shared Player Controls — Auto-hide pauses while the pointer is over the controls bar or keyboard focus is inside it, but only… | [Keyboard ownership](../architecture/player-controls-contract.md#keyboard-ownership) | Existing contract |
| AGENTS.md:525–534 | Shared Player Controls — Persisted Settings.webPlayerSharedControls is default-ON (absent stored values coerce with !== false; only… | [Current status](../architecture/player-controls-contract.md#current-status) | Added missing detail |
| AGENTS.md:535–543 | Shared Player Controls — Settings.showCaptions is deliberately outside this rollout gate: it is engine state, not controls UI.… | [Current status](../architecture/player-controls-contract.md#current-status) | Existing contract |
| AGENTS.md:544–554 | Shared Player Controls — The modes differ in how long the preference is enforced. Shared controls are authoritative for the… | [Caption preference in both modes](../architecture/player-controls-contract.md#caption-preference-in-both-modes) | Existing contract |
| AGENTS.md:555–563 | Shared Player Controls — Shared controls include a per-session quality menu (Auto + "1080p"-style levels via setQualityLevel;… | [Quality (bitrate/level) selection](../architecture/player-controls-contract.md#quality-bitratelevel-selection) | Existing contract |
| AGENTS.md:564–568 | Shared Player Controls — Embedded MPV ignores the web-player preference. Frame-copy always uses shared DOM controls through its… | [Embedded MPV rendering constraints](../architecture/player-controls-contract.md#embedded-mpv-rendering-constraints) | Existing contract |
| AGENTS.md:569–576 | Shared Player Controls — Frame-copy shared controls own DOM surface interactions, shortcuts, fullscreen, and recording feedback.… | [Embedded MPV rendering constraints](../architecture/player-controls-contract.md#embedded-mpv-rendering-constraints) | Existing contract |
| AGENTS.md:577–660 | Shared Player Controls — WebPlayerViewComponent renders app-fullscreen-channel-panel… | [Fullscreen channel panel](../architecture/player-controls-contract.md#fullscreen-channel-panel) | Existing contract |
| AGENTS.md:661–670 | Shared Player Controls — Embedded MPV seek steps (arrow keys, ±10 s buttons, PlayerController.seekBy) go through the relative… | [Resume And Track Handling](../architecture/embedded-mpv-native.md#resume-and-track-handling) | Existing contract |
| AGENTS.md:671–676 | Shared Player Controls — M3U Favorites and Recently Viewed resolve Channel.drm into ResolvedPortalPlayback.drm through… | [DASH + ClearKey Playback](../architecture/m3u-playlist-module.md#dash--clearkey-playback) | Existing contract |
| AGENTS.md:677–694 | Shared Player Controls — DASH (.mpd) sources play through a lazily imported Shaka Player source engine… | [DASH + ClearKey Playback](../architecture/m3u-playlist-module.md#dash--clearkey-playback) | Existing contract |
| AGENTS.md:695–701 | Shared Player Controls — mpegts.js 1.8.1 errors from HTML5, Video.js, and ArtPlayer cross one version-locked structured evidence… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| AGENTS.md:702–834 | Shared Player Controls — Browser playback diagnostics and recovery policy live in libs/playback/util and are exported by… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| AGENTS.md:835–852 | Shared Player Controls — The built-in HTML5/hls.js player is the second guarded consumer. HtmlVideoPlayerComponent provides a… | [Web adapter and web-engine bridges](../architecture/player-controls-contract.md#web-adapter-and-web-engine-bridges) | Existing contract |
| AGENTS.md:853–886 | Shared Player Controls — Video.js is the third guarded consumer. VjsPlayerComponent provides a component-scoped… | [Web adapter and web-engine bridges](../architecture/player-controls-contract.md#web-adapter-and-web-engine-bridges) | Existing contract |
| AGENTS.md:887–904 | Shared Player Controls — ArtPlayer is the fourth guarded consumer. ArtPlayerComponent provides a component-scoped… | [Web adapter and web-engine bridges](../architecture/player-controls-contract.md#web-adapter-and-web-engine-bridges) | Existing contract |
| AGENTS.md:905–914 | Shared Player Controls — Shared web picture-in-picture stays inside that default-on rollout. PlayerController exposes capability… | [Standard element picture-in-picture](../architecture/player-controls-contract.md#standard-element-picture-in-picture) | Existing contract |
| AGENTS.md:915–933 | Shared Player Controls — WebVideoControlsAdapter supplies its current video and binding generation to… | [Standard element picture-in-picture](../architecture/player-controls-contract.md#standard-element-picture-in-picture) | Existing contract |
| AGENTS.md:934–935 | Shared Player Controls — Canonical docs: docs/architecture/player-controls-contract.md and docs/architecture/embedded-mpv-native.md | [Web adapter and web-engine bridges](../architecture/player-controls-contract.md#web-adapter-and-web-engine-bridges) | Existing contract |
| AGENTS.md:939–946 | Display Sleep During Playback — PlaybackKeepAwakeService (apps/web/src/app/services/playback-keep-awake.service.ts) watches every &lt;video&gt;… | [Display sleep during playback](../architecture/player-controls-contract.md#display-sleep-during-playback) | Added missing detail |
| AGENTS.md:947–953 | Display Sleep During Playback — Electron: a main-process powerSaveBlocker behind window.electron.setPlaybackKeepAwake… | [Display sleep during playback](../architecture/player-controls-contract.md#display-sleep-during-playback) | Added missing detail |
| AGENTS.md:954–956 | Display Sleep During Playback — Radio's &lt;audio&gt; deliberately never blocks display sleep. Embedded MPV holds its own blocker in… | [Display sleep during playback](../architecture/player-controls-contract.md#display-sleep-during-playback) | Added missing detail |
| AGENTS.md:960–962 | Windows Embedded MPV Pin Maintenance — PR, master, and tag builds resolve the Windows runtime only from… | [Windows CI pin lifecycle](../../tools/embedded-mpv/README.md#windows-ci-pin-lifecycle) | Existing contract |
| AGENTS.md:963–964 | Windows Embedded MPV Pin Maintenance — Validate the checked-in schema and provenance with pnpm embedded-mpv:windows-runtime-pin:check. | [Windows CI pin lifecycle](../../tools/embedded-mpv/README.md#windows-ci-pin-lifecycle) | Existing contract |
| AGENTS.md:965–968 | Windows Embedded MPV Pin Maintenance — Prepare a manual rotation with pnpm embedded-mpv:windows-runtime-pin:refresh -- --force. The weekly… | [Windows CI pin lifecycle](../../tools/embedded-mpv/README.md#windows-ci-pin-lifecycle) | Existing contract |
| AGENTS.md:969–972 | Windows Embedded MPV Pin Maintenance — The PAT-backed refresh job must keep every third-party action pinned to a full commit. Do not mirror the… | [Windows CI pin lifecycle](../../tools/embedded-mpv/README.md#windows-ci-pin-lifecycle) | Added missing detail |
| AGENTS.md:976–979 | Linux Embedded MPV Packaging — Official Linux frame-copy artifacts are x64-only. AppImage, DEB, RPM, Pacman, Snap, and Flatpak are… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:980–986 | Linux Embedded MPV Packaging — Packaging runs three isolated profiles: - system: DEB/RPM/Pacman, no private native/lib, with package… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:987–990 | Linux Embedded MPV Packaging — Flatpak is an isolated packaging pass and keeps iptvnator as the real Electron ELF so Electron Builder's… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:991–993 | Linux Embedded MPV Packaging — The DEB system-runtime contract is Ubuntu 24.04+ (libmpv2). Ubuntu 22.04 provides libmpv1, so use the x64… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:994–997 | Linux Embedded MPV Packaging — Only iptvnator_mpv_helper may link libmpv. The Electron executable, Electron libraries, embedded_mpv.node,… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:998–1002 | Linux Embedded MPV Packaging — electron-backend/native{,//} is excluded from app.asar; afterPack exclusively writes the… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:1003–1005 | Linux Embedded MPV Packaging — Packaged addon, frame-reader, and helper discovery is package-owned app.asar.unpacked only. Writable… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:1006–1010 | Linux Embedded MPV Packaging — Pristine afterPack/unpacked layouts scan Electron libraries recursively. Extracted Snap payloads exclude… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:1011–1015 | Linux Embedded MPV Packaging — Linux frame-copy availability is fail-closed. The packaged manifest, artifact modes, declared bundled… | [Linux Support Matrix](../architecture/embedded-mpv-native.md#linux-support-matrix) | Existing contract |
| AGENTS.md:1016–1027 | Linux Embedded MPV Packaging — Snap is core22/strict and uses an exact private shared-memory plug plus the graphics-core22 content plug… | [Frame-Copy Engine (Experimental, Apple Silicon, Linux and Windows)](../architecture/embedded-mpv-native.md#frame-copy-engine-experimental-apple-silicon-linux-and-windows) | Existing contract |
| AGENTS.md:1028–1054 | Linux Embedded MPV Packaging — The probe and playback helper share one sanitized loader environment: ambient audit, preload, library,… | [Frame-Copy Engine (Experimental, Apple Silicon, Linux and Windows)](../architecture/embedded-mpv-native.md#frame-copy-engine-experimental-apple-silicon-linux-and-windows) | Existing contract |
| AGENTS.md:1055–1059 | Linux Embedded MPV Packaging — In the exact packaged Flatpak /app context, reconstruct only Freedesktop Platform 24.08's immutable… | [Frame-Copy Engine (Experimental, Apple Silicon, Linux and Windows)](../architecture/embedded-mpv-native.md#frame-copy-engine-experimental-apple-silicon-linux-and-windows) | Existing contract |
| AGENTS.md:1060–1063 | Linux Embedded MPV Packaging — The packaged x64 Playwright smoke runs its fixture-contract target first and passes Chromium… | [Same-Version Desktop Release Gate](../architecture/embedded-mpv-native.md#same-version-desktop-release-gate) | Existing contract |
| AGENTS.md:1064–1116 | Linux Embedded MPV Packaging — Bundled Linux releases must publish the exact source archives/git records, checksums, licenses, flags,… | [Same-Version Desktop Release Gate](../architecture/embedded-mpv-native.md#same-version-desktop-release-gate) | Existing contract |
| AGENTS.md:1120–1120 | Repo Skills — .codex/skills/iptvnator-nx-architecture/SKILL.md | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1121–1121 | Repo Skills — .codex/skills/iptvnator-sqlite-db-worker/SKILL.md | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1122–1122 | Repo Skills — .codex/skills/iptvnator-theme-style/SKILL.md | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1123–1123 | Repo Skills — .codex/skills/iptvnator-ui-design/SKILL.md | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1124–1124 | Repo Skills — .codex/skills/release-cut/SKILL.md | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1125–1125 | Repo Skills — .codex/skills/release-notes/SKILL.md | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1126–1126 | Repo Skills — .codex/skills/stalker-portal/SKILL.md | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1127–1127 | Repo Skills — .codex/skills/xtream-electron/SKILL.md | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1129–1130 | Repo Skills — Descriptions and trigger conditions are canonical in each skill's frontmatter; do not duplicate them here. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| AGENTS.md:1137–1137 | General Guidelines for working with Nx — For navigating/exploring the workspace, invoke the nx-workspace skill first when it is available - it has… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1138–1138 | General Guidelines for working with Nx — When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through nx… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1139–1139 | General Guidelines for working with Nx — Prefix nx commands with the workspace's package manager (e.g., pnpm nx build, npm exec nx test) - avoids… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1140–1140 | General Guidelines for working with Nx — You have access to the Nx MCP server and its tools, use them to help the user | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1141–1141 | General Guidelines for working with Nx — For Nx plugin best practices, check node_modules/@nx/&lt;plugin&gt;/PLUGIN.md. Not all plugins have this file -… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1142–1142 | General Guidelines for working with Nx — NEVER guess CLI flags - always check nx_docs or --help first when unsure | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1146–1146 | Scaffolding & Generators — For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the nx-generate skill… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1150–1150 | When to use nx_docs — USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1151–1151 | When to use nx_docs — DON'T USE for: basic generator syntax (nx g @nx/react:app), standard commands, things you already know | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1152–1152 | When to use nx_docs — The nx-generate skill handles generator discovery internally - don't call nx_docs just to look up… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| AGENTS.md:1158–1162 | Catch-Up URL Copying — EPG timeline/list programme details expose Copy archive URL for supported Xtream/M3U archives, including… | [Copy archive URL](../architecture/m3u-playlist-module.md#copy-archive-url) | Existing contract |
| AGENTS.md:1166–1196 | Xtream Archive Downloads — Desktop Xtream Live TV programme details can enqueue completed catch-up as contentType: catchup. The queue… | [Xtream archive downloads](../architecture/download-manager.md#xtream-archive-downloads) | Existing contract |
| AGENTS.md:1200–1205 | Desktop Source Health — Electron switcher/source rows share bounded, cached Xtream/Stalker/M3U URL checks through… | [Desktop source health](../architecture/m3u-playlist-module.md#desktop-source-health) | Existing contract |
| AGENTS.md:1207–1213 | Desktop Source Health — Desktop Sources also offers library-wide selective cleanup through dialog-scoped SourceCleanupService.… | [Desktop inactive-source cleanup](../architecture/m3u-playlist-module.md#desktop-inactive-source-cleanup) | Existing contract |
| AGENTS.md:1215–1217 | Desktop Source Health — Startup source auto-refresh uses SourceActivityService to protect busy IDs from cleanup. Late batch… | [Desktop inactive-source cleanup](../architecture/m3u-playlist-module.md#desktop-inactive-source-cleanup) | Existing contract |
| CLAUDE.md:3–3 | CLAUDE.md — This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:5–5 | CLAUDE.md — &gt; The process sections below (Plan Mode, Documentation After Changes, Upgrade And Migration Compatibility,… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:9–9 | Plan Mode — When Claude Code is in Plan Mode and produces a final &lt;proposed_plan&gt;, it must also save that finalized… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:10–10 | Plan Mode — Save only finalized plans. Do not write interim exploration, question turns, or draft revisions to .plans/. | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:11–11 | Plan Mode — Use the filename pattern YYYY-MM-DD-short-topic.md such as .plans/2026-03-12-channel-filtering.md. | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:12–12 | Plan Mode — If the intended filename already exists, append a numeric suffix such as -2, -3, and so on. | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:16–16 | Documentation After Changes — After implementing a meaningful change, Claude Code must assess whether canonical repo docs need updates… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:17–17 | Documentation After Changes — Meaningful changes include new or changed user-visible behavior, architecture or data-flow changes,… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:18–18 | Documentation After Changes — Skip doc updates for trivial refactors with unchanged behavior, formatting-only edits, and isolated… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:19–22 | Documentation After Changes — Prefer updating an existing authoritative doc before creating a new one: 1. README.md for top-level… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:23–23 | Documentation After Changes — Keep this file (CLAUDE.md) itself up to date. It is a living document: whenever a change touches something… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:24–24 | Documentation After Changes — When adding a new feature area, check whether the Architecture or Key Features sections of CLAUDE.md… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:25–25 | Documentation After Changes — Do not let CLAUDE.md drift: a stale path or route in this file poisons the context of every future agent… | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:26–26 | Documentation After Changes — Repo docs are canonical even when they were originally drafted by an LLM. | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:27–27 | Documentation After Changes — Final task summaries should state whether docs were updated and which doc changed. | [Maintaining canonical knowledge](../development/agent-workflow.md#maintaining-canonical-knowledge) | Moved / consolidated |
| CLAUDE.md:31–31 | Release Notes For User-Visible Changes — Any change a user could notice — new behavior, changed behavior, bug fix, performance win, breaking change… | [File](../../.changes/README.md#file) | Existing contract |
| CLAUDE.md:32–32 | Release Notes For User-Visible Changes — Name it &lt;area&gt;-&lt;short-slug&gt;.md; area matches the conventional-commit scope. There is no version field —… | [File](../../.changes/README.md#file) | Existing contract |
| CLAUDE.md:33–33 | Release Notes For User-Visible Changes — Write the body for a user, not a reviewer: "the player now remembers volume between episodes", not "hoist… | [Writing the body](../../.changes/README.md#writing-the-body) | Existing contract |
| CLAUDE.md:34–34 | Release Notes For User-Visible Changes — type: internal records invisible maintenance. Internal notes stay collapsed in CHANGELOG.md, are omitted… | [Writing the body](../../.changes/README.md#writing-the-body) | Existing contract |
| CLAUDE.md:35–35 | Release Notes For User-Visible Changes — highlight: &lt;short headline&gt; (max 60 characters, rejected on type: internal) marks a note as one of the… | [Writing the body](../../.changes/README.md#writing-the-body) | Existing contract |
| CLAUDE.md:36–36 | Release Notes For User-Visible Changes — Skip the note for test-only changes, docs, CI/workflow plumbing, and pure refactors with no behavior… | [When a note is not needed](../../.changes/README.md#when-a-note-is-not-needed) | Existing contract |
| CLAUDE.md:37–37 | Release Notes For User-Visible Changes — CI enforces this: the "Release note gate" job in .github/workflows/ci.yml fails PRs that change runtime… | [Surfaces built from one set of notes](../architecture/release-pipeline.md#surfaces-built-from-one-set-of-notes) | Existing contract |
| CLAUDE.md:38–38 | Release Notes For User-Visible Changes — The release-notes skill covers writing notes; the release-cut skill covers the full release sequence.… | [Surfaces built from one set of notes](../architecture/release-pipeline.md#surfaces-built-from-one-set-of-notes) | Existing contract |
| CLAUDE.md:39–39 | Release Notes For User-Visible Changes — Validate before finishing: pnpm run release:notes:validate. | [Commands](../../.changes/README.md#commands) | Existing contract |
| CLAUDE.md:40–40 | Release Notes For User-Visible Changes — Announcement drafts and highlight cards are built from the same notes: pnpm --silent run… | [Writing the body](../../.changes/README.md#writing-the-body) | Existing contract |
| CLAUDE.md:41–41 | Release Notes For User-Visible Changes — Pushes to master and v can publish Docker images. A v tag build creates a draft GitHub release. | [Two phases](../architecture/release-pipeline.md#two-phases) | Existing contract |
| CLAUDE.md:42–42 | Release Notes For User-Visible Changes — pnpm run release:verify:draft waits for that tag build (polling until the run is indexed, then gh run… | [Draft verification](../architecture/release-pipeline.md#draft-verification) | Existing contract |
| CLAUDE.md:43–43 | Release Notes For User-Visible Changes — Publishing the GitHub release verifies its Snap assets and automatically uploads them to edge;… | [After verification](../architecture/release-pipeline.md#after-verification) | Existing contract |
| CLAUDE.md:44–44 | Release Notes For User-Visible Changes — Release-post screenshots come only from the release capture script running against the mock servers. Never… | [Screenshots](../../.changes/README.md#screenshots) | Existing contract |
| CLAUDE.md:45–45 | Release Notes For User-Visible Changes — Final task summaries should state whether a release note was added or why it was skipped. | [Plans and completion reports](../development/agent-workflow.md#plans-and-completion-reports) | Moved / consolidated |
| CLAUDE.md:49–54 | AppImage Manager Metadata — AppManager full-download discovery uses appImage.desktop.entry URL fields. Electron Builder generates the… | [Surfaces built from one set of notes](../architecture/release-pipeline.md#surfaces-built-from-one-set-of-notes) | Existing contract |
| CLAUDE.md:58–58 | Upgrade And Migration Compatibility — Users may skip releases. The application must apply all required migrations in dependency order when… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| CLAUDE.md:59–59 | Upgrade And Migration Compatibility — Preserve migration paths for existing persisted data. Do not make deleting a database/profile or… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| CLAUDE.md:60–60 | Upgrade And Migration Compatibility — Create required tables first, add missing columns before dependent indexes/triggers/queries, and make… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| CLAUDE.md:61–61 | Upgrade And Migration Compatibility — For persistence changes, test real SQLite initialization with representative historical schemas and data,… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| CLAUDE.md:62–62 | Upgrade And Migration Compatibility — See libs/shared/database/README.md for SQLite migration ownership and validation guidance. | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| CLAUDE.md:66–66 | Regression Prevention And Test Updates — Before the final summary for any feature, behavior change, bug fix, data-flow change, Electron… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| CLAUDE.md:67–67 | Regression Prevention And Test Updates — Bug fixes must normally include regression coverage that fails on the old behavior and passes with the… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| CLAUDE.md:68–68 | Regression Prevention And Test Updates — Feature work and behavior changes must update existing tests when assertions, fixtures, mocks, routes, or… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| CLAUDE.md:69–73 | Regression Prevention And Test Updates — Default validation ladder: 1. Run targeted unit tests for directly affected projects with pnpm nx test… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| CLAUDE.md:74–74 | Regression Prevention And Test Updates — Electron-specific changes affecting IPC, SQLite, packaged runtime, external players, native file access,… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| CLAUDE.md:75–75 | Regression Prevention And Test Updates — Final task summaries must list tests added or updated, validation commands run with results, and any… | [Test impact and completion](../architecture/validation-map.md#test-impact-and-completion) | Existing contract |
| CLAUDE.md:79–79 | Project Overview — IPTVnator is a cross-platform IPTV player application built with Angular and Electron, supporting M3U/M3U8… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Existing contract |
| CLAUDE.md:81–81 | Project Overview — Dual Environment Support: The application is designed to work in both Electron and as a Progressive Web… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Existing contract |
| CLAUDE.md:87–90 | Agent Bootstrap — bash pnpm install --frozen-lockfile pnpm nx show projects | [Fresh Worktree Bootstrap and Discovery](../architecture/nx-workspace-boundaries.md#fresh-worktree-bootstrap-and-discovery) | Existing contract |
| CLAUDE.md:92–92 | Agent Bootstrap — Run the install step in a fresh worktree before relying on Nx discovery, lint, test, or build commands.… | [Fresh Worktree Bootstrap and Discovery](../architecture/nx-workspace-boundaries.md#fresh-worktree-bootstrap-and-discovery) | Existing contract |
| CLAUDE.md:93–93 | Agent Bootstrap — Re-run the install whenever the checkout moves — git pull, git reset --hard, a rebase, or a worktree… | [Fresh Worktree Bootstrap and Discovery](../architecture/nx-workspace-boundaries.md#fresh-worktree-bootstrap-and-discovery) | Existing contract |
| CLAUDE.md:94–94 | Agent Bootstrap — Never run prettier --write on CLAUDE.md, AGENTS.md or docs/. These files are not Prettier-clean upstream,… | [Protected Markdown edits](../development/agent-workflow.md#protected-markdown-edits) | Corrected safe restore |
| CLAUDE.md:95–95 | Agent Bootstrap — Use scoped path aliases from tsconfig.base.json such as @iptvnator/services, @iptvnator/shared/interfaces,… | [Import Aliases and Public APIs](../architecture/nx-workspace-boundaries.md#import-aliases-and-public-apis) | Existing contract |
| CLAUDE.md:96–96 | Agent Bootstrap — Do not add new imports from legacy bare aliases such as services, shared-interfaces, components,… | [Import Aliases and Public APIs](../architecture/nx-workspace-boundaries.md#import-aliases-and-public-apis) | Existing contract |
| CLAUDE.md:97–97 | Agent Bootstrap — Every Nx project should keep scope:, domain:, and type: tags in project.json. | [Project Tags](../architecture/nx-workspace-boundaries.md#project-tags) | Existing contract |
| CLAUDE.md:98–98 | Agent Bootstrap — See docs/architecture/nx-workspace-boundaries.md for the current Nx tag and alias policy. | [Import Aliases and Public APIs](../architecture/nx-workspace-boundaries.md#import-aliases-and-public-apis) | Existing contract |
| CLAUDE.md:99–100 | Agent Bootstrap — Keep nx and every official @nx/ package on the same exact version; run pnpm run deps:nx:validate after… | [Nx Dependency Updates](../architecture/nx-workspace-boundaries.md#nx-dependency-updates) | Existing contract |
| CLAUDE.md:101–102 | Agent Bootstrap — Use the Node version in .nvmrc for development and CI. Angular 22 requires Node ^22.22.3 &#124;&#124; ^24.15.0 and… | [Angular 22 Toolchain Compatibility](../architecture/nx-workspace-boundaries.md#angular-22-toolchain-compatibility) | Existing contract |
| CLAUDE.md:103–107 | Agent Bootstrap — Vite 8.1.5, resolved through Angular's build tooling, retains upstream precise matchers and adds bounded… | [Vite Dev-Server Patch](../architecture/nx-workspace-boundaries.md#vite-dev-server-patch) | Existing contract |
| CLAUDE.md:108–118 | Agent Bootstrap — app-builder-lib 26.15.7 (electron-builder's macOS signing) is patched in… | [Electron Builder signing patch](../architecture/nx-workspace-boundaries.md#electron-builder-signing-patch) | Added missing detail |
| CLAUDE.md:119–125 | Agent Bootstrap — node-gyp is a declared root devDependency because apps/electron-backend/build-embedded-mpv.js resolves it… | [Packaging State](../architecture/embedded-mpv-native.md#packaging-state) | Existing contract |
| CLAUDE.md:126–130 | Agent Bootstrap — nx-electron@22.0.0 uses a local Nx 23 export-path patch and an explicit webpack-node-externals package… | [Angular 22 Toolchain Compatibility](../architecture/nx-workspace-boundaries.md#angular-22-toolchain-compatibility) | Existing contract |
| CLAUDE.md:131–137 | Agent Bootstrap — A directory holding files consumed by other projects must be an Nx project. Nx builds its graph from… | [Shared Stylesheets and Cache Inputs](../architecture/nx-workspace-boundaries.md#shared-stylesheets-and-cache-inputs) | Existing contract |
| CLAUDE.md:138–141 | Agent Bootstrap — Update Nx with pnpm nx migrate nx@&lt;target&gt; --skipInstall, regenerate the lockfile, run generated… | [Nx Dependency Updates](../architecture/nx-workspace-boundaries.md#nx-dependency-updates) | Existing contract |
| CLAUDE.md:142–142 | Agent Bootstrap — Repository-specific skills live under .codex/skills/. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| CLAUDE.md:143–144 | Agent Bootstrap — Frontmatter descriptions are trigger-only and begin with Use when; keep each skill at or below 500 words. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| CLAUDE.md:145–146 | Agent Bootstrap — Run pnpm run skills:validate after editing a committed skill or a literal path it documents. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| CLAUDE.md:147–148 | Agent Bootstrap — Keep .codex and .claude copies of release-notes and release-cut byte-identical. | [Repository skills](../development/agent-workflow.md#repository-skills) | Moved / consolidated |
| CLAUDE.md:152–192 | Building and Serving — bash # Serve the Angular web app only (development mode, baseHref="/") pnpm run serve:frontend # or nx… | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:196–198 | Windows Embedded MPV Pin Maintenance — PR, master, and tag builds resolve the Windows runtime only from… | [Windows CI pin lifecycle](../../tools/embedded-mpv/README.md#windows-ci-pin-lifecycle) | Existing contract |
| CLAUDE.md:199–200 | Windows Embedded MPV Pin Maintenance — Validate the checked-in schema and provenance with pnpm embedded-mpv:windows-runtime-pin:check. | [Windows CI pin lifecycle](../../tools/embedded-mpv/README.md#windows-ci-pin-lifecycle) | Existing contract |
| CLAUDE.md:201–204 | Windows Embedded MPV Pin Maintenance — Prepare a manual rotation with pnpm embedded-mpv:windows-runtime-pin:refresh -- --force. The weekly… | [Windows CI pin lifecycle](../../tools/embedded-mpv/README.md#windows-ci-pin-lifecycle) | Existing contract |
| CLAUDE.md:205–208 | Windows Embedded MPV Pin Maintenance — The PAT-backed refresh job must keep every third-party action pinned to a full commit. Do not mirror the… | [Windows CI pin lifecycle](../../tools/embedded-mpv/README.md#windows-ci-pin-lifecycle) | Existing contract |
| CLAUDE.md:212–212 | Electron CDP Debugging — Start Electron in dev mode with: nx serve electron-backend | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:213–213 | Electron CDP Debugging — Package-script equivalent: pnpm run serve:backend | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:214–214 | Electron CDP Debugging — The workspace is configured to always launch Electron with: --remote-debugging-port=9222 | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:215–215 | Electron CDP Debugging — Use CDP clients (Chrome DevTools Protocol tools) against: 127.0.0.1:9222 | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:216–216 | Electron CDP Debugging — When the task is Electron automation/debugging, use the electron skill | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:217–217 | Electron CDP Debugging — Do not auto-open DevTools during normal CDP automation. In development, DevTools is opt-in via… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:218–218 | Electron CDP Debugging — If DevTools is open, agent-browser --cdp 9222 ... may attach to the DevTools page instead of the IPTVnator… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:219–219 | Electron CDP Debugging — The app holds a single-instance lock (acquireSingleInstanceLock in… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:221–221 | Electron CDP Debugging — For startup tracing or white-screen debugging: | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:223–225 | Electron CDP Debugging — bash IPTVNATOR_TRACE_STARTUP=1 nx serve electron-backend | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:227–227 | Electron CDP Debugging — Useful narrower flags: | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:229–229 | Electron CDP Debugging — IPTVNATOR_TRACE_IPC=1 traces renderer window.electron. bridge calls | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:230–230 | Electron CDP Debugging — IPTVNATOR_TRACE_DB=1 traces DB worker requests and DB progress events | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:231–231 | Electron CDP Debugging — IPTVNATOR_TRACE_SQL=1 traces SQLite statements in both main and worker connections | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:232–232 | Electron CDP Debugging — IPTVNATOR_TRACE_WINDOW=1 traces BrowserWindow navigation/load lifecycle | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:233–233 | Electron CDP Debugging — IPTVNATOR_TRACE_PLAYER=1 traces external-player activity, bounded Embedded MPV runtime-probe stderr, and… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:234–234 | Electron CDP Debugging — IPTVNATOR_TRACE_RENDERER_CONSOLE=1 mirrors renderer console logs into the Electron terminal | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:235–235 | Electron CDP Debugging — IPTVNATOR_PERF_CAPTURE=1 enables development/test-only, redacted M3U and Xtream preload IPC… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:236–236 | Electron CDP Debugging — IPTVNATOR_PERF_WORKER_PROFILING=1 enables development/test-only, request-scoped worker… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:238–240 | Electron CDP Debugging — Settings, portal request/response, and trace payloads must use @iptvnator/shared/logging or the redacting… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:242–242 | Electron CDP Debugging — If the Nx daemon gets into a bad state before rerunning Electron: | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:244–246 | Electron CDP Debugging — bash pnpm nx reset | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:248–248 | Electron CDP Debugging — Use global agent-browser (preferred): | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:250–263 | Electron CDP Debugging — bash # Verify CDP targets agent-browser --cdp 9222 tab list # Switch to the app tab and inspect… | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:265–265 | Electron CDP Debugging — If agent-browser is not in PATH, use: | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:267–269 | Electron CDP Debugging — bash npx --yes agent-browser --cdp 9222 tab list | [Start and attach](../development/electron-debugging.md#start-and-attach) | Moved / consolidated |
| CLAUDE.md:273–294 | Testing — bash # Run frontend tests pnpm run test:frontend # or pnpm nx test web # Run backend tests pnpm run… | [Unit And Type Checks](../architecture/validation-map.md#unit-and-type-checks) | Existing contract |
| CLAUDE.md:296–296 | Testing — Before finishing behavior changes or bug fixes, follow Regression Prevention And Test Updates above and… | [Unit And Type Checks](../architecture/validation-map.md#unit-and-type-checks) | Existing contract |
| CLAUDE.md:300–307 | Linting — bash # Lint all projects (CI runs this on master; PRs lint affected projects) pnpm run lint # Lint a… | [Unit And Type Checks](../architecture/validation-map.md#unit-and-type-checks) | Existing contract |
| CLAUDE.md:309–315 | Linting — CI lints affected projects on PRs (nx affected) and every project on master pushes… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:317–317 | Linting — Production TypeScript: hard maximum 400 lines. | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:318–324 | Linting — Tests: 1200. /.spec.ts, /.spec-data.ts, /.e2e.ts and everything under apps/-e2e/ — a spec is a flat list… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:325–326 | Linting — Blank lines and comments are not counted (skipBlankLines, skipComments), so a docblock is never the reason… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:328–340 | Linting — Pre-existing oversized files are baselined in tools/eslint/max-lines-baseline.mjs; regenerate the baseline… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:342–349 | Linting — Project lint targets that shell out to eslint must quote the glob, e.g. eslint "apps/&lt;project&gt;//.ts". An… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:353–360 | Legacy Desktop Profile Migration — electron-profile-bootstrap.ts selects the known v0.19 electron-backend profile before eager main-process… | [Desktop upgrades from legacy profiles](../architecture/m3u-playlist-module.md#desktop-upgrades-from-legacy-profiles) | Existing contract |
| CLAUDE.md:362–368 | Legacy Desktop Profile Migration — Startup shows AppStartupStatusComponent until the initial route and source inventory are ready, including… | [Desktop upgrades from legacy profiles](../architecture/m3u-playlist-module.md#desktop-upgrades-from-legacy-profiles) | Existing contract |
| CLAUDE.md:374–374 | Monorepo Structure (Nx Workspace) — This is an Nx monorepo with the following structure: | [Placement Decision](../architecture/nx-workspace-boundaries.md#placement-decision) | Existing contract |
| CLAUDE.md:376–376 | Monorepo Structure (Nx Workspace) — apps/web - Angular application (frontend, shared by Electron and PWA) | [Placement Decision](../architecture/nx-workspace-boundaries.md#placement-decision) | Existing contract |
| CLAUDE.md:377–377 | Monorepo Structure (Nx Workspace) — apps/electron-backend - Electron main process | [Main-process ownership](../development/electron-debugging.md#main-process-ownership) | Moved / consolidated |
| CLAUDE.md:378–378 | Monorepo Structure (Nx Workspace) — apps/web-backend - HTTP backend for the self-hosted PWA (/parse, /parse-xml, /xtream, /stalker CORS proxy… | [Web Backend](../architecture/pwa-self-hosted.md#web-backend) | Existing contract |
| CLAUDE.md:379–379 | Monorepo Structure (Nx Workspace) — apps/remote-control-web - Mobile remote-control web app served by the Electron backend | [Remote Web App](../architecture/remote-control.md#remote-web-app) | Existing contract |
| CLAUDE.md:380–380 | Monorepo Structure (Nx Workspace) — apps/web-e2e - Playwright E2E tests against the web app | [E2E](../architecture/validation-map.md#e2e) | Existing contract |
| CLAUDE.md:381–381 | Monorepo Structure (Nx Workspace) — apps/electron-backend-e2e - Playwright E2E tests against the Electron app | [E2E](../architecture/validation-map.md#e2e) | Existing contract |
| CLAUDE.md:382–382 | Monorepo Structure (Nx Workspace) — apps/stalker-mock-server - Mock Stalker/Ministra portal for dev and E2E | [Stalker Mock Server Architecture](../architecture/stalker-mock-server.md#stalker-mock-server-architecture) | Existing contract |
| CLAUDE.md:383–383 | Monorepo Structure (Nx Workspace) — apps/xtream-mock-server - Mock Xtream Codes API for dev and E2E | [Xtream Mock Server — Architecture](../architecture/xtream-mock-server.md#xtream-mock-server--architecture) | Existing contract |
| CLAUDE.md:384–384 | Monorepo Structure (Nx Workspace) — apps/website - Astro + Tailwind landing page, blog (guides carry faq: frontmatter → FAQPage JSON-LD and… | [IPTVnator Website](../../apps/website/README.md#iptvnator-website) | Existing contract |
| CLAUDE.md:385–411 | Monorepo Structure (Nx Workspace) — libs/ - Shared libraries: - epg/data-access - EPG services, runtime bridge, program normalization -… | [Placement Decision](../architecture/nx-workspace-boundaries.md#placement-decision) | Existing contract |
| CLAUDE.md:415–415 | Frontend Architecture (Angular) — State Management: Uses NgRx for playlist state management: | [State Management (libs/m3u-state/)](../architecture/m3u-playlist-module.md#state-management-libsm3u-state) | Existing contract |
| CLAUDE.md:417–417 | Frontend Architecture (Angular) — Store configuration in apps/web/src/app/app.config.ts | [State Management (libs/m3u-state/)](../architecture/m3u-playlist-module.md#state-management-libsm3u-state) | Existing contract |
| CLAUDE.md:418–418 | Frontend Architecture (Angular) — Playlist state, actions, effects, and reducers in libs/m3u-state/ | [State Management (libs/m3u-state/)](../architecture/m3u-playlist-module.md#state-management-libsm3u-state) | Existing contract |
| CLAUDE.md:419–419 | Frontend Architecture (Angular) — Entity adapter pattern for managing playlists collection | [State Management (libs/m3u-state/)](../architecture/m3u-playlist-module.md#state-management-libsm3u-state) | Existing contract |
| CLAUDE.md:420–420 | Frontend Architecture (Angular) — Router store integration for route-based state | [State Management (libs/m3u-state/)](../architecture/m3u-playlist-module.md#state-management-libsm3u-state) | Existing contract |
| CLAUDE.md:422–422 | Frontend Architecture (Angular) — XtreamStore Architecture (Signal Store with Feature Composition): | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:424–424 | Frontend Architecture (Angular) — The Xtream Codes module uses NgRx Signal Store with a layered architecture: | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:426–455 | Frontend Architecture (Angular) — ┌─────────────────────────────────────────────────────────────────┐ │ PRESENTATION LAYER │ │ Components… | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:457–457 | Frontend Architecture (Angular) — File structure: | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:459–492 | Frontend Architecture (Angular) — libs/portal/xtream/ ├── data-access/src/lib/ │ ├── stores/ │ │ ├── features/ │ │ │ ├──… | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:494–494 | Frontend Architecture (Angular) — Key patterns: | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:496–496 | Frontend Architecture (Angular) — Feature stores: Each with.feature.ts uses signalStoreFeature() for focused functionality | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:497–497 | Frontend Architecture (Angular) — Facade pattern: XtreamStore composes all features, maintaining backward compatibility | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:498–499 | Frontend Architecture (Angular) — Data source abstraction: IXtreamDataSource has SQLite-backed and API/in-memory implementations | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:500–503 | Frontend Architecture (Angular) — Factory injection: provideXtreamDataSource() selects ElectronXtreamDataSource only when… | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:504–516 | Frontend Architecture (Angular) — Catalog lazy loading: catalog grids scroll infinitely instead of paging. withSelection keeps a… | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:518–518 | Frontend Architecture (Angular) — Xtream data strategies by runtime capability: | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:520–523 | Frontend Architecture (Angular) — &#124; Capability &#124; Strategy &#124; &#124; --------------------------------- &#124;… | [Store composition and catalog windowing](../architecture/xtream-portal-compatibility.md#store-composition-and-catalog-windowing) | Added / consolidated |
| CLAUDE.md:527–527 | M3U Playlist Module Architecture: — The M3U playlist module handles traditional M3U/M3U8 playlists with support for 90,000+ channels. | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:529–543 | M3U Playlist Module Architecture: — ┌─────────────────────────────────────────────────────────────────────┐ │ VIDEO PLAYER PAGE │ │… | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:545–545 | M3U Playlist Module Architecture: — The live EPG panel is a horizontal timeline ribbon under the player (app-epg-timeline,… | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:547–547 | M3U Playlist Module Architecture: — Collapsible live channel rail (M3U player, Xtream/Stalker live layouts, unified favorites/recent live… | [Collapsible Live Sidebar](../architecture/iptvnator-ui-guidelines.md#collapsible-live-sidebar) | Existing contract |
| CLAUDE.md:549–549 | M3U Playlist Module Architecture: — Cover grids (Xtream/Stalker VOD + series catalogs, favorites/recent, dashboard rails): sized by… | [Cover Grids](../architecture/iptvnator-ui-guidelines.md#cover-grids) | Existing contract |
| CLAUDE.md:551–551 | M3U Playlist Module Architecture: — Radio Channel Layout (when channel.radio === 'true'): | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:553–567 | M3U Playlist Module Architecture: — ┌─────────────────────────────────────────────────────────────────────┐ │ ┌─────────────┐… | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:569–569 | M3U Playlist Module Architecture: — Key radio behavior: | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:571–571 | M3U Playlist Module Architecture: — Detection: channel.radio === 'true' (string from M3U radio attribute) | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:572–572 | M3U Playlist Module Architecture: — The audio player always renders inline — shouldShowInlinePlayer is bypassed for radio | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:573–573 | M3U Playlist Module Architecture: — EPG panel is conditionally hidden in the template when radio is active | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:574–574 | M3U Playlist Module Architecture: — Volume is shared with video player via localStorage key 'volume' | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:575–575 | M3U Playlist Module Architecture: — Keyboard: ArrowUp/Down adjusts volume by 5%, M toggles mute | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:576–576 | M3U Playlist Module Architecture: — Component: libs/ui/playback/src/lib/audio-player/audio-player.component.ts | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:578–591 | M3U Playlist Module Architecture: — M3U Movie Recognition (VOD detail instead of the EPG zone): an M3U entry recognized as a movie FILE swaps… | [Movie Recognition (VOD Detail View)](../architecture/m3u-playlist-module.md#movie-recognition-vod-detail-view) | Existing contract |
| CLAUDE.md:593–601 | M3U Playlist Module Architecture: — M3U playback mode is independent of this metadata gate: isLikelyM3uVod recognizes video-file extensions… | [M3U Playback Mode](../architecture/m3u-playlist-module.md#m3u-playback-mode) | Existing contract |
| CLAUDE.md:603–603 | M3U Playlist Module Architecture: — Channel List Component Structure (parent coordinator pattern): | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:605–613 | M3U Playlist Module Architecture: — libs/ui/components/src/lib/channel-list-container/ ├── channel-list-container.component.ts # Parent -… | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:615–615 | M3U Playlist Module Architecture: — Key patterns: | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:617–617 | M3U Playlist Module Architecture: — EnrichedChannel: Pre-computed EPG data attached to channels for performance | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:618–618 | M3U Playlist Module Architecture: — Parent coordinator: Manages shared signals (channelEpgMap, progressTick, favoriteIds) | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:619–619 | M3U Playlist Module Architecture: — Virtual scrolling: CDK virtual scroll for 90,000+ channel lists | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:620–620 | M3U Playlist Module Architecture: — Infinite scroll: IntersectionObserver in groups view loads 50 items at a time | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:621–621 | M3U Playlist Module Architecture: — Global progress tick: Single 30s interval instead of per-item intervals | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:623–623 | M3U Playlist Module Architecture: — State management via NgRx (libs/m3u-state/): | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:625–625 | M3U Playlist Module Architecture: — PlaylistActions: loadPlaylists, addPlaylist, removePlaylist, parsePlaylist | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:626–626 | M3U Playlist Module Architecture: — ChannelActions: setChannels, setActiveChannel, setAdjacentChannelAsActive | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:627–627 | M3U Playlist Module Architecture: — EpgActions: setActiveEpgProgram, setCurrentEpgProgram, setEpgAvailableFlag | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:628–628 | M3U Playlist Module Architecture: — FavoritesActions: updateFavorites, setFavorites, hydrateFavorites | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:630–630 | M3U Playlist Module Architecture: — See docs/architecture/m3u-playlist-module.md for complete documentation. | [Channel List Container](../architecture/m3u-playlist-module.md#channel-list-container) | Existing contract |
| CLAUDE.md:632–632 | M3U Playlist Module Architecture: — Routing: Lazy-loaded routes in apps/web/src/app/app.routes.ts. All user-facing routes are nested under the… | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:634–634 | M3U Playlist Module Architecture: — Dashboard: /workspace/dashboard; sources overview: /workspace/sources | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:635–635 | M3U Playlist Module Architecture: — M3U player: /workspace/playlists/:id (children: favorites, recent, :view) — routes in… | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:636–636 | M3U Playlist Module Architecture: — Xtream Codes: /workspace/xtreams/:id (children: live, vod, series, search, actor/:personId, discover,… | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:637–637 | M3U Playlist Module Architecture: — Stalker portal: /workspace/stalker/:id (children: itv, vod, radio, series, favorites, recent, search,… | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:638–638 | M3U Playlist Module Architecture: — Global collections: /workspace/global-favorites, /workspace/global-recent | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:639–639 | M3U Playlist Module Architecture: — Global search: /workspace/search (Electron-only; a guard redirects the PWA to /workspace/sources) | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:640–644 | M3U Playlist Module Architecture: — Downloads: /workspace/downloads with focused /workspace/downloads/:downloadId; source-scoped equivalents… | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:645–645 | M3U Playlist Module Architecture: — Settings: /workspace/settings/:section — one page per section (general, playback, epg, dashboard,… | [Route Contract](../architecture/workspace-shell.md#route-contract) | Existing contract |
| CLAUDE.md:647–647 | M3U Playlist Module Architecture: — Service Architecture (Factory Pattern): | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:649–649 | M3U Playlist Module Architecture: — Abstract DataService class in libs/services/src/lib/data.service.ts defines the contract | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:650–652 | M3U Playlist Module Architecture: — Two environment-specific implementations: - ElectronService… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:653–659 | M3U Playlist Module Architecture: — Factory function DataFactory() in apps/web/src/app/app.config.ts determines which implementation to… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:663–666 | Data Storage (Environment-Specific): — Electron: SQLite database via Drizzle ORM (better-sqlite3 driver) - Location:… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:667–670 | Data Storage (Environment-Specific): — PWA (Web): IndexedDB via ngx-indexed-db - Browser-based NoSQL storage - Same schema structure but… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:674–678 | TypeScript File Size Rule: — Keep production TypeScript files under 300 lines. Hard maximum is 350–400 lines, and CI enforces the 400.… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:680–680 | TypeScript File Size Rule: — When creating new files, design them to stay within this limit from the start. | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:681–681 | TypeScript File Size Rule: — When adding a feature to an existing file that would push it past 350 lines, refactor first: extract… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:682–682 | TypeScript File Size Rule: — When you notice a file already exceeds 350 lines, proactively suggest a refactoring (or perform it if the… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:684–684 | TypeScript File Size Rule: — Typical split strategies: | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:686–686 | TypeScript File Size Rule: — Angular components: extract child components, move logic to a dedicated service or store feature | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:687–687 | TypeScript File Size Rule: — Signal store features: split into smaller with feature functions in separate files | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:688–688 | TypeScript File Size Rule: — Services: split by responsibility (e.g. separate API, transformation, and state concerns) | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:689–689 | TypeScript File Size Rule: — Utility files: group by domain and export from a barrel index.ts | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:691–691 | TypeScript File Size Rule: — This rule exists to keep the codebase navigable and reviewable. A 150-line file is always preferable to a… | [TypeScript File Size](../architecture/nx-workspace-boundaries.md#typescript-file-size) | Existing contract |
| CLAUDE.md:697–697 | Angular Coding Standards: — This project uses modern Angular signal-based APIs and patterns. ALWAYS use the following: | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:699–699 | Angular Coding Standards: — Component Queries: Use viewChild(), viewChildren(), contentChild(), contentChildren() instead of… | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:701–709 | Angular Coding Standards: — typescript // ✅ Correct - Signal-based readonly menu = viewChild.required&lt;MatMenu&gt;('menuRef'); readonly… | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:711–711 | Angular Coding Standards: — Important: When using signals in templates with properties that expect non-signal values, unwrap the… | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:713–719 | Angular Coding Standards: — html &lt;!-- ✅ Correct - Unwrap the signal --&gt; &lt;button [matMenuTriggerFor]="menu()"&gt;Open Menu&lt;/button&gt; &lt;!-- ❌… | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:721–721 | Angular Coding Standards: — Component Inputs/Outputs: Use input() and output() functions instead of @Input() and @Output() decorators | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:723–733 | Angular Coding Standards: — typescript // ✅ Correct - Signal-based readonly title = input.required&lt;string&gt;(); readonly size =… | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:735–735 | Angular Coding Standards: — Reactive State: Use signal primitives for reactive state management | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:737–747 | Angular Coding Standards: — typescript // ✅ Use signal(), computed(), effect(), linkedSignal() readonly count = signal(0); readonly… | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:749–749 | Angular Coding Standards: — Host Bindings: Use @HostBinding() and @HostListener() decorators (these don't have signal equivalents yet) | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:751–754 | Angular Coding Standards: — typescript @HostBinding('class.active') get isActive() { return this.active(); } @HostListener('click')… | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:756–756 | Angular Coding Standards: — Control Flow: Use @if, @for, @switch instead of ngIf, ngFor, ngSwitch | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:758–771 | Angular Coding Standards: — typescript // ✅ Correct - Modern syntax @if (isLoggedIn()) { &lt;p&gt;Welcome!&lt;/p&gt; } @for (item of items();… | [Angular conventions](../development/agent-workflow.md#angular-conventions) | Moved / consolidated |
| CLAUDE.md:775–775 | Backend Architecture (Electron) — Main Entry: apps/electron-backend/src/main.ts | [Window Chrome And Custom Title Bar](../architecture/workspace-shell.md#window-chrome-and-custom-title-bar) | Existing contract |
| CLAUDE.md:777–777 | Backend Architecture (Electron) — Bootstraps Electron app and initializes database | [Window Chrome And Custom Title Bar](../architecture/workspace-shell.md#window-chrome-and-custom-title-bar) | Existing contract |
| CLAUDE.md:778–778 | Backend Architecture (Electron) — Registers event handlers for IPC communication | [Window Chrome And Custom Title Bar](../architecture/workspace-shell.md#window-chrome-and-custom-title-bar) | Existing contract |
| CLAUDE.md:779–779 | Backend Architecture (Electron) — Creates the main window per the startup window mode (app/app.ts initMainWindow, resolver in… | [Window Chrome And Custom Title Bar](../architecture/workspace-shell.md#window-chrome-and-custom-title-bar) | Existing contract |
| CLAUDE.md:780–780 | Backend Architecture (Electron) — Persists the app zoom level (issue #1109): the preload restores it with webFrame.setZoomLevel (temporary,… | [Window Chrome And Custom Title Bar](../architecture/workspace-shell.md#window-chrome-and-custom-title-bar) | Existing contract |
| CLAUDE.md:781–781 | Backend Architecture (Electron) — Recovers a renderer reload on an in-app route: the packaged renderer is index.html over file:// with path… | [Window Chrome And Custom Title Bar](../architecture/workspace-shell.md#window-chrome-and-custom-title-bar) | Existing contract |
| CLAUDE.md:782–782 | Backend Architecture (Electron) — Holds a single-instance lock (app/services/single-instance.ts), requested after the userData override so… | [Window Chrome And Custom Title Bar](../architecture/workspace-shell.md#window-chrome-and-custom-title-bar) | Existing contract |
| CLAUDE.md:786–786 | Database: — ORM: Drizzle ORM with better-sqlite3 (local SQLite file) | [Exports](../../libs/shared/database/README.md#exports) | Existing contract |
| CLAUDE.md:787–787 | Database: — Location: ~/.iptvnator/databases/iptvnator.db (avoids spaces in path) | [Exports](../../libs/shared/database/README.md#exports) | Existing contract |
| CLAUDE.md:788–801 | Database: — Schema (libs/shared/database/src/lib/schema.ts — canonical;… | [Exports](../../libs/shared/database/README.md#exports) | Existing contract |
| CLAUDE.md:802–805 | Database: — Connection: libs/shared/database/src/lib/connection.ts - createTables() auto-creates tables on init… | [Exports](../../libs/shared/database/README.md#exports) | Existing contract |
| CLAUDE.md:809–812 | IPC Communication: — Preload script: apps/electron-backend/src/app/api/main.preload.ts - Exposes window.electron API via… | [Preload API Type Contract](../architecture/electron-security.md#preload-api-type-contract) | Existing contract |
| CLAUDE.md:813–823 | IPC Communication: — Event handlers: apps/electron-backend/src/app/events/ - database.events.ts - Database CRUD operations -… | [Preload API Type Contract](../architecture/electron-security.md#preload-api-type-contract) | Existing contract |
| CLAUDE.md:825–825 | IPC Communication: — Workers (apps/electron-backend/src/app/workers/): | [Current Ownership](../architecture/sqlite-db-worker.md#current-ownership) | Existing contract |
| CLAUDE.md:827–827 | IPC Communication: — EPG parsing: epg-parser.worker.ts; main-process worker lifecycle is coordinated from… | [Current Ownership](../architecture/sqlite-db-worker.md#current-ownership) | Existing contract |
| CLAUDE.md:828–828 | IPC Communication: — Non-EPG SQLite work: database.worker.ts (see docs/architecture/sqlite-db-worker.md). Catalog deletes and… | [Current Ownership](../architecture/sqlite-db-worker.md#current-ownership) | Existing contract |
| CLAUDE.md:829–829 | IPC Communication: — Playlist refresh: playlist-refresh.worker.ts; explicit cancellation is main-process-owned and terminates… | [Current Ownership](../architecture/sqlite-db-worker.md#current-ownership) | Existing contract |
| CLAUDE.md:833–837 | Xtream Category Management — The Electron Live TV, Movies, and Series category dialog applies Select/Deselect to search results while a… | [Behavior Notes](../architecture/category-management.md#behavior-notes) | Existing contract |
| CLAUDE.md:843–851 | Xtream Connection Test — Add/Edit source Test HTTPS and HTTP discloses plaintext credential use before the click and can replace an… | [Connection Input](../architecture/xtream-portal-compatibility.md#connection-input) | Existing contract |
| CLAUDE.md:855–863 | Xtream Live Auto Format — The routed Xtream live host supplies liveAutoTsUrl only for Auto with explicit HLS+TS account evidence,… | [Initial Auto HLS failure](../architecture/xtream-portal-compatibility.md#initial-auto-hls-failure) | Existing contract |
| CLAUDE.md:867–883 | Xtream Catch-Up Server Timezone — The {Y-m-d:H-M} segment of a timeshift URL is read by the panel in ITS timezone (server_info.timezone),… | [Catch-Up Playback URLs](../architecture/xtream-portal-compatibility.md#catch-up-playback-urls) | Existing contract |
| CLAUDE.md:887–889 | M3U URL User-Agent — PlaylistsService.getPlaylist() joins the per-playlist mutation queue so a route opened during refresh… | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| CLAUDE.md:890–894 | M3U URL User-Agent — The URL import form accepts an optional User-Agent and stores it as Playlist.userAgent. Electron sends it… | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| CLAUDE.md:895–897 | M3U URL User-Agent — Reuse the existing source editor and channel-over-playlist playback header precedence. Contract:… | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| CLAUDE.md:901–901 | Playlist Support: — M3U/M3U8 files (local or URL) | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| CLAUDE.md:902–902 | Playlist Support: — Xtream Codes API (username, password, serverUrl) | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| CLAUDE.md:903–903 | Playlist Support: — Stalker portal (macAddress, url) | [User-Agent for URL sources](../architecture/m3u-playlist-module.md#user-agent-for-url-sources) | Existing contract |
| CLAUDE.md:905–920 | Playlist Support: — Stalker playback links: create_link runs only when the catalog row sets use_http_tmp_link or… | [Playback Link Resolution](../architecture/stalker-portal.md#playback-link-resolution) | Corrected; see decisions |
| CLAUDE.md:922–941 | Playlist Support: — Opening a playlist from the OS (Electron only): a .m3u/.m3u8 path passed on the command line, opened… | [Opening playlists from the operating system](../architecture/m3u-playlist-module.md#opening-playlists-from-the-operating-system) | Added / consolidated |
| CLAUDE.md:943–959 | Playlist Support: — The OS-level registration that makes those paths reachable is fileAssociations in electron-builder.json —… | [Opening playlists from the operating system](../architecture/m3u-playlist-module.md#opening-playlists-from-the-operating-system) | Added / consolidated |
| CLAUDE.md:963–968 | Video Players: — The Embedded MPV native-view dock follows app theme tokens as a solid app surface, including Material… | [Player And EPG Theme Boundaries](../architecture/iptvnator-ui-guidelines.md#player-and-epg-theme-boundaries) | Existing contract |
| CLAUDE.md:970–977 | Video Players: — Built-in web players: HTML5+hls.js, Video.js, and ArtPlayer. The HTML5 player and ArtPlayer pick their… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| CLAUDE.md:978–986 | Video Players: — mpegts.js 1.8.1 errors from all three built-in players cross one version-locked structured evidence… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| CLAUDE.md:987–1119 | Video Players: — Browser playback diagnostics and recovery policy live in libs/playback/util and are exported by… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| CLAUDE.md:1120–1125 | Video Players: — M3U Favorites and Recently Viewed resolve Channel.drm into ResolvedPortalPlayback.drm through… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| CLAUDE.md:1126–1150 | Video Players: — DASH + ClearKey (M3U module): .mpd channels play through a lazily loaded Shaka Player source engine inside… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| CLAUDE.md:1151–1165 | Video Players: — Stream info popover: an info button in the top-right corner of the shared controls overlay shows live… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| CLAUDE.md:1166–1166 | Video Players: — External players: MPV, VLC (via IPC to Electron backend) | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| CLAUDE.md:1167–1181 | Video Players: — Display sleep during playback: PlaybackKeepAwakeService… | [Codec And Container Diagnostics](../architecture/embedded-inline-playback.md#codec-and-container-diagnostics) | Existing contract |
| CLAUDE.md:1182–1182 | Video Players: — Embedded MPV (experimental, macOS/Windows/Linux): renders mpv video inside the Electron window through a… | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1182–1182 | Video Players: — Two per-session knobs are captured at session creation from the main-process settings mirror… | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1182–1182 | Video Players: — Contract: docs/architecture/embedded-mpv-native.md ("Session Options", "Network Auto-Reconnect"). macOS… | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1182–1182 | Video Players: — Windows uses in-process libmpv with --wid against an app-owned child HWND; | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1182–1182 | Video Players: — Linux spawns an out-of-process mpv --wid=&lt;x11-window&gt; controlled over a JSON IPC socket (X11/XWayland… | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1182–1182 | Video Players: — Renderer bounds are CSS pixels; the service converts them to native units in the main process… | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1182–1182 | Video Players: — Arrow-key and ±10 s button steps go through the relative seekEmbeddedMpvBy IPC (mpv seek &lt;delta&gt;… | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1182–1182 | Video Players: — Service: apps/electron-backend/src/app/services/embedded-mpv-native.service.ts; full architecture:… | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1183–1314 | Video Players: — Embedded MPV frame-copy engine (experimental, macOS Apple Silicon + Linux x64 + Windows; enabled via… | [How It Is Embedded](../architecture/embedded-mpv-native.md#how-it-is-embedded) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Shared player-controls layer: libs/ui/playback/src/lib/player-controls/ exports the engine-neutral… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Its subtitle menu carries capability-gated advanced subtitle support (#1408): external subtitle file… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — HTML5/ArtPlayer implement it through the neutral source bridge (.srt/.vtt via a DOM file picker with… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Embedded MPV frame-copy implements it through new helper protocol commands… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Video.js shared mode, vendor-chrome paths, native-view, and the Linux out-of-process path advertise no… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Contract details: docs/architecture/player-controls-contract.md ("Advanced subtitle support"). | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Shared controls include a per-session quality menu (Auto + “1080p”-style levels via setQualityLevel; | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — AUTO_QUALITY_LEVEL_ID restores ABR): the capability derives from the manifest — advertised only when the… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — In fullscreen, app-player-controls shows a pointer-transparent media-title overlay at the top while… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Persisted Settings.webPlayerSharedControls is default-ON (absent stored values coerce with !== false in… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — The shared surface has explicit touch semantics (ControlsSurface.wasTouchInteraction): viewport taps… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Only keyboard-originated focus pins the bar open: Chromium also focuses a clicked &lt;button&gt;, so… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — A completed pointer click then releases the focus it left on the control (onBarClick →… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Keyboard activation (empty pointerType) keeps focus, only buttons and range sliders are released, Chromium… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Deliberately dropped vs. vendor chrome (opt-out retains them): Video.js spatial navigation, ArtPlayer… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — WebPlayerViewComponent snapshots the preference into the immutable token for each new player host. | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — The parent /workspace route awaits the initial SettingsStore load, including cold-start direct links,… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Saving applies to the next host without an application restart; an existing session never changes controls… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Embedded MPV ignores the web-player preference: frame-copy always uses shared DOM controls through… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — The Embedded MPV host selects exactly one controls UI for its reported engine. | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — showControls=false detaches the shared surface, modal overlays gate frame-copy playback shortcuts,… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Same-session IPC replies yield to a broadcast snapshot received while the command was pending, so a… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — The built-in HTML5/hls.js player is the second guarded consumer: HtmlVideoPlayerComponent provides a… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — HtmlVideoElementSession owns native video-event lifecycle, persisted volume, and start-time/time/ended… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Video.js is the third guarded consumer: VjsPlayerComponent provides a component-scoped… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Reset-driven raw MPEG-TS changes pause first, coalesce to the latest desired source, preserve actual… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — In shared-controls mode, Video.js native controls, click/double-click/hotkey actions, and spatial… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — ArtPlayer is the fourth guarded consumer: ArtPlayerComponent provides a component-scoped… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — ArtPlayerSourceSession owns HLS/DASH(Shaka)/MPEG-TS/native sources, the neutral web-video bridge, exact… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Shared ArtPlayer mode uses authoritative live/VOD metadata, HLS/Shaka/native tracks and caption… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Vendor chrome/hotkeys are disabled, and a transparent capture layer gives shared controls exclusive click… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — WebPlayerViewComponent.resolvedIsLive supplies authoritative metadata; visible playback diagnostics… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — The view also renders app-fullscreen-channel-panel beside the engine, staged on fullscreenSurface and… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Providers: M3U VideoPlayerComponent (app-m3u-fullscreen-channel-list, a local icon-only… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Xtream's two PortalChannelsListComponent instances relay favorite toggles through… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — CDK overlays follow the fullscreen element via FullscreenOverlayContainer in app.config.ts. | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Series playback gets the same panel as an episode list: PortalInlinePlayerComponent — the component both… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Movies never get it (contentType !== 'episode' → null), external MPV/VLC never mount the inline player,… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — M3U also zaps with PageUp/PageDown, yielding to already-handled events and menu/dialog or scrollable-list… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — While the live web-player host owns fullscreen (itself, or through the nested surface a legacy player… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Contract section "Fullscreen channel panel" in docs/architecture/player-controls-contract.md. | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — On the preference-off path, all three web players retain their existing controls, source behavior, and… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — The legacy Video.js chrome also releases the focus a pointer interaction leaves on a control… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — It is driven mainly by focusin, not the click, because choosing a menu item moves focus to the menu button… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — The release is scoped to .vjs-control-bar so the caption-settings dialog (a modal sibling of the bar)… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Settings.showCaptions is deliberately outside this rollout gate: it is engine state, so the preference-off… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — The two modes differ in how long it is enforced: shared controls are authoritative for the session (user… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Mode selection is the optional playbackStarted probe the legacy owners pass to all three helpers (HLS,… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — WebPlayerViewComponent reads it from SettingsStore instead of a host input so every host (M3U,… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1315–1315 | Video Players: — Contract: docs/architecture/player-controls-contract.md. | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1316–1325 | Video Players: — Shared web picture-in-picture stays inside that default-on rollout. PlayerController exposes capability… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1326–1344 | Video Players: — WebVideoControlsAdapter supplies its current video and binding generation to… | [Landed architecture](../architecture/player-controls-contract.md#landed-architecture) | Existing contract |
| CLAUDE.md:1348–1376 | Download Manager: — Fresh Xtream movie and series-episode downloads propagate the playlist's User-Agent, Referer, and Origin,… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1377–1380 | Download Manager: — The desktop-only manager shares one global download store across the global, Xtream-scoped, and… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1381–1420 | Download Manager: — Series details route individual and selected-season episode downloads through the provider-neutral… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1421–1438 | Download Manager: — Episode ownership uses normalized episode.id as the canonical xtreamId for both providers; Stalker… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1439–1444 | Download Manager: — Ready cards (movies, grouped series, and standalone episodes) open a focused local detail; local file… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1445–1449 | Download Manager: — Downloads capture a versioned metadata snapshot from the rendered Xtream or Stalker movie/episode detail… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1450–1459 | Download Manager: — View in portal resolves a concrete Xtream category/item route. Stalker accepts a recently-viewed shape… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1460–1462 | Download Manager: — Download rows and local files survive source deletion. The global offline library remains visible with no… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1463–1465 | Download Manager: — If a finalized file disappears while a focused detail is open, the authoritative download list refreshes… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1466–1497 | Download Manager: — Live-TV recordings (Embedded MPV stream-record) are tracked beside downloads in their own recordings table… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1498–1499 | Download Manager: — Canonical contract: docs/architecture/download-manager.md; provider handoff:… | [Queuing, persistence, and UX notes](../architecture/download-manager.md#queuing-persistence-and-ux-notes) | Existing contract |
| CLAUDE.md:1501–1501 | Download Manager: — Collection Detail Portal Handoff (View in portal for inline details): | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1503–1507 | Download Manager: — Details opened outside portal category context — /workspace/global-favorites, /workspace/global-recent… | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1508–1515 | Download Manager: — Visibility is DI-gated, never URL-sniffed: app-view-in-portal-action… | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1516–1521 | Download Manager: — Targets come from getUnifiedCollectionDetailNavigation()… | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1522–1558 | Download Manager: — Stalker section resolution mirrors resolveStalkerCollectionDetailMode()… | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1559–1561 | Download Manager: — Unlike the download handoff this bridge does NOT pass detailPresentation: 'provider-only' — the item… | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1562–1562 | Download Manager: — Contract: docs/architecture/portal-detail-navigation.md. | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1566–1566 | VOD/Series Detail Pages (two-state layout): — Xtream and Stalker detail pages use the shared PortalDetailShellComponent… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1567–1567 | VOD/Series Detail Pages (two-state layout): — The inline player (PortalInlinePlayerComponent) renders a full-width theater stage… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1568–1568 | VOD/Series Detail Pages (two-state layout): — For inline series playback on wide windows the stage instead docks the player left and shows an "Up Next"… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1569–1569 | VOD/Series Detail Pages (two-state layout): — Watch state derives from inlinePlayback() !== null only; external MPV/VLC playback keeps the browse… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1570–1570 | VOD/Series Detail Pages (two-state layout): — Xtream VOD treats metadata presentation and playability as separate contracts. Empty or sparse… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1571–1571 | VOD/Series Detail Pages (two-state layout): — A successful external MPV/VLC episode launch immediately persists the selected episode as the latest… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1572–1572 | VOD/Series Detail Pages (two-state layout): — Stalker preserves this contract for regular /series, embedded VOD series[], and lazy Ministra VOD… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1573–1573 | VOD/Series Detail Pages (two-state layout): — Hosts pass hero chips/meta/actions as appDetailTags/appDetailMeta/appDetailActions templates; the shell… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1574–1574 | VOD/Series Detail Pages (two-state layout): — Seasons are tabs (SeasonTabsComponent, dropdown beyond 6 seasons; the dropdown's menu rows and closed… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1575–1575 | VOD/Series Detail Pages (two-state layout): — The season header hosts a bulk watched toggle next to "Download season" (both portals): marking writes… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1576–1576 | VOD/Series Detail Pages (two-state layout): — Movies get the same manual toggle in the detail action row (Xtream: icon square after Favorite,… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1577–1577 | VOD/Series Detail Pages (two-state layout): — The dashboard hero CTA and the Continue Watching cards' explicit "Resume episode" ⋮ action for an Xtream… | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1578–1578 | VOD/Series Detail Pages (two-state layout): — See docs/architecture/embedded-inline-playback.md ("Two-State Detail Layout") | [Two-State Detail Layout (Browse ↔ Watch)](../architecture/embedded-inline-playback.md#two-state-detail-layout-browse--watch) | Existing contract |
| CLAUDE.md:1580–1580 | VOD/Series Detail Pages (two-state layout): — VOD Multi-Source (alternative sources for a movie): | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Finds the same movie in the user's other imported playlists and adds a "Sources N" chip to the Xtream VOD… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — The chip opens a 660px anchored CDK-overlay popover (libs/ui/components/src/lib/vod-sources/; not MatMenu,… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — It opens ABOVE the chip (right edges aligned, pressed state on the chip while open), height-capped by the… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — A row's language is vodSourceLanguage (libs/shared/interfaces/src/lib/vod-source-language.util.ts): the… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Unicode lookalikes, bracketed, or ALL-CAPS spaced-dash form; | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Latin/Cyrillic 2–4 letters + MULTI; only the legacy pipe form is permissive — bracket/dash matches must… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Both forms are parsed guesses: browse filter and chips only, never ranking/failover/dub-warning inputs. | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Recognition alone is not enough — normalizeTitleKeys must STRIP the same tag or the copy is never… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — It goes no further on purpose: a wrong guess costs a filter option, a wrong strip corrupts identity, and… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — The one shape that cannot decide itself is a strip leaving NO real word behind — decided by running the… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Every vocabulary entry is one the catalog proves prefixes hundreds of ordinary titles — never one that… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Verify such widenings against the real catalog before shipping them, over movies AND series: a movie-only… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Checks run through a 4-slot queue and settled verdicts are cached 10 min per movie+source… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — Both chips are handed the same matchKind and vodAutoFailover and both write the setting back. | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — The details-page chip badge counts TOTAL copies across all playlists (the in-player chip still counts… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1582–1582 | VOD/Series Detail Pages (two-state layout): — The action row's Favorites and Download buttons are icon-only 64px squares: filled red heart when… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1583–1583 | VOD/Series Detail Pages (two-state layout): — Scope v1 is Xtream ↔ Xtream, movies only, Electron only. Stalker never reaches the content table and M3U… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1584–1584 | VOD/Series Detail Pages (two-state layout): — Metadata provenance is the core contract. Every field is {value, provenance} where api/probe are facts… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1585–1585 | VOD/Series Detail Pages (two-state layout): — Discovery (DB_FIND_TITLE_SOURCES, trigram FTS over content_title_fts) is lazy and returns only what the… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1585–1585 | VOD/Series Detail Pages (two-state layout): — A source that is never read looks exactly like one that does not exist, so: the current playlist is… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1585–1585 | VOD/Series Detail Pages (two-state layout): — The year gate covers BOTH match tiers: normalizeTitleKeys strips bracketed segments, so "Dune (1984)"… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1585–1585 | VOD/Series Detail Pages (two-state layout): — A non-ASCII token cannot be folded by LOWER() (ASCII-only) but CAN be by a GLOB character class (UTF-8… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1585–1585 | VOD/Series Detail Pages (two-state layout): — The movie's own year comes from releaseTagYear (bracketed or trailing only), never extractYear: a year… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1585–1585 | VOD/Series Detail Pages (two-state layout): — One row inside the excluded playlist is kept when the caller names it (keepContentId), because a pin can… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1585–1585 | VOD/Series Detail Pages (two-state layout): — Resolution is deferred to click/pin/check because content stores no container_extension and… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1586–1586 | VOD/Series Detail Pages (two-state layout): — Switching = one inlinePlayback.set({...next, startTime}), never null-then-set, so the player and engine… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1587–1587 | VOD/Series Detail Pages (two-state layout): — Pins are keyed portal-agnostically (tmdb:{id} else title:{base}:{year} else the yearless title:{base}:,… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1588–1588 | VOD/Series Detail Pages (two-state layout): — Claims in the present tense (the "Playing from" caption and the source row's Playing badge) are gated on… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1589–1589 | VOD/Series Detail Pages (two-state layout): — Pins are included in playlist backup as the optional sourcePins collection, carried under the playlist… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1590–1590 | VOD/Series Detail Pages (two-state layout): — Auto-failover is Settings.vodAutoFailover, opt-in and off by default, web engines only — the toggle is… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1591–1591 | VOD/Series Detail Pages (two-state layout): — HEAD probe reuses the main-process handler extracted to… | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1592–1592 | VOD/Series Detail Pages (two-state layout): — See docs/architecture/vod-multi-source.md | [VOD Multi-Source](../architecture/vod-multi-source.md#vod-multi-source) | Existing contract |
| CLAUDE.md:1596–1596 | Radio Player: — Dedicated audio player for channels with radio="true" M3U attribute | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:1597–1597 | Radio Player: — Cinematic layout: blurred station logo as backdrop, floating artwork card, transport controls | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:1598–1598 | Radio Player: — Always uses the built-in inline player — external player settings (MPV/VLC) are ignored for radio | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:1599–1599 | Radio Player: — EPG panel is hidden for radio channels (radio streams have no EPG data) | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:1600–1600 | Radio Player: — Volume synced with video player via shared localStorage key 'volume' | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:1601–1601 | Radio Player: — Keyboard shortcuts: ArrowUp/ArrowDown (volume), M (mute) | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:1602–1602 | Radio Player: — Component: libs/ui/playback/src/lib/audio-player/audio-player.component.ts | [Radio audio player](../architecture/player-controls-contract.md#radio-audio-player) | Added / consolidated |
| CLAUDE.md:1606–1606 | EPG (Electronic Program Guide): — XMLTV format support, from http(s) links or local files (Electron only): a file: URL, an absolute POSIX… | [EPG Integration](../architecture/m3u-playlist-module.md#epg-integration) | Existing contract |
| CLAUDE.md:1607–1607 | EPG (Electronic Program Guide): — Background parsing in worker thread; HTTP/file gzip compatibility follows… | [EPG Integration](../architecture/m3u-playlist-module.md#epg-integration) | Existing contract |
| CLAUDE.md:1608–1608 | EPG (Electronic Program Guide): — Stored in database for quick lookup | [EPG Integration](../architecture/m3u-playlist-module.md#epg-integration) | Existing contract |
| CLAUDE.md:1609–1609 | EPG (Electronic Program Guide): — Global display-time offset (Settings.epgOffsetMinutes, Settings → EPG, ±720 min, Electron only):… | [EPG Integration](../architecture/m3u-playlist-module.md#epg-integration) | Existing contract |
| CLAUDE.md:1610–1610 | EPG (Electronic Program Guide): — Programme guide (Electron, M3U): app-epg-guide in libs/ui/epg fed by the host-provided EPG_GUIDE_SOURCE;… | [EPG Integration](../architecture/m3u-playlist-module.md#epg-integration) | Existing contract |
| CLAUDE.md:1611–1611 | EPG (Electronic Program Guide): — Manual EPG mapping (Electron only): right-click a channel in any list (M3U views, Xtream portal list,… | [EPG Integration](../architecture/m3u-playlist-module.md#epg-integration) | Existing contract |
| CLAUDE.md:1613–1613 | EPG (Electronic Program Guide): — TMDB Metadata Enrichment (opt-in): | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1615–1615 | EPG (Electronic Program Guide): — Enriches Xtream and Stalker VOD/series detail views with TMDB data (plot, cast with avatar chips,… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1616–1616 | EPG (Electronic Program Guide): — The M3U player consumes it too: entries recognized as movie files open in the VOD detail shell fed purely… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1617–1617 | EPG (Electronic Program Guide): — "Similar" rail in ALL detail views: TMDB recommendations matched against the provider catalog by… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1618–1618 | EPG (Electronic Program Guide): — Season/episode enrichment: opening a season lazily fetches /tv/{id}/season/{n} and overlays real episode… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — Dashboard: opt-in "Trending this week" rail (weekly TMDB trending matched against imported Xtream… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — Electron-only, dashboardRails.tmdbTrending toggle), a "Because you watched" recommendations rail… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — TMDB has no account-free "for you" endpoint, so DashboardRecommendationsService seeds per-title… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — The hero lookup must carry the same identity the detail view used, not just the display title —… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — The lookup key is the WHOLE attempt sequence, since two rows can share title/year/id yet differ in whether… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — Stalker items never reach the content table, so their backdrop rides in the stored entry… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — Xtream rows carry the same identity on the content row: the detail views back-fill… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — Writes are per-column and never overwrite (enrichment supplies the pieces at different times, so a… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — release_year is the year the PROVIDER stated, never one read out of the title (readers still apply that… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1619–1619 | EPG (Electronic Program Guide): — Both sides validate through normalizeContentMetadataPatch (libs/shared/interfaces), so legacy rows,… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1620–1620 | EPG (Electronic Program Guide): — Series detail views show a TMDB production-status chip (tmdb_status, e.g. Ended / Returning) — TMDB sends… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1621–1621 | EPG (Electronic Program Guide): — Actor pages: cast avatar chips are clickable (TMDB person id) and open actor/:personId inside the current… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1622–1622 | EPG (Electronic Program Guide): — Discover pages (clickable metadata chips, issue #1449): year, genre, and country chips on all four detail… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1623–1623 | EPG (Electronic Program Guide): — Actor page "All portals" scope (Electron only): batched DB_MATCH_TITLES worker op (trigram FTS over all… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1624–1624 | EPG (Electronic Program Guide): — All DB_MATCH_TITLES consumers (Trending rail, "Because you watched" recommendations rail, cross-portal… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1625–1625 | EPG (Electronic Program Guide): — Opt-in via Settings &gt; Metadata (TMDB) (sends titles to TMDB); the section also has a "check key" button… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1626–1626 | EPG (Electronic Program Guide): — Match confidence: a provider tmdb_id is a strong hint, not gospel — its payload is weighed against the… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1627–1627 | EPG (Electronic Program Guide): — Detail views render provider data immediately; enrichment patches the selection asynchronously… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1628–1628 | EPG (Electronic Program Guide): — Cached in SQLite tmdb_metadata (Electron, via DB worker ops DB_GET/SET_TMDB_METADATA, plus… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1629–1629 | EPG (Electronic Program Guide): — Service layer: libs/services/src/lib/tmdb/; store glue:… | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1630–1630 | EPG (Electronic Program Guide): — TMDB attribution (logo + disclaimer) is required and shown in the settings TMDB section and About | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1631–1631 | EPG (Electronic Program Guide): — See docs/architecture/tmdb-metadata-enrichment.md | [TMDB Metadata Enrichment](../architecture/tmdb-metadata-enrichment.md#tmdb-metadata-enrichment) | Existing contract |
| CLAUDE.md:1635–1635 | Portal Account Info: — Both portal types expose an account-info dialog through the same entry points: header playlist switcher… | [Account Info Dialog](../architecture/stalker-portal.md#account-info-dialog) | Existing contract |
| CLAUDE.md:1636–1636 | Portal Account Info: — Xtream: AccountInfoComponent (libs/portal/xtream/feature/src/lib/account-info/), queries get_account_info… | [Account Info Dialog](../architecture/stalker-portal.md#account-info-dialog) | Existing contract |
| CLAUDE.md:1637–1637 | Portal Account Info: — Stalker: StalkerAccountInfoComponent (libs/portal/stalker/feature/src/lib/stalker-account-info/),… | [Account Info Dialog](../architecture/stalker-portal.md#account-info-dialog) | Existing contract |
| CLAUDE.md:1638–1638 | Portal Account Info: — Dashboard source cards carry a passive subscription-expiry chip (amber within 7 days, error-toned once… | [Source subscription expiry](../architecture/workspace-dashboard.md#source-subscription-expiry) | Added / consolidated |
| CLAUDE.md:1642–1642 | Stalker Portal Mode and Endpoint Discovery: — Every resolved Edit commit is guarded by the source connection authority captured when Edit began.… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1643–1643 | Stalker Portal Mode and Endpoint Discovery: — Portal mode (full vs. simple) follows OBSERVED behavior, never a URL substring. The single predicate is… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1644–1644 | Stalker Portal Mode and Endpoint Discovery: — Import requires an explicit HTTP(S) scheme but accepts a bare host, /c, or a concrete .php address. It… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — The playlist-info Edit dialog loads the complete persisted Stalker row before enabling the form, because… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — A metadata-only Save omits connection/mode fields from its queued update, so the stored connection stays… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — A persisted portalUrl keeps the row on the Stalker save path even if legacy Xtream fields remain. | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — Changing URL, MAC, credentials, serial, device IDs or signatures blocks duplicate saves, disables dialog… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — Before discovery, PWA acquires a shared playlist-authority barrier plus an exclusive origin-wide… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — Add/delete, backup restore, and bulk replacement take the same row lock, while Delete All takes the… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — A concurrent Edit or stale dialog fails before remote discovery; a replacement waits for the current owner. | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — Same-tab Save first publishes its local authentication owner, drains an existing lazy repair through… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — PWA fails closed if Web Locks are unavailable, while Electron relies on its single-instance local owner. | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — The reservation blocks every new authentication (including fingerprint-equivalent URL edits) and repair,… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — If discovery returns after its bounded drain while an abandoned authentication is still on the wire, that… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — Once Save starts, navigation or dialog destruction does not discard a later successful result: get_profile… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — That late commit uses transformPlaylistMeta() inside the per-playlist write queue to merge only… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — Success uses one awaited write to atomically replace endpoint, mode, normalized identity and session… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — This preserves playback headers and other metadata absent from the form. | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — Runtime configuration authority covers the observed full/simple mode as well as the session fingerprint,… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — A changed authority may rebase only when the persisted row proves that it owns the same playlist ID,… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1645–1645 | Stalker Portal Mode and Endpoint Discovery: — The transient PlaylistMetaUpdate.stalkerSessionPatch preserves on absence, clears on null, and fully… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — executeStalkerRequest() (stores/utils/stalker-request.utils.ts) is the choke point for catalog, content… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — Four callers are deliberately outside it because they run below or before the thing it routes on —… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — They are exempt from the routing, not from the repair it hooks, but only fetchViaProfile() wires… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — Anything new that is not auth or discovery belongs on executeStalkerRequest(). | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — Existing playlists are repaired LAZILY (StalkerPortalRepairService) — only after a request fails with a… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — Before an unrecorded repair reads the persisted source or calls discovery, PWA takes the same… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — This prevents repair in another tab from authenticating alongside Edit or crossing delete/restore. | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — The persisted-row preflight still verifies that the caller owns the failing source, so a late pre-Edit… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — Its in-session override is bound to source endpoint, mode, device identity, and credentials; an Edit or… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — Each repair installs a session-level authentication fence synchronously, drains the existing token slot… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1646–1646 | Stalker Portal Mode and Endpoint Discovery: — There is deliberately no eager one-shot migration: a portal that works is never re-probed. | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1647–1647 | Stalker Portal Mode and Endpoint Discovery: — Explicit Edit advances the repair generation before installing its resolved session. Lazy repair captures… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1648–1648 | Stalker Portal Mode and Endpoint Discovery: — Both transports build the wire format from the same shared builders in @iptvnator/shared/interfaces —… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1649–1649 | Stalker Portal Mode and Endpoint Discovery: — Simple portals skip the auth lifecycle (no handshake, token or watchdog) but their requests are not… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1650–1650 | Stalker Portal Mode and Endpoint Discovery: — Contract: docs/architecture/stalker-portal.md ("Portal Mode and Endpoint Discovery", "Request Transport… | [Portal Mode and Endpoint Discovery](../architecture/stalker-portal.md#portal-mode-and-endpoint-discovery) | Existing contract |
| CLAUDE.md:1654–1654 | Stalker Session Authentication: — Full portals authenticate through StalkerSessionService… | [Session Authentication Lifecycle](../architecture/stalker-portal.md#session-authentication-lifecycle) | Existing contract |
| CLAUDE.md:1655–1655 | Stalker Session Authentication: — get_profile's js.status decodes as: full profile/0 = OK, 1 = refused (device-conflict when the message… | [Session Authentication Lifecycle](../architecture/stalker-portal.md#session-authentication-lifecycle) | Existing contract |
| CLAUDE.md:1656–1656 | Stalker Session Authentication: — Refusals throw StalkerPortalError (login-required / login-rejected / device-conflict / blocked /… | [Session Authentication Lifecycle](../architecture/stalker-portal.md#session-authentication-lifecycle) | Existing contract |
| CLAUDE.md:1657–1657 | Stalker Session Authentication: — Auth failures are HTTP 200 + plain text (Authorization failed. / Access denied. / Unauthorized request.),… | [Session Authentication Lifecycle](../architecture/stalker-portal.md#session-authentication-lifecycle) | Existing contract |
| CLAUDE.md:1658–1658 | Stalker Session Authentication: — The handshake is idempotent, so Playlist.stalkerToken is re-presented and get_profile is skipped when it… | [Session Authentication Lifecycle](../architecture/stalker-portal.md#session-authentication-lifecycle) | Existing contract |
| CLAUDE.md:1659–1659 | Stalker Session Authentication: — Watchdog: get_events immediately (init=1), then every watchdog_timeout s (default 120, clamped 30–3600)… | [Session Authentication Lifecycle](../architecture/stalker-portal.md#session-authentication-lifecycle) | Existing contract |
| CLAUDE.md:1660–1660 | Stalker Session Authentication: — Full contract: docs/architecture/stalker-portal.md ("Session Authentication Lifecycle"). | [Session Authentication Lifecycle](../architecture/stalker-portal.md#session-authentication-lifecycle) | Existing contract |
| CLAUDE.md:1664–1664 | Stalker Identity Hardening: — The MAC is canonicalized to 00:1A:79:XX:XX:XX by normalizeStalkerMacAddress (@iptvnator/shared/interfaces)… | [Stalker Identity Policy](../architecture/stalker-portal.md#stalker-identity-policy) | Existing contract |
| CLAUDE.md:1665–1665 | Stalker Identity Hardening: — Format is enforced, the Infomir OUI is advisory only: hasInfomirMacOui drives a hint, never a rejection.… | [Stalker Identity Policy](../architecture/stalker-portal.md#stalker-identity-policy) | Existing contract |
| CLAUDE.md:1666–1666 | Stalker Identity Hardening: — deriveStalkerDeviceIdsFromMac returns the StbEmu / stalker-to-m3u PAIR: SHA256(MAC) for device_id and… | [Stalker Identity Policy](../architecture/stalker-portal.md#stalker-identity-policy) | Existing contract |
| CLAUDE.md:1667–1667 | Stalker Identity Hardening: — get_profile reports one coherent MAG250 via STALKER_STB_PROFILE_PARAMS (ver, stb_type — previously empty… | [Stalker Identity Policy](../architecture/stalker-portal.md#stalker-identity-policy) | Existing contract |
| CLAUDE.md:1668–1668 | Stalker Identity Hardening: — Contract: docs/architecture/stalker-portal.md ("Stalker Identity Policy"). | [Stalker Identity Policy](../architecture/stalker-portal.md#stalker-identity-policy) | Existing contract |
| CLAUDE.md:1672–1672 | Favorites and Recently Viewed: — Per-playlist favorites and global favorites | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1673–1673 | Favorites and Recently Viewed: — Recently viewed tracks watch history | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1674–1690 | Favorites and Recently Viewed: — Live channels in the unified favorites/recent live tab (global collections and a portal's own tabs) carry… | [Summary](../architecture/portal-detail-navigation.md#summary) | Existing contract |
| CLAUDE.md:1694–1694 | Internationalization: — Uses @ngx-translate with 19 language files in apps/web/src/assets/i18n/ | [Features](../../README.md#features) | Existing contract |
| CLAUDE.md:1700–1700 | Environment Detection and Dual-Mode Architecture — The app determines whether it's running in Electron or as a PWA by checking: | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1702–1704 | Environment Detection and Dual-Mode Architecture — typescript window.electron; // truthy in Electron, undefined in browser | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1707–1707 | Why Dual Mode? — IPTVnator supports both Electron (desktop app) and PWA (web browser) to provide flexibility: | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1709–1709 | Why Dual Mode? — Electron: Full-featured desktop experience with local database, external player support (MPV/VLC), and… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1710–1710 | Why Dual Mode? — PWA: Lightweight web version that runs in any browser without installation | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1714–1714 | Environment-Specific Behavior: — app.config.ts - DataFactory() selects DataService implementation based on environment | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1715–1715 | Environment-Specific Behavior: — app.routes.ts - Same /workspace/... route tree in both environments; guards keep Electron-only routes… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1716–1718 | Environment-Specific Behavior: — Storage layer switches automatically: - Electron → SQLite/Drizzle ORM →… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1719–1719 | Environment-Specific Behavior: — External player support (MPV/VLC) only available in Electron | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1720–1720 | Environment-Specific Behavior: — File system operations only available in Electron (uploading playlists from disk) | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1723–1723 | Base Href Configuration: — The app uses different base href values depending on the build target: | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1725–1727 | Base Href Configuration: — Development & PWA: baseHref="/" (from index.html) - Used by: pnpm run serve:frontend, pnpm run… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1728–1730 | Base Href Configuration: — Electron Production: baseHref="./" (overridden in build config) - Used by: pnpm run build:backend, pnpm… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1732–1732 | Base Href Configuration: — Build configurations in apps/web/project.json: | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1734–1734 | Base Href Configuration: — production: Electron build with baseHref="./" | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1735–1735 | Base Href Configuration: — pwa: Web deployment with baseHref="/" | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1736–1736 | Base Href Configuration: — development: Dev mode with baseHref="/" from index.html | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1739–1739 | Factory Pattern Implementation: — The factory pattern ensures a single codebase works in both environments without conditional checks… | [Service factory and build bases](../architecture/pwa-self-hosted.md#service-factory-and-build-bases) | Corrected; see decisions |
| CLAUDE.md:1742–1742 | Build Commit In About: — CI injects the git commit into apps/web/src/environments/build-commit.ts via… | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1745–1745 | Nightly Builds And Update Channel: — Master pushes are the nightly channel. | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1745–1745 | Nightly Builds And Update Channel: — The leading nightly-version job computes one &lt;patch&gt;-nightly.&lt;commit date&gt;.&lt;run number&gt; version per run… | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1745–1745 | Nightly Builds And Update Channel: — Settings.updateChannel (Settings → About, Electron only, default stable) is mirrored into the main-process… | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1745–1745 | Nightly Builds And Update Channel: — AppUpdateService applies the channel to electron-updater before every check (app-update-feed.ts: feed… | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1745–1745 | Nightly Builds And Update Channel: — Switching back to stable is forward-only: the nightly stays until a newer stable release exists, because a… | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1745–1745 | Nightly Builds And Update Channel: — The About section's status badge names the channel the verdict describes (status.verdictChannel, stamped… | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1745–1745 | Nightly Builds And Update Channel: — setChannel re-checks on its own) — a check against an unsaved channel is deliberately not offered. | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1745–1745 | Nightly Builds And Update Channel: — Contract: docs/architecture/release-pipeline.md ("Nightly channel"). | [Nightly channel](../architecture/release-pipeline.md#nightly-channel) | Existing contract |
| CLAUDE.md:1749–1749 | Testing Strategy — Unit tests: Jest with jest-preset-angular and ng-mocks | [Unit And Type Checks](../architecture/validation-map.md#unit-and-type-checks) | Existing contract |
| CLAUDE.md:1750–1750 | Testing Strategy — E2E tests: Playwright testing the web app and Electron app | [Unit And Type Checks](../architecture/validation-map.md#unit-and-type-checks) | Existing contract |
| CLAUDE.md:1751–1751 | Testing Strategy — Backend tests use standard Jest | [Unit And Type Checks](../architecture/validation-map.md#unit-and-type-checks) | Existing contract |
| CLAUDE.md:1752–1752 | Testing Strategy — Bug fixes should add focused regression coverage unless there is a documented reason not to. | [Unit And Type Checks](../architecture/validation-map.md#unit-and-type-checks) | Existing contract |
| CLAUDE.md:1753–1753 | Testing Strategy — Use the impact-based validation policy in Regression Prevention And Test Updates to choose targeted unit… | [Unit And Type Checks](../architecture/validation-map.md#unit-and-type-checks) | Existing contract |
| CLAUDE.md:1757–1757 | Nx Commands — Use nx CLI for better performance: | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:1759–1763 | Nx Commands — bash pnpm nx run &lt;project&gt;:&lt;target&gt; # Example: pnpm nx run web:build # Example: pnpm nx run… | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:1765–1765 | Nx Commands — To run multiple projects: | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:1767–1769 | Nx Commands — bash pnpm nx run-many --target=test --all | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:1773–1773 | Electron Build Process — The Electron backend depends on the web app being built first: | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:1775–1775 | Electron Build Process — electron-backend:build depends on web:build | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:1776–1776 | Electron Build Process — Output goes to dist/apps/electron-backend (backend) and dist/apps/web (frontend) | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:1777–1777 | Electron Build Process — Packaging combines both into distributable | [Build and serve commands](../development/agent-workflow.md#build-and-serve-commands) | Moved / consolidated |
| CLAUDE.md:1781–1781 | Database Migrations — Database initialization is owned by libs/shared/database/src/lib/connection.ts. createTables() creates… | [Upgrade Compatibility And Migrations](../../libs/shared/database/README.md#upgrade-compatibility-and-migrations) | Existing contract |
| CLAUDE.md:1787–1790 | IPC Communication: — 1. Define handler in appropriate events file (e.g., database.events.ts) 2. Register with ipcMain.handle()… | [Adding behavior across layers](../development/agent-workflow.md#adding-behavior-across-layers) | Moved / consolidated |
| CLAUDE.md:1794–1797 | Adding New Playlist Source: — 1. Add type to libs/shared/interfaces/src/lib/playlist.interface.ts 2. Create event handler in… | [Adding behavior across layers](../development/agent-workflow.md#adding-behavior-across-layers) | Moved / consolidated |
| CLAUDE.md:1801–1801 | State Management: — Use NgRx for global application state (M3U playlists, libs/m3u-state) | [Adding behavior across layers](../development/agent-workflow.md#adding-behavior-across-layers) | Moved / consolidated |
| CLAUDE.md:1802–1802 | State Management: — Use NgRx Signal Store with signalStoreFeature() composition for portal/feature state (XtreamStore,… | [Adding behavior across layers](../development/agent-workflow.md#adding-behavior-across-layers) | Moved / consolidated |
| CLAUDE.md:1803–1803 | State Management: — Use NgRx signals for reactive data streams | [Adding behavior across layers](../development/agent-workflow.md#adding-behavior-across-layers) | Moved / consolidated |
| CLAUDE.md:1810–1810 | General Guidelines for working with Nx — For navigating/exploring the workspace, invoke the nx-workspace skill first when it is available - it has… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1811–1811 | General Guidelines for working with Nx — When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through nx… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1812–1812 | General Guidelines for working with Nx — Prefix nx commands with the workspace's package manager (e.g., pnpm nx build, npm exec nx test) - avoids… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1813–1813 | General Guidelines for working with Nx — You have access to the Nx MCP server and its tools, use them to help the user | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1814–1814 | General Guidelines for working with Nx — For Nx plugin best practices, check node_modules/@nx/&lt;plugin&gt;/PLUGIN.md. Not all plugins have this file -… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1815–1815 | General Guidelines for working with Nx — NEVER guess CLI flags - always check nx_docs or --help first when unsure | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1819–1819 | Scaffolding & Generators — For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the nx-generate skill… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1823–1823 | When to use nx_docs — USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1824–1824 | When to use nx_docs — DON'T USE for: basic generator syntax (nx g @nx/react:app), standard commands, things you already know | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1825–1825 | When to use nx_docs — The nx-generate skill handles generator discovery internally - don't call nx_docs just to look up… | [General Guidelines for working with Nx](../../AGENTS.md#general-guidelines-for-working-with-nx) | Retained; tools conditional |
| CLAUDE.md:1831–1835 | XMLTV Response Compression — Electron decodes HTTP compression before the gzip file layer. For .gz/gzip metadata plus HTTP gzip, a… | [XMLTV response compression](../architecture/m3u-playlist-module.md#xmltv-response-compression) | Existing contract |
| CLAUDE.md:1839–1857 | XMLTV Source Removal — Saving Settings → EPG reconciles cached XMLTV with committed global URLs and all enabled M3U playlist… | [XMLTV source lifecycle](../architecture/m3u-playlist-module.md#xmltv-source-lifecycle) | Existing contract |
| CLAUDE.md:1861–1870 | Web Backend Provider Redirects — All four provider proxy routes use ValidatedHttpClient: automatic redirects are disabled, the initial URL… | [Web Backend](../architecture/pwa-self-hosted.md#web-backend) | Existing contract |
| CLAUDE.md:1874–1879 | Portal Connectivity Preference — Half-open trial slots follow the complete request lifetime with no elapsed-time expiry. All four… | [Desktop preference and account feedback](../architecture/host-connectivity-guard.md#desktop-preference-and-account-feedback) | Existing contract |
| CLAUDE.md:1880–1887 | Portal Connectivity Preference — Desktop Settings &gt; General &gt; Portal connections exposes default-on Settings.portalConnectivityGuard. Only… | [Desktop preference and account feedback](../architecture/host-connectivity-guard.md#desktop-preference-and-account-feedback) | Existing contract |
| CLAUDE.md:1888–1890 | Portal Connectivity Preference — Both account-info dialogs explain guard refusals with localized paused-request copy and Retry now; Stalker… | [Desktop preference and account feedback](../architecture/host-connectivity-guard.md#desktop-preference-and-account-feedback) | Existing contract |
| CLAUDE.md:1894–1920 | Live TV Panel Levels — Portal live layouts (Xtream live, Stalker itv/radio) fold their panels from the outside in, in three… | [Collapsible Live Sidebar](../architecture/iptvnator-ui-guidelines.md#collapsible-live-sidebar) | Existing contract |
| CLAUDE.md:1924–1930 | Live Channel Return — Xtream and Stalker (including radio) capture displayed playback order on explicit selection. Remote… | [Live channel return and playback order](../architecture/remote-control.md#live-channel-return-and-playback-order) | Existing contract |
| CLAUDE.md:1934–1940 | Stalker Live Search — ITV sidebar and fullscreen searches independently filter the complete selected category; only All Items… | [Full ITV Channel List Cache](../architecture/stalker-portal.md#full-itv-channel-list-cache) | Existing contract |
| CLAUDE.md:1944–1959 | Channel and Detail Keyboard Scrolling — Channel scroll owners use ChannelScrollFocusDirective; pointer selection focuses the viewport, native… | [Detail Scroll and Focus](../architecture/portal-detail-navigation.md#detail-scroll-and-focus) | Existing contract |
| CLAUDE.md:1963–1967 | Catch-Up URL Copying — EPG timeline/list programme details expose Copy archive URL for supported Xtream/M3U archives, including… | [Copy archive URL](../architecture/m3u-playlist-module.md#copy-archive-url) | Existing contract |
| CLAUDE.md:1971–2001 | Xtream Archive Downloads — Desktop Xtream Live TV programme details can enqueue completed catch-up as contentType: catchup. The queue… | [Xtream archive downloads](../architecture/download-manager.md#xtream-archive-downloads) | Existing contract |
| CLAUDE.md:2005–2010 | Desktop Source Health — Electron switcher/source rows share bounded, cached Xtream/Stalker/M3U URL checks through… | [Desktop source health](../architecture/m3u-playlist-module.md#desktop-source-health) | Existing contract |
| CLAUDE.md:2012–2018 | Desktop Source Health — Desktop Sources also offers library-wide selective cleanup through dialog-scoped SourceCleanupService.… | [Desktop source health](../architecture/m3u-playlist-module.md#desktop-source-health) | Existing contract |
| CLAUDE.md:2020–2022 | Desktop Source Health — Startup source auto-refresh uses SourceActivityService to protect busy IDs from cleanup. Late batch… | [Desktop source health](../architecture/m3u-playlist-module.md#desktop-source-health) | Existing contract |
## Master integration follow-up
During conflict resolution with master on 2026-09-21, the compact root guidance
was retained and the new upstream knowledge was checked against canonical docs:
- Live channel playlist handoff, including Stalker arrival and fallback behavior:
[portal navigation](../architecture/portal-detail-navigation.md).
- Shared locale-independent search folding and SQLite search variants:
[agent workflow](../development/agent-workflow.md#shared-search-text-folding).
- Stalker series resume, watch kind versus routing kind, and dashboard labels:
[portal navigation](../architecture/portal-detail-navigation.md) and
[workspace dashboard](../architecture/workspace-dashboard.md).
- Scoped XMLTV fallback and Xtream programme refresh helpers:
[M3U contracts](../architecture/m3u-playlist-module.md).
- Lazy portal EPG queues, revision handling and dashboard fallback:
[workspace dashboard](../architecture/workspace-dashboard.md).
The original 716-entry inventory above remains tied to its immutable source.
A subsequent master integration on 2026-09-21 also preserved TMDB year-evidence
ranking, its accepted older-season ambiguity, and v4 lookup-cache migration in
the updated [TMDB contract](../architecture/tmdb-metadata-enrichment.md). These
upstream additions stay in that canonical document rather than CLAUDE.md.
+4
View File
@@ -71,6 +71,7 @@
"serve:website": "nx serve website",
"build:website": "nx build website",
"i18n:check": "node tools/i18n/check-drift.mjs",
"agents:validate": "node tools/skills/validate-agent-guidance.mjs",
"skills:validate": "node tools/skills/validate-repository-skills.mjs",
"release:artwork:dry-run": "tsx --tsconfig tsconfig.base.json tools/release/generate-marketing-artwork.ts --dry-run",
"release:artwork:manifest": "tsx --tsconfig tsconfig.base.json tools/release/generate-marketing-artwork.ts --manifest",
@@ -214,6 +215,7 @@
"eslint-plugin-import": "2.32.0",
"eslint-plugin-playwright": "^1.6.2",
"express": "5.2.1",
"github-slugger": "2.0.0",
"globals": "15.9.0",
"html-escaper": "3.0.3",
"istanbul-lib-coverage": "3.2.2",
@@ -231,6 +233,8 @@
"node-gyp": "12.4.0",
"nx": "23.2.1",
"nx-electron": "22.0.0",
"parse-srcset": "1.0.2",
"parse5": "8.0.1",
"prettier": "^3.9.6",
"sharp": "0.35.4",
"tailwindcss": "^3.4.19",
+14
View File
@@ -404,6 +404,9 @@ importers:
express:
specifier: 5.2.1
version: 5.2.1
github-slugger:
specifier: 2.0.0
version: 2.0.0
globals:
specifier: 15.9.0
version: 15.9.0
@@ -455,6 +458,12 @@ importers:
nx-electron:
specifier: 22.0.0
version: 22.0.0(patch_hash=4d5ac9c5b10268dcc40998d7a2b166d004105ae7875fa182130d6810871a1e84)(@nx/devkit@23.2.1(nx@23.2.1(@swc-node/register@1.12.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@swc/core@1.16.1(@swc/helpers@0.5.23))(@swc/types@0.1.28)(typescript@6.0.3))(@swc/core@1.16.1(@swc/helpers@0.5.23))))(@nx/workspace@23.2.1(@swc-node/register@1.12.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@swc/core@1.16.1(@swc/helpers@0.5.23))(@swc/types@0.1.28)(typescript@6.0.3))(@swc/core@1.16.1(@swc/helpers@0.5.23)))(@swc/core@1.16.1(@swc/helpers@0.5.23))(electron-builder-squirrel-windows@26.15.7)(electron@43.3.0)(esbuild@0.28.2)(rxjs@7.8.2)(typescript@6.0.3)
parse-srcset:
specifier: 1.0.2
version: 1.0.2
parse5:
specifier: 8.0.1
version: 8.0.1
prettier:
specifier: ^3.9.6
version: 3.9.6
@@ -8024,6 +8033,9 @@ packages:
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
engines: {node: '>= 0.10'}
parse-srcset@1.0.2:
resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
parse5-html-rewriting-stream@8.0.1:
resolution: {integrity: sha512-NaRku2aMpUN1Sh1Gyk1KWUh2A7EJx2c6qYzvwsPtqhoHoaURshdrceYK3LunVCm3WHhm6FS7Vcczbvdh3/UIVw==}
@@ -18615,6 +18627,8 @@ snapshots:
parse-node-version@1.0.1:
optional: true
parse-srcset@1.0.2: {}
parse5-html-rewriting-stream@8.0.1:
dependencies:
entities: 8.0.0
+2
View File
@@ -84,6 +84,8 @@ pnpm embedded-mpv:stage-runtime -- linux x64 /tmp/linux-prefix
### Windows CI pin lifecycle
The PAT-backed refresh job must pin every third-party action to a full commit.
Windows package builds consume the one validated record in
`windows-runtime-pin.json`; URL and checksum repository variables are not build
inputs. Check it locally with:
+392
View File
@@ -0,0 +1,392 @@
import { randomUUID } from 'node:crypto';
import parseSrcset from 'parse-srcset';
import GithubSlugger from 'github-slugger';
import { Marked, Tokenizer } from 'marked';
import { parseFragment } from 'parse5';
export const DOCUMENT_EXTENSION =
/\.(?:md|markdown|mdown|mkd|mdx|txt|json|ya?ml|html?|rst|rest|adoc|asciidoc|pdf|doc[xm]?|dot[xm]?|od[tspgfbm]|ot[tspg]|fod[tspg]|rtf|org|tex|latex)$/iu;
// Inspection only: generated HTML is parsed in memory, never executed or emitted.
const markdownLexer = new Marked({
tokenizer: {
reflink(source, links) {
const token = Tokenizer.prototype.reflink.call(this, source, links);
if (token?.type !== 'text') return token;
// Marked otherwise turns unresolved references into ordinary text.
// Retain explicit full/collapsed forms and shortcut images;
// a bare [word] without a definition remains ordinary prose.
const full = this.rules.inline.reflink.exec(source);
const collapsed = this.rules.inline.nolink.exec(source);
const match =
full ??
(collapsed?.[0].endsWith('[]') ||
collapsed?.[0].startsWith('![')
? collapsed
: undefined);
if (!match) return token;
return {
type: 'unresolved-reference',
raw: match[0],
text: match[0],
label: match[2] || match[1],
};
},
},
});
function inlineText(tokens) {
return tokens
.map((token) => {
if (token.type === 'html') return '';
if (token.tokens) return inlineText(token.tokens);
return token.type === 'text'
? decodeEntities(token.text ?? '')
: (token.text ?? '');
})
.join('');
}
function decodeEntities(text, attribute = false) {
if (attribute) {
const html = `<a href="${text.replace(/"/gu, '&quot;')}"></a>`;
return parseFragment(html).childNodes[0].attrs[0].value;
}
// RCDATA decodes the full HTML character-reference grammar without
// interpreting literal tags. The prefix preserves an initial newline.
const html = `<textarea>x${text.replace(/</gu, '&lt;')}</textarea>`;
return parseFragment(html).childNodes[0].childNodes[0].value.slice(1);
}
function htmlNavigation(html, inspect = () => {}) {
const anchors = [];
const references = [];
let baseHref;
function visit(node) {
if (['script', 'style', 'template'].includes(node.tagName)) return;
inspect(node);
if (node.tagName === 'base' && baseHref === undefined)
baseHref = node.attrs?.find(
(attribute) => attribute.name === 'href'
)?.value;
for (const attribute of node.attrs ?? []) {
if (
attribute.name === 'id' ||
(node.tagName === 'a' && attribute.name === 'name')
)
anchors.push(attribute.value);
if (
(['a', 'area', 'image', 'use'].includes(node.tagName) &&
attribute.name === 'href') ||
([
'img',
'video',
'audio',
'source',
'track',
'iframe',
'embed',
].includes(node.tagName) &&
attribute.name === 'src') ||
(node.tagName === 'video' && attribute.name === 'poster') ||
(node.tagName === 'object' && attribute.name === 'data') ||
(node.tagName === 'input' &&
attribute.name === 'src' &&
node.attrs.some(
(attr) =>
attr.name === 'type' &&
attr.value.toLowerCase() === 'image'
))
)
references.push({
svgUse: node.tagName === 'use',
target: attribute.value
.replace(/[\t\n\r]/gu, '')
.replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/gu, ''),
image: !['a', 'area', 'iframe', 'object', 'embed'].includes(
node.tagName
),
});
if (node.tagName === 'iframe' && attribute.name === 'srcdoc') {
const embedded = htmlNavigation(attribute.value);
for (const reference of embedded.references)
references.push(
reference.target.startsWith('#') &&
!reference.embeddedAnchors &&
!reference.bases?.length
? {
...reference,
embeddedAnchors: embedded.anchors,
}
: reference
);
}
if (
['img', 'source'].includes(node.tagName) &&
attribute.name === 'srcset'
) {
const candidates = parseSrcset(attribute.value);
if (!candidates.length)
references.push({ target: '', image: true });
for (const candidate of candidates)
references.push({ target: candidate.url, image: true });
}
}
for (const child of node.childNodes ?? []) visit(child);
}
visit(parseFragment(html));
for (const reference of references) {
if (
reference.svgUse &&
!reference.embeddedAnchors &&
reference.target.startsWith('#')
) {
reference.image = false;
reference.embeddedAnchors = anchors;
}
}
return {
anchors,
references:
baseHref === undefined
? references
: references.map((reference) => ({
...reference,
bases: [baseHref, ...(reference.bases ?? [])],
})),
};
}
export function guidanceProse(markdown) {
function text(node, preceding = '') {
if (
['script', 'style', 'template', 'pre', 'code'].includes(
node.tagName
)
)
return ' ';
if (node.tagName === 'a') {
const href = node.attrs?.find(
(attribute) => attribute.name === 'href'
)?.value;
if (
href &&
/^[a-z][a-z\d+.-]*:(?!\/\/)/iu.test(href) &&
node.childNodes?.length === 1 &&
node.childNodes[0].nodeName === '#text' &&
node.childNodes[0].value === href
)
return ' ';
}
if (node.nodeName === '#text')
return node.value.replace(
/(?:\b[a-z][a-z\d+.-]*:\/\/|\/\/|\bwww\.)[^\s]*?(?=[)\]}>][.,;:!?]*@|\s|$)/giu,
(url, offset) => {
const opening = (
preceding + node.value.slice(0, offset)
).at(-1);
const closing = {
'"': '"',
"'": "'",
'“': '”',
'”': '”',
'‘': '’',
'’': '’',
}[opening];
const boundary = closing ? url.indexOf(closing) : -1;
return boundary >= 0 &&
/^[.,;:!?]*@/u.test(url.slice(boundary + 1))
? ' ' + url.slice(boundary)
: ' ';
}
);
let content = '';
for (const child of node.childNodes ?? [])
content += text(child, preceding + content);
return [
'address',
'article',
'aside',
'details',
'summary',
'dialog',
'dl',
'dt',
'dd',
'fieldset',
'legend',
'figure',
'figcaption',
'footer',
'form',
'header',
'hgroup',
'hr',
'main',
'nav',
'ol',
'ul',
'section',
'table',
'caption',
'thead',
'tbody',
'tfoot',
'tr',
'td',
'th',
'p',
'li',
'blockquote',
'div',
'br',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
].includes(node.tagName)
? '\n' + content + '\n'
: content;
}
return text(parseFragment(new Marked().parse(markdown)));
}
export function guidanceStandaloneImports(markdown) {
const candidates = markdownLexer
.lexer(markdown)
.filter((token) => token.type === 'paragraph')
.flatMap((token) => [
...token.raw.matchAll(/^ {0,3}@([^\s]+)[\t ]*$/gmu),
])
.map((match) => match[1]);
// Markdown can split an HTML container across several top-level tokens.
// Check the parsed output tree as well as raw source formatting. Only text
// directly inside a root paragraph can supply the standalone directive.
const document = parseFragment(new Marked().parse(markdown));
const visible = new Map();
for (const node of document.childNodes) {
if (node.tagName !== 'p') continue;
const text = node.childNodes
.map((child) =>
child.nodeName === '#text' ? child.value : '\uFFFC'
)
.join('');
for (const match of text.matchAll(/^ {0,3}@([^\s]+)[\t ]*$/gmu))
visible.set(match[1], (visible.get(match[1]) ?? 0) + 1);
}
return candidates.filter((candidate) => {
const count = visible.get(candidate) ?? 0;
if (!count) return false;
visible.set(candidate, count - 1);
return true;
});
}
export function guidanceAnchors(markdown) {
const slugger = new GithubSlugger();
const found = new Set();
const headings = [];
const marker = `data-guidance-${randomUUID()}`;
const renderer = new Marked({
renderer: {
heading(token) {
const index = headings.push(inlineText(token.tokens)) - 1;
return `<h${token.depth} ${marker}="${index}">${this.parser.parseInline(token.tokens)}</h${token.depth}>\n`;
},
},
});
const navigation = htmlNavigation(renderer.parse(markdown), (node) => {
const attribute = node.attrs?.find((attr) => attr.name === marker);
if (attribute)
found.add(slugger.slug(headings[Number(attribute.value)]));
});
return new Set([...found, ...navigation.anchors]);
}
function isLiteralRepositoryPath(token) {
// A typo in the directory or a new root filename must still be checked.
// Exclude recognizable prose/code forms instead of allowlisting paths.
if (/^(?:@|--|[a-z][a-z\d+.-]*:|\/\/)/iu.test(token)) return false;
const explicitRelative = /^(?:\.\/|\.\.\/)/u.test(token);
if (!explicitRelative && /[^\p{L}\p{N}_./#-]/u.test(token)) return false;
if (token.includes('YYYY-MM-DD') || /(?:^|\/)\.\.\.(?:\/|$)/u.test(token))
return false;
const path = token.split('#')[0];
// Bare dotted identifiers are ambiguous. Recognize conventional file
// suffixes; other filenames can be made explicit with ./ or a Markdown link.
// This applies to user-defined symbols as well as JavaScript globals.
if (
/^[\p{L}_][\p{L}\p{N}_]*(?:\.[\p{L}_][\p{L}\p{N}_]*)+$/u.test(path) &&
!DOCUMENT_EXTENSION.test(path) &&
!/\.(?:md|mdx|json|jsonc|ya?ml|[cm]?[jt]sx?|html?|css|scss|sass|less|toml|xml|txt|sh|py|sql|svg|png|jpe?g|webp|gif|m3u8?|conf|ini|lock)$/iu.test(
path
)
)
return false;
return (
path.includes('/') ||
/^(?:Dockerfile|Containerfile|Makefile|GNUmakefile|Justfile|Procfile|Gemfile|Rakefile|Vagrantfile|LICENSE|LICENCE|NOTICE|COPYING|AUTHORS|CONTRIBUTORS|README|CHANGELOG)$/u.test(
path
) ||
/^(?:\.[\p{L}\p{N}_-][\p{L}\p{N}_.-]*|[\p{L}\p{N}_-][\p{L}\p{N}_.-]*\.[\p{L}][\p{L}\p{N}_-]*)$/u.test(
path
)
);
}
export function guidanceReferences(markdown, includeLiterals) {
const tokens = markdownLexer.lexer(markdown);
const markerTag = `guidance-reference-${randomUUID()}`;
const metadata = [];
function mark(token, reference) {
const index = metadata.push(reference) - 1;
token.type = 'html';
token.raw = `<${markerTag} data-index="${index}"></${markerTag}>`;
token.text = token.raw;
}
markdownLexer.walkTokens(tokens, (token) => {
if (token.type === 'def')
mark(token, {
target: decodeEntities(token.href, true),
definition: true,
});
else if (token.type === 'unresolved-reference')
mark(token, { unresolvedReference: token.label });
else if (
includeLiterals &&
token.type === 'codespan' &&
isLiteralRepositoryPath(token.text)
)
mark(token, { target: token.text, literal: true });
});
// Let Markdown rendering and HTML tree construction retain container context
// for ordinary links and for metadata that has no rendered navigation node.
const visible = [];
const navigation = htmlNavigation(new Marked().parser(tokens), (node) => {
if (node.tagName !== markerTag) return;
const index = Number(
node.attrs.find((attr) => attr.name === 'data-index')?.value
);
if (metadata[index]) visible.push(metadata[index]);
});
const result = [];
const seen = new Set();
function add(reference) {
const key = JSON.stringify(reference);
if (!seen.has(key)) {
seen.add(key);
result.push(reference);
}
}
for (const reference of navigation.references) add(reference);
const usedTargets = new Set(
navigation.references.map((reference) => reference.target)
);
for (const { definition, ...reference } of visible) {
if (!definition || !usedTargets.has(reference.target)) add(reference);
}
return result;
}
+21 -5
View File
@@ -9,20 +9,36 @@
"executor": "nx:run-commands",
"cache": true,
"inputs": [
"{workspaceRoot}/tools/skills/validate-repository-skills.mjs",
"{workspaceRoot}/tools/skills/validate-repository-skills.test.mjs"
"{projectRoot}/*.mjs",
"{projectRoot}/project.json",
{
"externalDependencies": [
"marked",
"parse5",
"github-slugger",
"parse-srcset",
"typescript"
]
}
],
"options": {
"command": "node --test tools/skills/validate-repository-skills.test.mjs",
"command": "node --test tools/skills/validate-repository-skills.test.mjs tools/skills/validate-agent-guidance.test.mjs",
"cwd": "{workspaceRoot}"
}
},
"lint": {
"executor": "nx:run-commands",
"options": {
"command": "node --check tools/skills/validate-repository-skills.mjs",
"commands": [
"node --check tools/skills/validate-repository-skills.mjs",
"node --check tools/skills/validate-repository-skills.test.mjs",
"node --check tools/skills/validate-agent-guidance.mjs",
"node --check tools/skills/validate-agent-guidance.test.mjs",
"node --check tools/skills/agent-guidance-markdown.mjs"
],
"cwd": "{workspaceRoot}"
}
},
"inputs": ["{projectRoot}/*.mjs", "{projectRoot}/project.json"]
}
}
}
+321
View File
@@ -0,0 +1,321 @@
import ts from 'typescript';
import { readFile, realpath, stat } from 'node:fs/promises';
import {
dirname,
extname,
isAbsolute,
relative,
resolve,
sep,
} from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import {
DOCUMENT_EXTENSION,
guidanceAnchors as anchors,
guidanceProse,
guidanceStandaloneImports,
guidanceReferences as references,
} from './agent-guidance-markdown.mjs';
const MARKDOWN_EXTENSION = /\.(?:md|markdown|mdown|mkd|mdx)$/iu;
const SURFACES = [
'AGENTS.md',
'CLAUDE.md',
'docs/maintenance/agent-context-map.md',
'docs/maintenance/agent-guidance-migration.md',
];
const LIMITS = { 'AGENTS.md': [200, 16384], 'CLAUDE.md': [30, 2048] };
function within(root, path) {
const local = relative(root, path);
return (
!isAbsolute(local) && local !== '..' && !local.startsWith(`..${sep}`)
);
}
async function validateReference(
rootDir,
source,
{ target, literal, image, unresolvedReference, embeddedAnchors, bases }
) {
if (unresolvedReference !== undefined)
return `${source}: unresolved Markdown reference "${unresolvedReference}"`;
if (/^[a-z]:[\\/]/iu.test(target))
return `${source}: use a repository-relative path instead of a Windows drive path: ${target}`;
if (/^file:/iu.test(target))
return `${source}: use a repository-relative path instead of a file URL: ${target}`;
if (image && !target) return `${source}: empty media target`;
let resolvedBasePath;
if (bases?.length) {
try {
let base = pathToFileURL(resolve(rootDir, source));
for (const href of bases) {
if (
/^(?:file:|[a-z]:[\\/])/iu.test(
href.replace(/[\t\n\r]/gu, '').trimStart()
)
)
return `${source}: use a repository-relative HTML base instead of a file URL or Windows drive path`;
base = new URL(href, base);
}
const url = new URL(target, base);
if (url.protocol !== 'file:') return;
resolvedBasePath = fileURLToPath(url);
target = url.pathname + url.search + url.hash;
embeddedAnchors = undefined;
} catch {
return `${source}: malformed HTML base or target: ${target}`;
}
}
if (/^(?:[a-z][a-z\d+.-]*:|\/\/)/iu.test(target)) return;
let path;
let anchor;
try {
const hash = target.indexOf('#');
const pathAndQuery = hash < 0 ? target : target.slice(0, hash);
path = decodeURIComponent(pathAndQuery.split('?')[0]);
anchor =
hash < 0 ? undefined : decodeURIComponent(target.slice(hash + 1));
} catch {
return `${source}: malformed local link: ${target}`;
}
if (image && !path && !resolvedBasePath)
return `${source}: media target requires a path: ${target}`;
if (embeddedAnchors && !path && !image)
return !anchor || embeddedAnchors.includes(anchor)
? undefined
: `${source}: missing anchor "${anchor}" in iframe srcdoc`;
const absolute =
resolvedBasePath ??
(path
? resolve(
literal ? rootDir : dirname(resolve(rootDir, source)),
path
)
: resolve(rootDir, source));
if (!within(rootDir, absolute))
return `${source}: referenced path escapes repository root: ${target}`;
try {
const actual = await realpath(absolute);
if (!within(rootDir, actual))
return `${source}: referenced path escapes repository root: ${target}`;
if (image && !(await stat(actual)).isFile())
return `${source}: media target is not a file: ${target}`;
if (
anchor &&
!image &&
MARKDOWN_EXTENSION.test(extname(actual)) &&
!anchors(await readFile(actual, 'utf8')).has(anchor)
) {
return `${source}: missing anchor "${anchor}" in ${target}`;
}
} catch (error) {
if (['ENOENT', 'ENOTDIR'].includes(error.code))
return `${source}: referenced path does not exist: ${target}`;
if (error.code === 'EISDIR')
return `${source}: anchor target is a directory: ${target}`;
throw error;
}
}
async function packageMentions(rootDir) {
async function readJson(path) {
try {
const text = await readFile(resolve(rootDir, path), 'utf8');
if (path !== 'tsconfig.base.json') return JSON.parse(text);
const parsed = ts.parseConfigFileTextToJson(path, text);
if (parsed.error)
throw new Error(
ts.flattenDiagnosticMessageText(
parsed.error.messageText,
'\n'
)
);
return parsed.config;
} catch (error) {
if (error.code === 'ENOENT') return {};
throw error;
}
}
const manifest = await readJson('package.json');
const config = await readJson('tsconfig.base.json');
const packages = [
...Object.keys(manifest.dependencies ?? {}),
...Object.keys(manifest.devDependencies ?? {}),
...Object.keys(manifest.optionalDependencies ?? {}),
...Object.keys(manifest.peerDependencies ?? {}),
];
const names = [
...packages,
...Object.keys(config.compilerOptions?.paths ?? {}),
];
const declared = names
.filter((name) => /^@[^/]+\//u.test(name))
.map((name) => name.slice(1));
const scopes = new Set(declared.map((name) => name.split('/')[0]));
return (raw) => {
if (packages.includes(`@${raw}`) || packages.includes(raw)) return true;
// ASCII punctuation also belongs to package names and version ranges.
let token = raw.split(/[,;:!?([{]|(?=[^\x00-\x7f])\p{P}/u, 1)[0];
token = token.replace(/[?!.,;:)"'\]}]+$/u, '');
token = token.replace(/['’]s$/iu, '');
token = token.replace(
/^([^/@]+(?:\/[^/@]+)?)@(?:(?:[~^]|[<>]=?|=)?\d[\w.*+-]*|\*|[a-z][\w-]*)$/iu,
'$1'
);
if (
token.split(/[\/\\]/u).some((part) => part === '.' || part === '..')
)
return false;
if (packages.includes(token) || packages.includes(`@${token}`))
return true;
const path = token.split(/[?#]/u, 1)[0];
if (
/%[\da-f]{2}/iu.test(token) ||
MARKDOWN_EXTENSION.test(path) ||
/(?:^|\/)(?:AGENTS|CLAUDE|INSTRUCTIONS|README|LICENSE|LICENCE|NOTICE|COPYING|AUTHORS|CONTRIBUTORS|CHANGELOG|CONTRIBUTING|SECURITY|CODE_OF_CONDUCT|SUPPORT)$/iu.test(
path
) ||
DOCUMENT_EXTENSION.test(path)
)
return false;
if (packages.includes(token)) return true;
if (token.endsWith('/*') && scopes.has(token.slice(0, -2))) return true;
return declared.some((name) => {
const star = name.indexOf('*');
return star < 0
? token === name ||
(packages.includes(`@${name}`) &&
token.startsWith(`${name}/`))
: token.startsWith(name.slice(0, star)) &&
token.endsWith(name.slice(star + 1));
});
};
}
export async function validateAgentGuidance({ rootDir }) {
rootDir = await realpath(rootDir);
const diagnostics = [];
const isPackageMention = await packageMentions(rootDir);
for (const source of SURFACES) {
let markdown;
try {
markdown = await readFile(resolve(rootDir, source), 'utf8');
} catch (error) {
if (error.code !== 'ENOENT') throw error;
diagnostics.push(`${source}: required guidance file is missing`);
continue;
}
if (LIMITS[source]) {
const [maxLines, maxBytes] = LIMITS[source];
const lines =
markdown === ''
? 0
: markdown
.replace(/(?:\r\n|[\r\n])$/u, '')
.split(/\r\n|[\r\n]/u).length;
const bytes = Buffer.byteLength(markdown, 'utf8');
if (lines > maxLines)
diagnostics.push(
`${source}: at most ${maxLines} lines allowed (received ${lines})`
);
if (bytes > maxBytes)
diagnostics.push(
`${source}: at most ${maxBytes} UTF-8 bytes allowed (received ${bytes})`
);
const prose = guidanceProse(markdown);
const imports = guidanceStandaloneImports(markdown);
const inlineImports = [];
for (const match of prose.matchAll(
/(?=(?:^|[^\p{L}\p{N}_@])@([^\s]+))/gu
)) {
const token = match[1];
if (isPackageMention(token)) continue;
if (
/^[\w.-]+@(?:[a-z\d](?:[a-z\d-]*[a-z\d])?\.)+[a-z]{2,}$/iu.test(
token
.split(/[([{]/u, 1)[0]
.replace(/\p{P}+$/gu, '')
.replace(/['’]s$/iu, '')
)
)
continue;
if (
/[./\\]/u.test(token) ||
/^(?:LICENSE|Makefile|Dockerfile|AGENTS|CLAUDE)(?:$|[.,;)])/u.test(
token
)
) {
inlineImports.push(token);
continue;
}
// Check real filenames before interpreting punctuation as prose.
const candidates = new Set([
token,
token.replace(/[?!.,;:)"'\]}]+$/u, ''),
]);
for (const boundary of token.matchAll(
/[,;:!?([{]|(?=[^\x00-\x7f])\p{P}/gu
))
candidates.add(token.slice(0, boundary.index));
for (const candidate of candidates) {
try {
if (
(await stat(resolve(rootDir, candidate))).isFile()
) {
inlineImports.push(token);
break;
}
} catch (error) {
if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR')
throw error;
}
}
}
if (
inlineImports.some((token) => token !== 'AGENTS.md') ||
(source === 'AGENTS.md' && inlineImports.length) ||
(source === 'CLAUDE.md' &&
inlineImports.filter((token) => token === 'AGENTS.md')
.length !== 1)
) {
diagnostics.push(
`${source}: additional or inline guidance imports are not allowed`
);
}
if (source === 'CLAUDE.md') {
if (imports.length !== 1 || imports[0] !== 'AGENTS.md')
diagnostics.push(
`${source}: exactly one standalone @AGENTS.md import is required; no other imports are allowed`
);
} else if (imports.length)
diagnostics.push(`${source}: imports are not allowed`);
}
for (const reference of references(
markdown,
!source.endsWith('agent-guidance-migration.md')
)) {
const diagnostic = await validateReference(
rootDir,
source,
reference
);
if (diagnostic) diagnostics.push(diagnostic);
}
}
return { checkedFiles: SURFACES.length, diagnostics };
}
if (
process.argv[1] &&
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
) {
const { checkedFiles, diagnostics } = await validateAgentGuidance({
rootDir: process.cwd(),
});
if (diagnostics.length) {
for (const diagnostic of diagnostics) console.error(diagnostic);
process.exitCode = 1;
} else console.log(`Validated ${checkedFiles} agent guidance files.`);
}
File diff suppressed because it is too large. Load diff