Skip to content

Quality, stability, and performance overhaul - #208

Merged
kristofferR merged 51 commits into
mainfrom
quality-stability-fixes
Jul 23, 2026
Merged

Quality, stability, and performance overhaul#208
kristofferR merged 51 commits into
mainfrom
quality-stability-fixes

Conversation

@kristofferR

@kristofferR kristofferR commented Jul 18, 2026

Copy link
Copy Markdown
Owner

A full-codebase review pass (48 commits): correctness bugs, proxy hardening, performance work, and structural decomposition of the four oversized files. No feature changes.

Bugs fixed

  • Orphaned Tauri listener races in useScan (scan events were processed twice under StrictMode remounts), useChromecast, and the App settings listener — all now use the Promise.all + cancellation-guard pattern.
  • Xtream account-info fetch aborted its endpoint fallback on the first transport error instead of trying plain player_api.php.
  • Side effects inside setState updaters in ChannelTable could double-start playback on arrow-key navigation.
  • The quick-check status match was missing "Placeholder", silently degrading those results to Dead — channel status is now a typed enum end-to-end.
  • Cancellation no longer rewrites an already-completed Alive verdict to Dead (and no longer poisons resume checkpoints).

Security/stability

  • The stream proxy's SSRF guard now validates every redirect hop (clients use Policy::none() and hop manually); previously a public upstream could 302 to 127.0.0.1/169.254.169.254 unchecked.
  • All unbounded upstream body reads are capped (buffered proxy responses 64 MiB, liveness manifests 8 MiB, JSON APIs 64 MiB); playlist downloads stream to disk instead of buffering up to 200 MB in RAM.
  • ffmpeg children get kill_on_drop; the cast pump uses escalating reconnect backoff with a healthy-session threshold instead of hammering dead upstreams at 5 req/s; raw proxy request heads are read to the terminator under a deadline; app setup no longer blocks on the proxy bind.
  • Credentialed Xtream URLs are redacted from debug logs.

Performance

  • Channel table overscan 250 → 20 (it mounted ~500 extra rows per flush); Toolbar and table now share one memoized filter pass per scan flush.
  • Playlist parsing, checkpoint loads, history writes, and cache writes moved off the tokio runtime; the parser reads EXTINF attributes once per channel instead of ~7 times.
  • Preview cache hands out Arcs instead of deep-copying full playlists; retained per-channel diagnostics capped at 64 KiB; screenshot sidebar cache is LRU-capped; TS pacer scans payloads in place.

Structure

  • playlist.rs (3.4k lines) split into engine/{xtream,stalker,remote_cache} + commands/server_test; scan.rs's 1,050-line execute_scan_run decomposed (24-param helper now takes a context struct); App.tsx 3.2k → 1.7k lines via usePlaylistSources/useUpdateCheck/useMenuEventBridge/StartScreen/AppBanners; useStreamPlayer policy extracted to lib/playback.ts.
  • Deduplicated: URL canonicalization (4 copies), atomic temp-write (3), streaming-proxy lazy start (3 — only the dead copy had the liveness probe), the HLS manifest rewriter (2 divergent copies), the checker retry driver, ffmpeg stderr readers, Xtream normalizers, and the platform menu builders.
  • Dead code removed (11 unused store setters, never-populated RemuxState.child, legacy last_error_reason); AppError gains Validation/State variants; tauri-plugin-mcp pinned to its locked rev.

Review notes

A local CodeRabbit preflight ran over the branch; genuine findings were fixed (diagnostics cap overshoot, JSON read caps, log redaction, request-head deadline, pump backoff threshold, banner a11y labels, two App effect races). Findings targeting pre-existing behavior that this branch only relocated (keychain credential storage, server-test probe semantics, M3U attribute escaping, semver prerelease ordering, LL-HLS tag coverage, DNS-rebinding TOCTOU pinning, module layout preferences) were deliberately left out of scope.

Validation

  • 250 Rust unit + 4 integration tests, 79 frontend tests, tsc strict — all green, zero warnings.
  • Live smoke test: launched the app, opened a playlist through the new download/parse path, ran a scan (results and diagnostics flowing), and verified cancel leaves no channels mislabeled Dead.

Summary by CodeRabbit

  • New Features

    • Added support for opening Stalker/Ministra portal playlists and improved Xtream playlist handling.
    • Added server testing with ranked results, latency, stream details, and optional screenshots.
    • Added a refreshed start screen for opening files, URLs, saved playlists, and recent sources.
    • Added update notifications with download and dismiss actions.
  • Bug Fixes

    • Improved playlist loading, scan responsiveness, playback recovery, proxy safety, and HLS handling.
    • Added clearer validation errors and safer user-facing error messages.
    • Improved report-panel behavior and filtering performance.

useScan registered 11 listeners with sequential await listen() and no
cancellation guard, so a cleanup during registration (StrictMode remount)
orphaned every listener that resolved afterwards — duplicating scan event
processing and inflating telemetry. Register via Promise.all with a
cancelled flag, mirroring the menu-listener fix in App.tsx.

useChromecast had the same race in miniature: an unmount while
listen("cast://status") was in flight leaked the listener.
moveFocusBy ran selection emits, cast-redirect scheduling, and even
onOpenChannel (which starts playback) inside its setFocusedRow updater,
and updateSelection called emitSelection inside setSelectedIndices.
Updater functions must be pure: StrictMode double-invokes them in dev —
arrow-key navigation could start playback twice — and concurrent React
may re-run them in production. Compute the next value first, then apply
state and side effects outside the updater.
fetch_xtream_account_info used .ok()? inside its two-endpoint fallback
loop, so a transport error, body-read failure, or invalid JSON from the
get_account_info endpoint returned None for the whole function without
ever trying plain player_api.php. Convert those early returns into
logged continues so the fallback endpoint actually runs.
overscan is a row count, not pixels: 250 mounted ~500 extra ChannelRows
beyond the ~25 visible ones, and every scan batch flush reconciled all
of them — largely defeating the virtualization. The mac header-reveal
band only needs a handful of rows above the viewport (it filters to
within toolbarHeight of the top edge), so 20 keeps it populated.
If cancellation landed right after a channel check completed,
compute_shared_url_result rewrote the finished verdict — possibly
Alive — to Dead, then emitted and checkpointed that false result.
Return AppError::Cancelled instead (the worker already exits cleanly
on it), so the channel stays unscanned and resume re-checks it,
consistent with the diagnostics cancellation paths.
The checker returned statuses as plain strings ("Alive", "DRM",
"Geoblocked (Confirmed)", …) that scan.rs re-parsed in two
hand-maintained match blocks. The quick-check block was missing
"Placeholder", silently degrading placeholder results to Dead — and
any future typo or new variant would fail the same way. CheckOutcome,
the internal attempt structs, and proxy::confirm_geoblock now carry
ChannelStatus directly; debug-log verdict strings come from its
Display impl, which already produced the identical labels.
The stream proxy validated only the initial upstream URL while its
reqwest clients silently followed up to 10 redirects, so a public
upstream that 302'd to 127.0.0.1, 169.254.169.254, or another private
target sailed straight past the private-network block. The proxy
clients now use redirect::Policy::none() and all three upstream fetch
paths (scheme handler, raw TCP stream, reconnect loop) hop redirects
manually via fetch_with_hop_validation, re-running is_safe_upstream_url
on every hop.

The cast proxy is deliberately unchanged: it has no private-network
guard because casting LAN sources (e.g. a local Dispatcharr server) to
a LAN receiver is a legitimate, common flow.
handle_proxy_request buffered entire upstream responses with bytes()
and checker::verify() read manifest bodies with text(), neither with a
size limit — the 30s timeouts bound time, not bytes, so a misclassified
live MPEG-TS behind an .m3u8-looking URL could push hundreds of MB into
memory per request. Move the cast proxy's capped streaming reader into
a shared engine::proxy_common module and use it in both places: 64 MiB
for buffered scheme-handler responses (manifests and VOD segments),
8 MiB for liveness-check manifests (over the cap now verdicts Dead with
an explicit reason instead of buffering).
Cancellation via token is handled, but if the enclosing future is
dropped instead — task aborted, runtime shutdown mid-scan, app exit —
the spawned ffmpeg/ffprobe would be orphaned and a process stuck on a
stalling stream could live indefinitely. The proxies' spawns already
set kill_on_drop; these three (tool runner, bitrate profiler, combined
diagnostics) now match.
spawn_upstream_pump retried a dead upstream every 200ms indefinitely —
5 requests per second for the whole cast session against a server
returning 404s, which is the exact pattern IPTV providers ban for.
Consecutive failures (error status, connect failure, or a connection
that broke without delivering a byte) now grow the delay exponentially
up to 5s, and a connection that actually produced data resets it, so
genuine mid-stream reconnects stay fast.
The double-checked lazy-start dance existed in three copies: the
canonical stream_proxy::ensure_streaming_proxy_port (the only one that
TCP-probes the stored port and restarts a dead listener) had no
callers, while ffmpeg.rs and player.rs each kept a private probe-less
copy that would happily return a stale port after the listener died.
Both now delegate to the canonical version, so every path gets the
liveness check.
recent.rs carried its own normalize_xtream_server that didn't strip a
trailing /get.php (and silently dropped query strings instead of
rejecting them), so recent-entry dedup keys could disagree with the
saved-playlist and source-identity keys built from playlist.rs —
producing duplicate recents or failed backfills for .../get.php URLs.
It now delegates to playlist::normalize_xtream_server. The copy-pasted
xtream_host_label in saved.rs and recent.rs is also collapsed into one
shared helper.
playlist.rs, scan.rs, and history.rs (twice) each hand-rolled the same
strip-fragment/strip-default-port logic feeding dedup keys, history
identity keys, and shared-URL scan caching. A future edit to one copy
would silently desynchronize history diffs from scan dedup. One module
now owns the three shapes (parsed-URL identity, raw stream URL with
fallback, http/https-only identity).
The write-tmp/rename/fallback-remove-and-rename/cleanup dance was
implemented three times (remote playlist cache persist, cached-bytes
writer, history v2 index). One helper in engine::disk now owns it,
including the Windows rename-over-existing fallback. The playlist
persist path loses only the sub-step "Finalizing cache entry"
progress detail, which flashed for the duration of a rename.
Parsing a playlist (up to the 200 MB download cap) is seconds of
CPU-bound work, and it ran inline in async commands — pinning a tokio
worker that is simultaneously driving proxies and scan events. The
three copy-pasted parse-progress blocks in playlist.rs collapse into
one parse_playlist_off_thread helper that runs the parser under
spawn_blocking; scan.rs now also parses off-thread and does the same
for checkpoint loading, scan-history writes (which serialize the full
results Vec and re-read the previous run for diffing), and the cache
file write in the download path.
The cache held up to 8 full PlaylistPreviews by value and deep-copied
one on every hit and insert — for a 100k-channel playlist that's
hundreds of MB of redundant allocation across opens and scan starts.
Entries are now Arc<PlaylistPreview>: hits hand out a reference, and
the scan start clones only the channel list it actually mutates.
ChannelDebugLog embedded complete ffprobe/ffmpeg output and is held in
memory for the entire scan, cloned into scan history, and stored in the
window scan log — an unbounded multi-MB tool dump multiplied across
tens of thousands of channels. Retained diagnostics are now capped at
64 KiB per channel, keeping the tail (errors conclude a tool run) with
an explicit truncation marker.
Each accepted channel re-ran the full attribute char-walk up to seven
times: group extraction (twice), language detection, four tvg-* lookups
via extract_tvg_metadata, plus again in is_line_needed under a group
filter. The hot loop now parses the attribute list once per EXTINF line
and threads it (with the derived group name) through filtering and
channel construction; the public per-line helpers remain as thin
wrappers for external callers.
profile_bitrate inlined a verbatim copy of parse_bytes_read's
Statistics-line parsing, and the 1 MB tail-buffer stderr drain task was
copy-pasted between profile_bitrate and run_combined_diagnostics. Both
now share spawn_stderr_tail_reader and parse_bytes_read.
RemuxState.child was always stored as None (the remux worker task owns
and kills the ffmpeg child), so the start_kill in cleanup_blocking and
the graceful_kill in cleanup_remux_async were dead code that misleadingly
suggested cleanup could terminate ffmpeg. Cleanup now just removes the
temp directory, and a comment points at the real owner.
delay_for_payload allocated a fresh tail+payload Vec for every 64 KB
chunk on the playback hot path just to look for PCRs. The payload is
now scanned in place; only the chunk boundary (previous <=752-byte tail
plus the first packets of the new payload) is stitched, to catch a PCR
packet straddling two chunks.
Both raw-TCP proxies parsed the HTTP request from a single 8 KB read.
If the request line and headers arrived split across TCP segments
(seen with some Cast receivers sending Range headers), parsing silently
failed with a 400 or dropped the Range header. A shared helper now
reads until \r\n\r\n, still bounded at 8 KB.
The dependency floated on the default branch of an external repo; a
cargo update could silently pull breaking or unreviewed changes into
debug builds. Pin the rev Cargo.lock already resolved.
check_channel_status_with_ffprobe_debug and _with_debug each declared
an identical AttemptOutcome struct and duplicated the base-pass /
if-Dead-run-extended-pass / merge logic. One module-level struct and a
generic run_attempts_with_extended_timeout driver now own that flow;
only the probe bodies differ. Also scopes the AsyncReadExt import to
the proxy test modules where it's actually used.
The two ~150-line menu closures were identical except for the modifier
key (Cmd vs Ctrl), the macOS-only app and Window menus, and whether
Settings/Quit live in the app menu or the File menu. One builder now
covers all platforms with an accelerator helper and two small cfg
blocks, so future menu items can't be added to one platform and
forgotten on the other.
The menu event handler listed ten hand-written arms each for
menu.file.recent.N and menu.file.saved.N. A small suffix parser now
handles any index, so raising the recent/saved menu capacity can't
silently break event forwarding.
Setup waited synchronously (with no timeout) for the proxy listener to
bind before letting window creation proceed — a pathological bind hang
meant the app never launched. The wait existed to prevent an early
Play click racing the port, but every play path now goes through
ensure_streaming_proxy_port, which shares the start lock and probes
liveness, so the warm-up can be a plain background task.
The stream proxy and cast proxy each carried a near-identical manifest
line-walker and URI="..." tag parser — two divergent copies of the
trickiest parsing in the proxy layer. proxy_common now owns a single
rewriter parameterized by a URL mapper closure (returning None leaves a
reference unrewritten, which expresses the cast proxy's origin filter),
plus the shared is_m3u8 detection.
App.tsx mixed playlist source management, update checking, native menu
plumbing, the start screen, and six ad-hoc banners into one 3,200-line
component. Extracted along its natural seams:

- usePlaylistSources: recent/saved refresh, loadAndCommitSource, the
  open handlers (path/URL/Xtream/Stalker/saved/recent), saved-playlist
  CRUD, and source-filter application
- useUpdateCheck: version compare, cooldown cache, checkForUpdates
- useMenuEventBridge: all menu:// listeners; one render-updated
  handlersRef replaces ~20 individual ref+effect mirror pairs (the
  Promise.all + cancelled-flag registration pattern is preserved)
- StartScreen: presentational empty-state / load-progress / lists
- AppBanners: the six banners behind a shared useAutoDismiss helper
- lib/errors and lib/recentPlaylists: formatting helpers the extracted
  modules share

App.tsx drops from 3,199 to 1,682 lines with no behavior change.
The hook returned a fresh object literal each render, so App's
chromecast useMemo keyed on it never hit and every App render pushed a
new chromecast prop into SelectedChannelSidebar, ThumbnailPanel, and
CastMenu — ThumbnailPanel explicitly documents depending on prop
stability. Memoize over the actual state fields.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb5d1bca7f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/engine/xtream.rs Outdated
Comment thread src-tauri/src/engine/remote_cache.rs
@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/src/commands/server_test.rs`:
- Around line 77-83: Extract the duplicated reqwest::Client construction used by
fetch_xtream_stream_ids, discover_working_channels, probe_server_channels, and
test_single_server_api into one shared helper accepting the request timeout.
Preserve the common limited-redirect, connect-timeout, invalid-certificate, and
user-agent settings, while retaining each caller’s existing timeout and error
mapping.
- Around line 100-104: Update the live-stream fetch error handling around
build_xtream_player_api_action_url so reqwest::Error messages cannot expose
Xtream username/password query parameters; sanitize or replace the URL before
storing the message in last_error. Apply the same redaction to the parse-failure
path that formats reqwest::Error, while preserving the existing error context
and retry flow.

In `@src-tauri/src/commands/settings.rs`:
- Line 168: Update the preset name maximum-length rejection in the settings
command to return AppError::Validation instead of AppError::Other, matching the
existing empty-name validation branch. Keep the length check and error behavior
otherwise unchanged.

In `@src-tauri/src/engine/remote_cache.rs`:
- Around line 151-156: Gate invalid TLS certificate acceptance behind the
explicit local/trustless IPTV source setting in the reqwest clients used by the
remote-cache downloader and both Xtream request paths. Update the client
builders in remote_cache.rs (151-156) and xtream.rs (381-387, 527-533) so normal
certificate verification remains enabled by default and
.danger_accept_invalid_certs(true) is applied only when that setting is enabled.

In `@src-tauri/src/engine/xtream.rs`:
- Around line 362-366: Update the M3U generation in the relevant Xtream live,
VOD, and series blocks to escape provider-supplied EXTINF values before writing
them. Reuse or extract the existing escape_extinf_attribute helper from
engine::stalker::build_stalker_preview, applying it to tvg_id, tvg_logo, group,
and name while preserving the current stream URL output.

In `@src/App.tsx`:
- Around line 1253-1261: Move the shortcutHandlersRef.current assignment out of
the render body and into an effect that runs after committed renders, using the
existing shortcutHandlers value as the effect dependency. Keep the keydown
handler reading shortcutHandlersRef.current at event time and preserve the
current handler object contents.

In `@src/hooks/usePlaylistSources.ts`:
- Around line 326-337: Replace the raw sourceLabel with the redacted safeLabel
in both debug logging branches within the cached preview handling, including the
channel-search and source-loaded messages, while preserving the existing message
structure and values.

In `@src/hooks/useUpdateCheck.ts`:
- Around line 96-144: Update the latest-release fetch in checkForUpdates to use
a bounded AbortSignal timeout and ensure cancellation or an obsolete request
cannot apply subsequent store or localStorage updates. Preserve the existing
success, force-message, and error handling behavior while treating aborts safely
through the existing catch flow.

In `@src/lib/errors.ts`:
- Around line 21-42: Extract the shared error normalization and
conditional-prefix behavior from formatPlaylistOpenError and
formatSourceReloadError into a helper parameterized by the message label and any
prefix-removal pattern. Update both functions to use the helper while preserving
their existing fallback messages, prefix text, and playlist-prefix normalization
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 169ef18c-f399-4624-bbdc-f05755a02ff5

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6aa30 and bb5d1bc.

⛔ Files ignored due to path filters (1)
  • src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (60)
  • src-tauri/Cargo.toml
  • src-tauri/examples/backend_bench.rs
  • src-tauri/src/commands/history.rs
  • src-tauri/src/commands/mod.rs
  • src-tauri/src/commands/player.rs
  • src-tauri/src/commands/playlist.rs
  • src-tauri/src/commands/recent.rs
  • src-tauri/src/commands/saved.rs
  • src-tauri/src/commands/scan.rs
  • src-tauri/src/commands/server_test.rs
  • src-tauri/src/commands/settings.rs
  • src-tauri/src/engine/cast_proxy.rs
  • src-tauri/src/engine/checker.rs
  • src-tauri/src/engine/disk.rs
  • src-tauri/src/engine/ffmpeg.rs
  • src-tauri/src/engine/mod.rs
  • src-tauri/src/engine/parser.rs
  • src-tauri/src/engine/proxy.rs
  • src-tauri/src/engine/proxy_common.rs
  • src-tauri/src/engine/remote_cache.rs
  • src-tauri/src/engine/stalker.rs
  • src-tauri/src/engine/stream_proxy.rs
  • src-tauri/src/engine/xtream.rs
  • src-tauri/src/error.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/models/channel.rs
  • src-tauri/src/models/saved_playlist.rs
  • src-tauri/src/models/scan.rs
  • src-tauri/src/models/scan_log.rs
  • src-tauri/src/state.rs
  • src-tauri/src/urlnorm.rs
  • src-tauri/tests/scan_pipeline_integration.rs
  • src/App.tsx
  • src/components/AppBanners.tsx
  • src/components/ChannelTable.tsx
  • src/components/StartScreen.tsx
  • src/components/Toolbar.tsx
  • src/hooks/useChromecast.ts
  • src/hooks/useMenuEventBridge.ts
  • src/hooks/usePlaylistSources.ts
  • src/hooks/useScan.helpers.ts
  • src/hooks/useScan.ts
  • src/hooks/useStreamPlayer.ts
  • src/hooks/useUpdateCheck.ts
  • src/lib/channelResults.ts
  • src/lib/errors.ts
  • src/lib/filters.ts
  • src/lib/playback.ts
  • src/lib/recentPlaylists.ts
  • src/lib/runtimeMonitor.ts
  • src/lib/types.ts
  • src/store/slices/scanSlice.ts
  • src/store/slices/uiSlice.ts
  • src/store/types.ts
  • tests/channelResults.test.ts
  • tests/duplicates.test.ts
  • tests/exportScope.test.ts
  • tests/filters.sort.test.ts
  • tests/format.test.ts
  • tests/useStreamPlayer.test.ts
💤 Files with no reviewable changes (7)
  • src/lib/types.ts
  • tests/filters.sort.test.ts
  • tests/exportScope.test.ts
  • tests/format.test.ts
  • tests/duplicates.test.ts
  • src/hooks/useScan.helpers.ts
  • src/store/slices/scanSlice.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
src-tauri/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Use Rust conventions: snake_case naming, 4-space indentation, thiserror for error types, and serde for serialization.

Files:

  • src-tauri/src/commands/mod.rs
  • src-tauri/src/urlnorm.rs
  • src-tauri/src/models/saved_playlist.rs
  • src-tauri/tests/scan_pipeline_integration.rs
  • src-tauri/src/engine/proxy.rs
  • src-tauri/src/error.rs
  • src-tauri/src/engine/disk.rs
  • src-tauri/src/models/scan_log.rs
  • src-tauri/src/engine/mod.rs
  • src-tauri/src/models/channel.rs
  • src-tauri/src/commands/player.rs
  • src-tauri/src/engine/proxy_common.rs
  • src-tauri/examples/backend_bench.rs
  • src-tauri/src/commands/history.rs
  • src-tauri/src/state.rs
  • src-tauri/src/commands/saved.rs
  • src-tauri/src/commands/server_test.rs
  • src-tauri/src/commands/settings.rs
  • src-tauri/src/engine/stalker.rs
  • src-tauri/src/engine/parser.rs
  • src-tauri/src/engine/remote_cache.rs
  • src-tauri/src/commands/recent.rs
  • src-tauri/src/engine/xtream.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/models/scan.rs
  • src-tauri/src/engine/stream_proxy.rs
  • src-tauri/src/engine/ffmpeg.rs
  • src-tauri/src/commands/playlist.rs
  • src-tauri/src/engine/cast_proxy.rs
  • src-tauri/src/commands/scan.rs
  • src-tauri/src/engine/checker.rs
src-tauri/src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

src-tauri/src/**/*.rs: Keep backend responsibilities organized into models, engine, commands, state.rs, error.rs, and lib.rs according to their documented roles.
Use event-driven scanning: emit scan://channel-result events per channel, with frontend event updates batched using requestAnimationFrame.

Files:

  • src-tauri/src/commands/mod.rs
  • src-tauri/src/urlnorm.rs
  • src-tauri/src/models/saved_playlist.rs
  • src-tauri/src/engine/proxy.rs
  • src-tauri/src/error.rs
  • src-tauri/src/engine/disk.rs
  • src-tauri/src/models/scan_log.rs
  • src-tauri/src/engine/mod.rs
  • src-tauri/src/models/channel.rs
  • src-tauri/src/commands/player.rs
  • src-tauri/src/engine/proxy_common.rs
  • src-tauri/src/commands/history.rs
  • src-tauri/src/state.rs
  • src-tauri/src/commands/saved.rs
  • src-tauri/src/commands/server_test.rs
  • src-tauri/src/commands/settings.rs
  • src-tauri/src/engine/stalker.rs
  • src-tauri/src/engine/parser.rs
  • src-tauri/src/engine/remote_cache.rs
  • src-tauri/src/commands/recent.rs
  • src-tauri/src/engine/xtream.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/models/scan.rs
  • src-tauri/src/engine/stream_proxy.rs
  • src-tauri/src/engine/ffmpeg.rs
  • src-tauri/src/commands/playlist.rs
  • src-tauri/src/engine/cast_proxy.rs
  • src-tauri/src/commands/scan.rs
  • src-tauri/src/engine/checker.rs
src-tauri/src/commands/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Implement Tauri IPC commands in the commands module, organized by playlist, scan, export, and settings responsibilities.

Files:

  • src-tauri/src/commands/mod.rs
  • src-tauri/src/commands/player.rs
  • src-tauri/src/commands/history.rs
  • src-tauri/src/commands/saved.rs
  • src-tauri/src/commands/server_test.rs
  • src-tauri/src/commands/settings.rs
  • src-tauri/src/commands/recent.rs
  • src-tauri/src/commands/playlist.rs
  • src-tauri/src/commands/scan.rs
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Define agent interfaces and implementations in TypeScript for agent systems
Use clear naming conventions for agent classes and methods
Document agent behavior and responsibilities in comments or docstrings
Implement error handling in agent methods

Files:

  • src/lib/errors.ts
  • src/hooks/useMenuEventBridge.ts
  • src/lib/recentPlaylists.ts
  • tests/useStreamPlayer.test.ts
  • src/hooks/useUpdateCheck.ts
  • src/lib/filters.ts
  • src/hooks/useChromecast.ts
  • src/lib/channelResults.ts
  • src/lib/runtimeMonitor.ts
  • src/components/StartScreen.tsx
  • src/components/Toolbar.tsx
  • src/components/ChannelTable.tsx
  • src/hooks/usePlaylistSources.ts
  • tests/channelResults.test.ts
  • src/store/slices/uiSlice.ts
  • src/hooks/useScan.ts
  • src/store/types.ts
  • src/lib/playback.ts
  • src/components/AppBanners.tsx
  • src/hooks/useStreamPlayer.ts
  • src/App.tsx
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use strict TypeScript with no unused locals or parameters.

Files:

  • src/lib/errors.ts
  • src/hooks/useMenuEventBridge.ts
  • src/lib/recentPlaylists.ts
  • src/hooks/useUpdateCheck.ts
  • src/lib/filters.ts
  • src/hooks/useChromecast.ts
  • src/lib/channelResults.ts
  • src/lib/runtimeMonitor.ts
  • src/components/StartScreen.tsx
  • src/components/Toolbar.tsx
  • src/components/ChannelTable.tsx
  • src/hooks/usePlaylistSources.ts
  • src/store/slices/uiSlice.ts
  • src/hooks/useScan.ts
  • src/store/types.ts
  • src/lib/playback.ts
  • src/components/AppBanners.tsx
  • src/hooks/useStreamPlayer.ts
  • src/App.tsx
src-tauri/src/engine/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Place core IPTV processing logic, including parsing, checking, ffmpeg, proxy, and resume functionality, in the engine module.

Files:

  • src-tauri/src/engine/proxy.rs
  • src-tauri/src/engine/disk.rs
  • src-tauri/src/engine/mod.rs
  • src-tauri/src/engine/proxy_common.rs
  • src-tauri/src/engine/stalker.rs
  • src-tauri/src/engine/parser.rs
  • src-tauri/src/engine/remote_cache.rs
  • src-tauri/src/engine/xtream.rs
  • src-tauri/src/engine/stream_proxy.rs
  • src-tauri/src/engine/ffmpeg.rs
  • src-tauri/src/engine/cast_proxy.rs
  • src-tauri/src/engine/checker.rs
src/components/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Implement components as functional React components using hooks, and use Tailwind CSS for styling instead of CSS-in-JS.

Files:

  • src/components/StartScreen.tsx
  • src/components/Toolbar.tsx
  • src/components/ChannelTable.tsx
  • src/components/AppBanners.tsx
src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use a virtualized table via @tanstack/react-virtual for playlists containing 1000 or more channels.

Files:

  • src/components/StartScreen.tsx
  • src/components/Toolbar.tsx
  • src/components/ChannelTable.tsx
  • src/components/AppBanners.tsx
🪛 ast-grep (0.44.1)
src-tauri/src/commands/server_test.rs

[warning] 76-80: Dangerously accepting invalid TLS
Context: reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.connect_timeout(PLAYLIST_DOWNLOAD_CONNECT_TIMEOUT)
.timeout(XTREAM_JSON_API_TIMEOUT)
.danger_accept_invalid_certs(true)
Note: [CWE-295]: Improper Certificate

(reqwest-accept-invalid-rust)


[warning] 179-183: Dangerously accepting invalid TLS
Context: reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.connect_timeout(PLAYLIST_DOWNLOAD_CONNECT_TIMEOUT)
.timeout(SERVER_TEST_DISCOVERY_HTTP_TIMEOUT)
.danger_accept_invalid_certs(true)
Note: [CWE-295]: Improper Certificate

(reqwest-accept-invalid-rust)


[warning] 262-266: Dangerously accepting invalid TLS
Context: reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.connect_timeout(PLAYLIST_DOWNLOAD_CONNECT_TIMEOUT)
.timeout(SERVER_TEST_STREAM_TIMEOUT)
.danger_accept_invalid_certs(true)
Note: [CWE-295]: Improper Certificate

(reqwest-accept-invalid-rust)


[warning] 371-375: Dangerously accepting invalid TLS
Context: reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.connect_timeout(PLAYLIST_DOWNLOAD_CONNECT_TIMEOUT)
.timeout(XTREAM_PLAYER_API_TIMEOUT)
.danger_accept_invalid_certs(true)
Note: [CWE-295]: Improper Certificate

(reqwest-accept-invalid-rust)

src-tauri/src/engine/remote_cache.rs

[warning] 150-154: Dangerously accepting invalid TLS
Context: reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.connect_timeout(connect_timeout)
.timeout(timeout)
.danger_accept_invalid_certs(true)
Note: [CWE-295]: Improper Certificate

(reqwest-accept-invalid-rust)

src-tauri/src/engine/xtream.rs

[warning] 380-384: Dangerously accepting invalid TLS
Context: reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.connect_timeout(PLAYLIST_DOWNLOAD_CONNECT_TIMEOUT)
.timeout(XTREAM_JSON_API_TIMEOUT)
.danger_accept_invalid_certs(true)
Note: [CWE-295]: Improper Certificate

(reqwest-accept-invalid-rust)


[warning] 526-530: Dangerously accepting invalid TLS
Context: reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.connect_timeout(PLAYLIST_DOWNLOAD_CONNECT_TIMEOUT)
.timeout(XTREAM_PLAYER_API_TIMEOUT)
.danger_accept_invalid_certs(true)
Note: [CWE-295]: Improper Certificate

(reqwest-accept-invalid-rust)

🪛 React Doctor (0.7.6)
src/App.tsx

[error] 1261-1261: This ref is mutated during render. React can replay or discard render work, so the mutation can leak from UI that never commits.

Move ref writes into an event handler or effect. Render must stay pure because React can replay or discard it. The predictable null-guarded lazy initialization pattern remains supported.

(no-ref-current-in-render)


[warning] 1261-1261: This component misses React Compiler's automatic memoization & re-renders more than it should: Cannot access refs during render. Rewrite the flagged code so the compiler can optimize it.

React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the current property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).

(refs)

🔇 Additional comments (60)
src/lib/filters.ts (1)

331-390: LGTM!

src-tauri/src/commands/settings.rs (1)

679-724: LGTM!

src/components/AppBanners.tsx (1)

1-171: LGTM!

src/components/ChannelTable.tsx (1)

5-6: LGTM!

Also applies to: 317-324, 358-362, 383-397, 682-720

src/components/Toolbar.tsx (1)

47-48: LGTM!

Also applies to: 173-180, 200-206

src/hooks/useChromecast.ts (1)

1-1: LGTM!

Also applies to: 92-92, 110-116, 129-134

src/hooks/useMenuEventBridge.ts (1)

1-136: LGTM!

src/hooks/useScan.ts (1)

287-298: LGTM!

Also applies to: 294-433, 434-482

src/hooks/useUpdateCheck.ts (1)

1-186: LGTM!

src/lib/recentPlaylists.ts (2)

36-59: LGTM!


6-15: 🔒 Security & Privacy

Manual verification needed for recent Xtream credential storage.

The available evidence is inconclusive: direct repository access failed, and public codebase indexes do not align precisely with the referenced src/lib/recentPlaylists.ts serializer.

src/lib/errors.ts (1)

21-42: 🔒 Security & Privacy

Verify backend error text redaction for Xtream credentials.

The repository is unavailable for inspection, so it cannot be determined from the current evidence whether formatPlaylistOpenError() or formatSourceReloadError() can surface raw backend error strings containing Xtream source URLs with username and/or password query parameters. Check the upstream Rust error path/formatters and add query-credential redaction before rendering.

src-tauri/Cargo.toml (1)

39-41: LGTM!

src-tauri/src/engine/parser.rs (1)

160-219: LGTM!

Also applies to: 317-335, 349-370, 417-418, 439-446, 465-484

src-tauri/src/commands/history.rs (1)

92-109: LGTM!

Also applies to: 165-170, 275-286

src/App.tsx (1)

195-239: LGTM!

Also applies to: 662-696

src/hooks/usePlaylistSources.ts (1)

235-266: LGTM!

src/components/StartScreen.tsx (1)

33-248: LGTM!

src-tauri/src/commands/player.rs (1)

181-183: LGTM!

src-tauri/src/commands/playlist.rs (2)

526-570: LGTM!


707-729: LGTM!

src-tauri/src/commands/recent.rs (1)

35-41: LGTM!

Also applies to: 99-99

src-tauri/src/commands/saved.rs (1)

80-80: LGTM!

src-tauri/src/engine/mod.rs (1)

9-15: LGTM!

src-tauri/src/engine/stalker.rs (1)

409-521: LGTM!

src-tauri/src/urlnorm.rs (1)

12-45: LGTM!

src-tauri/src/lib.rs (3)

578-654: LGTM!


771-794: LGTM!


880-894: LGTM!

src-tauri/examples/backend_bench.rs (1)

179-190: LGTM!

src-tauri/src/commands/scan.rs (3)

139-193: LGTM!

Also applies to: 277-312


1354-1481: LGTM!

Also applies to: 1564-1798, 1800-2091


2135-2303: LGTM!

Also applies to: 2391-2426, 2454-2595

src-tauri/src/engine/checker.rs (3)

699-728: LGTM!

Also applies to: 841-892


894-1140: LGTM!

Also applies to: 1145-1417, 1874-2091


1420-1458: 🗄️ Data Integrity & Integration

Confirm in-crate checks_channel_status consumers are updated

Manual confirmation is still needed for the repository because the codebase scan failed. Check any remaining check_channel_status/ChannelCheckOutcome.status consumers for string-literal comparisons or format strings that still expect Alive/Dead/Drm/DRM/Geoblocked/Placeholder as a String.

src-tauri/src/engine/disk.rs (1)

11-39: LGTM!

src-tauri/src/models/scan_log.rs (1)

38-73: LGTM!

src-tauri/src/state.rs (1)

25-41: LGTM!

Also applies to: 139-174

src-tauri/src/error.rs (1)

23-31: LGTM!

src-tauri/src/models/scan.rs (1)

74-124: LGTM!

src-tauri/tests/scan_pipeline_integration.rs (1)

181-181: LGTM!

Also applies to: 216-216

src-tauri/src/models/channel.rs (1)

83-83: LGTM!

src-tauri/src/commands/mod.rs (1)

9-9: LGTM!

src-tauri/src/commands/server_test.rs (1)

77-83: 🔒 Security & Privacy | 💤 Low value

Confirm danger_accept_invalid_certs(true) is an intentional, accepted trade-off.

Static analysis flags this on all four client builders (Lines 77, 180, 263, 372). Disabling certificate validation while sending credential-bearing Xtream URLs makes the connection MITM-able, which could expose those credentials. If this mirrors an established codebase-wide policy for IPTV endpoints, it's acceptable; please confirm rather than leave it implicit.

Source: Linters/SAST tools

src-tauri/src/models/saved_playlist.rs (1)

11-17: LGTM!

src/lib/channelResults.ts (1)

44-48: LGTM!

Also applies to: 56-60

tests/channelResults.test.ts (1)

95-98: LGTM!

Also applies to: 108-116

src/store/slices/uiSlice.ts (1)

44-83: LGTM!

src/store/types.ts (1)

168-173: 🎯 Functional Correctness

No remaining toggleSidebar updates needed.

toggleSidebar has no remaining TypeScript/TSX references to migrate.

src-tauri/src/engine/cast_proxy.rs (1)

36-43: LGTM!

Also applies to: 95-108, 629-631, 652-730, 758-770, 882-889, 1482-1484, 1511-1529, 1543-1543

src-tauri/src/engine/proxy_common.rs (1)

1-24: LGTM!

Also applies to: 26-122, 124-163, 165-193

src-tauri/src/engine/stream_proxy.rs (1)

3-3: LGTM!

Also applies to: 27-32, 134-146, 280-328, 344-435, 490-507, 573-601, 1049-1082, 1179-1356, 1390-1393

src-tauri/src/engine/ffmpeg.rs (1)

2-2: LGTM!

Also applies to: 407-407, 697-801, 1473-1608, 1842-1850

src-tauri/src/engine/proxy.rs (1)

196-217: LGTM!

src/hooks/useStreamPlayer.ts (1)

2-29: LGTM!

Also applies to: 430-448

src/lib/playback.ts (2)

1-131: LGTM!

Also applies to: 144-369


133-142: 🎯 Functional Correctness

Manual verification needed: ensure the backend accepts remux=1.

The available repository access failed, so the parser can’t be checked against toStreamingProxyUrl’s emitted &remux=1 vs the backend’s accepted value.

src/lib/runtimeMonitor.ts (1)

1-102: LGTM!

Also applies to: 104-232, 234-370, 372-444

tests/useStreamPlayer.test.ts (1)

22-22: LGTM!

Comment thread src-tauri/src/commands/server_test.rs Outdated
Comment thread src-tauri/src/commands/server_test.rs Outdated
Comment thread src-tauri/src/commands/settings.rs Outdated
Comment thread src-tauri/src/engine/remote_cache.rs
Comment thread src-tauri/src/engine/xtream.rs
Comment thread src/App.tsx Outdated
Comment thread src/hooks/usePlaylistSources.ts
Comment thread src/hooks/useUpdateCheck.ts
Comment thread src/lib/errors.ts Outdated
@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@kristofferR

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4669294fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/commands/playlist.rs
Comment thread src/components/ChannelTable.tsx Outdated
Comment thread src-tauri/src/engine/stalker.rs
Comment thread src-tauri/src/engine/stalker.rs Outdated
Comment thread src-tauri/src/commands/server_test.rs Outdated
Comment thread src-tauri/src/engine/stalker.rs Outdated
@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src-tauri/src/commands/server_test.rs (1)

495-510: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the concurrent server-test work.

test_xtream_servers_inner schedules each API test in Phase 1 with join_all, and Phase 3 schedules each server’s probe work into another join_all; each probe path can emit HTTP requests, ffprobe, and screenshot jobs. Use a shared concurrency limit such as buffer_unordered or a tokio::sync::Semaphore for the Phase 1 and Phase 3 futures so large pasted lists do not open unbounded work.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/commands/server_test.rs` around lines 495 - 510, Bound
concurrency in test_xtream_servers_inner for both Phase 1 API tests and Phase 3
probe futures instead of awaiting unbounded join_all collections. Use a shared
tokio::sync::Semaphore or bounded buffer_unordered limit across both phases,
preserving each result’s association with its raw server and existing probe
behavior.
src/hooks/usePlaylistSources.ts (1)

322-366: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Prevent an older source load from overwriting a newer one.

Two opens can overlap at await loadFullSourcePreview(). Whichever resolves last commits its preview, descriptor, and scan initialization—even if the user selected a newer source. Add a request generation or cancellation guard so only the latest load can commit state and clear loading progress.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/usePlaylistSources.ts` around lines 322 - 366, Protect the async
load flow around loadFullSourcePreview and commitLoadedPlaylist with a
request-generation or cancellation guard. Ensure each new source load
invalidates prior requests, and verify the request is still current before
committing the preview, descriptor, scan initialization, or clearing loading
progress; stale loads must exit without updating state.
src/App.tsx (1)

1252-1264: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Don’t depend on the freshly created shortcut-handler object.

shortcutHandlers is a new object on every render, so this layout effect re-runs each time and re-assigns the ref unnecessarily. Depend on the individual callbacks, or keep the ref assignment inside an effect with no object dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/App.tsx` around lines 1252 - 1264, Update the shortcutHandlersRef
synchronization in the App component so it does not depend on the freshly
created shortcutHandlers object. Use the individual callback dependencies in the
useLayoutEffect dependency array, while preserving the existing ref assignment
and handler behavior.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/src/commands/playlist.rs`:
- Around line 420-430: Update AppSettings::default() so accept_invalid_certs is
false for fresh installations and missing settings.json files. Preserve
accepts_invalid_certs as the runtime reader of the configured value, without
adding an implicit enablement in the scan profile flow.

In `@src-tauri/src/commands/server_test.rs`:
- Around line 36-47: The build_server_test_client flow must stop using automatic
redirects, which cannot validate intermediate destinations. Configure the client
with reqwest::redirect::Policy::none(), then update the server-testing request
flow to follow redirects manually and validate every hop using the existing
stream-proxy approach from fetch_with_hop_validation, rejecting loopback,
private, and metadata-network destinations before each request.

In `@src-tauri/src/engine/xtream.rs`:
- Around line 271-279: Extract the duplicated ReadCappedError-to-message match
into a shared helper near the existing Xtream fetch functions, preserving the
TooLarge wording and Read(error).without_url() formatting. Update both
fetch_xtream_json_array and fetch_xtream_account_info to call the helper before
logging, removing their inline matches.

In `@src/lib/errors.ts`:
- Line 58: Update the prefix check and return expression in the
error-normalization logic to use normalized rather than raw. Test whether
normalized already starts with the existing prefix, return normalized when it
does, and otherwise prepend the prefix once to prevent duplicate messages.

---

Outside diff comments:
In `@src-tauri/src/commands/server_test.rs`:
- Around line 495-510: Bound concurrency in test_xtream_servers_inner for both
Phase 1 API tests and Phase 3 probe futures instead of awaiting unbounded
join_all collections. Use a shared tokio::sync::Semaphore or bounded
buffer_unordered limit across both phases, preserving each result’s association
with its raw server and existing probe behavior.

In `@src/App.tsx`:
- Around line 1252-1264: Update the shortcutHandlersRef synchronization in the
App component so it does not depend on the freshly created shortcutHandlers
object. Use the individual callback dependencies in the useLayoutEffect
dependency array, while preserving the existing ref assignment and handler
behavior.

In `@src/hooks/usePlaylistSources.ts`:
- Around line 322-366: Protect the async load flow around loadFullSourcePreview
and commitLoadedPlaylist with a request-generation or cancellation guard. Ensure
each new source load invalidates prior requests, and verify the request is still
current before committing the preview, descriptor, scan initialization, or
clearing loading progress; stale loads must exit without updating state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 98fea0bc-e8f9-415a-a1b4-51e8fdad7f36

📥 Commits

Reviewing files that changed from the base of the PR and between bb5d1bc and b466929.

📒 Files selected for processing (12)
  • src-tauri/src/commands/playlist.rs
  • src-tauri/src/commands/server_test.rs
  • src-tauri/src/commands/settings.rs
  • src-tauri/src/engine/parser.rs
  • src-tauri/src/engine/remote_cache.rs
  • src-tauri/src/engine/stalker.rs
  • src-tauri/src/engine/xtream.rs
  • src/App.tsx
  • src/hooks/usePlaylistSources.ts
  • src/hooks/useUpdateCheck.ts
  • src/lib/errors.ts
  • tests/errors.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Define agent interfaces and implementations in TypeScript for agent systems
Use clear naming conventions for agent classes and methods
Document agent behavior and responsibilities in comments or docstrings
Implement error handling in agent methods

Files:

  • tests/errors.test.ts
  • src/lib/errors.ts
  • src/hooks/usePlaylistSources.ts
  • src/hooks/useUpdateCheck.ts
  • src/App.tsx
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use strict TypeScript with no unused locals or parameters.

Files:

  • src/lib/errors.ts
  • src/hooks/usePlaylistSources.ts
  • src/hooks/useUpdateCheck.ts
  • src/App.tsx
src-tauri/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Use Rust conventions: snake_case naming, 4-space indentation, thiserror for error types, and serde for serialization.

Files:

  • src-tauri/src/commands/settings.rs
  • src-tauri/src/engine/parser.rs
  • src-tauri/src/commands/server_test.rs
  • src-tauri/src/engine/remote_cache.rs
  • src-tauri/src/commands/playlist.rs
  • src-tauri/src/engine/xtream.rs
  • src-tauri/src/engine/stalker.rs
src-tauri/src/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

src-tauri/src/**/*.rs: Keep backend responsibilities organized into models, engine, commands, state.rs, error.rs, and lib.rs according to their documented roles.
Use event-driven scanning: emit scan://channel-result events per channel, with frontend event updates batched using requestAnimationFrame.

Files:

  • src-tauri/src/commands/settings.rs
  • src-tauri/src/engine/parser.rs
  • src-tauri/src/commands/server_test.rs
  • src-tauri/src/engine/remote_cache.rs
  • src-tauri/src/commands/playlist.rs
  • src-tauri/src/engine/xtream.rs
  • src-tauri/src/engine/stalker.rs
src-tauri/src/commands/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Implement Tauri IPC commands in the commands module, organized by playlist, scan, export, and settings responsibilities.

Files:

  • src-tauri/src/commands/settings.rs
  • src-tauri/src/commands/server_test.rs
  • src-tauri/src/commands/playlist.rs
src-tauri/src/engine/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Place core IPTV processing logic, including parsing, checking, ffmpeg, proxy, and resume functionality, in the engine module.

Files:

  • src-tauri/src/engine/parser.rs
  • src-tauri/src/engine/remote_cache.rs
  • src-tauri/src/engine/xtream.rs
  • src-tauri/src/engine/stalker.rs
🪛 React Doctor (0.7.6)
src/App.tsx

[error] 1264-1264: Your useLayoutEffect runs every render because dep shortcutHandlers is a new object built fresh each time, so === always fails.

Move the value inside the hook body and depend on its simple inputs instead, or wrap it in useMemo / useCallback so it stays the same between renders.

(no-effect-with-fresh-deps)

🔇 Additional comments (16)
src-tauri/src/commands/settings.rs (1)

168-173: LGTM!

Also applies to: 678-684, 687-693, 694-702, 705-717, 718-726, 1138-1149

src/hooks/useUpdateCheck.ts (1)

1-8: LGTM!

Also applies to: 89-175, 210-212

src/lib/errors.ts (1)

21-36: LGTM!

Also applies to: 61-78

tests/errors.test.ts (1)

1-61: LGTM!

src/hooks/usePlaylistSources.ts (1)

293-315: LGTM!

src-tauri/src/engine/remote_cache.rs (3)

155-172: Resolves prior TLS-bypass finding.

accept_invalid_certs is now threaded as an explicit parameter into download_playlist_to_file's ClientBuilder (line 172) instead of the previous unconditional danger_accept_invalid_certs(true). This is the exact location flagged in the earlier review comment and it is now fixed.

Also applies to: 296-394


133-153: LGTM!
Using error.without_url() to build the error message avoids leaking credential-bearing query strings in logs, which resolves the credential-logging concern noted in the PR objectives.


423-593: LGTM!
Good coverage: deterministic cache naming, stale-temp cleanup selectivity, oversized-response rejection, and timeout handling with temp-file cleanup on failure are all exercised, now with the accept_invalid_certs parameter explicitly set to false.

src-tauri/src/engine/xtream.rs (2)

370-381: Resolves prior EXTINF-injection finding.

Escaping tvg-id/tvg-logo/group-title via parser::escape_extinf_value and flattening the display name via flatten_extinf_title closes the previously flagged gap where provider-supplied values could break #EXTINF attribute parsing or inject spurious lines. The new test exercises quotes, backslashes, and embedded newlines end-to-end.

Also applies to: 469-474, 585-611


387-397: Resolves prior TLS-bypass finding.

accept_invalid_certs is now an explicit parameter threaded into both fetch_xtream_playlist_via_json_api's and fetch_xtream_account_info's ClientBuilders, replacing the previous hard-coded danger_accept_invalid_certs(true) flagged at these exact locations in the earlier review.

Also applies to: 492-507

src-tauri/src/commands/playlist.rs (4)

26-30: LGTM!


420-430: Good fix: TLS bypass now gated behind an explicit setting.

This correctly threads accept_invalid_certs from app settings, defaulting to false (fail-closed) when no AppHandle is available. This resolves the previously flagged issue where danger_accept_invalid_certs(true) was applied unconditionally in remote_cache.rs and xtream.rs.


623-649: LGTM!
The accept_invalid_certs value is computed once per Xtream open and reused consistently across the parallel account-info fetch, the /get.php download, and the JSON-API fallback, and the JSON-fallback cache write correctly moves to spawn_blocking.

Also applies to: 676-758, 760-768, 938-945


538-591: 🩺 Stability & Availability

No change needed.

parse_playlist_off_thread already maps JoinError from spawn_blocking to AppError::Other, and the progress callback only captures a cloned AppHandle for the duration of the blocking parse.

src-tauri/src/engine/stalker.rs (1)

459-464: LGTM!
Switching to the shared parser::escape_extinf_value / flatten_extinf_title helpers removes duplicated escaping logic and keeps Stalker-generated EXTINF lines consistent with the Xtream and file-based parsing paths. The new test verifies quote/backslash/newline handling correctly.

Also applies to: 604-628

src-tauri/src/engine/parser.rs (1)

13-27: LGTM!
escape_extinf_value correctly escapes backslashes before quotes (avoiding double-escaping) and flattens line breaks to prevent EXTINF-line injection; flatten_extinf_title intentionally leaves quotes/backslashes untouched since the display name is unquoted text after the metadata comma. The added test verifies the exact escaping order.

Also applies to: 769-776

Comment thread src-tauri/src/commands/playlist.rs
Comment thread src-tauri/src/commands/server_test.rs
Comment thread src-tauri/src/engine/xtream.rs Outdated
Comment thread src/lib/errors.ts Outdated
@kristofferR

Copy link
Copy Markdown
Owner Author

@codex review

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: e0b51b1f84

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kristofferR
kristofferR merged commit 5b8e5e9 into main Jul 23, 2026
9 checks passed
@kristofferR
kristofferR deleted the quality-stability-fixes branch July 23, 2026 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant