Merge remote-tracking branch 'origin/master' into claude/admiring-bell-7132d7

This commit is contained in:
4gray committed 2026-09-27 09:36:50 +02:00
commit 8eaa5fcf7c
127 files changed
+6071 -456

No files matched your search

@@ -232,6 +232,29 @@ sub-entrypoints are `@iptvnator/shared/interfaces/ipc-commands` and
`@iptvnator/shared/interfaces` barrel pulls in `ngx-indexed-db`, which the
preload bundle must not carry, so the preload only type-imports the barrel
and value-imports those two dependency-free modules directly.
`@iptvnator/workspace/shell/util/settings-search` exists for the opposite
reason: the main `workspace-shell-util` barrel is imported eagerly, and the
settings search index must stay in the lazy settings and shell chunks.
The web app's root shell (`app.component.ts`, `app.config.ts` and the services
they construct) is on the renderer's initial path, where a barrel costs its
whole library: esbuild keeps every Angular component module a barrel
re-exports, because their static definitions count as side effects. Importing
`WindowControlsComponent` and `DialogService` from `@iptvnator/ui/components`
once put the channel lists, EPG views, `@angular/forms`, `date-fns` and the
Stalker data layer into `main.js`. The root shell therefore uses file-level
entries: `@iptvnator/ui/components/window-controls`,
`@iptvnator/ui/components/confirm-dialog` and
`@iptvnator/playlist/shared/ui/stalker-connection-editor` (like the existing
`@iptvnator/ui/epg/progress-panel`), and loads anything used only on demand
through a local file it imports dynamically (the Stalker connection editor,
the release-notes dialog, the external-player info dialog). A local file,
not the library alias, is the dynamic-import target because
`@nx/enforce-module-boundaries` forbids static imports of a library the same
project also loads dynamically. `renderer.initialBytes` in
[performance journeys](performance-journeys.md) guards the result; to see why a
module is eager, build with `pnpm nx build web --stats-json` and follow the
static imports in `dist/apps/web/stats.json` from `apps/web/src/main.ts`.
For a buildable library that has a local `package.json`, its `name` must match
the scoped alias. Nx uses that package name when rewriting buildable dependency
+25 -2
View File
@@ -82,6 +82,28 @@ type-checking each through a language service; spec type errors therefore do
not fail Jest (the web configs already ran with `diagnostics: false`), while
`isolatedModules`-incompatible syntax such as a type re-export without
`export type` still fails at load time.
In CI, a pull request skips the Tier A suite (and the merged-coverage upload)
when every changed file is outside Tier A test inputs:
`tools/coverage/unit-coverage-scope.mjs` holds the allowlist (Markdown, `docs/`,
notes and plans, agent guidance, workflows other than `ci.yml` and
`build-and-make.yaml`, the website, E2E and mock-server apps,
release/packaging/skills/performance tooling, and a `package.json` edit
confined to non-`coverage:*` scripts). Anything else, including files no Nx
project owns such as `jest.preset.js` or `tsconfig.base.json`, and every
`{workspaceRoot}` input a Tier A test target declares, runs the full suite;
master pushes always run it. `nx affected` is deliberately not used for this
decision because a change to an unowned file affects no project. A node test
parses every Tier A source for string literals that point at repository files
outside the owning project (`tier-a-external-references.mjs`) and fails if the
allowlist would skip any of them, so a new cross-project read cannot be
silently exempted.
Jest's transform cache is kept in `JEST_CACHE_DIRECTORY` and persisted with
`actions/cache`: pull requests restore it, while only master pushes (starting
from an empty cache, so it holds exactly the current tree) and maintainer
dispatches save it. A shared Nx task cache is not used: Nx indexes its local
cache in a machine-specific database, so a restored cache folder is never hit,
and sharing it safely needs Nx Cloud or another supported remote cache.
`coverage:merge` requires every configured Tier A report before replacing the
merged output. Strict health validation also requires the merged Istanbul map
itself to contain usable instrumentation for every runtime-owning Tier A file,
@@ -146,8 +168,9 @@ After an E2E run, generate the semantic summary with:
pnpm run coverage:e2e:summary
```
CI runs the Electron suite as three Playwright shards per OS
(`--shard=<n>/3`, split by spec file because the suite is sequential). Each
CI runs the Electron suite as Playwright shards (`--shard=<n>/<total>`, split
by spec file because the suite is sequential): three shards on Ubuntu and
Windows, two on macOS, whose runners are the scarcest and queued longest. Each
shard uploads `playwright-report-electron-<os>-<n>`; the follow-up
`Electron E2E summary` job downloads the shards of each OS into their own
directory and runs the summary per OS with `--input=<directory>` and
+51 -3
View File
@@ -169,7 +169,8 @@ restarting playback; remote commands retain captured playback order. See the
Search is shell-owned and route-aware:
1. Disabled on settings routes.
1. On settings routes, searches the settings themselves (see
[Settings search](#settings-search)).
2. Enabled on sources routes.
3. Enabled for `/workspace/search`, which is the Electron-only routed
global-search view. `Ctrl/Cmd+F` in Electron opens this route and
@@ -214,8 +215,10 @@ Rail navigation is also shell-owned:
Command palette behavior is shell-owned but view-extensible:
1. The shell resolves commands into three groups in fixed order: current view,
this playlist, then global.
1. The shell resolves commands into groups in fixed order: current view,
this playlist, global, then settings. The settings group appears only for a
non-empty query and holds at most six settings matches (see
[Settings search](#settings-search)).
2. Shell-owned commands are derived from route context and current playlist
state; empty groups are omitted instead of rendering disabled placeholders.
3. Workspace features contribute current-view commands through
@@ -245,6 +248,51 @@ Command palette behavior is shell-owned but view-extensible:
is disabled. The new player setting applies to the next playback session; an
existing stream is not re-mounted.
### Settings search
Settings rows are searchable from the header search on `/workspace/settings`
and from the command palette. Both use the same index and ranking.
1. The index is `SETTINGS_SEARCH_ENTRIES` in
`libs/workspace/shell/util/src/lib/settings-search/`, published through the
`@iptvnator/workspace/shell/util/settings-search` sub-entrypoint. Eager
code imports the main shell util barrel, so the index stays out of it and
ships only in lazy chunks (the initial-bytes ratchet enforces this).
2. Each entry names its section, title and description translation keys,
untranslated synonyms (`keywords`), runtime `requires`, and an optional
`fallbackId`. Section definitions (`SETTINGS_SECTION_DEFINITIONS`) are the
single source for the settings navigation too.
3. Every titled `.setting-item` in the section templates carries
`data-setting-id`. `settings-search-registry.spec.ts` fails when a row, id,
title key or description key drifts from the index, so a new settings row
must be added to the index in the same change.
4. `SettingsSearchService.search()` matches the translated title and
description of the current language plus the keywords; every query token
must match (AND), and a label prefix outranks a word start, which outranks
an inner match. Rows whose `requires` the runtime lacks are never returned.
Embedded MPV rows depend on a lazy support probe
(`ensureEmbeddedMpvSupportLoaded()`), run when the settings page or the
command palette opens, never from shell bootstrap; frame copy also needs
`frameCopyAvailable`, matching the settings page gate.
5. Settings routes use `local-filter` search mode, so the term lives in `q`.
While `q` is set, the settings page shows ranked results in place of the
section page and the settings context panel shows per-section match
counts, muting sections without matches. The search box is shown on
settings even when no playlist exists.
6. Choosing a result, pressing `Enter` in the header search (best match), or
picking a settings command in the palette calls `reveal()`: it navigates
to the section page without `q` (which clears the box) and the page
scrolls to, focuses, and briefly highlights the row once the form is
hydrated. A row hidden by the current form state falls back to its
`fallbackId`, the control that makes it appear. A reveal must win over the
typed term: `WorkspaceShellSearchSyncService` drops a keystroke still
waiting for its debounce through `onReveal()`, and Enter does not apply
the term first, because either `q` sync navigation would supersede the
reveal navigation. Keyboard users keep a `:focus-visible` ring on the row
after the highlight fades.
7. `Ctrl/Cmd+F` on settings focuses the header search instead of opening
global search.
Keyboard shortcut help is shell-owned:
1. `WorkspaceKeyboardShortcutsService` is provided by `WorkspaceShellComponent`.
+7
View File
@@ -91,6 +91,13 @@ exit. The retained handle still provides the actual exit code and signal.
The Linux portable build uploads `packaged-frame-copy-smoke` reports and traces
even when the smoke fails. Check the paused-frame screenshot and trace before
classifying a zero rendered-frame signal as an infrastructure flake.
The Snap and Flatpak `--embedded-mpv-runtime-probe` launches in
`build-and-make.yaml` run under GNU `timeout --verbose -k 10 300` with
`ELECTRON_ENABLE_LOGGING=1`, so a main process that throws before app ready
and blocks on Electron's uncaught-exception dialog under xvfb fails within
five minutes with a distinct `::error::` (exit 124, or 137 after timeout's
announced KILL escalation) and its stderr in the step log, instead of holding
the job until the 120-minute limit.
## Main-process ownership