Imported from ozankaraali/QiTV (
AGENTS.md). Install upstream withnpx skills add ozankaraali/QiTV. Copyright stays with the author.
QiTV — Agent Guide and Working Plan
Overview
- Purpose: Maintain a clear, shared plan and conventions for ongoing refactors and fixes.
- Scope: Applies to the entire repository unless a more specific AGENTS.md is present in a subdirectory.
Principles
- Keep the UI responsive: no blocking network or heavy I/O on the UI thread.
- Fix root causes, not symptoms; keep changes small and focused.
- Prefer composition over monoliths; split large modules by responsibility.
- Use consistent logging over prints and propagate errors meaningfully to the UI when needed.
- Write code that’s testable; isolate logic from PySide UI where possible.
Style & Tooling
- Format: black + isort (configured via pre-commit).
- Lint: flake8 (treats most issues as warnings except syntax/undefined names).
- Types: add gradual type hints; keep mypy green on changed modules.
- Logging: use
logging.getLogger(__name__)rather thanprint.
Current Work Plan (Living TODO)
-
Input/UI polish and correctness
- Separate dblclick fullscreen from single-click play/pause (video_player.py)
- Remove unused
installEventFilter(self)onvideo_frameor implementeventFilterexplicitly - Normalize progress bar behavior for live/VOD; avoid toggling visibility repeatedly
- Add keyboard shortcuts as QActions (Play/Pause, Mute, Fullscreen, PiP) and bind menu/toolbar if added later
-
Networking and responsiveness
- Identify and thread key
requests(M3U load, STB categories, link creation) - Standardize timeouts/retries across network calls (added timeouts; moved update check to QThread)
- Move remaining UI-thread
requeststo workers (exports OK as is) - Ensure all worker completions marshal back to the UI thread (no cross-thread timers)
- Consolidate provider/EPG URL building and headers in one place
- Identify and thread key
-
Modularity and structure
- Extract delegates to
widgets/delegates.py - Move M3U parsing to
services/m3u.py - Move export helpers to
services/export.py - Split remaining
channel_list.pyinto widgets/ (panels) and services/ (provider, epg) - Move EPG parsing unit-testable logic out of UI code paths
- Refactor image pipeline: workers only cache bytes; GUI builds QPixmap/QIcon on main thread
- Consider a small event-bus/signal helper to decouple UI components
- Extract delegates to
- Add network security toggles (Prefer HTTPS, SSL verify) and plumb into requests/aiohttp (XTREAM/STB/M3U)
- Debug log EPG endpoints for providers (STB
load.phpget_epg_info, Xtreamxmltv.php) to help diagnose ID mismatches - Show all EPG entries in channel program list (no windowing), with local time formatting
- Investigate VideoPlayer seeking: prevent playback ending on seek; ensure double-clicking the progress slider controls the bar (not window drag)
- Investigate optional VLC quality enhancements for low-bitrate streams (audio compressor/equalizer and lightweight upscaling/sharpening), gated by settings and disabled by default
-
Logging and error handling
- Add module-level loggers; remove stray prints
- Downgrade transient image fetch errors to info; reduce log noise
- Plumb important errors to the UI via signals (non-modal first, modal where necessary)
-
Testing and stability
- Fix native QThread teardown races without blocking the GUI; retain wrappers through a successful nonblocking join
- Add tests for provider cache pruning and image cache accounting
- Add tests for XMLTV parsing and MultiKeyDict behavior
- Add simple smoke tests for content loader pagination/aggregation
-
Packaging & config
- Completing the Github Actions for UV environment usage.
- Pin more dependency versions in requirements.txt (PySide6, orjson, aiohttp, tzlocal)
- Add a
pyproject.tomlfor tool config (black/isort/mypy) to keep settings centralized - Drive bundle/app version from
pyproject.tomlin PyInstaller specs
Next Steps (Paused)
- Extract panels from
channel_list.pyintowidgets/:- content info panel, list panel, media controls
- Add
services/provider_api.pyto centralize STB/Xtream calls with timeouts + QThread wrappers - Move remaining UI-thread
requeststo workers (exports may stay synchronous) - Introduce lightweight dataclasses for Channel/Program for safer data access
- Add cancelation support to network workers (or switch to aiohttp within QThreads)
- Add unit tests for
services/m3u.pyandservices/export.py
Recent Changes (for context)
- v1.13.8 (unreleased): Restore missing window geometry during config migration without overwriting saved windows or user preferences. Returning from the MPV experiment left no
window_positions.video_player, so the VLC-based 1.13.7 release raisedKeyError: 'video_player'before displaying its windows. A regression covers player initialization and subsequent geometry persistence while preserving existing state. - Verification: Confirmed the installed Intel 1.13.7 bundle fails with the MPV-shaped config; a rebuilt 1.13.8 bundle starts with it and restores the missing entry. Native source startup displayed the local catalog, produced a window capture, and closed normally; all 14 tests, focused config types, lint, and the lock check passed. Frozen verification observed native windows and config migration; OS screenshot/interaction permissions were unavailable, and the isolated frozen process was stopped through its supervisor. No new cross-platform CI run has been performed for this patch.
- Release v1.13.7: Fixed an intermittent native PySide/QThread cleanup crash, observed immediately after waking from sleep and reproduced with repeated local M3U loads. Shared
services/thread_cleanup.pyretains thread/worker wrappers through a successful nonblocking native join before notifying the GUI or deleting the thread. Applied to catalog, images, provider setup/verification, playback links, and updates; app closure waits asynchronously for pending cleanup. VLC playback and existing packaging remain unchanged; no MPV migration was merged. - Verification: On Intel macOS/Python 3.14.0/PySide6 6.11.2, 3,000 local M3U worker lifecycles, all 13 regressions, controlled-response update/download success/error/cancellation smoke checks, focused seven-module type checks, syntax/undefined-name lint, and the version/lock consistency check passed. New subprocess regressions cover delayed native teardown, GUI responsiveness, and consumer destruction. Broader mypy still reports 22 errors in unchanged export/dialog modules. Frozen bundles and other operating systems have not yet been reverified for this patch.
- Release v1.13.6: Dependency/security upgrades and the Xtream duplicate-request fix (#51). Verified 11 regressions, 11-module type checks, native macOS single-request playback and VOD resume, asynchronous image loading, and the macOS bundle build.
- Fix #51: Xtream catalog loading no longer probes media URLs, and the embedded player no longer requests network preparsing before playback. Provider metadata determines stream URLs/formats; native playback, redirects, reconnects, and VOD error/seek handling remain intact.
tests/test_xtream_requests.pycovers API-only live/VOD catalog requests, explicit scheme/port precedence, HLS-only providers, and VOD container metadata. - Dependencies: Updated runtime/tooling pins and all transitive dependencies, aligned pre-commit tool versions, and pinned supported CI action releases by commit SHA. Removed unused m3u-parser/asyncio and obsolete tzlocal stubs. Python is constrained to 3.14 by current Qt/theme support; requirements.txt is generated with hashes from uv.lock.
- Compatibility: Routed video-frame double-clicks through the existing Qt event filter instead of assigning a Qt virtual method. Made PiP geometry restoration explicit and removed obsolete mouse-button enum fallbacks; separated asyncio completion awaitables from cancellation tasks for current type stubs.
- Release v1.13.5: Large content lists populate in cancellable GUI batches with an eight-millisecond row-construction budget. Detached row construction reduces model notifications; numeric sort values are cached and initial sorting runs once. EPG rows populate per batch, and display refresh preserves category, selected item identity, and sort order.
- Fix: Catalog refresh/navigation uses nonblocking workers, rejects superseded results, reuses cached STB/Xtream seasons and STB episodes, and batches list updates. Logo/poster jobs never lock navigation and map results to item identity after sorting.
- Feature: Provider connection changes and Apply/Verify followed by Save trigger automatic content refresh. Provider drafts are isolated until Save; verification runs in an isolated background session.
- Cache: Six-hour per-content freshness with connection identity checks; five-minute visible-catalog checks defer while inside a series. Cache serialization/writes are ordered, asynchronous, atomic, and invalidation-safe. Local M3U parsing and STB category indexing run in workers.
- Verification: Eight focused regressions in
tests/test_catalog_refresh.pycover cache expiry, same-name provider edits, pending-write invalidation, cached/interrupted Back navigation, sorted/retired logos, numeric/EPG sorting, and refresh selection restoration (uv run python -m unittest discover -s tests -v). - UX: Added QActions for playback controls (Space: Play/Pause, M: Mute, F: Fullscreen, Alt+P: PiP) for future menu/toolbar binding (video_player.py)
- UX: Normalized VOD vs Live progress behavior; avoid repeated visibility toggles and only update values on VOD (video_player.py)
- Refactor: Centralized STB URL building in
services/provider_api.py; updated STB workers and EPG to use it (channel_list.py, epg_manager.py) - Fix: Eliminated cross-thread timer warnings by posting worker completions to the GUI thread (channel_list.py: M3U/STB/link creators; update_checker.py). Also avoided unconditional signal disconnects that caused warnings.
- Refactor: Image loading pipeline avoids GUI objects in worker threads; workers cache files, GUI constructs QPixmap/QIcon (image_loader.py, image_manager.py, channel_list.py logos/posters).
- UX: Export button now uses a clean label; dropdown arrow provided by Qt via setMenu (channel_list.py).
- Fix: Robust list population (avoids None-to-Qt conversions) and EPG text handling; safer selectionChanged disconnects for program/content lists.
- Debug: Optional Qt warning capture via
QITV_DEBUG_QT=1prints timer/thread issues to stderr without crashing (main.py). - Feature: Resume Last Watched auto-switches provider on user confirmation, then resumes playback (channel_list.py).
- Feature: Modern toolbar UI with quick provider switcher (channel_list.py:394-538)
- Single-row toolbar with logical sections: Provider | File Ops | Navigation | Content Actions
- Quick provider dropdown at start - switch providers without opening Settings
- Compact gear icon (⚙) for Settings button
- Shortened button labels with tooltips (Update, Resume, Rescan Logos)
- Export button shows dropdown arrow (▼) and opens menu on click
- Visual section grouping with consistent 12px spacing between sections
- Auto-refreshes provider list after Settings dialog closes
- Fix: Removed incorrect @staticmethod decorator from load_stb_categories (channel_list.py:1877)
- Was causing AttributeError: 'str' object has no attribute 'provider_manager'
- The decorator caused parameter shift where self received url string instead of instance
- Feature: Enhanced export validation and tooltips (channel_list.py:430-456,1467-1544)
- Added helpful tooltips to each export menu option
- Export Complete now shows informative messages for inappropriate content types
- Validates provider type and content type before attempting fetch operations
- Feature: Consolidated export functionality into single dropdown menu (fixes #27) (channel_list.py:430-456,1467-1650; README.md:36-46)
- Replaced "Export Browsed" and "Export All Live" buttons with unified "Export" dropdown menu
- Export Cached Content: Quickly exports only browsed/cached content
- Export Complete (Fetch All): For STB series, fetches all seasons/episodes before exporting with progress dialog
- Export All Live Channels: Exports all available live channels from cache
- Changed popup mode to InstantPopup for cleaner UX
- Added synchronous fetch methods for seasons and episodes
- Feature: Added portable mode support via
portable.txtfile (fixes #26) (config_manager.py:79-109; README.md:27-34)- When
portable.txtexists in program directory, config and cache are stored locally instead of system directories - Works for both script and PyInstaller executable modes
- When
- Fix: PyInstaller spec files now use SPECPATH instead of file (qitv-*.spec:10)
- Fix: Updated to new UV dependency-groups format (pyproject.toml:49-50)
- Fix: Delayed main window activation to prevent cursor blinking issues (main.py:60-64)
- Fix: Video player no longer steals focus from channel list on playback (video_player.py:250-251)
- Feature: Added optional Serial Number and Device ID fields for STB providers (fixes #31) (options.py:179-187,362-363,408-411,424-431,525-530,537-538; provider_manager.py:115-118,77-82,175,189,197-207,217)
- Feature: Added "Resume Last Watched" button to quickly resume previous content (channel_list.py:412-414,1921-1973; config_manager.py:131-134,205-211)
- Fix: Resume Last Watched now recreates links for STB providers (tokens expire) (channel_list.py:1963-1966)
- Fix: Video player now properly activates on playback start (resolves focus-dependent mouse events) (video_player.py:249-250)
- Fix: CI changelog generation now uses body_path instead of non-existent output (.github/workflows/main.yml:185)
- Fix: App now properly raises and activates on startup (main.py:58-59)
- Tweak: Removed manual window activation/raise calls to avoid focus stealing (main.py, video_player.py)
- Fix: Progress bar seek no longer causes window drag (video_player.py:114)
- Fix: Movies/Series content type switching now correctly fetches respective categories (channel_list.py:71-101,1649)
- Fix: Single-click pause/play now works correctly; dragging only marked when mouse moves (video_player.py:340,369)
- Fix: Prevent single-click pause when double-click toggles fullscreen (video_player.py)
- Fix: Provider cache pruning now matches hashed provider-name files (provider_manager.py)
- Fix: Image cache accounting bug when file missing on disk (image_manager.py)
- Fix: Country field mapping typo in content info (channel_list.py)
- Infra: Centralized logging config (main.py); replaced prints with loggers across modules
- UX: Buffering progress bar visibility consistent for live/VOD (video_player.py)
- UX: Mouse Back/Forward buttons map to Back/Forward navigation (VideoPlayer emits backRequested/forwardRequested -> ChannelList.go_back/go_forward)
- UX: Optional "Keyboard/Remote Mode" setting moves list highlight with Up/Down; auto-plays when item is playable (options.py, video_player.py, channel_list.py)
- Perf: Update checker moved to QThread and added network timeouts; added timeouts in several requests
- Arch: Extracted delegates to
widgets/delegates.py; moved M3U parsing toservices/m3u.py; moved export helpers toservices/export.py - Packaging: Added
__init__.pytoservices/andwidgets/to satisfy mypy package resolution - CI: Switched GitHub Actions to uv; centralized tool configs in
pyproject.toml - Feature: Added global Network settings: "Prefer HTTPS when available" and "Verify SSL certificates". Applied to Xtream, STB, and M3U fetchers (options.py, config_manager.py, channel_list.py, provider_manager.py, epg_manager.py, services/provider_api.py, content_loader.py, image_loader.py)
- Behavior: Xtream base resolution no longer auto-enforces HTTPS; respects entered scheme unless Prefer HTTPS is enabled (services/provider_api.py, channel_list.py)
- Player: Replaced plain
QProgressBarwith seekable progress bar subclass; drag/double-click seeks without window drag. Seeking clamps near end to avoid playback ending. - EPG UX: Program list highlights the currently airing entry with a "▶ Now" prefix and a light blue background; times are localized.
- EPG: Added Settings control for STB EPG server fetch period (hours); loader now uses
epg_stb_period_hoursinstead of fixed 5.
Conventions for New Code
- Keep UI and data/services separate. Long-running network calls must run in QThread.
- Avoid coupling VLC/player code to UI state more than necessary; use signals.
- Prefer dataclasses or typed dicts for structured data passed between layers.
- Never create Qt GUI objects (QPixmap/QIcon) or start timers from worker threads; emit plain data and build UI in the main thread.
How to Contribute
- Update this AGENTS.md when you pick up or complete an item.
- Keep PRs small; focus on one area at a time.