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

+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.