The watched live session for the six deferred lifecycle items, each verified
against real yt-dlp/Electron behavior on Windows:
- R4: cancel now deletes the download's orphaned partials. The engine captures
the final output path via a new `--print before_dl:dest|%(filename)s`
(empirically %(filepath)s is still 'NA' that early) and the close handler
sweeps only unambiguous intermediates (.part / .part-Frag* / .ytdl /
<stem>.fNNN.<ext>) via the pure, unit-tested lib/partials.ts — never a bare
<stem>.<ext> or another download's files. Live-verified with decoys.
- L65: verified-acceptable, no code change — a taskkill /F pause leaves the
.part byte-intact and yt-dlp --continue resumes at the exact byte offset.
- L139: spawn↔self-update interlock (new ytdlpLock.ts). The updater refuses
while any yt-dlp spawn is live; download/terminal IPC handlers hold (await)
behind an in-progress exe rewrite instead of failing.
- B2: source indexing is cancellable — AbortSignal through the probe walk
(kills the in-flight child, live-verified via tasklist), sources:index-cancel
IPC, and a Cancel button in the Library add-source row.
- B6: app-update download is cancellable — app:update-cancel routes through
the existing finish() teardown (abort + destroy + unlink, live-verified on
Electron net.request); Cancel button under the update progress bar. The
checksum phase is cancelable too.
- L143: closing the window mid-download (non-tray mode) now offers a native
tray-independent "Quit anyway / Keep downloading" dialog, replacing the
passive "use the tray to quit" notification.
Peer-review pass caught and fixed: a non-reentrant update lock (concurrent
begin clobbered the settle promise), a dead Cancel window during the B6
checksum fetch + a canceller slot-ownership bug (L140 shape), and a
persist-after-cancel hole in the index walk.
typecheck + 304 tests + eslint + production build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The contained consistency consolidations; the sweeping ones are deferred with
their standard documented (they're the audit's own post-1.0 refactors, and
several would be reckless to do unattended).
Landed:
- src/main/lib/lineBuffer.ts (createLineBuffer): one newline line-splitter for
streamed child-process output, replacing the duplicated buffer loop in
download.ts and terminal.ts (CC3/CC4/SIMP5). Pure + unit-tested
(test/lineBuffer.test.ts, 7 cases: partial lines, CRLF, empty lines, flush,
multi-line chunks). download.ts keeps byte-identical marker parsing and no
close-flush (yt-dlp template lines are always newline-terminated).
- ytdlp.ts: getYtdlpVersion/updateYtdlp now run stderr through cleanError, the
same `cleanError(r.stderr) || r.error?.message || ''` idiom already used by
download/probe/indexer (CC6) — a failed check shows the trailing error line.
- CL6 resolved: the one real redundancy was L15; clearDir/openFile/showInFolder
are deliberate view-model wrappers (CC13), left as-is.
Deferred with documented standard + rationale (CODE-AUDIT.md): CC1 (breaking
persisted-key rename → migration), CC5+CL4 (security-critical updater
net.request, not live-verifiable here), CC7 (cosmetic arg-order), CC9 (zod dep),
CC10 (electron-store↔jsonStore split is deliberate for DPAPI), CC11 (config.ts
move), CC12 (file-tree reorg), CC13→L93, CC14→L142.
Verified: typecheck (node+web) + 275 tests + eslint + production build green;
touched files prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hand-rolled `new Promise((resolve) => execFile(…, cb))` wrappers in
probe / ytdlp (×2) / ffmpeg / indexer.probeFlat / download.probeMeta with one
lib/exec.ts execFileAsync returning a normalized
{ok,stdout,stderr,timedOut,error} that never rejects. Each caller keeps its own
error mapping (timedOut -> "Timed out …" vs stderr) and JSON parsing; behaviour
is unchanged. windowsHide defaults on; per-call timeout/maxBuffer preserved.
Foundation for CC4/CC5 — the stdout line-buffer half lands with SIMP5, the
net.request half with SIMP16, at which point those checkboxes close.
Unit-tested (exec.test.ts) against the node binary: ok / stderr+nonzero-exit /
missing-binary / timeout. typecheck + 262 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L46: Introduced YTDLP_MISSING_MSG constant in binaries.ts. All four callers
(download, indexer, probe, ytdlp) now emit the same user-facing string:
"yt-dlp.exe is missing. Open Settings -> Software update to re-download it."
This removes newline-embedded messages and four divergent phrasings.
L71: Removed readOnly from videoDir/audioDir folder inputs in SettingsView.
Added onChange handler so users can paste or type a path directly without
being forced to use the Browse button. The path still flows through
applySettings() in main, which validates it.
L74: Accent color swatches were triple-labeled (aria-pressed + aria-label +
title). Replaced with a single aria-label that embeds "(selected)" state,
plus title for the hover tooltip. Removed the incorrect aria-pressed
(these are selection buttons, not toggles).
typecheck + 242 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
yt-dlp is now a managed, auto-updating binary rather than a static bundled
one. On launch AeroFetch seeds a writable copy under userData from the
bundled seed, self-heals it if it goes missing, and — throttled to once a
day — self-updates it to the configured channel (default: nightly). This
keeps the binary current so YouTube changes don't silently cause 403s, and
an app reinstall (or portable re-extraction) can no longer roll it back.
Settings shows an auto-update toggle, the live version, last-checked time,
and the channel selector.
Also surface the bundled ffmpeg/ffprobe versions in Settings (display only:
they have no self-update path and, unlike yt-dlp, don't break as sites
change, so there is no auto-update for them).
- binaries: split the read-only bundled seed from the managed userData copy
- ytdlp: ensureManagedYtdlp (seed + self-heal), startup auto-update runner
- ytdlpPolicy: pure once-a-day throttle decision (unit-tested)
- ffmpeg: parse ffmpeg/ffprobe -version for the Settings panel
- settings/ipc/preload/renderer: new settings, IPC channels, types, and UI
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Seven-tier security audit of the main process, each finding fixed with a
regression test. Typecheck (node + web) clean; unit tests 106 -> 140.
- Tier 1 (command exec/argv): allowlist the yt-dlp --update-to channel
(blocks arbitrary-binary-install RCE); gate per-download extraArgs behind
the customCommandEnabled consent flag in main (blocks --exec RCE); resolve
taskkill/schtasks by absolute System32 path; validate per-download
outputDir; normalize the URL in assertHttpUrl and use it at every spawn.
- Tier 2 (input validation): fix isSafeFilenameTemplate drive-relative
('C:foo') and Windows dotted-'..' traversal bypasses; percent-encode the
untrusted id in entryUrl; catch reserved device names with extensions in
sanitizeDirSegment.
- Tier 3 (fs/backup): drop malformed template rows in importBackup;
normalize deep-link URLs via assertHttpUrl.
- Tier 4 (cookies): confine the sign-in window's navigations/popups to web
URLs (recursively) and deny all web permissions on its session.
- Tier 5 (deep-link/argv): bound the .url file read to 64 KB; match the
aerofetch:// scheme case-insensitively.
- Tier 6 (Electron window): deny camera/mic/geolocation/USB/HID/serial/
Bluetooth permissions on the app window.
- Tier 7 (network/persistence): restrict the watched-source RSS fetch to
youtube.com feed URLs (SSRF guard); complete isValidSource validation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase C had been implemented in the working tree but never committed:
named custom-command templates (CRUD + per-download override), command
preview, and the in-app yt-dlp self-updater.
Phase D adds: history search/kind-filter/multi-select/bulk-delete/
re-download; native OS notifications on completion/failure; a private/
incognito download mode that skips history; settings+template backup/
restore via JSON; and a persistent error log surfaced as a Diagnostics
report in Settings.
See ROADMAP.md for the full per-item breakdown.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase A of the Seal feature-parity roadmap: configurable post-processing via a
shared DownloadOptions model (src/shared/ipc.ts), editable as persisted defaults
in Settings and overridable per-download in the download bar.
- audio formats (mp3/m4a/opus/flac/wav/aac), video container (mp4/mkv/webm),
preferred-codec sort (-S vcodec)
- subtitles (download/embed/langs/auto), SponsorBlock (remove/mark + categories
+ force-keyframes-at-cuts), embed chapters/metadata/thumbnail, square-crop
audio artwork
- playlist downloads: probe via `-J --flat-playlist`, inline selection UI
(checkbox list, select-all/none, live count); each entry enqueued as its own
single-video download, reusing the existing queue/concurrency/history
- reusable DownloadOptionsForm shared by Settings and the download bar so the
defaults and per-download override never drift
- ROADMAP.md tracking full Seal parity (phases A-E)
Also includes in-tree security hardening that landed alongside: preload sandbox
(CJS preload output), http(s)-only window-open + blocked renderer navigation,
argv-injection guard for URLs (src/main/url.ts), extension-allowlisted file
open/reveal (src/main/reveal.ts), yt-dlp version-check timeout, and a minimal
window.electron presence marker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Electron + React (Fluent UI) desktop frontend for yt-dlp:
- Download queue with live progress, concurrency cap, cancel/retry
- Format/quality picker via yt-dlp probe; audio extraction to MP3
- Settings + history persistence (electron-store / JSON)
- Clipboard link auto-detect; persisted light/dark theme
- NSIS + portable packaging (binaries bundled at build time, not in git)
UI: "Studio" sidebar layout with a toffee-brown theme (light + neutral dark).
Dropdowns use a native <select> and tooltips a CSS-only Hint, to avoid a
dev-machine GPU overlay flicker/blank issue.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>