Files
iptvnator/.changes
4gray deae0a2a4d fix(xtream): keep sparse VOD details playable (#1303)
* fix(xtream): keep sparse VOD details playable

* fix(xtream): scope VOD fallback to active playlist

* fix(xtream): render sparse VOD before recovery

* fix(xtream): recover Similar VOD provider categories
2026-07-29 08:12:05 +02:00
..

Release notes (.changes/)

Every PR with a user-visible change drops one file here describing that change in plain language. At release time tools/release/build-release-notes.mjs turns the accumulated files into the GitHub release body, the CHANGELOG.md section, and a blog-post scaffold for the website — then deletes them.

The point is to write the note while the context is still fresh, instead of reconstructing three months of work from commit titles at release time.

File

Name it <area>-<short-slug>.md, e.g. .changes/playback-up-next-rail.md.

---
type: feature
area: playback
issues: [1187]
screenshot: up-next-rail
---

Series now show an "Up Next" rail beside the player on wide windows: the rest
of the current season, watch progress, and click-to-play inline.
Field Required Value
type yes breaking, feature, fix, perf, or internal
area yes lowercase slug, same as the conventional-commit scope
issues no issue numbers this closes — [1187] or 1187
screenshot no slug from tools/release/screenshots.manifest.json

There is no version field. The release version is chosen deliberately at release time, not derived from these files.

You never write a PR number: the generator resolves it from the commit that added the file.

Writing the body

One to three sentences, present tense, written for a user, not a reviewer. The body is capped at 400 characters — depth belongs in the blog post.

  • ❌ "Refactor WebVideoControlsAdapter to hoist volume state into the session"

  • ✅ "The player now remembers volume between episodes"

  • ❌ "Fix off-by-one in resolveEnrichmentSeasonNumber"

  • ✅ "Series whose title carries a season marker no longer show the wrong season"

type: internal is for changes with no user-visible effect that are still worth recording (dependency bumps with behaviour risk, packaging moves). They stay out of the release body and blog post, and land collapsed in CHANGELOG.md.

When a note is not needed

Skip the note — and apply the no-release-note label — for test-only changes, docs, CI/workflow plumbing, and pure refactors with no behaviour change.

Commands

pnpm run release:notes:validate
pnpm run release:notes:github
pnpm run release:notes:changelog
pnpm run release:notes:blog
node tools/release/build-release-notes.mjs --consume

The release version comes from the root package.json — bump it first, then generate. --version 0.24.0 overrides it to preview a release before the bump:

pnpm run release:notes:github --version 0.24.0

A bare -- separator is accepted and ignored, so the npm habit of pnpm run release:notes:github -- --version 0.24.0 works too: pnpm forwards that separator to the script rather than consuming it the way npm does.

--validate and --format github only read and print. --format changelog and --format blog write their target file (rerunning changelog for the same version replaces that section rather than duplicating it). Only --consume deletes anything.

The release sequence is: bump the version → release:notes:changelog → release:notes:blog → --consume → commit → tag → push. The tag build then extracts the new CHANGELOG.md section into the GitHub release body (tools/release/extract-changelog-section.mjs) and fails the release if the section is missing — a tag cut without the changelog step cannot silently ship PR-title-only notes.

The website publishes one post per minor version (v0-18 … v0-22), and release screenshots live under the matching blog/v0-24/ directory. A patch release therefore edits the existing post rather than generating a new one, so --format blog refuses to overwrite unless you pass --force.

Screenshots

pnpm run release:screenshots captures every manifest shot in dark and light against the built app plus the Xtream mock server — never a real account. The run is fail-closed: it proves the real ~/.iptvnator/databases directory (including the SQLite WAL sidecars, checked after Electron exits) was not touched, launches the app with an allowlisted environment, records and blocks all non-localhost traffic, scans every frame for external resources and credential-shaped text, and asserts TMDB enrichment stays disabled. Frames are staged outside the repository and published only once every shot and every guard has passed.

Adding a shot for a new feature = one entry in tools/release/screenshots.manifest.json (plus, if navigation is new, one named action in tools/release/capture-navigation.ts).

pnpm nx run electron-backend:build-e2e   # once, before capturing
pnpm run release:screenshots             # all shots, both themes
pnpm run release:screenshots -- --only dashboard --theme dark
pnpm run release:screenshots -- --release v0-24