* fix(m3u): parse playlists with URLs longer than 2084 characters
Pluto TV style playlists (issue #1189) embed a session JWT in every
stream URL (~2200 chars). validator.isURL inside iptv-playlist-parser
rejected anything over its IE-era 2084-char default, and the parser's
stalled item index then collapsed the whole playlist into a single
channel.
Sync the 4gray/iptv-playlist-parser fork with upstream v0.15.2, which
removes URL validation entirely and adds an explicit branch so '#'
comments and unknown directives are never treated as URLs. Two fork
deltas are preserved on top: the radio attribute (radio player
detection) and pipe stripping (item.url is cut at the first '|' while
|User-Agent=/|Referer= params still land in item.http). The now-dead
validator/is-valid-path dependencies are dropped from the fork.
- pin iptv-playlist-parser to the fork commit SHA
- add a parser contract spec guarding long URLs, comment handling,
radio, pipe stripping, and header EPG attrs
- document the parser fork contract in the M3U architecture doc
Fixes#1189
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(m3u): bump parser to optimized fork build
Pulls the fork's optimized parse() rewrite (2.5-3x faster: 100k
channels ~780ms -> ~285ms, 10k ~79ms -> ~25ms) and the README
documenting fork deltas. Output is differential-verified byte-identical
to the previous build; all parser-contract, unit, and import E2E suites
rerun green against the new pin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(m3u): bump parser for input robustness and library hygiene
Pulls the fork's real-world input tolerance: UTF-8 BOM, blank lines and
whitespace before the header, and case-insensitive #EXTM3U no longer
reject the playlist (all VLC-accepted forms); Node Buffers are decoded
as UTF-8 and other non-string input throws a clear TypeError. Also
brings truthful types (url?: string), fork metadata, an enforced 100%
coverage gate, and the fork CHANGELOG.
Extends the contract spec with a BOM regression test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(m3u): pin parser to the tagged fork release v0.15.2-iptvnator.1
Same commit as before (33f5e9c) — the readable tag replaces the raw
SHA in package.json while pnpm-lock still records the immutable
codeload tarball by commit. Fork release:
https://github.com/4gray/iptv-playlist-parser/releases/tag/v0.15.2-iptvnator.1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(types): make ParsedPlaylistItem.url optional to match runtime
The parser fork's d.ts now truthfully declares url?: string (a trailing
#EXTINF without a stream URL yields url === undefined at runtime, and
always has). The local ParsedPlaylistItem mirrored the old type lie and
made the production typecheck reject the parser's Playlist type.
createChannel and createPlaylistObject already tolerate the absent url.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Enables Xtream catch-up/timeshift from the Favorites and Recent surfaces (per-playlist and global), not just Live TV, and adds start-over replay of the currently-airing programme. Carries tv_archive/tv_archive_duration through the favorites and recently-viewed DB projections and maps them onto UnifiedCollectionItem; tv_archive_duration is interpreted as days, matching live-stream-layout.controlledArchiveDays.
Closes#1138.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(epg): wire catch-up Watch button in unified live tab, surface resolve failures
The favorites/recent live views (UnifiedLiveTabComponent) passed
archivePlaybackAvailable/archiveDays to the EPG timeline and list view, so
the "Watch" button and the dialog's "Watch from start" action rendered —
but neither (programActivated) nor (returnToLive) was bound. Clicking
Watch emitted an event nobody listened to: no error, no feedback, no
playback change.
- Handle programActivated in the unified live tab: resolve the catch-up
URL via resolveM3uCatchupUrl, swap the inline player's playback target
(or hand it to the configured external player), show the
"Archive playback" summary label, "From archive" block tag and
"Return to live"; clear the override on channel switch/close/return.
- Never fail silently: both the unified tab and the m3u-state effect now
show an EPG.TIMELINE.CATCHUP_FAILED snackbar when the replay URL can't
be resolved. New i18n key translated into all 17 locales via the
i18n-fill workflow.
- Extract the effect's resolve branch into a pure
resolveActiveEpgProgramAction util and the unified tab's EPG summary
helpers into unified-live-epg-summary.util.ts (component shrinks
598 → 572 lines despite the new feature).
- Regression coverage: 6 component tests for the catch-up flow,
3 unit tests for the resolver action; docs updated in
m3u-playlist-module.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(epg): address PR review — untrack progress tick in archive summary, open live external without prior timeshift
- liveEpgPanelSummary no longer tracks the 30s progressTick while an
archived programme is shown (Greptile P2): the archive summary is
frozen, so re-running the computed each tick only produced needless
re-renders.
- returnToLivePlayback no longer early-returns when no timeshift is
active (Codex P2): with an external player configured and
openStreamOnDoubleClick enabled, the programme dialog's "Watch live"
action now launches the external player instead of being dropped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* chore(cleanup): delete nine dead components, orphaned i18n keys and unused deps
Removes verified-dead components (0 class/selector references outside
their own files): EpgListComponent (+ epg-list-item), EpgViewComponent,
LiveEpgPanelComponent, StalkerCollectionChannelsListComponent,
NavigationComponent, FilterSortMenuComponent, video-player
ToolbarComponent, PortalCollectionShellComponent and
LoadingOverlayComponent, together with their barrel exports.
Alive code extracted from the deleted trees:
- LiveEpgPanelSummary -> libs/ui/shared-portals/src/lib/live-epg-panel-summary.ts
- EpgProgramActivationEvent -> libs/ui/epg/src/lib/epg-program-activation-event.ts
- epg-list.utils.ts trimmed to the three timeline-used helpers and moved
to libs/ui/epg/src/lib/epg-program.utils.ts
- epg-item-description/ moved up out of the deleted epg-list/ folder
Also removes 18 i18n keys now unused (from all 18 locales), dead CSS
selectors targeting the deleted elements, and unused dependencies:
lodash (+ @types/lodash), semver, @ngrx/component-store and
@videojs/http-streaming (videojs-quality-selector-hls declares no peer
dependency on it; video.js 8 bundles VHS).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(coverage): move shared-portals to Tier C, fix stale doc references
The Tier A gate failed in CI because deleting the dead epg-view and
live-epg-panel components removed the only specs in libs/ui/shared-portals.
The lib now contains a single type-only interface (LiveEpgPanelSummary),
so there is no runtime code to unit test; reclassify it to Tier C with a
documented reason, matching the gate's own guidance.
Also update remaining doc references to the deleted components in
docs/architecture/stalker-epg.md, iptvnator-ui-guidelines.md and
CLAUDE.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(epg): add vertical list view for the live EPG panel
Add an EPG list view — a vertical, single-day programme list — as an
alternative rendering of the live EPG panel, selectable via a new
Settings → EPG → "Guide view" toggle (epgViewMode: 'timeline' | 'list',
default 'timeline' so existing users see no change).
- New EpgListViewComponent (app-epg-list-view) mirrors
EpgTimelineComponent's input/output contract 1:1, so all four live
hosts (M3U player, unified live tab, Xtream, Stalker) swap the panel
with a plain @if and identical bindings.
- Reuses the shared view-agnostic EPG modules (classifyTimelineWhen,
hasProgramsForDateKey, epg-archive.util catch-up gating,
epg-summary.util collapsed-summary maths, epg-date helpers,
EpgProgrammeDialogService, app-epg-timeline-empty-state) — no
duplicated logic.
- Rows show time range, title, optional description, live progress on
the on-air row, catch-up "Watch" on past rows when archive playback
is available, and a details dialog; keyboard activation guards
nested buttons (target === currentTarget).
- Auto-focuses the on-air row on channel select, restores it across
collapse/expand remounts, and shows a sticky in-flow "On now" strip
(never overlaying rows) when the current programme is scrolled away;
all scroll maths is rect-based relative to the scroller.
- List mode raises only the inline panel height via an epg--list
modifier (--epg-inline-height clamp); timeline and collapsed heights
are unchanged.
- Setting flows end-to-end (Settings interface → DEFAULT_SETTINGS →
SettingsStore/StorageMap → segmented control in the EPG section);
Electron-only UI, PWA stays on the timeline default. i18n keys added
to all 18 locales.
- Tests: new component/row/utils/scroll-controller specs, settings
persistence spec, swap tests in all four host specs, and Electron
E2E for the settings round-trip and the rendered list view. Docs
updated (m3u-playlist-module.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(epg): address list-view review findings from Codex and Greptile
- Reset the list view to today when a new channel's programme set
arrives while the user is parked on another day (timeline parity):
the scroll controller now keys by the full programme-set identity
(programsFocusKey) and commits today before focusing, instead of
silently stranding the new channel on the stale day. (Codex P2)
- Centralise the 'timeline' fallback as a resolvedEpgViewMode computed
on SettingsStore; the four live hosts consume the derived signal
instead of duplicating the `?? 'timeline'` expression. (Greptile P2)
- Extract the component's reactive plumbing into
registerEpgListViewEffects(), bringing the component back under the
300-line guideline (290). (Greptile P2)
- Controller spec rewritten around programme-set fixtures with new
coverage: return-to-today on channel switch, day navigation left
alone, no-takeover when today has no data, empty-set no-op.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(epg): drop malformed programmes from the list-view day filter
Reject programmes whose stop is not after their start in
buildEpgListRows — same as the timeline's buildTimelineBlocks. Bad
provider data would otherwise render impossible time ranges and could
even be offered as catch-up playable. (Codex P2 on e6fd0d08)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the vertical EPG list with a shared horizontal `app-epg-timeline`
ribbon across all live surfaces (M3U player, unified live tab, Xtream, Stalker):
zoom, day navigation, short-programme grouping, catch-up/timeshift, and
per-state empty views. Backend gains timezone-aware `datetime()` comparisons,
unscoped source fallback, non-ASCII candidate matching, and chunked candidate
queries.
Timeline split into reusable, view-agnostic modules (archive/summary/dialog
service/render util/scroll controller) for the future EPG list view.
Fixes landed during review:
- honor the controlled `selectedDate` input (seed via linkedSignal)
- restore ribbon position across collapse/expand
- keep the ribbon mounted when scrolling across a gap day
- don't trigger block playback on Enter from nested watch/info buttons
- don't reset timeshift playback on the 30s now-tick during EPG gaps
Greptile 5/5 (safe to merge); Codex clean; CI green.
- Implemented a loading state for channel lists in various components, enhancing user experience during data fetching.
- Added `ChannelListLoadingStateComponent` to display loading skeletons for channels and groups.
- Updated `M3uWorkspaceRouteSession` to manage loading states more effectively when fetching playlists.
- Modified `VideoPlayerComponent`, `SidebarComponent`, and `ChannelListContainerComponent` to utilize the new loading state.
- Enhanced tests to cover new loading behaviors and ensure proper dispatching of loading actions.
- Moved Stalker-related components and services from apps/web to libs/portal for better modularity.
- Introduced SQLite DB Worker to handle non-EPG database operations, improving UI responsiveness.
- Updated documentation for the Workspace Dashboard and Shell, detailing current implementation and routing structure.
- Added new EPG fixture scenarios to the Xtream mock server for testing purposes.
- Enhanced the overall architecture documentation to reflect recent changes and improvements.