mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
master
28
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7fd3d1dab0 | fix(tools): capture the Xtream guide shot against the current connection test (#1807) | ||
|
|
ab8460338a | feat(ui): cinematic movie and series details pages and dashboard hero (#1792) | ||
|
|
a8dd1eaa97 |
fix(import): consistent add-source forms with masked passwords and URL errors (#1796)
* fix(import): consistent add-source forms with masked passwords and URL errors - Mask the Xtream password in add and edit (and the Stalker one in edit) behind a shared PasswordVisibilityToggleDirective: one translated "Show password" label, state in aria-pressed, type="button". - Give the Xtream server URL its own mat-error and a neutral hint instead of the EPG file error; give the M3U URL a mat-error. - Use "Playlist title" in every add form, "MAC address" casing, a single ellipsis in "Validating portal…" and one "Add playlist" submit label; translate the method radiogroup's aria-label. - Show Stalker refusals inline under the portal URL (role="status", like the Xtream connection test), translated in the template and cleared by edits; translate the snackbars for outcomes that close the dialog. - Translate new strings into all locales; reuse the identical Stalker URL error translations; fix MAC casing and ellipses; drop unused keys. - Unit specs per form, edit-dialog spec, new add-source-forms web E2E; update E2E locators; UI guidelines Forms section; Stalker contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(import): mask the password again when an add form is cleared Clear erased the password but left the visibility toggle on, so the next password typed in the Xtream or Stalker form showed in plain text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cb252940b2 | fix(workspace): move detail Back into the header and drop the rail brand (#1789) | ||
|
|
388fa9e29d |
chore(release): pick the 0.25 highlights and add a settings search screenshot (#1727)
* chore(release): add 0.25 highlights and a settings search screenshot Mark the deferred Electron startup wiring as the "Faster startup" highlight next to settings search, and give the settings search note a screenshot: a new `open-settings-search=<term>` capture action types the term into the header search and waits for the ranked results. Guard the manifest tooling with tests that validate the committed screenshots.manifest.json and keep KNOWN_ACTIONS in step with the capture navigation action tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(release): hash the capture action tables for release-tools:test The KNOWN_ACTIONS parity test reads capture-navigation-*-actions.ts, so those files must invalidate the cached test result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(release): trim 0.25 to three highlights Keep redesigned player controls, parental lock and the cinematic dashboard hero as the headline changes. Settings search, faster startup, the player settings panel and the up next card stay as regular notes; settings search keeps its screenshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
807b5ea259 | docs(website): illustrate feature guides with app screenshots | ||
|
|
9a3aa63490 | ci(nightly): set the electron-builder publish channel for nightly builds (#1611) | ||
|
|
6f7973a9fb |
feat(updater): nightly builds and a stable/nightly update channel (#1608)
* feat(updater): nightly builds and a stable/nightly update channel Every master push publishes its artifacts as a prerelease of 4gray/iptvnator-nightly instead of the rolling test-master draft, with a version of <next patch>-nightly.<commit date>.<run number> applied in every build job. Settings → About gains an Update channel switch; AppUpdateService re-points electron-updater per check (feed repository, allowPrerelease, channel name, allowDowngrade reset) and reads release notes from the repository the requested version belongs to. Channel switches are forward-only: a nightly build stays until a newer stable release exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(updater): compute the nightly version once and keep re-runs safe Review follow-ups: the nightly version is resolved by a leading job and handed to every build job, and the patch is bumped only when the base tag already exists so the release-cut window stays below the imminent release. A re-run never deletes a published nightly; only a draft left by a failed run is replaced. Typed update-status literals in the remaining specs carry the new channel fields. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(packaging): expect the nightly-version prerequisite in the build workflow graph Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
a7f3860102 |
feat(website): add the TMDB metadata guide with the required attribution
New guide at /blog/tmdb-metadata-guide/: a sent/not-sent table for the opt-in enrichment (title, year, app language and the build's API key leave the machine; nothing about the user, the provider or the playlist does), how to switch it on, when to use your own free key, what the feature unlocks, and what the local cache holds. Eight FAQ entries, and a section for readers who would rather leave it off. The post states plainly that TMDB is a metadata database and not a content source, and carries TMDB's required attribution through a reusable TmdbAttribution component (their logo plus "This product uses the TMDB API but is not endorsed or certified by TMDB."), the same wording the app shows in Settings and About. Its screenshot is the settings section itself: the capture's G5 guard keeps enrichment disabled for the whole run, so no licensed poster or still can reach a published frame. The new action stages the switch in the form only, which reveals the key field, the cache panel and the attribution while the stored setting stays off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
93e759e1da |
fix(m3u): accept standard Base64 ClearKey values (#1575)
* fix(m3u): accept standard Base64 ClearKey values * refactor(release): move M3U fixture generation out of capture driver |
||
|
|
186497bf42 |
feat(website): add the EPG troubleshooting post, fix the mock identity check
New post at /blog/epg-wrong-program-fix/: a table separating the three symptoms (an empty row, a channel showing another station's schedule, and every programme shifted by a fixed amount), how the tvg-id → tvg-name → name lookup chain produces the first two, the manual "Map EPG channel" flow, and the display-only EPG time offset with the sign to use. Seven FAQ entries. The three screenshots are mock-backed. The Xtream mock gains /demo/guide.xml, an XMLTV guide for its marketing live channels whose ids deliberately match no playlist tvg-id, which is what makes the manual mapping worth showing; the capture imports it through the settings, accepting the private-network confirmation a loopback source raises, then right-clicks a channel of the M3U fixture and searches the dialog. The new actions live in capture-navigation-epg-actions.ts, alongside the setup, portal and download modules, and borrow one entry point from each of its two neighbours instead of duplicating their navigation. Also fixes assertMockServerIdentity: it checked both expected categories against get_vod_categories, but "Urban Drama" is a series category, so the reuse path rejected every already-running mock and a capture could only run when the port happened to be free. Each category is now checked against its own endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
97b0264dee |
fix(xtream): render catch-up start times in the panel timezone (#1563)
* fix(xtream): render catch-up start times in the panel timezone
The `{Y-m-d:H-M}` segment of an Xtream timeshift URL is read by the panel
with `strtotime()` in ITS timezone (`server_info.timezone`), never the
viewer's. The timezone was learned in memory only, by the store's
`checkPortalStatus()`, so the Favorites / Recent catch-up resolver — which
reads the STORED playlist row — always fell back to the viewer's local
clock and asked the panel for the wrong programme (#1562).
- Normalize the panel's clock once (`resolveXtreamServerTimezone`): an
ICU-resolvable name is kept, otherwise a `UTC±HH:MM` offset is derived
from the `time_now` / `timestamp_now` clock pair, so spellings such as
`UTC+3` no longer silently mean "local time".
- Persist it on the playlist row through `transformPlaylistMeta` (no-op
when unchanged) and project it back from the payload in
`DB_GET_PLAYLIST`, so both catch-up entry points and a restart see it.
- Format with `hourCycle: 'h23'` (server midnight is `00`, never `24`) and
read timestamp-less EPG `start`/`end` strings in the panel's clock.
- Mock: `tzoffset:tzoffset` scenario with an unusable timezone name and a
+03:00 clock pair; Electron e2e covers Live TV, Favorites, a restart into
Global favorites, and the clock-pair derivation at a UTC-3 viewer.
Closes #1562
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(xtream): guard the account-info answer by playlist identity and reject rolled-over dates
Review follow-ups (Greptile):
- A source switch while `get_account_info` is in flight no longer hands
playlist A's status or clock to playlist B: the store is patched only
while the asking playlist is still selected, the timezone is persisted
under the asking playlist's id regardless, and a late failure cannot mark
the newly selected playlist unavailable.
- `parseNaiveUtcMs` reads the constructed date back, so out-of-range panel
strings (`2026-13-01 25:00:00`) are rejected instead of silently rolling
over into a real instant.
- Document that a clock-derived fixed offset is a DST-less snapshot, refreshed
by every account-info check and only ever used for non-standard servers.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(xtream): drop a panel clock that no longer belongs to the source
Review follow-ups (Codex + Greptile):
- A metadata update or DB_UPDATE_PLAYLIST that points the source at another
server drops the persisted `serverTimezone` (payload-only) until the next
account-info check, so Favorites / Recent cannot keep rendering the OLD
panel's clock; an update that supplies a clock keeps it.
- A late account-info answer is persisted only onto a row that still points
at the panel it came from — an edit that moved the source during the
request keeps the clock the edit flow dropped.
- The PWA data source and the route-session converter carry the persisted
timezone into the store playlist, so a later response without a usable
clock has a previous value to preserve.
- Mirror the catch-up timezone contract into AGENTS.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(xtream): drop the stale panel clock inside the UPDATE statement
Review follow-up (Codex): the database worker interleaves requests, so a
read-modify-write of the playlist payload could hand a concurrent upsert's
newer payload back to the past. The `serverTimezone` removal on a server
URL change is now one `CASE … json_remove(payload, '$.serverTimezone')`
expression inside the same UPDATE, guarded by `json_valid`; the spec runs
the real statement against Electron's SQLite on the actual `playlists`
table (moved, renamed, clock-less, malformed-payload and NULL-URL rows).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(xtream): split the server-clock primitives out of the timezone util
Review follow-up (Greptile): `xtream-server-timezone.util.ts` had grown past
the 300-line file guideline. The zone-agnostic wall-clock primitives (stored
forms, Intl parts, naive parsing) now live in `xtream-server-clock.util.ts`;
the timezone util keeps the Xtream policy and re-exports the public helpers,
so every import and the spec are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(xtream): offer the learned panel clock to storage on every check
Review follow-up (Codex): a transient storage failure left the clock in the
store but not on the row, and the next check compared the answer with the
in-memory value and never retried. The resolved timezone is now always
handed to `transformPlaylistMeta`, whose row-level equality check keeps the
common case a read without a write; a failed write is retried by the next
account-info check.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(xtream): apply an account-info answer only to the panel it came from
Review follow-up (Codex): an in-place edit keeps the playlist id while
moving the source, so an answer already on the wire for the OLD panel
passed the id-only guard and patched the new panel's status and clock into
the store. One `answersFor(candidate, credentials)` predicate now gates the
store patch, the error path and the persisted-row transform alike.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(xtream): never report another panel's status for the selected playlist
Review follow-up (Greptile): callers gate content initialization on the
value `checkPortalStatus()` returns for whatever is selected NOW. When the
answer no longer describes the selected playlist (source switch or in-place
edit during the request), the store's own verdict about the current
selection is returned instead of the old panel's status — on success and on
failure alike.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(xtream): persist the panel clock with one conditional UPDATE
Review follow-up (Codex): `transformPlaylistMeta` reads the row and then
upserts it whole, while the Xtream edit dialog saves through
`DB_UPDATE_PLAYLIST` outside `PlaylistsService`'s queue and the database
worker interleaves requests — an edit landing between that read and the
upsert was silently undone.
Persistence now goes through `IXtreamDataSource.rememberServerTimezone`:
- Electron: new `DB_SET_PLAYLIST_SERVER_TIMEZONE` worker op — one UPDATE
that `json_set`s the payload only while the row still points at the
request's connection and does not already carry the value; a malformed
payload is never rewritten (CASE, not AND, so json_extract cannot run
before json_valid). Wired through the worker types, main handler,
preload, bridge interface, both IPC contract tables and
`DatabaseService.setXtreamPlaylistServerTimezone`.
- PWA: `transformPlaylistMeta`, whose read and write share one IndexedDB
readwrite cursor transaction, plus the localStorage copy.
The store no longer injects `PlaylistsService`; it offers the resolved clock
to the data source and keeps only its in-memory guards. Real-SQLite coverage
for the op (fresh / same / moved / NULL / malformed / missing rows),
delegation specs for both data sources, docs updated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(xtream): keep the stored panel clock across clockless full upserts
Review follow-up (Codex): a `PlaylistsService` mutation that read the row
before `DB_SET_PLAYLIST_SERVER_TIMEZONE` landed and upserted afterwards
replaced the payload with its clockless snapshot. `DB_UPSERT_APP_PLAYLIST(S)`
now carry the STORED clock into a snapshot that has none while the row still
points at the same connection (`playlistConflictUpdate`, nested CASE so the
json_* readers never run on a malformed payload); a snapshot with its own
clock, or one that moves the source, wins as is. Real-SQLite coverage for
kept / moved / own-clock / batch rows.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(release): split capture-navigation under the max-lines cap
`tools/release/capture-navigation.ts` had grown to 567 counted lines, past
the 400-line rule, which failed `release-tools:lint` and — because the file
was not in the baseline — the max-lines baseline test on master and on
every PR branched from it. The 19 named setup actions are now grouped by
subject over one leaf module of shared page helpers:
- `capture-navigation-helpers.ts`: playlist-id registry, dialog handling,
navigation moves, `settleUi`
- `capture-navigation-setup-actions.ts`: add-playlist dialogs, settings
sections, remote control
- `capture-navigation-portal-actions.ts`: portal catalogs, live lists,
alternative sources (the two identical live-category flows share one
helper)
- `capture-navigation-download-actions.ts`: the download manager shots
- `capture-navigation.ts`: the `runAction` dispatcher, theme switching and
the re-exported API the seeding driver and the capture script import
Actions call their siblings directly instead of recursing through
`runAction`, so no module depends on the dispatcher. The action vocabulary
is unchanged (same 19 names, same waits and timeouts); every file is under
300 lines and the new modules are listed in the `release-tools` lint target.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(electron): move the panel-clock SQL into its own operations module
Review follow-up (Greptile): the timezone persistence, invalidation,
upsert-preservation and row projection had landed in
`playlist.operations.ts`, a baselined 1,000-line file. They now live in
`playlist-server-timezone.operations.ts` (155 lines) — the three SQL
shapes plus the payload projection — and the playlist operations compose
them; the baselined file shrinks by 107 lines. Behaviour and the
real-SQLite coverage are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
98da686cea |
feat(website): add the phone remote control guide
New guide at /blog/remote-control-guide/: enabling the remote in Settings, opening it on a phone from the QR code, what each control does and which list it navigates, a checklist for a page that does not load, and why the remote must stay on the local network. Eight FAQ entries. The remote-control feature page now links to it instead of the M3U guide. Both screenshots are mock-backed. The phone view is the first "browser" shot: a manifest entry names a loopback URL and a mobile viewport, and the capture frames it in a separate Chromium page behind the same network and content guards, with the manifest validator accepting loopback origins only. The setup saves the remote-control setting so the app's own server answers, then selects a live channel; the Xtream mock's marketing scenario now serves live stream URLs from local bytes so that selection never leaves the machine. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
c7f1784dbd |
feat(website): add the alternative sources guide
New guide at /blog/alternative-sources-guide/: where the Sources chip comes from (a local lookup across the reader's own Xtream playlists, never a search outside them), how to read fact tags versus ~guesses, check availability, switch playlists mid-film without losing the timecode, pin a preferred copy per movie, and what the opt-in auto-switch does and on which players. Eight FAQ entries, opening with the general ContentDisclaimer. The two screenshots are mock-backed: the Xtream mock gains a marketing2 scenario that serves the identical marketing catalog under a second credential pair (with a spec proving the catalogs match), the capture seeds it as a "Fictional Xtream Backup" source only for shots that walk into it, opens its category so the movies reach the local content cache that discovery reads, and two new setup actions open the chip and the checked popover. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
0d03140661 |
feat(website): add the offline downloads guide with a reusable content disclaimer
New guide at /blog/offline-downloads-guide/: what the desktop download manager can save, choosing the folder, downloading a movie, episodes and seasons, following the queue (pause, resume, automatic reconnects), the offline library, and the Needs attention states, with a nine-question FAQ. The prose frames the feature as offline viewing of content the reader already streams and defers legality to the provider's terms and local law. ContentDisclaimer.astro carries that notice in a general and an offline variant so later guides reuse it instead of rewording it. The three screenshots are mock-backed captures. The Xtream mock's marketing scenario now serves movie and episode stream URLs from generated local bytes (downloadStreamFixture: 'local-media'), because the capture's network gate rejects the public HLS stub every other scenario redirects to; the capture stubs Electron's folder dialog so "Change Folder" authorizes a folder inside the isolated data dir rather than the real OS Downloads folder; two new setup actions queue a movie and two episodes and open the manager and the offline detail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
50b980af7a |
feat(website): add the M3U playlist and EPG setup guide
Publish "How to Load an M3U Playlist and Add an EPG in IPTVnator": the three import methods plus drag-and-drop and OS file opening, the playlist views, refresh and startup auto-update, attaching an XMLTV guide through Settings or a url-tvg header, the tvg-id / tvg-name / name matching order with manual mapping, catch-up attributes, troubleshooting and a seven-question FAQ. The three guides now link to each other, the download pages point at all three, and llms.txt lists the new one. Three guide shots join the manifest: the M3U URL dialog, the Groups view (reusing open-m3u-groups under the guides group) and the EPG settings section with a staged source row. A settings shot leaves the form dirty, which arms the app's close guard and blocked app.close() indefinitely; the capture now discards unsaved settings before every action and before teardown, and bounds every locator wait. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
6cfdaf5900 |
feat(website): add the Stalker portal setup guide with mock-backed screenshots
Publish "How to Connect a Stalker or Ministra Portal to IPTVnator": the portal URL shapes discovery accepts, MAC normalization, the optional serial/device-ID/signature fields and the pinning rules behind the "generate device IDs" toggle, what endpoint discovery does on Add, the sections a portal source gets, Account info, and a troubleshooting list built from the app's own refusal messages, plus a seven-question FAQ. The guide is cross-linked from the download pages and llms.txt. Guide screenshots come from the capture script. Shots that walk into a Stalker portal start the stalker-mock-server and seed its marketing-demo portal for that run only, so release shots never gain a third source card. The frame guard allowlists exactly that scenario's MAC and keeps rejecting every other MAC-shaped string. To keep the live-TV frame free of third-party images, the fictional live channel list and the channel-logo SVG renderer move into @iptvnator/shared/marketing-fixtures; both mocks now serve /assets/marketing/logo/<slug>.svg, the Stalker marketing-demo scenario builds its ITV categories, channels and schedule from those fixtures instead of faker names with picsum logos, and the mock resolves asset URLs on get_all_channels too, which is the response the app renders the channel list from. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
90d26d499f |
feat(website): add the Xtream Codes setup guide with FAQ and guide screenshots
Publish "How to Add an Xtream Codes Account to IPTVnator" as the first evergreen guide: what the server URL, username and password are, the Add playlist flow with the connection test and its four verdicts, the Auto-detect method for pasted provider messages, what the import syncs, Account info, refresh, troubleshooting and a seven-question FAQ. The guide is cross-linked from the three download pages and llms.txt. Blog posts gain an optional `faq` frontmatter list: BlogPost.astro renders it as an accordion after the body and emits FAQPage JSON-LD next to the BlogPosting entry. LinkCards and PostButton keep internal links in the same tab. Guide screenshots come from the release capture script: manifest shots may carry a `group`, `--group guides` captures only those into apps/website/public/blog/guides/screenshots/, and a release run skips them. New setup actions open the Add playlist dialog with the mock's fictional Xtream credentials (connection test shown), the Auto-detect method with a labeled hand-out, and the Xtream Live TV view. Dialog helpers and fixture identities move into shared modules so the driver and the navigation actions cannot import each other cyclically. tools/testing/website-guides.test.mjs checks the FAQPage schema, the download-hub link and the shipped screenshots of every guide; screenshot-guards.test.mjs covers group validation and output routing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
81ce8e8c90 |
feat(release): scaffold the blog post in its published shape
`release:notes:blog` used to emit the notes as a type-grouped inventory with area prefixes and the highlight sections buried after the feature list; the v0.23 post shipped in exactly that form and had to be restructured after publication. The scaffold now starts from the shape the posts end up in: a "What changed" table with one row per highlight, one `##` section per highlight ahead of everything else, breaking changes on their own, the remaining features folded into reader-facing themed sections instead of conventional-commit scopes, Performance, every remaining fix under a Spoiler grouped by theme, and the before-updating alert, Thanks and Download cards (including the compare link to the previous version). Only the components a post uses are imported. The blog renderer moves to `release-notes-blog.mjs`; `release-notes-render.mjs` keeps the GitHub/CHANGELOG renderers and exports the shared text helpers. Editorial work stays editorial and is marked with TODOs: which fixes deserve promotion out of the spoiler, one-line bullets, lead-ins, intro and thanks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
29ca94aa43 | feat(release): announcement formats, highlight cards, and draft verification (#1480) | ||
|
|
2ac0de752f |
fix(skills): align repository guidance with implementation (#1315)
* docs(skills): design implementation synchronization * docs(skills): plan implementation synchronization * fix(release): filter internal notes from public body * docs(release): synchronize release workflow guidance * fix(stalker): normalize catalog series flags * fix(stalker): preserve progress with scoped episode IDs * fix(playback): expose strict position persistence * docs(stalker): record series position compatibility * test(skills): validate repository skill contracts * fix(database): keep SQL trace values private * docs(skills): refresh Nx and SQLite ownership * docs(skills): align provider and UI guidance * docs(skills): tighten validated guidance * docs(release): require exact release pushes * style(electron): remove trailing blank line * fix(ci): classify repository skills coverage |
||
|
|
e55d55b47f |
feat(mock-data): add shared screenshot-safe poster catalog (#1271)
Moves the fictional movie catalog into `libs/shared/marketing-fixtures` so the
Xtream and Stalker mocks describe the same titles, and adds 20 rendered posters
plus the shared fixture types behind them.
Supporting changes made while getting it green:
- `shared-marketing-fixtures` is classified Tier B in the coverage policy. Not
Tier A: it is fictional fixture data, so a statement percentage over it means
nothing, and a Tier A entry would pull it into the merged coverage map and the
ratchet. Tier B still runs its spec in CI. `stalker-mock-server` needs no entry
of its own — it is already Tier C and the Tier B/C runner falls back to
`pnpm nx test <project>`, so its new `marketing-poster-url.spec.ts` runs.
- Two release-capture defects the catalog reorder introduced, both fixed in
`tools/release/capture-app-driver.ts`:
- VOD stream ids are `MARKETING_VOD_STREAM_ID_BASE + index` and the generator
now lists the showcase movies first, so 62000-62002 became Black Harbor, The
Paper Astronaut and Summer Static while the dashboard seeding still mapped
those ids to the previous titles' backdrops.
- the raw `tsx` spawn of the Xtream mock lacked `--tsconfig
tsconfig.base.json`, so the mock could not resolve
`@iptvnator/shared/marketing-fixtures` and the capture never started. Both
mock projects' own serve targets already passed the flag.
|
||
|
|
02b966895d |
docs(release): backfill curated 0.23.0 release notes, fix the notes CLI -- trap (#1263)
Backfills the 0.23.0 release notes the .changes/ pipeline missed: it landed after most of the release was already merged, leaving 4 notes for 79 commits. Adds 22 curated notes (26 total: 14 features, 11 fixes, 1 perf). Curated rather than exhaustive — the GitHub release body renders these above GitHub's own list of every merged PR, so related PRs are folded into one note per user-facing story: shared player controls (8 PRs), embedded MPV frame-copy (4), manual EPG mapping (3), plus five more pairs. Tooling-only scopes get no note. No screenshot slugs: none of the five manifest shots depicts a 0.23 headline feature, and the capture run asserts TMDB enrichment stays disabled. Two tooling fixes found while writing them: - parseArgs now ignores a bare `--`. npm needs it to forward arguments past the script name; pnpm hands it to the script verbatim, so `pnpm run release:notes:github -- --version 0.24.0` died on the very separator typed to make forwarding work. Unknown flags and missing values still fail as before. Covered by new subprocess CLI tests wired into the release-tools target. - .changes/README.md claimed every non-consume mode was a safe dry run; --format changelog and --format blog write their target file. No app or lib code, no version bump, no --consume, no CHANGELOG.md or website changes — those stay owned by release-cut. |
||
|
|
6c946978b4 |
chore(release): drop the superseded v0.20 screenshot script (#1262)
#1261 replaced this one-off capture with a manifest-driven script, so the v0.20 version is dead weight: hard-coded slugs, paths and output directory, none of the fail-closed guards, and two `RegExp`-from-string constructions of the kind CodeQL flags (one of which it flagged on the replacement before that was rewritten to use predicates). Removing it also drops its `tools/eslint/max-lines-baseline.mjs` entry, so the baseline no longer carries a file that does not exist. Not a pure dead-code deletion, and worth stating: two capabilities go with it, neither reachable from the new pipeline — `createDesignedCopy` (title/kicker overlays on captured frames) and `createHeroImage` (a 1600x900 canvas collage built from three screenshots). The v0.20 assets they produced are already committed under apps/website/public/blog/v0-20/, so nothing published breaks; a future release wanting the same collage needs it ported deliberately rather than resurrected here. Docs: docs/architecture/xtream-mock-server.md now points at capture-release-screenshots.ts and notes its mock-identity check. Verified: no references to the removed file remain anywhere in the repo, and `pnpm run lint` passes for all 42 projects with the shortened baseline. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b4ec68c1fa |
feat(release): manifest-driven screenshot capture with fail-closed mock-data guards (#1261)
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> |
||
|
|
4e5132cbb5 |
ci(release): gate PRs on an authored release note (#1257)
* 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> |
||
|
|
270350c2e1 |
chore(release): author release notes in .changes instead of reconstructing them (#1256)
* chore(release): author release notes in .changes instead of reconstructing them CHANGELOG.md has been frozen at 0.12.0 since 2023 while the app shipped 0.23.0, semantic-release sat in devDependencies with no config, and the real user-facing notes were a 280-line MDX post written from memory at release time. The gap was never version math — it was authored notes captured while the context is still fresh. Add a `.changes/*.md` note format (type, area, issues, screenshot; no version field, since the release version is chosen deliberately) plus a generator that composes the GitHub release body, the CHANGELOG.md section and a blog-post scaffold from the accumulated notes. Changesets was considered and rejected: it versions multiple published packages, and this repo has exactly one private package. Its `version` step would also rewrite CHANGELOG.md into a flatter format than the blog post and fight the deliberate, updater-constrained version choice. - hand-rolled frontmatter parser over a YAML engine: the schema is closed, so it can reject unknown keys, which is what catches typos - PR numbers are resolved from the commit that added the note, never written by the author - MDX-significant characters in note bodies are escaped so a stray `<` cannot break the website build - blog scaffold ships `draft: true` with explicit TODO headings; the prose is editorial work, only the inventory is mechanical - revive CHANGELOG.md with an honest pointer for 0.13.0-0.23.0 rather than fabricating the missing history - drop the five unused semantic-release/conventional-changelog packages Docs: `.changes/README.md`, plus a "Release Notes For User-Visible Changes" section mirrored in CLAUDE.md and AGENTS.md, and a PR template checkbox for contributors who never read either. Tests: 26 unit tests in tools/release/release-notes.test.mjs covering parsing, validation, grouping and all three renderers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(release): default the notes version to package.json and harden alt escaping Review follow-ups on the release-notes generator. - `--version` now defaults to the root package.json version, so the `release🎶*` package scripts run bare instead of failing on a missing argument. Bumping package.json is the deliberate act that starts a release, which makes it the right single source of truth; `--version` remains as an override for dry runs before the bump. The notice goes to stderr so `--format github` keeps a pipeable stdout. - Escape backslashes before apostrophes when building the MDX `alt` string literal. A note body ending in a backslash previously produced an unterminated string and would have broken the website build. - Document that release posts are one per minor version, in the slug helper, the overwrite error, and `.changes/README.md` — a patch release edits the existing post rather than creating a second one. Tests: +1 regression test for the alt escaping, verified to fail without the fix (27 total, all passing). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(ci): put authored notes into the tag release body, fail-closed Wires the .changes pipeline into the release workflow (Codex review P1 on #1256). Calling the generator from the tag build cannot work — --consume deletes .changes/ before the tag exists — so the tag build reads what the generator already wrote: release-meta now fills BODY from the CHANGELOG.md section matching the tag's version via tools/release/extract-changelog-section.mjs. generate_release_notes stays on, so GitHub's commit list renders below the authored notes; the existing draft-metadata repair step already concatenates RELEASE_BODY with the generated notes, so the rare duplicate-draft path keeps the same layering unchanged. The extractor exits non-zero when the section is missing or empty, failing the release instead of silently shipping PR-title-only notes. A hotfix tag cut without running release:notes:changelog therefore fails at create-release by design; the error message names the exact commands to run. Tests: 5 new extractor tests (32 total in release-tools, all passing); packaging suite (247) re-run green since build-and-make.yaml is one of its inputs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(release): escape all regex metacharacters in the changelog extractor CodeQL flagged the version-to-RegExp interpolation in extract-changelog-section.mjs (regex injection + incomplete escaping): only dots were escaped, and while the CLI validates its argument as bare semver before calling, the exported extractSection() carries no such guarantee on its own. Escape the full metacharacter set so no caller can inject pattern syntax, with tests covering wildcard dots, alternation, `.*` and backslashes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(release): make changelog generation idempotent per version Codex review P2 on #1256: rerunning `release:notes:changelog` for the same version — the normal move after correcting a note before --consume — prepended a second section instead of replacing the first, leaving duplicate release entries. Extract the marker insertion into upsertChangelogSection(): it removes any existing section for the version, then rebuilds around the marker rather than string-replacing into it, so the blank-line count on both sides stays exact on both the fresh-insert and replace paths. The CLI reports when a section was replaced. Tests: 4 new cases (insert, replace-not-duplicate, neighbours untouched, missing marker); 37 total passing. End-to-end rerun verified: one heading, latest date wins, extractor output unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1ec7176983 |
chore(xtream-mock-server): add artwork mock data and script to generate artwork for IPTVnator releases
- Implemented a TypeScript script that generates original fictional demo artwork for IPTVnator release screenshots. - The script supports multiple artwork kinds (poster and backdrop) and qualities (low, medium, high, auto). - It includes functionality for dry runs, generating artwork, manifest creation, and validation of generated assets. - Integrated with OpenAI's image generation API to create artwork based on provided prompts and fixture data. - Added a manifest file to track generated assets and their metadata. - Ensured output directories are created as needed and included error handling for API requests. Entire-Checkpoint: c6e522b4276c |