Third slice of the release-notes pipeline (#1256 format+generator, #1257 CI
gate): release screenshots become reproducible and provably mock-only.
The v0.20 capture script was single-use (hard-coded slugs, paths, hero) and
fail-open: a lost IPTVNATOR_E2E_DATA_DIR silently fell back to the user's
real ~/.iptvnator database, `...process.env` leaked ambient TMDB keys and
proxies, nothing gated network access, and no frame content was ever
validated. Each hole leaks real playlists, credentials, or copyrighted
artwork into published screenshots without a single signal.
New pipeline:
- tools/release/screenshots.manifest.json — declarative shots (slug, title,
named setup steps, themes). Adding a feature shot = one manifest entry.
- capture-release-screenshots.ts — orchestrator; output goes to
apps/website/public/blog/<release>/screenshots/<slug>-<theme>.png, release
slug derived from package.json (or --release), --only/--theme filters.
- capture-app-driver.ts / capture-navigation.ts — launch, seeding, theme,
and the named-action vocabulary; actions are order-independent (every
portal action starts from the dashboard).
- screenshot-guards.mjs — the fail-closed policy, pure and unit-tested:
G1 the real database is snapshotted (sha256+mtime) before launch and must
be byte-identical after; the isolated DB must actually exist
G2 the app receives an allowlisted environment, never ...process.env
G3 deny-by-default network gate; known app-level calls (GitHub update
check) are answered by local stubs; any other blocked request fails
the run — a silently-blocked TMDB call would leave a frame that looks
broken rather than unsafe
G4 every frame is scanned before capture: external img/background URLs,
credential-shaped text, MAC addresses, non-localhost m3u8 references
G5 TMDB enrichment asserted disabled via the renderer's IndexedDB
Any violation deletes every frame captured in the run and exits non-zero.
The guards paid for themselves on the first live run: G3 caught the mock
server redirecting stream endpoints to a public demo HLS
(test-streams.mux.dev) — meaning earlier hand-run captures could embed
third-party video frames. The M3U shot now deliberately captures the groups
layout without starting playback.
`.changes` validation now cross-checks `screenshot:` slugs against the
manifest, so a note cannot reference an image the capture run never
produces.
Verified end-to-end: 10/10 shots (5 slugs × dark/light) captured against
dist build + xtream-mock-server, frames visually inspected (fictional
titles/artwork only), guard-violation paths exercised live. 67 unit tests
in release-tools, lint green, script files within the repo size limit.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* ci(release): gate PRs on an authored release note
Second slice of the release-notes pipeline (#1256 landed the format and
generator): make the .changes/ habit survive contact with reality.
- "Release note gate" job in ci.yml, PR-only: validates every .changes/*.md,
then requires an added note (or the no-release-note label) when the PR
touches runtime code under apps/ or libs/. Tests, e2e projects, the
website, mock servers, shared testing helpers, snapshots and docs are
auto-exempt.
- Policy lives in tools/release/check-release-note-gate.mjs as a pure
function fed PR files+labels as JSON — unit-tested (10 cases) instead of
encoded in workflow bash. The failure message lists the triggering files
and names the exact fix.
- Labels are fetched live rather than from the stale event payload, so
applying the label and re-running the check works without a new push.
- The job is dependency-free Node: no pnpm install, runs in seconds.
- release-notes and release-cut skills added under .claude/skills/ and
mirrored to .codex/skills/; CLAUDE.md/AGENTS.md sections updated to point
at the gate and the skills.
The no-release-note label itself was created in the repository.
Tests: 47 passing in release-tools (10 new gate cases); gate-step shell
verified with shellcheck at the CI severity; ci.yml YAML-parse checked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(agents): make the release skills discoverable by Claude Code too
`.codex/skills/**` was un-ignored so Codex picks up repository skills in any
clone, but `.claude` was ignored wholesale — and Claude Code only discovers
skills under `.claude/skills/`. The release-notes and release-cut skills
therefore existed only on whichever machine authored them.
Mirror both skills into `.claude/skills/` and opt them in by name rather than
un-ignoring the directory: contributors keep personal skills there
(i18n-fill, website, …) which must stay local and out of `git status`.
CLAUDE.md/AGENTS.md updated so the "skills live under .codex/skills/" claim
does not go stale, including the requirement to keep mirrored copies in sync.
The CI gate and the CLAUDE.md/AGENTS.md section remain the load-bearing
enforcement; skills only carry the detail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(ci): only a note this PR authored satisfies the release-note gate
Review follow-ups on #1257 (Codex P2 ×2, Greptile P1).
- Drop `renamed` from the accepted statuses. The PR files API compares
base…head, so a note created and then renamed inside the same PR still
reports as `added`; a `renamed` entry means the file already existed on the
base branch. Accepting it let a runtime-code PR pass by moving another
PR's unconsumed note, which documents nothing and gives the generator no
adding commit to resolve a PR link from.
- Require a direct child of `.changes/`. `loadNotes()` reads only the
immediate directory, so `.changes/sub/note.md` satisfied the old prefix
check while never being validated or rendered into any release surface.
Tests: renamed and nested notes now assert a failing gate (12 gate cases).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Add webpack config and include it in the electron backend build so
worker scripts are correctly bundled for packaged apps. Move worker
files to be packaged as extraResources instead of asar unpack patterns
so the packaged app can load worker JS from Resources/dist/... at
runtime. Fix worker path resolution in epg.events: compute resourcesPath
from app.getAppPath() and point to dist/apps/electron-backend/workers
when app.isPackaged. Remove verbose app path logging and update comments
to reflect the actual packaged layout.
Why: ensure webworker entry points are included in the renderer/backend
bundle and accessible when the Electron app is packaged, preventing
worker loading failures in production builds.
Adjust documentation lists and code blocks for consistent
indentation and spacing across CLAUDE.md. Convert mixed dash
and nested list levels to use uniform two-space indentation for
subitems, reflow a few wrapped lines, and add blank lines where
needed to separate sections and examples. These changes improve
readability and maintain a consistent markdown structure for the
project documentation.
Add additional allowed commands to .claude/settings.local.json so the
assistant can run frontend build and view file contents during local
debugging. Specifically, permit Bash(npm run build:frontend:*) and
Bash(cat:*) alongside the existing WebFetch(domain:www.electronjs.org).
This change enables local automation for building the frontend and
inspecting files, improving developer workflow and troubleshooting
without broadening external network permissions.