# Changelog Releases, newest first. Each section follows [`release-notes.md`](release-notes.md): what changed for someone installing this build, then the upgrade notes, then anything still broken. ## v0.0.6 — 2026-10-21 This release is about what the app does when something fails. Crashes are captured locally and offered to you on the next launch, nothing leaves the device until you press Send, and the report names the build you actually installed. ### Fixed - Downloads in a subfolder no longer hide the interrupted-downloads card. The card's evidence probe now looks in the same places the resume scan does, and accepts an aria2c `.aria2` control file as well as a `.part`. - An unexpected Python engine exit now produces a report. The restart that follows a closed stdout used to clear the process before the exit code was read, so the exits worth reporting were the ones skipped. - Log redaction covers what actually leaked: `Basic` as well as `Bearer` credentials, whole `Authorization` and `Cookie` lines, every cookie in a multi-cookie header, and signed-URL `expire` values. - A long or non-ASCII report no longer opens an empty GitHub form. The composer uses the length-guarded issue URL and, when the text does not fit, copies it to the clipboard and keeps the crash queued instead of dropping it. - The report body says `Authorization: ***REDACTED***` instead of a literal `$1: ***REDACTED***`. ### Changed - Crash reports are captured whether or not automatic reporting is on. The switch now only decides whether a queued report may be filed without asking, and it is still off by default. The fatal path does no network I/O. - One version everywhere. About, the report composer and every crash body read the value stamped from `pubspec.yaml`; they used to quote three different strings. - "Detailed engine logging" reaches yt-dlp as well as the app log. - Opening the GitHub draft from the recovery banner retires that queued crash. Before, the same crash came back after three days. - Documentation says `Settings → Report a problem`, which is the row that exists. ### Upgrade notes - Nothing to do. Settings, history and cookies carry over. The report queue imports the older preferences blob on first launch. - Uninstalling still removes the app's private data, including queued reports. Files you downloaded stay where you saved them. ### Known issues - A native Android process crash and a forced OS termination are not captured. Neither is visible to the Dart error handlers. - A report can still contain private text the scrubber did not anticipate. The review panel shows the exact body for that reason. - The interrupted-downloads probe walks up to four levels and 2,000 entries. A download nested deeper than that is found by the scan but does not bring the card up by itself. ### For contributors - The broad-catch ratchet now refuses a raised budget unless the entry carries a `why`, so the reason lands in the same file as the number. - `tool/check_version_literals.py` fails the build on a hardcoded app version in `lib/`. - `skills/` gained `stop-slop`, `humanizer` and `write-in-my-style`; `AGENTS.md` requires the three in order for any prose. ## v0.0.5 — 2026-10-11 (feature + polish release; 57 commits + orphan redesign merge since v0.0.4) Diff vs v0.0.4: 238 files changed, 21499 insertions(+), 4285 deletions(-). Orphan `redesign/ui` (32a5947..351c8aa) merged via tree checkout + nightly fixup. - **Download & Format Picker (P4-3 / T01 / T03):** description + thumbnail toggles with session persistence, portrait-safe format ladder, batch quality picker, file-naming hierarchy (predefined + custom name field) and custom-name download path; opts row bound to active preset — **fixes audio+opus preset showing Video+4K** (now correctly Audio+Opus). - **Clipboard & Link Saver (T02 / T19):** `Proceed` reads clipboard + parser when input empty, startup auto-check offers to import clipboard link, queued `View` batch fixed; dedicated Link Saver menu + FAB bottom-right on home with per-link Delete/Download and Download All, persistent offline queue with one-tap batch dispatch and auto-retry. - **Library & Settings (T14 / T15):** History three-dots menu (play / delete / remove missing), Settings search bar with category list + sub-menus, grid-view toggle honored everywhere, storage meter, file import / skip-existing. - **Notifications:** removed in-app white-stripe bottom notification, replaced with native device notifications (`showAppNotification` / `styledSnackBar` with `inverseSurface`), split success/error native path and `POST_NOTIFICATIONS` gate, 7 migration batches. - **Cookies (yt-dlp compat):** hint expected format, auto-detect parser for Netscape `# Netscape HTTP Cookie File` (7 tab fields) / JSON exports / `Cookie:` header / Base64 wrappers (yt-dlp only reads Netscape), domain syntax validation + relate-check (YouTube cluster), unrelated count, empty-value preservation fix (351c8aa), WebView import with generated header. - **Share Intent (T02 / T04):** format menu via Share now includes playback (lazy-load heavy preview until Play tap) + direct download, auto-start queued/failed, FIFO share queue. - **App logo theme (redesign/ui):** adaptive light/dark logo (`adaptive_logo.dart` + `logo_variant` provider, `BricolageGrotesque`/`Figtree` headings), launcher dynamic icons per theme (light/dark/monochrome/legacy, all densities) and README adaptive `` logo. - **Perf & a11y:** lazy preview, throttled progress, bounded queues, history a11y semantics + 48dp touch target. - **Quality:** `flutter analyze` zero warnings, 632 tests (was 631+1), DeepSource Kotlin 11→0, Python 2/2, docs/link-check/secret-scan green; `desktop_engine` null guard for `_process` race. > Generated with `tool/generate_release_changelog.py v0.0.4 0.0.5`. That script > cannot see the orphan `redesign/ui` history, so those commits were merged in > by hand from `32a5947..351c8aa`: Diff vs v0.0.4: 238 files changed, 21499 insertions(+), 4285 deletions(-). #### Every change (grouped, newest last, from 32a5947..351c8aa plus main) ##### Redesign / Picker / Clipboard / Queue (32a5947..351c8aa) - `351c8aa` fix(cookie): preserve empty-valued JSON cookies (trim regression) - `ef4e801` fix(kotlin): collapse duplicate catch, suppress KT-W1064 - `fce131e` fix(kotlin): address remaining DeepSource KT-W1057/W1009 - `51fd47e` fix(kotlin): suppress DeepSource KT-R1006/W1042/W1009 - `dc465ce` test(flutter): add coverage for cookie imports, notifications, download flows, settings search, and history actions - `1cefbbd` docs(dart): clarify cookie parsing, notification, and download flow behavior - `798e6ad` fix(home): use showAppNotification for offline queue - `67315fd` fix(tests): repair widget & navigation test suites - `9a93bad` fix(analyze): zero flutter warnings - `3364666` fix(ci): flutter analyze errors + DeepSource config - `f541e2a` Merge branch 'flaws/notify-white-stripe-7' into redesign/ui - `b733fc8` fix(notify): migrate command/cookie-webview/settings/playlist/library/shell/live-log white-stripe - `f78b57c` Merge branch 'flaws/notify-white-stripe-6' into redesign/ui - `031469e` fix(notify): migrate profile/log/storage/about white-stripe - `b4577fe` Merge branch 'flaws/notify-white-stripe-5' into redesign/ui - `f77b951` fix(notify): migrate download_history/cookies/settings/offline-queue white-stripe - `39eb1ce` Merge branch 'flaws/notify-white-stripe-4' into redesign/ui - `19f6a73` fix(notify): finish share_overlay migration (auto-start queued/failed) - `96c3acd` fix(notify): migrate share_overlay white-stripe SnackBars - `965e598` Merge branch 'flaws/notify-white-stripe-3' into redesign/ui - `8271368` fix(notify): migrate playlist/log white-stripe SnackBars to styled - `1469f03` Merge branch 'flaws/notify-white-stripe-2' into redesign/ui - `85c7fce` fix(notify): migrate format_picker SnackBars to styledSnackBar - `71a4fbe` Merge branch 'flaws/history-a11y' into redesign/ui - `829d414` fix(a11y): history three-dots menu semantics, Dismissible hint, 48dp - `f881461` Merge branch 'flaws/share-lazy-preview' into redesign/ui - `f86b6ad` perf(share-sheet): lazy-load heavy preview until Play tap - `f9164b3` Merge branch 'flaws/cookie-domain-isolation' into redesign/ui - `9b46871` fix(cookies): validate domain syntax and relate-check on paste - `a231823` Merge branch 'flaws/notify-permission-split' into redesign/ui - `3d6f867` fix(notify): split success/error native path and gate POST_NOTIFICATIONS - `d88c899` Merge branch 'flaws/notify-white-stripe' into redesign/ui - `de82ec8` fix(notify): remove white-stripe SnackBars in home/link-saver/offline-banner - `d8533d1` feat(library+settings+cookies+share+notify): three-dots history, settings search, cookie auto-detect, native notifications, share playback - `dac5940` feat(clipboard+queue): clipboard proceed, startup offer, link saver FAB - `6266ad9` feat(picker): add description/thumbnail opts, file-naming hierarchy - `502b495` fix(home): bind opts row to active preset, show Audio+OPUS for audio presets - `c6e7607` docs(readme): update logo to adaptive light/dark variant - `9761834` fix(launcher+ui): make app logo follow theme and settings - `85dac20` docs(readme): add CodeRabbit PR reviews badge - `84b8e0c` refactor(ui): replace hardcoded hexes with theme roles for dark-mode support - `bad0851` fix(ci): make Verification green on redesign/ui - `4cd06c6` feat(ui): editorial hero, tonal nav pill, emblem logo, overflow polish - `ff2926e` feat(ui): picker header value-first hierarchy with sentence-case sections - `d11a06a` feat(ui): ledger-first de-templating pass - `e6c78ae` feat(ui): nav pill cleanup and outlined icon set - `b5898ea` feat(ui): home headline copy sharpened - `6717303` fix(ui): library filter chips trailing room - `14608ef` feat(ui): home pill, opts affordance and empty-state refinements - `e5eccd1` fix(ui): unmask aria2c package status to engine truth - `29ea731` fix(ui): seg edge + picker loading never black (R4) - `407bdbd` feat(ui): shared seg tabs + picker chrome + menu cancel (R3) - `ffef52b` feat(ui): seg bar distinction + settings title weight (R2) - `e2cad59` feat(ui): log overlay typography + download card .live (R1) - `3e22bcd` fix(engine): warn when aria2c enabled but binary unusable - `c3383fe` feat(ui): download hero + pill + opts + sections (P4-3) - `32a5947` test(settings): add targeted structural widget test for AboutScreen (P4-5) ##### Main line since v0.0.4 (reachable from v0.0.4..HEAD) - `d230347` Merge redesign/ui + PR #13 (351c8aa) into main for v0.0.5 - `b054b18` fix(ci): base nightly gate on last built commit, not the frozen nightly tag - `a6c2435` fix(deepsource): drop invalid secrets/test-coverage analyzers, fix python runtime_version - `c822087` chore(deepsource): add detailed .deepsource.toml for Flutter+Python+Kotlin repo - `09b0f65` fix(T19, T01, T02): unbreak widget tests after offline and cache changes - `3f17819` fix(T14): update widget tests for settings folder hierarchy - `865778f` style(T18): token-only design sweep (mono + onPrimary) - `4c02b6f` feat(T08): pre-download in-app preview with progressive stream - `bf1556f` feat(T20): opt-in inexact offline queue reminders - `b54afe1` feat(T19): persistent offline link saver with one-tap batch dispatch - `92e7116` feat(T21): strictly local analytics dashboard - `1ae4095` feat(T03): picker-integrated toggles with session persistence - `0daa232` feat(T16): indicator driven by fractional PageController.page - `b53a06c` feat(T07): persistent home thumbnails cached at job creation - `7ff3d6f` feat(T02): share auto-download mode with error notification - `f88418a` test(T06): lock source-URL persistence in comment/purl tags - `30003ad` feat(T12): defer chunk rotation across active video sessions - `f77e833` feat(T04): standalone share format-picker overlay - `ec39183` feat(T11): 1 MB chunked log rotation, drop retention-days - `eb01514` feat(T10): default log export to Downloads via MediaStore - `b7c4856` fix(T13): logger view scrolls full viewport via collapsible card - `f15a197` feat(T01): fast-fetch pipeline with placeholder skeleton and extraction caching - `7202277` feat(T05): context-menu Copy Link for active and completed - `ce1cd9e` fix(T17): launcher-icon safe-zone, monochrome, uncropped logo - `eb3d044` refactor(T15): permissions and appearance sub-menus - `05dc457` fix(T09): resolve Android 14 background freeze and UI-isolate blocking - `f924045` refactor(T14): settings root as category list with sub-menus - `eae1657` feat(batch): retry-failed, file import, skip-existing, storage meter, auto-retry - `f544a56` fix(library): history shows thumbnails and plays media - `6cea501` fix(downloads): portrait-safe format ladder + batch quality picker - `8b681f6` fix(logs): save engine log to Downloads via tail-export + full flush ## v0.0.4 — 2026-09-17 (huge improvement release; 118 commits since v0.0.2) - **System player**: finished downloads open in the device video player (`FileProvider` + `intent/open_file` channel with MIME fallback); desktop opens via OS resolver. - **Resume that works**: `writeinfojson` default-ON so interrupted downloads leave a URL sidecar for one-tap resume (cleaned on success); partials stream straight to the output dir (no more cache-eviction crashes); FFmpeg merges serialized (no thermal freeze) with a 600s hung-merge guard. - **Concurrency guard**: slider warns above 2 simultaneous downloads (battery/thermal); default follows CPU cores. Desktop IPC threaded — slow extractions no longer block cancel; longer budgets for formats/playlist/search. - **Links that open**: external links use try/catch instead of package-visibility-blocked `resolveActivity`; deep-link filter for shared URLs; legacy `Download/TrueStream` auto-renamed to `Download/Grablytic`. - **Library grids + onboarding**: playlists and history honor the grid-view toggle; permissions beat unified with the dark onboarding design. - **Log viewer survives big logs**: capped tail render (100KB/1000 lines) with truncation banner — 4–5MB logs no longer hang the phone; level filter cancellation fixed. - **Security**: SSRF guard closed for hex/octal/decimal IP spellings and DNS-rebinding hostnames; phantom per-download PO-token spawn gated off; installer publishes `SHA256SUMS` (fail-closed, API-digest fallback); shell-injection closed. - **Android lifecycle**: terminal events survive activity death; wakelock renewal + timeout sweep; active downloads cancelled on FGS timeout; atomic package extraction with CRC stamps; Kotlin SQLite access serialized with busy-timeout/retry. - **Perf**: throttled progress at source, bounded queues, single-card rebuilds, bundled Instrument Sans (no runtime webfont fetch). ### Release stats From `tool/generate_release_changelog.py v0.0.2 v0.0.4`. Diff vs v0.0.2: 152 files changed, 14221 insertions(+), 1494 deletions(-). ### Lines of code by area (+added / -removed) - Python engine: +6836 / -436 - Android native: +1413 / -374 - Flutter UI: +5081 / -565 - Packaging & install: +378 / -55 - CI & workflows: +368 / -21 - Docs & store: +49 / -34 - Other: +96 / -9 ### Every change (grouped, newest last) #### Security & Trust Boundaries - `3a1877f68` sec(engine): confine clear_download_archive to data_dir [T0-1] - `def504b7d` sec(install): fail closed when no checksum digest is published [T0-4] - `400251213` sec(bootstrap): verify existing binaries against manifest before trusting [T0-5] - `0581fb4ea` sec(format): allowlist explicit format IDs before selector interpolation [T0-3] - `5f0980a2a` sec(paths): confine IPC binary paths, kill PATH/sys.path injection [T0-2] - `3d5a2d9a4` sec(potoken): honest JS trust boundary, no remote solver without runtime [T0-6] - `327ab328b` fix(logging): close sanitize gaps — userinfo creds, cycles, map proxies [T3-15] - `bc4c8f0ca` fix(install): close shell injection via eval and python interpolation - `f17914cf8` fix(engine-logs): sanitize request params and redact engine-origin entries - `6ec6e27b6` fix(engine): gate SSRF targets and validate proxy at every sink - `7a0babc76` fix(flutter): enforce trust boundary on engine paths, exports, and proxy - `9bf556258` fix(resume): enforce trust boundary on resume candidate parsing and deletion - `23a0462cb` fix(engine): sanitize yt-dlp option builder sinks and config schema - `2f230c988` fix(core): add library directives to command_template and trust_boundary - `b01cd426c` fix(engine): close SSRF bypasses in url_guard non-decimal IP forms and DNS names - `8667ea679` fix(ci): publish SHA256SUMS and prefer it in install.sh with API-digest fallback #### Resume & Artifact Hygiene - `987404fba` fix(engine): strip only trailing .part when resolving resume .info.json [TEARDOWN-3] - `137675085` feat: FTL Robo gate, resume strike loop, cosign releases - `c0cd45f21` fix(network): gate unattended auto-resume on Wi-Fi Only setting - `779c6e628` fix(resume): harden resume discovery, strike tracking containment, and sidecar cleanup - `f1cb876d3` fix(engine): default writeinfojson on so interrupted downloads leave a resume sidecar #### Android Native & OS Integration - `a5d0580b8` feat(android): queue-active WakeLock + WifiLock for downloads [T2-1, needs SDK compile] - `af0c5d017` feat(android): FIFO share queue replaces single-slot var [T2-6 native half, needs SDK compile] - `ee8d432f2` fix(android): release event callbacks on terminal and replay missed terminals - `3a9b92da9` fix(history): sweep all non-terminal statuses and page restore backlog - `ccdb49b0b` fix(android): make terminal delivery survive activity death - `5f381f82e` fix(android): sweep all active download states on boot with schema guards - `eae2aaec1` fix(android): real worker channel, loud missing runtime, atomic extract - `1d1541a21` fix(android): atomic package extraction, recovery, and two-tier CRC32 stamping - `2ead2e9ae` fix(android): periodic wakelock renewal and onTimeout SQLite sweep - `ae3b88b19` fix(android): decouple terminal event path and prevent destroyed activity leak - `c7d724625` ci(nightly): generate detailed changelog with commit history and fix android compile references - `28461f7dd` fix(android): cancel active downloads on foreground-service timeout - `949f40724` fix(android): serialize Kotlin SQLite access with busy-timeout and retry #### Core Engine & yt-dlp - `1175d5571` fix(downloader): register in _active_downloads before t.start() [T1-1] - `16fb0e222` fix(downloader): close pump-queue cancel-loss window [T1-2] - `d735d7f40` fix(downloader): add graceful shutdown handling and lazy cleanup thread [T1-6] - `42683fcdb` fix(downloader): bind safe_url before try so early failures report real error [T1-9] - `de6817829` test(ipc): lock in error-ID threading for unparseable input [T1-8] - `1ea67d16c` fix(downloader): reject same-URL admission under a different ID [T1-5] - `9a904cbfa` fix(cancel): honest intermediate cancelling state + wedge watchdog [T1-3 engine half] - `fa5e98d24` fix(downloader): cap YDLogger.errors tail at 200 [T3-9] - `6f4ab9a8b` fix(opts): bound all numeric IPC params, reject zero throttle [T3-10] - `ad50d1eaa` fix(downloader): scan progress queue via public API only [T3-8] - `42d90f4c1` fix(errors): precise classification + scrubbed messages [T3-14 engine half] - `0ed1823e6` fix(downloader): classify single-item logger errors, not blanket POSTPROCESS [AARAV-1] - `3a3ff0480` feat(dart): FIFO share queue drains cold starts, chains sheets [T2-6 dart half] - `b804052e6` fix(engine): guard sleep_interval with _clamped_int [TEARDOWN-4] - `a36c23f25` fix(download): collapse setMaxConcurrent/syncConcurrency duplicate [TEARDOWN-5] - `a2e6c0883` fix: close BRUTAL-1/2a/2b/2c/5/6 backlog items (engine + IPC) - `cdd9ad4c6` perf(engine): throttle progress at source, bound queues, batch file writes - `b61f3d20f` fix(engine): resolve desktop search/query TypeError by binding search_query - `157d649d6` fix(engine): bound progress queues and harden poll loop against silent drops - `56002ec1c` fix(engine): make queued promotion pollable and slot close idempotent - `e3f5ec07d` fix(flutter): dispose every engine transport and forward bridge errors - `f6cd2fe68` fix(settings): manage aria2c speed controller lifecycle in State - `f6cccb147` fix(flutter): absorb stream errors in log ingester and detect transport death in desktop engine - `1fabe9dc2` fix(engine): unify IPC envelope contracts for queue_status and forward nodejs_path - `b58fbdff1` fix(engine): enforce admission queue depth limit and input bounds - `7b421fc50` fix(engine): resolve concurrency slot leaks, queue stalls, and dropped cancel events - `ea0391b89` fix(engine): configure JS runtime, extractor args, and cancel error guard - `68a68a208` test(engine): remove unused local variables and imports to satisfy ruff correctness gate - `9e31ef26e` fix(engine): write partial downloads directly to output dir and serialize ffmpeg - `6d458486b` fix(backlog): remaining log issues 3-8 — concurrency guard, player, links, storage, onboarding, grids - `8ade7996d` fix(engine): thread desktop IPC dispatch so slow extractions stop blocking cancel - `c55f8e337` fix(engine): gate PO-token spawn behind solver flag, stop per-download phantom deno eval - `f5c848eab` fix(engine): bound the FFmpeg serialization gate with a 600s timeout #### UI & Client State - `df178bc8f` docs(readme): real device screenshots gallery (11 captures, corrected names) - `2feacf55a` fix(app): gallery visibility, thumbnail toggle, GitHub open, onboarding reform, list jank - `73e437830` chore(flathub): 0.0.2 release entry + screenshots in metainfo - `b500b5500` fix(flutter): bound log pipeline costs and coalesce live rebuilds - `5863761a9` fix(logs): cap log viewer at file tail so multi-MB logs cannot hang the phone #### CI/CD, Build & Install - `ccba7992e` fix(ci): move WINGET_TOKEN gate from job-if (secrets context rejected by parser) to env-backed step - `a237cb28d` feat(ci): one-shot first WinGet submission workflow (fork + PR from verified manifests) - `e25009825` fix(ci): quote wingetcreate run line (colon broke YAML parse) - `ed1415370` feat(linux): universal install.sh (distro+arch auto-detect, SHA256 verify) + README one-liner - `932737a05` chore(release): bump to 0.0.2+3 for first Grablytic release - `92b884e83` fix(build): quote deb control heredoc, drop comment lines - `770fd444f` feat(winget): 0.0.2 Grablytic manifests + first-submit plumbing - `dbfab7e13` ci: nightly build + rolling release, all-branch verify, ruff-action [T4-5] - `5fd9e182e` ci(build): gate all build jobs on reusable verify workflow [TEARDOWN-6] - `b2e1fa217` ci(verify): pin ruff-action to immutable SHA [CI-FIX] - `49820b2f9` ci(nightly): add emulator Doze + install smoke job - `2b1f198e1` perf(ui): structural selectors make progress ticks rebuild one card - `166948ccf` docs(nightly): add user-facing benefits and feature overview to nightly notes generator - `cdbad3153` ci(nightly): remove emulator doze and ftl jobs to guarantee reliable release publishing - `6a8aa3bb9` ci(nightly): temporarily comment out emulator doze and ftl robo jobs #### Performance & Resources - `11515060c` perf(logs): keep DEBUG off the UI bridge to fix 10+ download hang - `5e1e82c5a` perf(lowend): halve fragment threads, indeterminate merge bar, fix search runtime - `155056594` chore: remove perf proof docs from repo (kept in local worklog) #### General Improvements - `e3fa20991` fix(ci): run token check under bash on windows runner - `9f2d8d909` fix(ci): wait for fork propagation before cloning - `d5031a5fe` docs(readme): one-command Linux install block (apt/dnf/zypper/paru) - `2c266d5db` docs(security): v0.0.1 full-matrix VirusTotal scan record (15/15 clean) - `922d9c09c` refactor: rename TrueStream to Grablytic - `71fb9ed6d` fix(install): quote usage heredoc delimiter to prevent command substitution - `490340792` feat(brand): swap in new Grablytic play-mark artwork - `64718e50d` fix(contact): point in-app feedback email to xianzo.help@gmail.com - `8419a704f` docs(readme): roll install links to v0.0.2 grablytic-* assets - `9e750abef` chore(distrib): roll AUR/Flathub/Izzy manifests to v0.0.2 - `bdf50b59a` fix(main): add stdout lock to prevent interleaved JSON lines [T1-7] - `3e4adee24` feat(dart): honest cancelling status across provider and home card [T1-3 dart half] - `cde767a1a` fix(scheduler): gate RSS XML against entity expansion + oversize [T3-16] - `fc4e74144` fix(desktop): terminate process before nulling handle in restart [T2-7] - `4a4e0ccbd` fix(desktop): crash-loop breaker — count abnormal exits, backoff, refuse restart [TEARDOWN-2] - `1ab32eac1` fix(download): evict per-download smoother on terminal events + history removal - `1e6a44d8e` fix(bootstrap): update_check compares archive-to-archive [T4-8] - `79c0687dd` fix(download): accept double byte counts in progress events - `9020a2757` chore: ignore session scratch files (never commit agent scratch) - `1a72e2aae` fix(thumbnails): force PNG cover art for m4a output - `1fa8fce16` chore: drop proof doc from PR (local worklog keeps copy) - `b76ee3d37` chore: drop proof doc from PR (local worklog keeps copy) - `c29fa856b` chore: drop proof doc from PR (local worklog keeps copy) - `9d9227590` chore: drop proof doc from PR (local worklog keeps copy) - `f9d012620` docs(readme): fix stale test counts and Arch package URLs - `aa176d9e8` fix(fonts): bundle Instrument Sans, remove runtime webfont fetch - `ea50f70ac` fix(metered): confirm user taps on metered links when Wi-Fi Only is on - `5849e6224` fix(logs): fix level filtering cancellation bug and improve disk fallback parsing ## Unreleased — 2026-09-13 (Speed sparkline, DB resume, background scheduler, share sheet, queue, playlist selection) - **Speed sparkline & ETA smoothing**: - Dual-stage exponential moving average (EMA) smoothing for download speeds ($\alpha=0.3$) and ETA countdowns ($\alpha=0.15$) eliminating erratic UI fluctuations. - Retains a 60-sample historical speed ring buffer in `DownloadItem`. - Zero-dependency `DownloadSparkline` custom painter with gradient fill embedded in active download cards. - **DB-driven download resume across process death & reboot**: - Upgraded SQLite database to schema v3 with full download execution snapshots (`configJson`, `queuePosition`, `attempts`, `downloadedBytes`, `totalBytes`, `speedBytesPerSecond`, `etaSeconds`). - Periodic 5-second database heartbeat in `DownloadNotifier` persisting active progress metrics. - Automatic startup recovery sweep in `DownloadNotifier` picking up and resuming interrupted downloads without user intervention. - Android `BootReceiver` handling `ACTION_BOOT_COMPLETED` and `ACTION_MY_PACKAGE_REPLACED` safely marking interrupted downloads in SQLite without violating Android 14+ background FGS launch restrictions. - **Background periodic scheduler for observed sources**: - Android WorkManager `ObservedSourcesPollWorker` executing periodic background checks (`observed-sources-poll`) with zero exact alarms. - Two-tier polling: Tier 1 lightweight YouTube Atom RSS feed fetch with zero heavy extractor or JS overhead; Tier 2 Chaquopy `extract_flat` fallback. - SQLite database schema v4 with `seen_source_videos` deduplication ledger. - Auto-queues newly discovered videos to SQLite `downloads` table with status `'pending'` and surfaces discovery notification. - Settings UI controls: interval (15m to 24h), Wi-Fi only, requires charging, battery optimization tile, and manual trigger. - Cross-layer sync via `EngineService.syncSchedule` and `schedule/sync` MethodChannel. - **Share intent** (was: prefill-and-auto-start): shared URLs now open a choice bottom sheet (quality/settings before anything downloads), gated on engine readiness; `autoStartDownloadOnShare` kept as an explicit opt-in that skips the sheet. Format Picker header always shows thumbnail + duration + stream counts. - **Download queue**: FIFO engine queue (default 2 concurrent, 1–5 in Settings, enforced on Android + desktop at every entry point), queued card status, scoped redownload flags + Clear-archive recovery. - **Per-download controls**: overflow menu per item (redownload, audio re-fetch, delete with file + history, per-download logs); Settings permission flow (rationale → request → open-system-settings on denial); native completion/error alerts. - **Per-download logs**: `download_id` correlation source → sink, log bottom sheet (queue card, history row, overflow menu), bounded retention (5000 global / 500 per download / LRU-50) proven by test. - **Thumbnails**: sidecar-preserving `EmbedThumbnail`, finished-event `thumbnail_path` (+ desktop IPC), history DB v1→v2 `thumbnailPath`, local-first Library rendering with fallbacks. - **Playlist selection**: new `PlaylistSelectionScreen` (entry multi-select, reverse/shuffle, unavailable marking) wired to the tested `build_playlist_items` engine contract; routed at paste + share entries. - Verification: `flutter analyze` clean (0 issues), `flutter test` 262/262 passed, `pytest` 309/309 passed. Device-dependent checks (release-APK icon, real downloads, Android 13+ permission/alerts, Kotlin CI build) remain open manual verification. ## Unreleased — 2026-09-12 (Linker Closure, Fail-Closed Probes & Library UI) ### Fixed - **Silent event-channel death** (completed files, 0% UI): `@Keep` on the Chaquopy callback contract, Kotlin sink-null/closed warnings, Python first-failure WARN — a dead delivery hop can never be silent again. - **FFmpeg Bionic Linker Gap (`CANNOT LINK EXECUTABLE: library "libexpat.so.1" not found`)**: - Replaced flawed `ytdlnis-packages` FFmpeg bundle with official, production-proven `io.github.junkfood02.youtubedl-android:ffmpeg:0.17.2` from Maven Central (used in Seal & upstream ytdlnis). - Clean `DT_NEEDED` closure verified across all bundled libraries (0 missing symbols). - Dropped flawed build-time native shim tasks that attempted to package `.so.1` files (rejected by Android Gradle Plugin and AOSP `NativeLibraryHelper`). - Gated binary assignments in `MainActivity.kt` with live `binStatuses[name].ok` checks. - **Engine Binary Health-Check Fail-Closed Gating**: - Fixed `bootstrap.py` marking broken executables as `ok=True` based purely on file existence. - Binaries are now evaluated strictly fail-closed: an executable path is only marked `ok=True` if the live `--version` probe returns exit code 0. - On probe failure (e.g. linker crashes, missing libs, permission errors), `ok` is set to `False`, the binary is scheduled for update, and the exact diagnostic probe output is recorded. - Added unit tests in `engine/tests/test_binstatus.py` simulating linker failure to prevent regressions. - **Library Screen Vanishing Card & Redraw Glitch**: - Fixed bug where downloads transitioning to `status: 'error'` vanished completely from the Library screen. - `LibraryScreen` now categorizes items into `pending`, `failed`, and `completed` buckets. - Added dedicated `FAILED` section in list view and failed cards in grid view with error styling, error details, and an inline `Retry` button. - Eliminates the UX failure where a blank screen caused users to frantically toggle `useGridView`. ## Night loop 2026-09-11 ### Security (applied) - SEC-01: backups locked down (`allowBackup=false` + extraction/backup rules excluding prefs, databases, cookies, logs). - SEC-02: `sig=`/`lsig=`/`signature` redaction in Dart logs (disk + buffer entries) and engine `bridge_event` before disk. - SEC-03: 8 KB cap on inbound SEND-intent text. - Binaries: additive `binaries[]` provenance in bootstrap (bundled / downloaded / system / runtime / unsupported / missing) + `yt_dlp_outdated` advisory; UI shows effective JS runtime (no more phantom QuickJS pending) and honest aria2c-unavailable state; per-row sources, details, and actionable Redownload gating. - SEC-04: template-arg validator (blocks `-o`/`--paths` escapes), `--exec` warning, engine `output_tmpl` confinement. - Fixed all Android downloads crashing in `<1s` with `TypeError: 'HashMap' object is not a mapping`: Chaquopy delivers Kotlin Maps as live proxies, killing `{**config}`. Kotlin now sends config as JSON; Python `coerce_config()` normalizes (None/dict/str/proxy-safe). - Download threads survive `BaseException` with a terminal error event (no more silent ghosts); per-download config summary + query-stripped URLs in engine logs; build-SHA in every log and report. - Bootstrap probe now records returncode/output (linker/permission verdict for bundled binaries) + exec-environment line; single-video downloads with swallowed post-processing failures report `ERROR_POSTPROCESS_FAILED` instead of a false `finished` (playlists stay lenient). - `LD_LIBRARY_PATH` carries EVERY support tree (ffmpeg + deno + native dir); `set_paths` accepts colon-joined dirs. - Linker fix: support-tree extraction recreates Unix symlinks (`commons-compress`, mirroring ytdlnis) — `java.util.zip` had been writing link targets as text files. Marker bump re-extracts once. - Linker, endgame: FFmpeg now comes self-contained from `youtubedl-android:ffmpeg` (Maven Central, production-proven in Seal — zero missing libs, so no expat shim needed; a same-night shim attempt was correctly dropped: the OS only extracts `*.so` from APKs). - Live per-download engine-log overlay on download cards (ytdlnis-style). - Extreme-but-sane logging: settings-change diffs (masked secrets), disk snapshots at bootstrap + download start, 25/50/75% stall milestones (bounded), slow-call WARNs over 10 s, sanitized full-opts dump on verbose. - Android JS runtime is now bundled Node.js (`nodejs-25.3.0` jniLibs; Deno's `libsqlite3.so` doesn't ship) with node-first priority on Android; bot-check errors map to cookie guidance; probe output uncapped for verbatim linker diagnostics. ### Fixed - Engine logs now reach Android: `logger.set_global_event_callback()` push bridge + wiring in `start_download`; file logging (`server_logs.log`, `engine_*.txt`) initialized in `set_paths` on all platforms. - Hook events dual-write (callback + queue) so terminal `finished` honors the `filesize_bytes` contract on the callback path. - Dead settings wired: `concurrent_fragments` → `concurrent_fragment_downloads`, `socket_timeout`, `write_description`/`write_info_json`, `use_aria2` alias, `format_code` override (below explicit IDs, above the ladder). - Diagnostics: terminal download outcome lines, single-owner engine-log ingestion (fixes desktop double-logging), retention slider persists on release, unique manual-report fingerprints, log-file export, full-log report toggle, real Flutter version stamp. - PageView tab animation no longer logs intermediate fly-through pages. - `yt-dlp` floor raised to `>=2025.11.12` (EJS/JS-runtime requirement). ## Unreleased — September 2026 ### Added - Android: `ffmpeg` + `deno` bundled in our own jniLibs (SHA-256-pinned `packages.gradle.kts` fetch, `BinaryPackageManager` resolve/probe/extract). - Android: `DownloadService` (`dataSync` foreground service) with progress notification, explicit lifetime, timeout handling. - Engine: full JS-runtime support — Deno → Node → QuickJS priority, `ejs:github` remote components, `yt-dlp-ejs` dependency. - Logging: buffered persistent logs both layers (`app_logs.txt`, `server_logs.log`), global error handlers, navigation/Riverpod/engine observers, GitHub search-dedup auto-report, live log viewer tab. - Engine: FFmpeg-only `download_sections` cutting with warn-and-skip validation. - Docs: `JS_RUNTIMES_ANDROID_RESEARCH.md` (Deno/Node/QuickJS/bgutil investigation). ### Fixed - Subtitles never embedded (`embedsubs` is not a real param) → real `FFmpegEmbedSubtitle` PP in canonical order (EmbedSubtitle → ModifyChapters → Metadata). - Re-audit items: `updatetime` (not ignored `no_mtime`), `writethumbnail` for `EmbedThumbnail`, SponsorBlock cutter ordering, `filesize_bytes` on terminal events, `suggests_vpn` on errors, desktop `ERROR_CANCELLED` → `cancelled`, R8-safe `EngineEventListener`. - Event delivery: replaced queue polling with Kotlin `onEvent` callback; fixed 7→8-arg `set_paths`/`deno_path` misalignment. - Playlists: generator guards, expanded video IDs, storage sanitization. - Security audit: Zip-Slip/Tar-Slip-hardened extraction, `java.android`-bridge detection (Termux-safe), terminal-vs-stream 99% contract, aria2c validation, Deno path resolution, aware-UTC datetimes. - CI: `flutter analyze` (AppLogger regex escapes), Python `deno_version` UnboundLocalError, `lintVital` disable, `zip.so` strip skip, `shutil.which` mocks. ## July 2026 - YouTube SABR format / PO-Token block resolution; aria2c DASH hardening. - Structured engine logger (rotation, IPC queue, thread-local context) + Dart log model/circular buffer/ingester + live viewer + `logStream` plumbing. ## June 2026 - Diagnostics & Logs screen with troubleshooting flow; `AppLogger`; home/format-picker instrumentation. - Android FFmpeg + checksum fallbacks; `setPaths`/bootstrap race fix. - Security audits: JS execution + tmpdir fixes, executable permissions, aria2c assets. - Format ladder: muxed streams for non-YouTube; platform-aware JS UI; duplicate-download guard; desktop IPC hardening (queueing, single startup, venv fixes, `sqflite_ffi`). - Sprint 4–5 features: history (SQLite), archive toggle, aria2c config, scheduler, media preview, SponsorBlock settings, templates, observed sources, batch import, cookie/WebView auth, share intent, resume UI, update channels, binary bootstrapper, error-recovery UI. ## How this file is kept Sections are written once, when the tag is cut, and then left alone. The rules for what belongs in one, and what stays in the commits, are in [`release-notes.md`](release-notes.md).