Route explicit no-limit speed overrides without inheriting the global cap.
Preserve dotted media titles when switching formats and drain retry gid completions through remember_gid.
Update yt-dlp to 2026.07.04 and refresh the bundled macOS runtime plus engine locks.
Route every non-media download through aria2 by removing the native HTTP fallback path, native GID handling, and the old direct-download harness.
Retry transient aria2 startup/RPC failures before failing, then show the real last error in the download row and Properties modal so Windows failures are diagnosable instead of silent.
Refresh docs and tests around the aria2-only file-download contract, and advance the Firefox extension submodule to its published wording cleanup.
When a server (like fuckingfast.co) ignores the upper bound of a range request (returning bytes=start-end/total), aria2 aborts with 'Invalid range header'. This patch treats that error as transient and mutates the download payload to force connections=1 before re-enqueuing. This ensures aria2 issues an open-ended range request (bytes=start-) on the retry, seamlessly resuming the download.
aria2 prefixes HTTP statuses with 'status=XXX' in its onDownloadError payload instead of 'HTTP XXX'. These were falling through the transient check and causing instant, permanent failure instead of triggering the escalating backoff schedule.
Fallback to GET Range for metadata when HEAD fails or misses Content-Length. Fetch aria2 WS error payload via tellStatus. Escalate 429 retries with specific 60/120/300s backoff. Persist download fraction for paused tasks.
Transient network drops and Wi-Fi timeouts no longer promote a download
straight to a hard Failed state. A shared retry engine classifies the
error, emits a transient `Retrying` state, and runs a 3-strike
exponential backoff (2s / 5s / 10s) while preserving the active download
allocation (semaphore permit / worker slot). Resumability primitives are
reused on every retry so no downloaded bytes are discarded.
Engine core (src-tauri/src/retry.rs, new):
- BACKOFF_SCHEDULE = [2s, 5s, 10s], MAX_RETRIES = 3
- backoff_for(strike): schedule index with graceful clamp beyond range
- is_transient_network_error(msg): string classifier covering reqwest,
yt-dlp, and aria2c phrasing; permanent conditions (HTTP 401/403/404/
410/451, not-found, permission denied, out-of-disk) checked first so
they always fail fast
- backoff_and_emit / backoff_and_emit_cancel: cancel-safe sleep helpers
driving a Retrying state emit before each delay
- 9 unit tests covering schedule math, clamping, transient vs permanent
classification, and permanent-wins-over-transient precedence
State model (src-tauri/src/ipc.rs):
- DownloadStatus::Retrying variant (serialized "retrying")
- DownloadStateEvent::retrying(id, reason) constructor
- Regenerated TypeScript binding: src/bindings/DownloadStatus.ts
Native reqwest backend (src-tauri/src/download.rs):
- DownloadEvent::Retrying variant (headless mirror)
- CoordinatorEventSink::emit_retrying() drives the production
download-state channel with status "retrying"
- download_file retry core rewritten: transient -> emit_retrying ->
backoff_for sleep inside a control_rx select (pause/cancel honored
mid-backoff) -> re-issue Range header; permanent errors or strike
exhaustion advance to the next URL then hard Failed
yt-dlp media backend (src-tauri/src/lib.rs):
- child spawn+stream loop wrapped in a 3-strike re-spawn loop; stderr
tail classified and, if transient, the process is re-spawned after
backoff (--continue resumes); --retry-wait=2 added to aria2c downloader
aria2c backend (src-tauri/src/queue.rs):
- retry-wait=2 option so aria2's internal retries are not rapid-fire
- handle_aria2_download_error: intercepts transient onDownloadError,
backs off, re-issues addUri, and rotates the stale gid -> id mapping
via rotate_aria2_gid (fresh addUri mints a new gid; not rotating would
detach subsequent WS events and leak the semaphore permit permanently)
- aria2_payloads / aria2_retry_strikes tracking with cleanup on terminal
outcomes
Verification: cargo build clean; cargo test --lib 37 passed / 0 failed.