Files
iptvnator/.changes
4gray 78df3e7dbb fix(portals): match Greek titles whichever sigma the provider typed (#1310)
Greek Σ has two lowercase forms — medial σ and word-final ς — and neither the
candidate query nor the confirmation treated them as one letter.

The GLOB scan built each character's class from a one-way reach that only
arrived at ς when it started from ς, so a request for "ΑΣ" never admitted a
stored "Ας". Classes are now built from a fold group — every character sharing
an uppercase form — derived by scanning the cased ranges at module load the way
ACCENTED_BY_BASE already is. It generalises past sigma on its own: dotless ı
folds with i, long ſ with s, historic Cyrillic letterforms with В Д О С Т Ъ Ѣ.
Only the 24 groups of 767 that a per-character fold would miss are kept.

Admitting the row was only half of it. normalizeTitleKeys then compared "ασ"
against "ας" and discarded it, because toLowerCase picks the sigma form by
position. Both SQL tiers already folded them together — SQLite's trigram
tokenizer does full Unicode folding natively, unlike LOWER() — so the JS
confirmation was the only tier that did not, making this a pre-existing gap on
the FTS path as well. Normalization now rewrites ς to σ after lowercasing,
which is what Unicode case folding does.

Guards unchanged: a case mapping that changes length (ß → SS, İ) or a GLOB
metacharacter still returns null rather than a partial pattern.
2026-07-29 23:14:59 +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