* fix(catchup): parse negative sub-hour XMLTV offsets correctly
XMLTV timestamps with offsets like "-0030" were treated as UTC because the
hour part "-00" numerically evaluates to -0 and Math.sign(-0) dropped the
minutes' sign. Derive the sign from the offset string instead, so
sub-hour negative offsets shift the catch-up start time correctly.
* test(catchup): cover positive sub-hour XMLTV offsets
---------
Co-authored-by: 4gray <serega05@gmail.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.