From fa6f988dd312a938d23eb00fe4f45a296694ff84 Mon Sep 17 00:00:00 2001 From: 4gray Date: Thu, 30 Jul 2026 12:16:27 +0200 Subject: [PATCH] docs(skills): refresh Nx and SQLite ownership --- .../skills/iptvnator-nx-architecture/SKILL.md | 98 +++++++--- .../iptvnator-sqlite-db-worker/SKILL.md | 76 ++++++-- docs/architecture/nx-workspace-boundaries.md | 182 ++++++++++-------- docs/architecture/sqlite-db-worker.md | 136 ++++++++++--- 4 files changed, 333 insertions(+), 159 deletions(-) diff --git a/.codex/skills/iptvnator-nx-architecture/SKILL.md b/.codex/skills/iptvnator-nx-architecture/SKILL.md index 94e643d4a..0e56a0817 100644 --- a/.codex/skills/iptvnator-nx-architecture/SKILL.md +++ b/.codex/skills/iptvnator-nx-architecture/SKILL.md @@ -1,44 +1,78 @@ --- name: iptvnator-nx-architecture -description: Repository-specific Nx monorepo structure, library placement rules, scoped path aliases, and migration guardrails for portal/workspace/app code. +description: Use when deciding where IPTVnator code belongs, creating or moving Nx projects, changing scoped aliases or tags, editing lint targets, or validating module boundaries. --- # IPTVnator Nx Architecture -Use this skill when deciding where code belongs, extracting libraries, changing imports, editing project tags, or refactoring portal/workspace/app boundaries. +## Discover Before Deciding -## Project Shape - -- `apps/web`: Angular renderer application. -- `apps/electron-backend`: Electron main process and native/runtime integration. -- `apps/*-e2e`: Playwright E2E projects. -- `apps/*-mock-server`: local development and E2E mock servers. -- `libs/playlist/*`: M3U/import/shared playlist functionality. -- `libs/portal/*`: Xtream, Stalker, and provider-neutral portal functionality. -- `libs/workspace/*`: workspace shell and dashboard. -- `libs/ui/*`: provider-neutral UI, playback, EPG, remote control, and pipes. -- `libs/shared/*`: contracts, database, and pure utility code. - -## Import Policy - -- Use scoped aliases from `tsconfig.base.json`, for example `@iptvnator/services` and `@iptvnator/shared/interfaces`. -- Do not reintroduce legacy bare aliases such as `services`, `components`, or `shared-interfaces`. -- Prefer library public APIs (`src/index.ts`) over deep imports unless a sub-entrypoint is explicitly configured. -- Keep app-only code in `apps/*`; shared behavior belongs in a domain library. - -## Tag Policy - -- Every project should have `scope:*`, `domain:*`, and `type:*` tags. -- Use `type:feature` for route/component orchestration, `type:data-access` for stores/API/persistence, `type:ui` for reusable components, and `type:util` for pure helpers/contracts. -- Keep `type:data-access` from depending on `type:feature` or `type:ui`. -- Add or update `@nx/enforce-module-boundaries` constraints when introducing new tag families. - -## Validation +In a fresh worktree, install first: ```bash +pnpm install --frozen-lockfile pnpm nx show projects -pnpm nx lint -pnpm nx test +pnpm nx show project +pnpm nx show projects --withTarget test +pnpm nx show projects --withTarget e2e ``` -When dependencies are not installed in a fresh worktree, run `pnpm install --frozen-lockfile` first. +Discovery is authoritative. The current app groups are `web`, +`electron-backend`, `web-backend`, `remote-control-web`, `website`, `web-e2e`, +`electron-backend-e2e`, `stalker-mock-server`, and `xtream-mock-server`. Current +tool projects are `eslint-tools`, `packaging`, `release-tools`, and +`repository-skills`. + +## Place Code by Ownership + +- `apps/`: runtime, development, E2E, and mock-server applications. +- `tools/`: repository automation; tag its projects `scope:tools`. +- `libs/`: domain libraries. +- `type:feature`: route and screen orchestration. +- `type:ui`: reusable visual components. +- `type:data-access`: injectable state, API, persistence, or orchestration. +- `type:util`: the destination for **new pure** helpers and contracts only. + +Provider-neutral collection services that coordinate persistence belong in +`libs/portal/shared/data-access`; pure collection helpers belong in +`libs/portal/shared/util`; reusable views belong in +`libs/portal/shared/ui`. Existing injectable/stateful services under a `util` +path are legacy debt, not placement precedent. + +## Preserve Boundaries + +Every project keeps one `scope:*`, `domain:*`, and `type:*` tag. Enforced type +directions are: + +| Source | Allowed dependencies | +| ----------------- | ------------------------------ | +| app, E2E, dev-app | feature, UI, data-access, util | +| website | UI, util | +| feature | feature, UI, data-access, util | +| UI | UI, data-access, util | +| data-access | data-access, util | +| util | util | + +Domain constraints are additive. Never weaken either constraint to solve a +placement problem. Preserve the documented `workspace-shell-util` path/tag +exception: its injectable services require `type:data-access` so app routes can +eagerly import them without loading the lazy shell feature. + +Use aliases from `tsconfig.base.json` and public `src/index.ts` barrels. Do not +add legacy bare aliases or deep imports. For a buildable library that has a +local `package.json`, its package name must match its scoped alias. + +## Validate the Change + +Production TypeScript targets under 300 lines; 400 is the hard limit. Tests are +limited to 1200. The legacy baseline may only shrink. See +`tools/eslint/max-lines-config.mjs` and regenerate after a split with +`tools/eslint/generate-max-lines-baseline.mjs`; never add a new baseline entry. + +Quote recursive globs in command-based lint targets, for example +`eslint "apps//**/*.ts"`, then compare coverage with +`find apps/ -name '*.ts' | wc -l`. + +Discover targets, then run affected lint/test/build targets and the closest +available E2E target; do not invent targets. Canonical guidance: +`docs/architecture/nx-workspace-boundaries.md`. diff --git a/.codex/skills/iptvnator-sqlite-db-worker/SKILL.md b/.codex/skills/iptvnator-sqlite-db-worker/SKILL.md index 49bf8a163..0a02dffa6 100644 --- a/.codex/skills/iptvnator-sqlite-db-worker/SKILL.md +++ b/.codex/skills/iptvnator-sqlite-db-worker/SKILL.md @@ -1,31 +1,69 @@ --- name: iptvnator-sqlite-db-worker -description: Repository-specific guidance for Electron non-EPG SQLite worker boundaries, request-scoped DB progress events, and validation for slow DB operations. +description: Use when changing Electron SQLite IPC, database-worker operations, request-scoped progress or cancellation, worker packaging, or runtime verification of non-EPG database work. --- # IPTVnator SQLite DB Worker -Use this skill when changing Electron SQLite operations, worker-backed database flows, DB progress events, or Xtream/playlist import/search/delete persistence. +## Ownership and Flow -## Ownership +- Renderer service: `libs/services/src/lib/database-electron.service.ts` +- Preload API: `apps/electron-backend/src/app/api/main.preload.ts` +- IPC handlers: `apps/electron-backend/src/app/events/database/` +- Client: `apps/electron-backend/src/app/services/database-worker-client.ts` +- Protocol: `apps/electron-backend/src/app/workers/database-worker.types.ts` +- Thin dispatcher: `apps/electron-backend/src/app/workers/database.worker.ts` +- Connection: `apps/electron-backend/src/app/workers/database.worker-connection.ts` +- Runtime paths: `apps/electron-backend/src/app/workers/worker-runtime-paths.ts` +- SQL operations: `apps/electron-backend/src/app/database/operations/` +- Shared schema: `libs/shared/database/src/lib/schema.ts` +- Bundler: `apps/electron-backend/build-worker.js` +- Canonical guide: `docs/architecture/sqlite-db-worker.md` -- Runtime worker client: `apps/electron-backend/src/app/services/database-worker-client.ts` -- Worker protocol: `apps/electron-backend/src/app/workers/database-worker.types.ts` -- Worker dispatcher: `apps/electron-backend/src/app/workers/database.worker.ts` -- SQL operation modules: `apps/electron-backend/src/app/database/operations/` -- Shared schema/path helpers: `libs/shared/database/src/` -- Architecture doc: `docs/architecture/sqlite-db-worker.md` +The chain is renderer `DatabaseService` → preload IPC → main event handler → +`DatabaseWorkerClient` and its request protocol → worker dispatcher → +worker connection → operation module → shared +`@iptvnator/shared/database/schema`, then response or request-scoped event back +to the originating renderer. Keep SQL-heavy logic in operation modules and the +worker entrypoint focused on dispatch/orchestration. -## Rules +The build script produces three bundles: EPG parser, database, and playlist +refresh. EPG parsing stays in its dedicated worker. Do not silently migrate +lightweight download handlers or EPG-specific main-process query/mapping/fetch +handlers as part of unrelated database work. -- Keep heavy non-EPG SQLite work off the Electron main thread. -- Keep SQL-heavy logic in operation modules; keep the worker entrypoint as dispatcher/orchestration. -- Emit request-scoped `DB_OPERATION_EVENT` progress for long-running operations. -- Preserve cancellation as cooperative and chunk-based. -- Do not break existing preload API method names without a coordinated renderer migration. +## Identity, Progress, and Cancellation -## Validation +`requestId` is generated for every client request and correlates worker +event/response transport. `operationId` is the renderer-visible identity for +long-operation progress and cooperative cancellation. -- Run `pnpm nx test electron-backend` for worker/client/operation changes. -- Run targeted Electron E2E for import, search, delete, backup/restore, or downloads flows when touched. -- Use `IPTVNATOR_TRACE_DB=1` or `IPTVNATOR_TRACE_SQL=1` for manual debugging. +Tracked operations are save content, delete Xtream content, restore Xtream user +data, delete playlist, and delete all playlists. The first four are +cancellable. Delete-all is deliberately tracked with `cancellable: false`. + +Cancellation is cooperative at chunk checkpoints. Committed chunks remain +committed and the request finally rejects with `AbortError`. For the +operation/busy lifecycle, only a terminal completed/error/cancelled event +settles UI state; a cancel-requested flag may update immediately. + +Inside synchronous `better-sqlite3` transactions, prepared writes must use +`.run()`. `.execute()` defers work and can commit a silent no-op. + +## Rebuild and Verify + +After worker source changes: + +```bash +pnpm nx test electron-backend +pnpm nx run electron-backend:build-worker +stat dist/apps/electron-backend/workers/database.worker.js +``` + +Confirm the artifact timestamp, restart Electron, then run the closest +Electron E2E or CDP workflow. + +For SQL output, `IPTVNATOR_TRACE_DB=1` and `IPTVNATOR_TRACE_SQL=1` emit only +fixed, allowlisted statement types through the shared redacting summary in +`libs/shared/logging/src/lib/sql-trace-summary.ts`; never log expanded SQL or +bound values. DB transport traces remain separately redacted. diff --git a/docs/architecture/nx-workspace-boundaries.md b/docs/architecture/nx-workspace-boundaries.md index 17c085040..e73a47b54 100644 --- a/docs/architecture/nx-workspace-boundaries.md +++ b/docs/architecture/nx-workspace-boundaries.md @@ -1,100 +1,130 @@ # Nx Workspace Boundaries -This document records the current monorepo boundary conventions for IPTVnator. +This document records the current monorepo placement, tagging, and validation +contract for IPTVnator. Nx discovery is the canonical project inventory; avoid +copying an exhaustive project list into documentation. -## Fresh Worktree Bootstrap +## Fresh Worktree Bootstrap and Discovery -Install dependencies before using Nx discovery or targets: +Install dependencies before relying on Nx: ```bash pnpm install --frozen-lockfile pnpm nx show projects ``` -`pnpm nx show projects` depends on the workspace-local Nx packages under -`node_modules`. In a fresh worktree without dependencies it will fail before it -can inspect project metadata. +`pnpm nx show projects` requires the workspace-local Nx packages in +`node_modules`. Inspect project ownership and available validation targets +before choosing commands: + +```bash +pnpm nx show project +pnpm nx show projects --withTarget test +pnpm nx show projects --withTarget e2e +``` + +Do not invent a `test`, `build`, or `e2e` target because a similarly named +project has one. Run affected lint/test/build targets that exist and the closest +available E2E target for the changed behavior. + +## Placement Decision + +- `apps/` owns runtime applications, development servers, E2E applications, + and provider mock servers. +- `libs/` owns reusable code grouped by product domain and architectural role. +- `tools/` owns repository automation such as lint, packaging, release, and + repository-skill validation. Nx projects there use `scope:tools`. + +Inside `libs/`, choose the role before the path: + +- `type:feature` owns routes, screens, and feature orchestration. +- `type:ui` owns reusable visual components. +- `type:data-access` owns injectable state, API access, persistence, and + orchestration. +- `type:util` is the destination for new pure helpers and contracts only. + +For example, provider-neutral collection services that coordinate favorites, +recents, EPG, or playback persistence belong in +`libs/portal/shared/data-access`. Pure collection types and transformations stay +in `libs/portal/shared/util`, while reusable collection views stay in +`libs/portal/shared/ui`. Existing injectable or stateful services in a `util` +path are legacy debt, not precedent for new placement. ## Project Tags -Every Nx project should carry three tag families in `project.json`: +Every Nx project keeps one tag from each family in `project.json`: -1. `scope:*` - ownership area, for example `scope:portal`, `scope:workspace`, - `scope:shared`, `scope:electron`, `scope:e2e`, or `scope:dev-tools`. -2. `domain:*` - product/runtime domain, for example `domain:xtream`, - `domain:stalker`, `domain:m3u`, `domain:playback`, `domain:web`, or - `domain:shared-runtime`. -3. `type:*` - architectural role, for example `type:app`, `type:e2e`, - `type:dev-app`, `type:feature`, `type:ui`, `type:data-access`, - `type:util`, `type:tool`, or `type:website`. +1. `scope:*` records ownership, such as `scope:portal`, `scope:workspace`, + `scope:shared`, `scope:electron`, `scope:e2e`, or `scope:tools`. +2. `domain:*` records the product/runtime domain. +3. `type:*` records the architectural role. -`eslint.config.mjs` uses these tags with `@nx/enforce-module-boundaries`. -When adding a project, choose tags before adding imports so dependency direction -is clear from the start. +`eslint.config.mjs` enforces these type directions: -## Import Aliases +| Source tag | Allowed dependency type tags | +| ------------------ | ------------------------------ | +| `type:app` | feature, UI, data-access, util | +| `type:e2e` | feature, UI, data-access, util | +| `type:dev-app` | feature, UI, data-access, util | +| `type:website` | UI, util | +| `type:feature` | feature, UI, data-access, util | +| `type:ui` | UI, data-access, util | +| `type:data-access` | data-access, util | +| `type:util` | util | -Use scoped `@iptvnator/*` aliases from `tsconfig.base.json`. +Domain constraints in the same rule are additive to type constraints. If an +import violates either family, move the contract or implementation to its +proper owner instead of weakening a constraint. -Examples: +`workspace-shell-util` is a deliberate path/tag exception: +`libs/workspace/shell/util` is tagged `type:data-access` because it exports +injectable services that depend on `@iptvnator/services`. The web app imports +those services eagerly from `apps/web/src/app/app.routes.ts` without pulling +the lazy workspace shell feature into the initial bundle. -```ts -import { SettingsStore } from '@iptvnator/services'; -import { Playlist } from '@iptvnator/shared/interfaces'; -import { DialogService } from '@iptvnator/ui/components'; +## Import Aliases and Public APIs + +Use scoped aliases from `tsconfig.base.json` and expose public imports through a +library's `src/index.ts`. Do not introduce legacy bare aliases such as +`services`, `components`, `shared-interfaces`, or `database`, and avoid deep +imports unless a sub-entrypoint is explicitly configured. + +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 +paths to `dist/` during `@nx/js:tsc` builds. + +## TypeScript File Size + +`tools/eslint/max-lines-config.mjs` is the single source of truth: + +- production TypeScript should stay below 300 lines and has a hard maximum of + 400; +- tests, E2E specs, and E2E infrastructure have a maximum of 1200; +- blank lines and comments are not counted. + +Pre-existing violations live in +`tools/eslint/max-lines-baseline.mjs`. That baseline may only shrink. After +splitting a baselined file, run +`node tools/eslint/generate-max-lines-baseline.mjs`; never add a new file to the +baseline. A genuinely inseparable new file needs a justified file-wide +directive, which the generator deliberately skips. + +## Command-Based Lint Targets + +Quote recursive globs so POSIX and Windows hosts lint the same files: + +```bash +eslint "apps//**/*.ts" +find apps/ -name '*.ts' | wc -l ``` -Do not introduce legacy bare aliases such as: - -- `components` -- `m3u-state` -- `m3u-utils` -- `services` -- `shared-interfaces` -- `shared-portals` -- `remote-control` -- `database` -- `database-schema` -- `database-path-utils` -- `workspace-dashboard-feature` -- `workspace-dashboard-data-access` - -The lint config blocks these aliases so new code uses the same visible -namespace and ownership convention. - -Buildable libraries that have a local `package.json` should use the same public -name as their scoped alias. Nx uses `package.json.name` when it rewrites -buildable dependency paths to `dist/` during `@nx/js:tsc` builds. - -## Dependency Direction - -- `type:feature` may use `type:feature`, `type:ui`, `type:data-access`, and - `type:util`. -- `type:ui` may use `type:ui`, `type:data-access`, and `type:util`. -- `type:data-access` may use `type:data-access` and `type:util`. -- `type:util` may use only `type:util`. - -If a change needs a dependency in the opposite direction, move the shared -contract into a lower-level library instead of weakening boundaries. - -Portal collection orchestration that reads/writes favorites, recent items, live -playback, or EPG data belongs in `libs/portal/shared/data-access`, not -`libs/portal/shared/util`. That keeps pure collection helpers importable by -Xtream/Stalker data-access libraries while allowing shared UI to use -provider-specific collection services without creating cycles. - -Note: `workspace-shell-util` (`libs/workspace/shell/util`) is tagged -`type:data-access` despite its path. It exports injectable services such as -`WorkspaceStartupPreferencesService` that depend on `@iptvnator/services`, and -it must stay eagerly importable from `apps/web/src/app/app.routes.ts` without -pulling the lazy-loaded workspace shell feature bundle into the initial chunk. +An unquoted `**` can expand to a shallow subset on POSIX while still returning +success. After editing such a target, compare ESLint's linted-file count with +the `find` count. ## CI Enforcement -The `Lint` job in `.github/workflows/ci.yml` runs -`pnpm nx affected --target=lint` on PRs and -`pnpm nx run-many --target=lint --all` on master pushes, so -`@nx/enforce-module-boundaries` violations, legacy bare-alias imports, and -`max-lines` violations fail CI. Root config or lockfile changes mark every -project affected, so the boundary rules cannot be dodged on PRs. Run -`pnpm run lint` locally before pushing. +The CI lint job runs affected projects on pull requests and all projects on +master pushes. Root config or lockfile changes affect every project, so module +boundaries, legacy-alias restrictions, and max-lines enforcement apply across +the workspace. diff --git a/docs/architecture/sqlite-db-worker.md b/docs/architecture/sqlite-db-worker.md index 9cd127449..13357bc3a 100644 --- a/docs/architecture/sqlite-db-worker.md +++ b/docs/architecture/sqlite-db-worker.md @@ -10,7 +10,9 @@ Related: ## Summary -- Heavy non-EPG SQLite work no longer runs on Electron's main thread. +- Heavy non-EPG SQLite work no longer runs on Electron's main thread. The + explicitly lightweight download and EPG-specific SQLite handlers remain in + main. - A dedicated long-lived database worker now handles the slow Xtream and playlist database operations that were freezing the UI. - Renderer APIs stay stable. The main change is that progress and long-running @@ -28,6 +30,14 @@ The worker cutover addresses three concrete problems: ## Current Ownership +### Renderer and preload boundary + +These files own the renderer-facing database service and stable Electron +bridge: + +1. `libs/services/src/lib/database-electron.service.ts` +2. `apps/electron-backend/src/app/api/main.preload.ts` + ### Main-process runtime wiring These files own worker lifecycle and IPC bridging: @@ -48,7 +58,7 @@ These files own the worker protocol and the SQLite work itself: 3. `apps/electron-backend/src/app/workers/database.worker-connection.ts` 4. `apps/electron-backend/src/app/workers/worker-runtime-paths.ts` -### Pure database operation modules +### Database operation and support modules Keep SQL-heavy logic here so the worker entry remains a thin dispatcher: @@ -63,25 +73,53 @@ Keep SQL-heavy logic here so the worker entry remains a thin dispatcher: 9. `apps/electron-backend/src/app/database/operations/title-match.operations.ts` 10. `apps/electron-backend/src/app/database/operations/tmdb.operations.ts` 11. `apps/electron-backend/src/app/database/operations/epg-mapping.operations.ts` +12. `apps/electron-backend/src/app/database/operations/title-sources.operations.ts` +13. `apps/electron-backend/src/app/database/operations/vod-source-pin.operations.ts` -(plus the shared cancellation helper `operation-control.ts` in the same directory) +Focused helpers in the same directory keep the operation modules and dispatcher +small: + +1. `content-search.util.ts` +2. `title-token-glob.ts` +3. `operation-control.ts` +4. `performance-phase-capture.ts` +5. `xtream-content-operation-steps.ts` + +Worker operations use the shared table contract in +`libs/shared/database/src/lib/schema.ts`, imported by worker code through +`@iptvnator/shared/database/schema`. ## Worker Architecture ### Request flow -1. Renderer calls the existing preload API such as `window.electron.dbSaveContent`. -2. `ipcMain.handle(...)` in the Electron backend builds a payload and delegates - to `DatabaseWorkerClient`. -3. `DatabaseWorkerClient` lazily starts one long-lived `worker_threads` worker +1. Renderer `DatabaseService` calls the stable preload API, such as + `window.electron.dbSaveContent`. +2. The preload invokes an IPC channel owned by the focused event modules under + `apps/electron-backend/src/app/events/database/`. +3. `ipcMain.handle(...)` builds a payload and delegates to + `DatabaseWorkerClient`. +4. `DatabaseWorkerClient` lazily starts one long-lived `worker_threads` worker and correlates requests with a generated `requestId`. -4. The worker executes SQLite work and sends back either: +5. The protocol reaches the worker dispatcher, which obtains the worker + connection and delegates SQL work to an operation module using the shared + schema. +6. The worker sends back either: 1. `ready` 2. `event` 3. `response` -5. The main process resolves the IPC request and forwards worker events back to +7. The main process resolves the IPC request and forwards worker events back to the originating renderer process. +`requestId` and `operationId` have different scopes: + +1. `DatabaseWorkerClient` generates a fresh `requestId` for every request. It + correlates worker `event` and `response` messages with the pending main-side + transport request and is not renderer-visible operation state. +2. A renderer supplies an `operationId` for tracked long-running work. Progress + events and cooperative cancellation use that stable identity across the + renderer, preload, main process, and worker. + ### Why one long-lived worker - It avoids worker startup cost on every search/delete/import. @@ -309,6 +347,11 @@ Current shipped operation names: 4. `delete-playlist` 5. `delete-all-playlists` +All five are tracked. Save content, delete Xtream content, restore Xtream user +data, and delete playlist are cancellable. Delete all playlists deliberately +uses `cancellable: false`: its renderer-visible progress is tracked, but a +cancel request does not interrupt it. + The event is forwarded to the renderer as `DB_OPERATION_EVENT`. ### Cancellation contract @@ -329,7 +372,10 @@ If a worker operation is canceled: 3. the UI clears its busy state without treating the operation as success Cancellation is cooperative and chunk-based. Already committed SQLite batches -stay committed. +stay committed. For an operation's busy lifecycle, only the terminal +`completed`, `error`, or `cancelled` event settles UI state. The UI may set a +separate cancel-requested flag immediately so the cancel action cannot be +clicked twice while it waits for the authoritative terminal event. With the exact `IPTVNATOR_PERF_WORKER_PROFILING=1` opt-in, receipt of a cancel for a correlated active request also emits a @@ -370,8 +416,27 @@ falls back to the legacy progress API if the newer event channel is missing. ## Migrated Operations -The worker now owns all heavy non-EPG SQLite paths plus the remaining portal -state handlers that still used direct main-thread SQLite access. +The worker owns heavy non-EPG SQLite paths and portal state operations that +would otherwise block Electron main. + +### Deliberate direct-main exceptions + +Lightweight work that coordinates main-process runtime or remains +EPG-specific is not migrated incidentally: + +1. `apps/electron-backend/src/app/events/database/downloads.events.ts` keeps + small download-row reads/writes beside native dialogs, filesystem cleanup, + and the main-process download runtime. +2. `apps/electron-backend/src/app/events/database/epg-db.events.ts` keeps the + EPG programme-search IPC handler. +3. `apps/electron-backend/src/app/events/epg-fetch.service.ts`, + `apps/electron-backend/src/app/events/epg-mapping.service.ts`, and + `apps/electron-backend/src/app/events/epg-query.service.ts` keep EPG + freshness, mapping, and lookup behavior in their focused main-process + owners, while EPG parsing/import remains in its dedicated worker. + +Do not move these paths as part of unrelated database work. Reassess the +boundary if a handler becomes heavy enough to block the main process. ### Categories @@ -613,8 +678,8 @@ Current implementation paths: 2. `libs/portal/xtream/data-access/src/lib/with-favorites.feature.ts` 3. `libs/portal/xtream/data-access/src/lib/with-recent-items.ts` 4. `libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.ts` -5. `libs/portal/shared/util/src/lib/collection/unified-recent-data.service.ts` -6. `libs/portal/shared/util/src/lib/collection/unified-favorites-data.service.ts` +5. `libs/portal/shared/data-access/src/lib/collection/unified-recent-data.service.ts` +6. `libs/portal/shared/data-access/src/lib/collection/unified-favorites-data.service.ts` ### Busy states @@ -635,10 +700,14 @@ renderer can actually paint the loading state instead of freezing. ### Worker bundling -`apps/electron-backend/build-worker.js` now bundles both: +`apps/electron-backend/build-worker.js` produces three bundles: -1. `epg-parser.worker.ts` -2. `database.worker.ts` +1. `apps/electron-backend/src/app/workers/epg-parser.worker.ts` → + `dist/apps/electron-backend/workers/epg-parser.worker.js` +2. `apps/electron-backend/src/app/workers/database.worker.ts` → + `dist/apps/electron-backend/workers/database.worker.js` +3. `apps/electron-backend/src/app/workers/playlist-refresh.worker.ts` → + `dist/apps/electron-backend/workers/playlist-refresh.worker.js` The worker build also aliases: @@ -666,12 +735,16 @@ artifacts for: 2. macOS app bundles 3. Windows unpacked app resources -The script checks: +The verifier's `workerFiles` list currently checks: 1. `epg-parser.worker.js` 2. `database.worker.js` -3. `better-sqlite3` in one approved unpacked node_modules location -4. Snap packaging compatibility settings for `better-sqlite3`: + +The playlist refresh bundle is produced by the worker build, but is not yet a +third explicit `workerFiles` check. The verifier also checks: + +1. `better-sqlite3` in one approved unpacked node_modules location +2. Snap packaging compatibility settings for `better-sqlite3`: - `snap.base = core22` - Snap launch args keep the X11 fallback - Snap and the other non-Flatpak Linux artifacts build on Ubuntu 22.04, while Flatpak builds on a separate Ubuntu 24.04 CI runner @@ -853,11 +926,15 @@ Available trace flags: Logs `window.electron.*` method calls crossing the preload bridge so you can see whether the renderer is still reaching Electron main. 3. `IPTVNATOR_TRACE_DB=1` - Logs `DatabaseWorkerClient` request dispatch, completion timing, and emitted - `DB_OPERATION_EVENT` payloads. + Logs redacted `DatabaseWorkerClient` request dispatch, completion timing, + and emitted `DB_OPERATION_EVENT` summaries. It also enables the safe SQL + statement-type summaries described below. 4. `IPTVNATOR_TRACE_SQL=1` - Logs SQLite statements for the shared main-process connection and the DB - worker connection using `better-sqlite3` verbose hooks. + Logs only a fixed allowlisted statement type such as `SELECT`, `INSERT`, or + `UPDATE` for the shared main-process and worker connections. The verbose + hook output passes through + `libs/shared/logging/src/lib/sql-trace-summary.ts`; expanded SQL text and + bound values are never emitted. 5. `IPTVNATOR_TRACE_WINDOW=1` Logs BrowserWindow loading, navigation, `unresponsive`, and `render-process-gone` transitions. @@ -882,19 +959,14 @@ CI=1 NX_TASKS_RUNNER_DYNAMIC_OUTPUT=false pnpm nx run electron-backend:build --s ## Current Limitations -These are intentionally still out of scope for this first cut: +These remain intentionally out of scope: 1. moving network-heavy Xtream fetches off the current path -2. migrating every remaining small SQLite IPC handler to the worker +2. migrating explicitly lightweight download and EPG-specific main-process + handlers without evidence that they block Electron main 3. richer delete progress reporting for bulk destructive operations 4. repo-wide Angular/Jest cleanup for the currently failing web test baseline -(Request cancellation, originally listed here, has since shipped — see the -"Cancellation contract" section above: `DB_CANCEL_OPERATION` in -`apps/electron-backend/src/app/api/main.preload.ts`, `AbortError` production in -`database.worker.ts`, and `DatabaseService.cancelOperation` in -`libs/services/src/lib/database-electron.service.ts`.) - ## Extending The Worker When adding another heavy SQLite operation: