docs(website): illustrate feature guides with app screenshots
No files matched your search
@@ -163,6 +163,13 @@ Evergreen how-to posts live in the blog collection next to release notes
|
||||
channel ids that match no playlist `tvg-id` on purpose) into the isolated
|
||||
database through the settings, then right-click a channel of the M3U
|
||||
fixture and search the dialog.
|
||||
The 0.23/0.24 feature articles use the separate `feature-guides` capture
|
||||
group (`pnpm release:screenshots --group feature-guides --theme dark`).
|
||||
Playback shots decode the generated local slate documented in
|
||||
`tools/release/fixtures/README.md`; settings and library shots use the same
|
||||
isolated demo profile. The multi-channel EPG image is the original image
|
||||
attached to the v0.24.0 GitHub release, explicitly selected by the maintainer;
|
||||
its provenance is recorded alongside the assets. It is not a mock capture.
|
||||
|
||||
`tools/testing/website-guides.test.mjs` (part of `pnpm nx test website`) checks
|
||||
each guide for the FAQPage schema, a link to the download hub and the presence
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Screenshot provenance
|
||||
|
||||
- `multi-epg-v0-24.jpg`: original JPEG attached to the
|
||||
[v0.24.0 release](https://github.com/4gray/iptvnator/releases/tag/v0.24.0),
|
||||
reused at the maintainer's explicit request on 2026-09-27.
|
||||
[Original attachment](https://github.com/user-attachments/assets/205dc53e-70cf-4c63-a49b-9b976fbeb5e5).
|
||||
Downloaded without edits; 2648×1626.
|
||||
- `screenshots/guide-*-dark.png`: actual Electron UI captured with isolated
|
||||
fictional playlists by the `feature-guides` group in
|
||||
`tools/release/screenshots.manifest.json`. Playback uses the original local
|
||||
demo slate documented in `tools/release/fixtures/README.md`.
|
||||
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 98 KiB |
@@ -3,6 +3,7 @@ title: Switch Channels and Episodes Without Leaving Fullscreen
|
||||
description: Open IPTVnator's fullscreen side panel with C or the left edge of the video. Search live channels, choose a season, and switch episodes while keeping the player fullscreen.
|
||||
pubDate: 2026-09-27
|
||||
author: 4gray
|
||||
heroImage: /iptvnator/blog/feature-guides/screenshots/guide-fullscreen-channels-dark.png
|
||||
tags:
|
||||
- guide
|
||||
- playback
|
||||
@@ -62,6 +63,10 @@ For M3U, **Page Up / Page Down** also let you step through channels without open
|
||||
panel. Those keys have a different job inside the separate programme Guide, where they
|
||||
move between days.
|
||||
|
||||

|
||||
|
||||
*Press C in player fullscreen to open the channel list. The pictured channels and video are local demo fixtures.*
|
||||
|
||||
## Choose another episode
|
||||
|
||||
Start an Xtream or Stalker series episode and open the panel the same way. Instead of a
|
||||
|
||||
@@ -3,13 +3,14 @@ title: "Keep Your Library Tidy: Sources, Favorites and Watched Titles"
|
||||
description: Review source availability, return from a favorite channel to its playlist, mark titles watched and simplify catalog views in IPTVnator without confusing hidden items with missing data.
|
||||
pubDate: 2026-09-27
|
||||
author: 4gray
|
||||
heroImage: /iptvnator/blog/feature-guides/screenshots/guide-library-watched-dark.png
|
||||
tags:
|
||||
- guide
|
||||
draft: true
|
||||
faq:
|
||||
- q: Does an unavailable source mean my channels were deleted?
|
||||
a: No. Availability describes a connection check. A timeout, a temporary outage or an unverified account is different from deleting a saved source or losing its channels.
|
||||
- q: Does source cleanup delete entries automatically?
|
||||
- q: Does library watched state delete entries automatically?
|
||||
a: No. The desktop cleanup dialog checks sources and lets you review the selection before deleting. Confirmed expired or disabled accounts can be preselected; uncertain results need review.
|
||||
- q: What happens when I delete a source?
|
||||
a: Its saved favorites, history and playback progress are removed with it. Downloaded files are kept. Export a playlist backup first if you want a restorable copy of supported source state.
|
||||
@@ -83,6 +84,7 @@ Deleting a source also removes its saved favorites, history and playback progres
|
||||
Downloaded files are kept. Hiding a category or collapsing a panel is a better fit when
|
||||
your goal is simply to see less in the current view.
|
||||
|
||||
|
||||
## Keep watch state in step with what you finished
|
||||
|
||||
For Xtream and Stalker movies, open the detail page and use **Mark as Watched** when you
|
||||
@@ -94,6 +96,10 @@ Series already have controls for marking a season or the whole series watched. U
|
||||
when you are catching the library up with your viewing, rather than opening every episode
|
||||
just to seek to its end.
|
||||
|
||||

|
||||
|
||||
*The checked watched button marks this fictional demo movie as finished. Click it again to mark the movie as unwatched.*
|
||||
|
||||
## Fit more covers on screen
|
||||
|
||||
Under **Settings → General**, turn off **Show titles under covers** for a denser movie
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
title: Browse the TV Guide Without Leaving Your Channel
|
||||
description: Keep watching while you compare channel schedules in IPTVnator's M3U programme guide. Open the grid, filter by group or favorites, and switch channels without closing it.
|
||||
pubDate: 2026-09-27
|
||||
updatedDate: 2026-09-27
|
||||
author: 4gray
|
||||
heroImage: /iptvnator/blog/feature-guides/multi-epg-v0-24.jpg
|
||||
tags:
|
||||
- guide
|
||||
- epg
|
||||
@@ -47,6 +49,10 @@ Opening Guide does not restart the current channel. The same player changes size
|
||||
room for the schedules. Collapse the preview strip when you want more space for the grid,
|
||||
then expand it when you want to see the picture again.
|
||||
|
||||

|
||||
|
||||
*The multi-channel Guide in v0.24. The highlighted grid button in the top toolbar opens this view. Screenshot from the v0.24.0 release.*
|
||||
|
||||
## Choose the channels you want to compare
|
||||
|
||||
The rows come from **your playlist**, rather than every channel present in an XMLTV file.
|
||||
|
||||
@@ -3,6 +3,7 @@ title: "Subtitles, Quality and Picture-in-Picture: Get More from the Player"
|
||||
description: Use IPTVnator's unified player controls to choose stream quality, load subtitles, adjust their timing and keep video visible in picture-in-picture. Learn why some options depend on the stream or player.
|
||||
pubDate: 2026-09-27
|
||||
author: 4gray
|
||||
heroImage: /iptvnator/blog/feature-guides/screenshots/guide-player-subtitles-dark.png
|
||||
tags:
|
||||
- guide
|
||||
- playback
|
||||
@@ -73,6 +74,10 @@ supports your file rather than simply changing its filename extension.
|
||||
If the stream already includes subtitle tracks, you can choose one directly from the
|
||||
same menu. **Off** hides subtitles without removing the movie or changing the audio track.
|
||||
|
||||

|
||||
|
||||
*Open Subtitles from the player control bar to load a subtitle file. Playback here uses a local demonstration stream.*
|
||||
|
||||
## Adjust timing and readability
|
||||
|
||||
For a selected external subtitle in the web players, use **Show subtitles earlier** or
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Back Up Your Playlists and Move to a New Computer
|
||||
description: Export and restore an IPTVnator playlist backup, understand which favorites and playback state it carries, and check what needs to be moved separately when changing computers.
|
||||
pubDate: 2026-09-27
|
||||
author: 4gray
|
||||
heroImage: /iptvnator/blog/feature-guides/screenshots/guide-backup-dark.png
|
||||
tags:
|
||||
- guide
|
||||
- troubleshooting
|
||||
@@ -45,6 +46,10 @@ Export the receiving computer's library too if it already has sources and favori
|
||||
care about. Restoring a snapshot can replace saved state for a matching source; preserving
|
||||
both backups gives you a record of each library before the import.
|
||||
|
||||

|
||||
|
||||
*Settings → Backup contains both actions: Export data creates the backup; Import data restores it.*
|
||||
|
||||
## What travels in the file
|
||||
|
||||
| Source or data | Included | Separate or rebuilt later |
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Stable or Nightly? How IPTVnator Updates Work
|
||||
description: Choose an update channel in the desktop app, understand what happens when you switch back to Stable, and find the build details that make a nightly bug report useful.
|
||||
pubDate: 2026-09-27
|
||||
author: 4gray
|
||||
heroImage: /iptvnator/blog/feature-guides/screenshots/guide-update-channel-dark.png
|
||||
tags:
|
||||
- guide
|
||||
draft: true
|
||||
@@ -41,6 +42,10 @@ Nightly can be useful when an issue you reported has been fixed but the next sta
|
||||
has not arrived. Include the build you tested when replying to that issue: two people using
|
||||
“nightly” may be running different code.
|
||||
|
||||

|
||||
|
||||
*The update-channel selector lives in Settings → About. Choose a channel here, then save the change.*
|
||||
|
||||
## Switch to Nightly
|
||||
|
||||
1. Export a playlist backup from **Settings → Backup** and keep the file somewhere you can
|
||||
|
||||
@@ -3,6 +3,7 @@ title: What Stream Info Can Tell You About Playback Problems
|
||||
description: Read resolution, frame rate, bitrate, buffer and dropped frames in IPTVnator's Stream info panel, then collect a useful playback report without guessing from a single number.
|
||||
pubDate: 2026-09-27
|
||||
author: 4gray
|
||||
heroImage: /iptvnator/blog/feature-guides/screenshots/guide-stream-info-dark.png
|
||||
tags:
|
||||
- guide
|
||||
- playback
|
||||
@@ -45,6 +46,10 @@ The panel shows only values the current engine can report. A missing audio bitra
|
||||
source frame rate is not a zero reading. Immediately after starting, the panel may say
|
||||
there is no stream data yet.
|
||||
|
||||

|
||||
|
||||
*The information button opens Stream info over the video. These values describe the local demonstration stream used for this screenshot.*
|
||||
|
||||
## Read the values in context
|
||||
|
||||
| Field | What it helps you understand |
|
||||
|
||||
@@ -89,10 +89,12 @@ export function writeM3uFixture(dataDir: string): string {
|
||||
['Culture', 'Atlas Culture', 'atlas-culture'],
|
||||
['Culture', 'Night Music', 'night-music'],
|
||||
];
|
||||
const stream = `${XTREAM_MOCK_ORIGIN}/live/marketing/marketing/52000.m3u8`;
|
||||
const lines = ['#EXTM3U'];
|
||||
|
||||
channels.forEach(([group, title, slug], index) => {
|
||||
// Distinct URLs keep the player from highlighting every demo channel
|
||||
// as the current one when a fullscreen list is captured.
|
||||
const stream = `${XTREAM_MOCK_ORIGIN}/live/marketing/marketing/${52000 + index}.m3u8`;
|
||||
lines.push(
|
||||
`#EXTINF:-1 tvg-id="demo-${index + 1}" tvg-name="${title}" tvg-logo="${XTREAM_MOCK_ORIGIN}/assets/marketing/logo/${slug}.svg?size=256x256" group-title="${group}",${title}`,
|
||||
stream
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/** Real UI captures for the 0.23/0.24 feature guides, using local demo media. */
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { type CaptureAction, openXtreamSection } from './capture-navigation-helpers';
|
||||
import { openM3uGroups } from './capture-navigation-portal-actions';
|
||||
import { openSettings } from './capture-navigation-setup-actions';
|
||||
|
||||
async function openSettingsSection(page: Page, section: string): Promise<void> {
|
||||
await openSettings(page);
|
||||
await page.locator(`[data-test-id="settings-section-${section}"]`).click();
|
||||
await page.locator(`#${section}`).waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async function openUpdateChannel(page: Page): Promise<void> {
|
||||
await openSettingsSection(page, 'about');
|
||||
await page.locator('[data-test-id="select-update-channel"]').click();
|
||||
await page.locator('[data-test-id="update-channel-nightly"]').waitFor();
|
||||
}
|
||||
|
||||
async function openBackup(page: Page): Promise<void> {
|
||||
await openSettingsSection(page, 'backup');
|
||||
}
|
||||
|
||||
async function openLibraryWatched(page: Page): Promise<void> {
|
||||
await openXtreamSection(page, 'vod', 'Action & Mystery');
|
||||
const watched = page.locator('[data-testid="vod-watched-toggle"]');
|
||||
await watched.waitFor();
|
||||
if ((await watched.getAttribute('aria-label')) === 'Mark as Watched') {
|
||||
await watched.click();
|
||||
}
|
||||
await page.getByRole('button', { name: 'Mark as Unwatched', exact: true }).waitFor();
|
||||
}
|
||||
|
||||
async function openDemoPlayback(page: Page): Promise<void> {
|
||||
await openSettingsSection(page, 'playback');
|
||||
const player = page.locator('[data-test-id="select-video-player"]');
|
||||
if (!(await player.innerText()).includes('HTML5')) {
|
||||
await player.click();
|
||||
await page.locator('[data-test-id="html5"]').click();
|
||||
await page.locator('[data-test-id="save-settings"]').click();
|
||||
await page.locator('[data-test-id="save-settings"]').waitFor({ state: 'hidden' });
|
||||
}
|
||||
// A local generated H.264 slate, never a provider stream. Use MPEG-TS
|
||||
// segments so the normal M3U/HLS player path decodes it without overrides.
|
||||
const fixture = path.resolve('tools/release/fixtures/guide-demo.mpegts');
|
||||
await page.route('**/live/marketing/marketing/*.m3u8', (route) =>
|
||||
route.fulfill({
|
||||
contentType: 'application/vnd.apple.mpegurl',
|
||||
body: '#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:20\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:20.0,\nhttp://localhost:3211/demo/guide-demo.ts\n#EXT-X-ENDLIST\n',
|
||||
})
|
||||
);
|
||||
await page.route('**/demo/guide-demo.ts', (route) =>
|
||||
route.fulfill({ contentType: 'video/mp2t', body: readFileSync(fixture) })
|
||||
);
|
||||
await openM3uGroups(page);
|
||||
await page.locator('[data-test-id="channel-item"]').first().click();
|
||||
await page.waitForFunction(() => {
|
||||
const video = document.querySelector('video');
|
||||
return video && video.readyState >= 2 && video.videoWidth === 1280;
|
||||
}, undefined, { timeout: 25_000 });
|
||||
// Freeze decoded media, not the interface, for a repeatable illustration.
|
||||
await page.locator('video').evaluate((video: HTMLVideoElement) => video.pause());
|
||||
await page.locator('video').hover();
|
||||
}
|
||||
|
||||
async function openFullscreenChannels(page: Page): Promise<void> {
|
||||
await openDemoPlayback(page);
|
||||
await page.getByRole('button', { name: 'Enter fullscreen', exact: true }).click();
|
||||
await page.keyboard.press('c');
|
||||
await page.locator('[data-test-id="fullscreen-channel-panel"][aria-hidden="false"]').waitFor();
|
||||
await page.locator('[data-test-id="m3u-fullscreen-view-all"]').click();
|
||||
}
|
||||
|
||||
async function openSubtitles(page: Page): Promise<void> {
|
||||
await openDemoPlayback(page);
|
||||
await page.getByRole('button', { name: 'Subtitles', exact: true }).click();
|
||||
await page.locator('[data-test-id="player-controls-load-subtitle"]').waitFor();
|
||||
}
|
||||
|
||||
async function openStreamInfo(page: Page): Promise<void> {
|
||||
await openDemoPlayback(page);
|
||||
await page.locator('[data-test-id="player-controls-stream-info-button"]').click();
|
||||
await page.locator('[data-test-id="player-controls-stream-info-panel"]').waitFor();
|
||||
}
|
||||
|
||||
export const FEATURE_ACTIONS: Readonly<Record<string, CaptureAction>> = {
|
||||
'open-update-channel': openUpdateChannel,
|
||||
'open-backup': openBackup,
|
||||
'open-library-watched': openLibraryWatched,
|
||||
'open-fullscreen-channels': openFullscreenChannels,
|
||||
'open-player-subtitles': openSubtitles,
|
||||
'open-stream-info': openStreamInfo,
|
||||
};
|
||||
@@ -25,6 +25,7 @@ import { DOWNLOAD_ACTIONS } from './capture-navigation-download-actions';
|
||||
import { EPG_ACTIONS } from './capture-navigation-epg-actions';
|
||||
import { PORTAL_ACTIONS } from './capture-navigation-portal-actions';
|
||||
import { SETUP_ACTIONS } from './capture-navigation-setup-actions';
|
||||
import { FEATURE_ACTIONS } from './capture-navigation-feature-actions';
|
||||
|
||||
export {
|
||||
clickDialogOption,
|
||||
@@ -41,6 +42,7 @@ const ACTIONS: Readonly<Record<string, CaptureAction>> = {
|
||||
...PORTAL_ACTIONS,
|
||||
...DOWNLOAD_ACTIONS,
|
||||
...EPG_ACTIONS,
|
||||
...FEATURE_ACTIONS,
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -93,6 +95,12 @@ export async function runAction(
|
||||
// dialog open or a dirty settings form. Clear whatever the previous step
|
||||
// left behind before this one starts navigating: a dialog backdrop
|
||||
// swallows every click, and unsaved settings raise a leave prompt.
|
||||
await page.evaluate(async () => {
|
||||
if (document.fullscreenElement) await document.exitFullscreen();
|
||||
});
|
||||
if (await page.locator('.cdk-overlay-backdrop-showing').count()) {
|
||||
await page.keyboard.press('Escape');
|
||||
}
|
||||
await dismissDialogs(page);
|
||||
await discardUnsavedSettings(page);
|
||||
await run(page, param);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Guide playback fixture
|
||||
|
||||
`guide-demo.mpegts` is an original, synthetic 20-second H.264 video slate at
|
||||
1280×720 / 25 fps, with no audio or third-party footage. Feature-guide captures
|
||||
serve it through a local HLS manifest, decode it with the real HTML5 player,
|
||||
and pause the decoded frame for repeatability. No player state or statistics
|
||||
are fabricated. Subtitle menus use the app's normal file-selection flow.
|
||||
|
||||
To regenerate on macOS with FFmpeg (Helvetica is a system font):
|
||||
|
||||
```sh
|
||||
ffmpeg -f lavfi -i 'gradients=s=1280x720:r=25:c0=0x14243e:c1=0x507f9f:n=2:d=20:speed=0.01:seed=24' \
|
||||
-vf "drawtext=fontfile=/System/Library/Fonts/Helvetica.ttc:text='IPTVnator':fontcolor=white:fontsize=52:x=(w-text_w)/2:y=(h-text_h)/2-30,drawtext=fontfile=/System/Library/Fonts/Helvetica.ttc:text='DEMO STREAM':fontcolor=white@0.6:fontsize=20:x=(w-text_w)/2:y=(h-text_h)/2+35" \
|
||||
-c:v libx264 -preset fast -crf 28 -pix_fmt yuv420p -g 50 -an \
|
||||
-f mpegts tools/release/fixtures/guide-demo.mpegts
|
||||
```
|
||||
|
||||
Capture the six feature-guide screens with:
|
||||
|
||||
```sh
|
||||
pnpm nx run electron-backend:build-e2e
|
||||
pnpm release:screenshots --group feature-guides --theme dark
|
||||
```
|
||||
|
||||
The EPG article uses the original GitHub release image selected by the
|
||||
maintainer, not this fixture. Its source is documented in
|
||||
`apps/website/public/blog/feature-guides/SOURCES.md`.
|
||||
@@ -40,6 +40,12 @@ export const HOST_RESOLVER_RULES =
|
||||
|
||||
/** Setup actions the driver implements. Manifest steps must match. */
|
||||
export const KNOWN_ACTIONS = [
|
||||
'open-update-channel',
|
||||
'open-backup',
|
||||
'open-library-watched',
|
||||
'open-fullscreen-channels',
|
||||
'open-player-subtitles',
|
||||
'open-stream-info',
|
||||
'open-dashboard',
|
||||
'open-settings',
|
||||
'open-xtream-vod',
|
||||
|
||||
@@ -203,6 +203,54 @@
|
||||
"setup": [
|
||||
"open-settings-tmdb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "guide-update-channel",
|
||||
"group": "feature-guides",
|
||||
"description": "update channel",
|
||||
"setup": [
|
||||
"open-update-channel"
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "guide-backup",
|
||||
"group": "feature-guides",
|
||||
"description": "backup",
|
||||
"setup": [
|
||||
"open-backup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "guide-library-watched",
|
||||
"group": "feature-guides",
|
||||
"description": "library watched state",
|
||||
"setup": [
|
||||
"open-library-watched"
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "guide-player-subtitles",
|
||||
"group": "feature-guides",
|
||||
"description": "player subtitles",
|
||||
"setup": [
|
||||
"open-player-subtitles"
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "guide-stream-info",
|
||||
"group": "feature-guides",
|
||||
"description": "stream info",
|
||||
"setup": [
|
||||
"open-stream-info"
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "guide-fullscreen-channels",
|
||||
"group": "feature-guides",
|
||||
"description": "fullscreen channels",
|
||||
"setup": [
|
||||
"open-fullscreen-channels"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -17,7 +17,7 @@ const SITE = 'https://4gray.github.io/iptvnator';
|
||||
const GUIDES = [
|
||||
{
|
||||
slug: 'm3u-programme-guide',
|
||||
screenshots: [],
|
||||
screenshots: ['blog/feature-guides/multi-epg-v0-24.jpg'],
|
||||
},
|
||||
{
|
||||
slug: 'xtream-codes-setup-guide',
|
||||
@@ -80,6 +80,31 @@ const GUIDES = [
|
||||
|
||||
const readDist = (relativePath) => readFile(new URL(relativePath, distRoot), 'utf8');
|
||||
|
||||
const FEATURE_GUIDE_SHOTS = {
|
||||
'stable-nightly-updates-guide': 'guide-update-channel',
|
||||
'playlist-backup-restore-guide': 'guide-backup',
|
||||
'library-organization-guide': 'guide-library-watched',
|
||||
'player-controls-guide': 'guide-player-subtitles',
|
||||
'stream-info-diagnostics-guide': 'guide-stream-info',
|
||||
'fullscreen-channel-episode-guide': 'guide-fullscreen-channels',
|
||||
};
|
||||
|
||||
if (process.env.PUBLIC_INCLUDE_DRAFTS === 'true') {
|
||||
for (const [slug, shot] of Object.entries(FEATURE_GUIDE_SHOTS)) {
|
||||
GUIDES.push({ slug, screenshots: [`blog/feature-guides/screenshots/${shot}-dark.png`] });
|
||||
}
|
||||
}
|
||||
|
||||
test('feature guide drafts reference screenshots that ship with the site', async () => {
|
||||
for (const [slug, shot] of Object.entries(FEATURE_GUIDE_SHOTS)) {
|
||||
const source = await readFile(new URL(`../../apps/website/src/content/blog/${slug}.mdx`, import.meta.url), 'utf8');
|
||||
const screenshot = `blog/feature-guides/screenshots/${shot}-dark.png`;
|
||||
assert.ok(source.includes(`](/iptvnator/${screenshot})`), `${slug}: inline screenshot`);
|
||||
assert.ok(source.includes(`heroImage: /iptvnator/${screenshot}`), `${slug}: card image`);
|
||||
await access(new URL(screenshot, distRoot));
|
||||
}
|
||||
});
|
||||
|
||||
const HUB_GROUPS = ['getting-started', 'live-tv-epg', 'playback', 'library', 'updates'];
|
||||
const DRAFT_GUIDES = [
|
||||
'm3u-programme-guide', 'stable-nightly-updates-guide', 'fullscreen-channel-episode-guide',
|
||||
|
||||