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.
- Move channel name/number into modal header title bar (was empty after removing duplicate title)
- Remove channel logo from modal (didn't fit well)
- Remove custom background color override, inherit from Mantine dark theme for consistency with VOD/Series modals
- Fix wrong channel shown in modal: pass channel directly from renderProgram instead of re-looking up by tvg_id
- Clean up unused imports (getChannelLogoUrl, Image/Group no longer needed in title)
- replaced $_pg_tmp_port with $POSTRGES_PORT in migration logic. Works because "pg_ctl stop -w" is run before entrypoint.sh restarts postgres. Verified that this does not cause a race condition.
- Correcting unquoted environment variables to prevent inappropriate variables from causing execution inconsistencies (still unquoted in comments and prints)
- Floating video player resize continuing after the mouse button was released outside the browser window. Because the `mouseup` event is not delivered when the pointer leaves the viewport, the `endResize` handler was never called and the `mousemove` listener remained active indefinitely. Fixed by checking `event.buttons === 0` at the top of `handleResizeMove`; when no button is held the resize session is torn down immediately and all move listeners are removed.
- Floating video player drag continuing after the mouse button was released outside the browser window, for the same reason as the resize bug above. Fixed by detecting `event.buttons === 0` in the `onDrag` handler and dispatching a synthetic `mouseup` event so react-draggable cleanly ends the drag session.
- Floating video player could be dragged off any edge of the screen, making the header (and therefore the drag handle) unreachable. The player is now fully bounded: the left and top edges are clamped to `x ≥ 0` / `y ≥ 0` so the header is always visible and reachable, and the right/bottom edges are clamped so the player never extends beyond the viewport. The player is also re-clamped automatically when the browser window is resized and proportionally scaled down if the saved size is larger than the new viewport.
Program title was intentionally being shown in the modal title bar and modal body, but when no program artwork was provided via EPG the title components were layered one after the other, making it look like a duplicated title.
PostgreSQL now runs as the PUID/PGID user ($POSTGRES_USER) instead of the internal postgres system user (UID 102). This fixes container startup failures when PUID/PGID is set, caused by chown permission errors on restricted filesystems (NFS root_squash, CIFS) and UID collisions with the postgres system user.
Changes:
- Run all PostgreSQL operations (initdb, pg_ctl, psql) as $POSTGRES_USER
- Auto-detect PUID/PGID from existing data owner when not explicitly set
- Validate PUID/PGID (reject zero, non-numeric values) before startup
- Migrate existing data ownership with sentinel-based skip optimization
- Use trust auth for local Unix sockets, md5 for network connections
- Add promote_app_role() and ensure_app_database() as idempotent startup guarantees that handle fresh installs, upgrades, and PUID changes
- Preserve postgres role as superuser for rollback compatibility
- Centralize /data/db ownership in 02-postgres.sh (sentinel-aware)
- Add integration test suite (20 scenarios) covering fresh installs, upgrades, restarts, PUID changes, UID collisions, bind mounts, modular mode, PG major upgrades, and end-to-end web UI verification
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