- Rewrite ghost client tests to call real ChannelStatus and ClientManager code paths instead of reimplementing detection logic in test helpers
- Rewrite initializing state tests to invoke StreamManager.run() so the real finally block executes under test
- Replace hardcoded state list tests with assertions against ChannelState.PRE_ACTIVE frozenset
- Add missing SOURCE_BITRATE and FFMPEG_BITRATE constants to ChannelMetadataField (referenced by channel_status.py but never defined, causing AttributeError on detailed stats)
- Change ChannelState.PRE_ACTIVE from list to frozenset (immutable,
O(1) membership tests, no risk of silent mutation)
- Add optional client_ids parameter to ClientManager.remove_ghost_clients()
so callers that have already fetched SMEMBERS can pass it through,
eliminating a redundant Redis round-trip
- Pass pre-fetched client_ids from get_basic_channel_info() into
remove_ghost_clients() to remove the duplicate SMEMBERS call
- Add MAX_KEEPALIVE_DURATION = 300s to TSConfig
- Track keepalive_start_time in _stream_data_generator; disconnect after cap is exceeded with a warning log
- Reset timer on real data so independent stalls don't accumulate
- Fix pre-existing test helper missing throttle attributes in NonOwnerWorkerKeepaliveTests
- Write ERROR via ownership check OR state guard fallback when ownership TTL expired during retries
- Add INITIALIZING to cleanup task grace period monitoring
- Validate client SET entries against metadata hashes in orphaned channel cleanup; remove ghosts and clean up when no real clients remain
- Stats page self-heals by removing ghost SET entries on read
- Extract remove_ghost_clients() helper to ClientManager
- Add ChannelState.PRE_ACTIVE constant
- Fix 6 pre-existing NonOwnerWorkerKeepalive test failures
Replace DRF ModelSerializer with .values() + plain dict construction in
EPGGridAPIView, eliminating per-row serializer instantiation, field
object creation, and OrderedDict overhead across ~12k programs.
Pre-compute season/episode at import time instead of on every API request:
- Extract S/E from onscreen episode-num inline in extract_custom_properties()
immediately after storing the raw value, rather than in a separate pass
- Add missing description-based S/E fallback to parse_programs_for_tvg_id(),
bringing it in line with parse_programs_for_source()
Remove runtime extract_season_episode() call from ProgramDataSerializer and
EPGGridAPIView — values are now guaranteed to be in custom_properties after
import, so season/episode resolution is a plain dict lookup at request time.
Also remove unnecessary select_related("epg") JOIN and .count() query from
the grid endpoint.
Replace expanded inline cards with a ProgramDetailModal for viewing full program
details. Add real-time progress bars to currently-airing programs using direct DOM manipulation. Extract and display season/episode info from EPG data with a 3-tier fallback (custom_properties → onscreen_episode → description text).
Backend:
- Add ProgramDetailSerializer for rich EPG data (categories, credits, ratings, images, external IDs) served on the detail endpoint
- Add infer_is_live() for status badges (live, new, premiere, finale)
- Add shared S/E extraction utilities in apps/epg/utils.py
- Optimize querysets with select_related("epg")
Frontend:
- ProgramDetailModal with conditional field rendering and image fallback chain
- Progress bars via scaleX transform updated every second
- Parallel data fetching with Promise.all for channels + programs
- Browser tab visibility recovery for progress bars
- Now-line with triangle marker and sub-millisecond precision
- Card layout: title, season/episode + subtitle, time + badges
- Shared formatSeasonEpisode() and external URL helpers
P1 (Critical):
- Replace undefined @swagger_auto_schema with @extend_schema
- Add custom_properties validation with size limits
- Add merge semantics in serializer update() to prevent data loss
P2 (Important):
- Add debouncing (800ms) for drag operations in NavOrderForm
- Add error handling with notifications for visibility toggle
- Use store getters in Sidebar instead of direct property access
- Cache hiddenNav before render loop
P3 (Nice-to-have):
- Add canHide: false to settings nav item
- Fix admin check to use >= instead of ==
- Fix ESLint unused variable warnings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Allow users to update their own profile via PATCH /api/accounts/users/me/
- Supports partial updates to custom_properties (including navOrder)
- Add comprehensive test coverage for the endpoint
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Stream.get_stream() was never called in the preview path of
generate_stream_url(), so the channel_stream and stream_profile
Redis keys were never written. This caused the stream_id to be
missing from channel metadata, which silently broke two things:
- Stats page showed no stream name or logo
- stream_stats were never saved to the database
Fix: replace the manual profile-selection loop in the preview path
with a direct call to Stream.get_stream(), matching the channel path
exactly. This also fixes a pre-existing non-atomic slot reservation
(read-then-check vs INCR-first) that could over-allocate connections
on concurrent previews.
Regression introduced in 49b7b9e2.
Add is_new, is_live, is_premiere, and is_finale fields to the EPG program API and render them as colored badges in the TV guide.
- Compact card: S/E, LIVE, and NEW badges on the title row
- Expanded card: all badges (S/E, LIVE, NEW, PREMIERE, FINALE) on the subtitle row, now moved directly after title for consistency
- PREMIERE/FINALE detected from premiere_text (case-insensitive)
- PREMIERE/FINALE only shown in expanded view to conserve space
- Badge order: S/E → LIVE → NEW → PREMIERE → FINALE
- Consolidate serializer season/episode parsing into a single to_representation
pass: one custom_properties read, regex runs at most once per program
- Compact card: S/E badge inline with title; secondary row shows sub_title with
description fallback
- Expanded card: sub_title and S/E badge share a row above the description
- Fix secondary text color for legibility on dark backgrounds (#a0aec0 → #e2e8f0)
- Fix expanded card overflow (height:100% → flex:1 1 0) so action buttons stay visible
- Bump expanded card height 180 → 200px, description max-height 80 → 100px
Display sub_title (episode name) and season/episode badge on guide program cards. Extract season/episode from custom_properties with fallback parsing of onscreen_episode format (e.g. "S12 E6").
- Add season/episode SerializerMethodFields to ProgramDataSerializer
- Parse onscreen_episode string as fallback when xmltv_ns data absent
- Display sub_title with S/E badge on guide cards
- Show description for programs without sub_title (movies)
- Add formatSeasonEpisode utility with full test coverage
- Add 15 backend serializer tests and 12 frontend utility tests
Fulfills #1065
Restructure cleanup in refresh_single_m3u_account so del statements
run before gc.collect(), enabling cyclic garbage collection. Add worker_max_memory_per_child as a safety net against memory fragmentation.
Closes#1012, Closes#1053