mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
First step of the performance-journeys ratchet (plan thread: J1 `launch`, counter `renderer.initialBytes`).
- `tools/performance/measure-initial-bytes.mjs` reads the built `dist/apps/web/index.html` and sums `index.html` plus every same-origin `<script src>`, `<link rel="stylesheet">` and `<link rel="modulepreload">` it references. Manifest, icons, external URLs and lazy chunks are not counted. A referenced file missing from the build fails the measurement instead of counting as zero bytes.
- `--json` prints the breakdown; `--summary <file>` writes the `journeys.<journey>.counters` shape a ratchet checker will consume (next PR).
- New Nx project `performance-tools` (test + lint targets), Tier B in `tools/coverage/coverage-policy.json`, root scripts `perf:initial-bytes` and `perf:tools:test`.
- New contract `docs/architecture/performance-journeys.md`, linked from the validation map, the agent context map and the README.
- **Review follow-ups:** resources are deduplicated by request URL (query kept, fragment dropped); `index.html` is parsed with parse5 (already a repository dependency, scripting enabled), so comments, bogus comments, raw-text bodies (script/style/noscript/title/textarea), inert `<template>` contents and character references in attributes all follow the HTML5 algorithm instead of a hand-written scanner; the review's edge cases stay as regression tests; docs show the `pnpm --silent` form for JSON output and explain how the counter relates to Angular's rounded "Initial total".
- **Found while measuring:** the environment files and the playback diagnostic panel imported the whole `package.json` (`import packageJson from '@package'`), which esbuild cannot tree-shake, so `main.js` carried the complete file and the counter moved with every script or dependency edit. They now import `{ version }` only (eb662c485): `main.js` shrinks by **11,539 bytes** and the counter no longer depends on `package.json`. Jest's ESM loader exposes JSON only as a default export, so the two web Jest configs map `@package` to a stub that serves the real file's fields as named exports. Release note: `.changes/web-version-only-from-package-json.md` (`type: perf`).
Production build after this PR measures **2,739,508 bytes** (11 files + `index.html`); Angular's "Initial total" is this minus `index.html` and `assets/app-config.js`. The baseline file and the CI check land in the follow-up PRs; C1 (lazy Angular date locales) then lowers it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
443 lines
19 KiB
JSON
443 lines
19 KiB
JSON
{
|
|
"version": 1,
|
|
"reporting": {
|
|
"mergedCoverageDir": "coverage/merged",
|
|
"e2eSummaryDir": "coverage/e2e",
|
|
"unitCodecovFlag": "unit",
|
|
"coverageRatchet": {
|
|
"merged": {
|
|
"statements": 69.54,
|
|
"branches": 59.07,
|
|
"functions": 67.44,
|
|
"lines": 69.85
|
|
},
|
|
"criticalFiles": [
|
|
{
|
|
"path": "apps/electron-backend/src/app/server/http-server.ts",
|
|
"statements": {
|
|
"minimumCovered": 83,
|
|
"minimumPercent": 90.21
|
|
}
|
|
},
|
|
{
|
|
"path": "apps/electron-backend/src/app/events/remote-control.events.ts",
|
|
"statements": {
|
|
"minimumCovered": 112,
|
|
"minimumPercent": 96.55
|
|
}
|
|
},
|
|
{
|
|
"path": "apps/electron-backend/src/app/events/settings.events.ts",
|
|
"statements": {
|
|
"minimumCovered": 26,
|
|
"minimumPercent": 96.29
|
|
}
|
|
},
|
|
{
|
|
"path": "apps/electron-backend/src/app/events/database/downloads.events.ts",
|
|
"statements": {
|
|
"minimumCovered": 79,
|
|
"minimumPercent": 62.69
|
|
}
|
|
},
|
|
{
|
|
"path": "apps/electron-backend/src/app/events/database/download-removal-requests.ts",
|
|
"statements": {
|
|
"minimumCovered": 53,
|
|
"minimumPercent": 91.37
|
|
}
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"unitCoverage": {
|
|
"description": "Tier A projects collect and trend source coverage. Tier B projects are validated without percentage coverage. Tier C projects are excluded from the source coverage baseline.",
|
|
"tierA": [
|
|
{
|
|
"name": "web",
|
|
"root": "apps/web",
|
|
"sourceRoot": "apps/web/src",
|
|
"jestRootMode": "project",
|
|
"validationCommand": "pnpm nx test web",
|
|
"e2eTags": ["@web", "@m3u", "@pwa", "@settings"]
|
|
},
|
|
{
|
|
"name": "electron-backend",
|
|
"root": "apps/electron-backend",
|
|
"sourceRoot": "apps/electron-backend/src",
|
|
"validationCommand": "pnpm nx test electron-backend",
|
|
"e2eTags": ["@electron", "@critical", "@persistence"]
|
|
},
|
|
{
|
|
"name": "web-backend",
|
|
"root": "apps/web-backend",
|
|
"sourceRoot": "apps/web-backend/src",
|
|
"validationCommand": "pnpm nx test web-backend",
|
|
"e2eTags": ["@self-hosted", "@web"]
|
|
},
|
|
{
|
|
"name": "services",
|
|
"root": "libs/services",
|
|
"sourceRoot": "libs/services/src",
|
|
"validationCommand": "pnpm nx test services",
|
|
"e2eTags": ["@m3u", "@persistence", "@settings"]
|
|
},
|
|
{
|
|
"name": "epg-data-access",
|
|
"root": "libs/epg/data-access",
|
|
"sourceRoot": "libs/epg/data-access/src",
|
|
"validationCommand": "pnpm nx test epg-data-access",
|
|
"e2eTags": ["@epg"]
|
|
},
|
|
{
|
|
"name": "m3u-state",
|
|
"root": "libs/m3u-state",
|
|
"sourceRoot": "libs/m3u-state/src",
|
|
"validationCommand": "pnpm nx test m3u-state",
|
|
"e2eTags": ["@m3u", "@persistence"]
|
|
},
|
|
{
|
|
"name": "playback-util",
|
|
"root": "libs/playback/util",
|
|
"sourceRoot": "libs/playback/util/src",
|
|
"validationCommand": "pnpm nx test playback-util",
|
|
"e2eTags": ["@playback", "@m3u", "@xtream", "@stalker"]
|
|
},
|
|
{
|
|
"name": "playlist-import-feature",
|
|
"root": "libs/playlist/import/feature",
|
|
"sourceRoot": "libs/playlist/import/feature/src",
|
|
"validationCommand": "pnpm nx test playlist-import-feature",
|
|
"e2eTags": ["@m3u", "@file-import"]
|
|
},
|
|
{
|
|
"name": "playlist-m3u-feature-player",
|
|
"root": "libs/playlist/m3u/feature-player",
|
|
"sourceRoot": "libs/playlist/m3u/feature-player/src",
|
|
"validationCommand": "pnpm nx test playlist-m3u-feature-player",
|
|
"e2eTags": ["@m3u", "@playback"]
|
|
},
|
|
{
|
|
"name": "playlist-shared-ui",
|
|
"root": "libs/playlist/shared/ui",
|
|
"sourceRoot": "libs/playlist/shared/ui/src",
|
|
"validationCommand": "pnpm nx test playlist-shared-ui",
|
|
"e2eTags": ["@m3u"]
|
|
},
|
|
{
|
|
"name": "playlist-shared-util",
|
|
"root": "libs/playlist/shared/util",
|
|
"sourceRoot": "libs/playlist/shared/util/src",
|
|
"validationCommand": "pnpm nx test playlist-shared-util",
|
|
"e2eTags": ["@m3u"]
|
|
},
|
|
{
|
|
"name": "portal-catalog-feature",
|
|
"root": "libs/portal/catalog/feature",
|
|
"sourceRoot": "libs/portal/catalog/feature/src",
|
|
"validationCommand": "pnpm nx test portal-catalog-feature",
|
|
"e2eTags": ["@xtream", "@stalker"]
|
|
},
|
|
{
|
|
"name": "portal-downloads-feature",
|
|
"root": "libs/portal/downloads/feature",
|
|
"sourceRoot": "libs/portal/downloads/feature/src",
|
|
"validationCommand": "pnpm nx test portal-downloads-feature",
|
|
"e2eTags": ["@downloads", "@electron", "@persistence"]
|
|
},
|
|
{
|
|
"name": "portal-shared-data-access",
|
|
"root": "libs/portal/shared/data-access",
|
|
"sourceRoot": "libs/portal/shared/data-access/src",
|
|
"validationCommand": "pnpm nx test portal-shared-data-access",
|
|
"e2eTags": ["@xtream", "@stalker", "@persistence"]
|
|
},
|
|
{
|
|
"name": "portal-shared-ui",
|
|
"root": "libs/portal/shared/ui",
|
|
"sourceRoot": "libs/portal/shared/ui/src",
|
|
"validationCommand": "pnpm nx test portal-shared-ui",
|
|
"e2eTags": ["@xtream", "@stalker"]
|
|
},
|
|
{
|
|
"name": "portal-shared-util",
|
|
"root": "libs/portal/shared/util",
|
|
"sourceRoot": "libs/portal/shared/util/src",
|
|
"validationCommand": "pnpm nx test portal-shared-util",
|
|
"e2eTags": ["@xtream", "@stalker"]
|
|
},
|
|
{
|
|
"name": "portal-stalker-data-access",
|
|
"root": "libs/portal/stalker/data-access",
|
|
"sourceRoot": "libs/portal/stalker/data-access/src",
|
|
"validationCommand": "pnpm nx test portal-stalker-data-access",
|
|
"e2eTags": ["@stalker"]
|
|
},
|
|
{
|
|
"name": "portal-stalker-feature",
|
|
"root": "libs/portal/stalker/feature",
|
|
"sourceRoot": "libs/portal/stalker/feature/src",
|
|
"validationCommand": "pnpm nx test portal-stalker-feature",
|
|
"e2eTags": ["@stalker"]
|
|
},
|
|
{
|
|
"name": "portal-xtream-data-access",
|
|
"root": "libs/portal/xtream/data-access",
|
|
"sourceRoot": "libs/portal/xtream/data-access/src",
|
|
"validationCommand": "pnpm nx test portal-xtream-data-access",
|
|
"e2eTags": ["@xtream"]
|
|
},
|
|
{
|
|
"name": "portal-xtream-feature",
|
|
"root": "libs/portal/xtream/feature",
|
|
"sourceRoot": "libs/portal/xtream/feature/src",
|
|
"validationCommand": "pnpm nx test portal-xtream-feature",
|
|
"e2eTags": ["@xtream"]
|
|
},
|
|
{
|
|
"name": "database",
|
|
"root": "libs/shared/database",
|
|
"sourceRoot": "libs/shared/database/src",
|
|
"validationCommand": "pnpm nx test database",
|
|
"e2eTags": ["@electron", "@persistence"]
|
|
},
|
|
{
|
|
"name": "shared-interfaces",
|
|
"root": "libs/shared/interfaces",
|
|
"sourceRoot": "libs/shared/interfaces/src",
|
|
"validationCommand": "pnpm nx test shared-interfaces",
|
|
"e2eTags": ["@m3u", "@xtream", "@stalker"]
|
|
},
|
|
{
|
|
"name": "shared-logging",
|
|
"root": "libs/shared/logging",
|
|
"sourceRoot": "libs/shared/logging/src",
|
|
"validationCommand": "pnpm nx test shared-logging",
|
|
"e2eTags": ["@electron", "@xtream", "@stalker", "@settings"]
|
|
},
|
|
{
|
|
"name": "shared-host-health",
|
|
"root": "libs/shared/host-health",
|
|
"sourceRoot": "libs/shared/host-health/src",
|
|
"validationCommand": "pnpm nx test shared-host-health",
|
|
"e2eTags": ["@xtream", "@stalker"]
|
|
},
|
|
{
|
|
"name": "m3u-utils",
|
|
"root": "libs/shared/m3u-utils",
|
|
"sourceRoot": "libs/shared/m3u-utils/src",
|
|
"validationCommand": "pnpm nx test m3u-utils",
|
|
"e2eTags": ["@m3u"]
|
|
},
|
|
{
|
|
"name": "components",
|
|
"root": "libs/ui/components",
|
|
"sourceRoot": "libs/ui/components/src",
|
|
"validationCommand": "pnpm nx test components",
|
|
"e2eTags": ["@m3u", "@search", "@persistence"]
|
|
},
|
|
{
|
|
"name": "ui-epg",
|
|
"root": "libs/ui/epg",
|
|
"sourceRoot": "libs/ui/epg/src",
|
|
"validationCommand": "pnpm nx test ui-epg",
|
|
"e2eTags": ["@epg"]
|
|
},
|
|
{
|
|
"name": "@iptvnator/pipes",
|
|
"root": "libs/ui/pipes",
|
|
"sourceRoot": "libs/ui/pipes/src",
|
|
"validationCommand": "pnpm nx test @iptvnator/pipes",
|
|
"e2eTags": ["@epg", "@settings"]
|
|
},
|
|
{
|
|
"name": "ui-playback",
|
|
"root": "libs/ui/playback",
|
|
"sourceRoot": "libs/ui/playback/src",
|
|
"validationCommand": "pnpm nx test ui-playback",
|
|
"e2eTags": ["@playback", "@m3u"]
|
|
},
|
|
{
|
|
"name": "workspace-dashboard-data-access",
|
|
"root": "libs/workspace/dashboard/data-access",
|
|
"sourceRoot": "libs/workspace/dashboard/data-access/src",
|
|
"validationCommand": "pnpm nx test workspace-dashboard-data-access",
|
|
"e2eTags": ["@dashboard", "@persistence"]
|
|
},
|
|
{
|
|
"name": "workspace-dashboard-feature",
|
|
"root": "libs/workspace/dashboard/feature",
|
|
"sourceRoot": "libs/workspace/dashboard/feature/src",
|
|
"validationCommand": "pnpm nx test workspace-dashboard-feature",
|
|
"e2eTags": ["@dashboard", "@persistence"]
|
|
},
|
|
{
|
|
"name": "workspace-shell-feature",
|
|
"root": "libs/workspace/shell/feature",
|
|
"sourceRoot": "libs/workspace/shell/feature/src",
|
|
"validationCommand": "pnpm nx test workspace-shell-feature",
|
|
"e2eTags": ["@search", "@settings", "@persistence"]
|
|
},
|
|
{
|
|
"name": "workspace-shell-util",
|
|
"root": "libs/workspace/shell/util",
|
|
"sourceRoot": "libs/workspace/shell/util/src",
|
|
"validationCommand": "pnpm nx test workspace-shell-util",
|
|
"e2eTags": ["@search", "@settings"]
|
|
}
|
|
],
|
|
"tierB": [
|
|
{
|
|
"name": "website",
|
|
"root": "apps/website",
|
|
"validationCommand": "pnpm nx test website",
|
|
"reason": "Astro marketing site; build output and targeted static checks are more meaningful than source percentage coverage."
|
|
},
|
|
{
|
|
"name": "packaging",
|
|
"root": "tools/packaging",
|
|
"validationCommand": "pnpm nx test packaging",
|
|
"reason": "Node package identity checks validate generated packaging metadata."
|
|
},
|
|
{
|
|
"name": "release-tools",
|
|
"root": "tools/release",
|
|
"validationCommand": "pnpm nx test release-tools",
|
|
"reason": "Node tests validate release-note parsing, rendering and gate policy; the scripts are release tooling, not shipped source."
|
|
},
|
|
{
|
|
"name": "repository-skills",
|
|
"root": "tools/skills",
|
|
"validationCommand": "pnpm nx test repository-skills",
|
|
"reason": "Node tests validate committed repository-skill metadata and documented paths; the validator is agent tooling, not shipped source."
|
|
},
|
|
{
|
|
"name": "eslint-tools",
|
|
"root": "tools/eslint",
|
|
"validationCommand": "pnpm nx test eslint-tools",
|
|
"reason": "Node tests assert the committed max-lines baseline still matches what the generator produces; the scripts are lint tooling, not shipped source, and percentage coverage over a generated list would not mean anything."
|
|
},
|
|
{
|
|
"name": "performance-tools",
|
|
"root": "tools/performance",
|
|
"validationCommand": "pnpm nx test performance-tools",
|
|
"reason": "Node tests validate the initial-bytes measurement over synthetic build output; the scripts are performance tooling, not shipped source."
|
|
},
|
|
{
|
|
"name": "shared-marketing-fixtures",
|
|
"root": "libs/shared/marketing-fixtures",
|
|
"validationCommand": "pnpm nx test shared-marketing-fixtures",
|
|
"reason": "Screenshot-safe fictional poster catalog shared by both mock servers; the spec asserts catalog order and shape, and percentage coverage over fixture data would not mean anything."
|
|
},
|
|
{
|
|
"name": "remote-control-web",
|
|
"root": "apps/remote-control-web",
|
|
"validationCommand": "pnpm nx build remote-control-web",
|
|
"reason": "Product surface without unit specs today; validate build output and add focused tests when behavior changes."
|
|
},
|
|
{
|
|
"name": "web-e2e",
|
|
"root": "apps/web-e2e",
|
|
"validationCommand": "pnpm nx run web-e2e:e2e -- --project=chromium",
|
|
"reason": "Playwright semantic flow coverage is reported separately."
|
|
},
|
|
{
|
|
"name": "electron-backend-e2e",
|
|
"root": "apps/electron-backend-e2e",
|
|
"validationCommand": "pnpm nx run electron-backend-e2e:e2e",
|
|
"reason": "Playwright semantic flow coverage is reported separately."
|
|
}
|
|
],
|
|
"tierC": [
|
|
{
|
|
"name": "xtream-mock-server",
|
|
"root": "apps/xtream-mock-server",
|
|
"reason": "Validated through Playwright API and user-flow tests unless mock behavior changes directly."
|
|
},
|
|
{
|
|
"name": "stalker-mock-server",
|
|
"root": "apps/stalker-mock-server",
|
|
"reason": "Validated through Playwright API and user-flow tests unless mock behavior changes directly."
|
|
},
|
|
{
|
|
"name": "shared-testing",
|
|
"root": "libs/shared/testing",
|
|
"reason": "Test helper library."
|
|
},
|
|
{
|
|
"name": "remote-control",
|
|
"root": "libs/ui/remote-control",
|
|
"reason": "No unit specs yet; validate through remote-control flows or add focused tests when behavior changes directly."
|
|
},
|
|
{
|
|
"name": "shared-portals",
|
|
"root": "libs/ui/shared-portals",
|
|
"reason": "Type-only library after the dead epg-view/live-epg-panel components were removed; only the LiveEpgPanelSummary interface remains, so there is no runtime code to unit test."
|
|
}
|
|
]
|
|
},
|
|
"e2eSemanticCoverage": {
|
|
"trackedTags": [
|
|
"@critical",
|
|
"@electron",
|
|
"@web",
|
|
"@xtream",
|
|
"@stalker",
|
|
"@m3u",
|
|
"@search",
|
|
"@epg",
|
|
"@persistence",
|
|
"@settings",
|
|
"@pwa",
|
|
"@self-hosted"
|
|
],
|
|
"criticalJourneys": [
|
|
{
|
|
"name": "Electron app starts and renders workspace",
|
|
"projects": ["electron-backend-e2e"],
|
|
"matchAnyTags": ["@critical"]
|
|
},
|
|
{
|
|
"name": "Xtream portal import, browse, and cached access",
|
|
"projects": ["web-e2e", "electron-backend-e2e"],
|
|
"matchAnyTags": ["@xtream"]
|
|
},
|
|
{
|
|
"name": "Stalker portal import and browse",
|
|
"projects": ["web-e2e", "electron-backend-e2e"],
|
|
"matchAnyTags": ["@stalker"]
|
|
},
|
|
{
|
|
"name": "M3U import, playback, and playlist state",
|
|
"projects": ["web-e2e", "electron-backend-e2e"],
|
|
"matchAnyTags": ["@m3u"]
|
|
},
|
|
{
|
|
"name": "Workspace search across providers",
|
|
"projects": ["electron-backend-e2e"],
|
|
"matchAnyTags": ["@search"]
|
|
},
|
|
{
|
|
"name": "EPG rendering and source management",
|
|
"projects": ["electron-backend-e2e"],
|
|
"matchAnyTags": ["@epg"]
|
|
},
|
|
{
|
|
"name": "Favorites, recent items, and restart persistence",
|
|
"projects": ["electron-backend-e2e"],
|
|
"matchAnyTags": ["@persistence"]
|
|
},
|
|
{
|
|
"name": "Settings persistence and playback options",
|
|
"projects": ["web-e2e", "electron-backend-e2e"],
|
|
"matchAnyTags": ["@settings"]
|
|
},
|
|
{
|
|
"name": "Self-hosted PWA/backend integration",
|
|
"projects": ["web-e2e"],
|
|
"matchAnyTags": ["@self-hosted", "@pwa"]
|
|
}
|
|
]
|
|
}
|
|
}
|