Each video row in the playlist panel gets an exact-format button that
lazily probes just that entry's URL (reusing the existing single-video
probe IPC, no new main-process code) and shows a native Select of its
real formats. The choice flows through addMany -> formatId per queue
item. Probing is on-demand per row, never all up front, since a
playlist can hold thousands of entries and per-entry probes are
rate-limited.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Command preview + incognito toggle were already wired end-to-end in the
DownloadBar (Show command button, Incognito checkbox) — flipped the two
stale ROADMAP.md checkboxes with notes; verified live in the preview.
Output-path setting (PINCHFLAT): new Settings.collectionOutputTemplate — a
raw yt-dlp -o template overriding the default Channel/Playlist/NNN-Title
layout for Library downloads (empty = default). collectionOutputTemplate()
takes an optional custom template (joined under outDir, left for yt-dlp to
expand); download.ts passes the setting; zod schema validates it as a safe
relative template; Settings -> Filenames field. Tests in buildArgs +
settingsSchema; live-verified the field renders.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The queue/scheduler lived only in renderer memory, so saved/scheduled and other
pending downloads were silently lost on quit (audit M4). Persist them:
- main: queue.ts store (proven cached-atomic createJsonStore, queue.json) +
queue:list / queue:save IPC; QUEUE_MAX cap.
- shared: PersistedQueueItem = the durable subset of DownloadItem (runtime-only
progress/speed/eta/sizeLabel/finishing dropped).
- renderer: mirror the persistable items on every queue change (a signature over
the subset means progress ticks don't re-save; main's jsonStore coalesces the
writes) and rehydrate on launch via an App useEffect. Pure mappers extracted
to store/queuePersist.ts and unit-tested.
Restore semantics: downloading -> queued (yt-dlp continues the .part), saved +
scheduledFor survives so the promoter fires when due, paused/error return
actionable; completed/canceled are never persisted. A queueHydrated gate stops a
launch-time store change from wiping queue.json before it's read.
Reconciles ROADMAP.md. typecheck + 268 tests + eslint + prettier green.
(OS-level quit/relaunch cycle is worth a manual smoke test.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Taskbar progress bar: renderer pushes summarizeQueue aggregate over a new
taskbar:progress channel (App store subscription); setProgressBar normal/error
- System tray (src/main/tray.ts) + minimize-to-tray (Settings.minimizeToTray);
close hides to tray, isQuitting guards real exits; icon via getAppIconPath()
(build/icon.ico added to extraResources)
- Jump list: app.setUserTasks "Open AeroFetch" (thumbnail toolbar deferred)
- Settings search: filters the ~11 SettingsView cards live by text match
Overlay badge deferred (needs a drawn NativeImage). typecheck + test (192) +
build all clean. Roadmap: Phase O COMPLETE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold Phases L-P into ROADMAP.md: media editing (trim/cut, split-chapters,
metadata editing), queue/daily-use UX, power-user surface, Windows-native
integration, and reliability/trust. Cross-link from ROADMAP-PINCHFLAT.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Index an entire YouTube channel or playlist once, then download it into
organized <Channel>/<Playlist>/<NNN> - <Title> folders, with incremental
re-sync. Built in six rebuild-gated phases (F-K); see ROADMAP-PINCHFLAT.md.
- F: channel-walk indexer (/playlists + /videos) -> persisted Source +
MediaItem JSON stores; pure classify/dedup logic in indexerCore.ts
- G: Windows-safe folder paths + dir sanitizer (collectionOutputTemplate)
- H: Library tab + source tree + "Download pending" into the existing queue
- I: state-preserving re-index merge + persist-downloaded-on-complete
- J: watched sources, RSS fast-check, Task Scheduler + --sync launch
(the OS-level scheduling/RSS need a real-install smoke test)
- K: .info.json / thumbnail / .description sidecars for media servers
Also gitignore .gitea-token. 106 unit tests pass; typecheck + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A live smoke test of buildArgs' argv against the bundled yt-dlp + ffmpeg found
that ffprobe.exe was never bundled (resources/bin/ shipped only ffmpeg.exe).
yt-dlp resolves both from --ffmpeg-location, so duration-aware post-processing
failed at runtime for every user with "ffprobe not found": --sponsorblock-remove,
--force-keyframes-at-cuts, and --split-chapters (CODE-AUDIT C1).
- Bundle ffprobe.exe (matching n8.1.2 LGPL build, sha256-verified against the
already-bundled ffmpeg.exe) and document it in resources/bin/README.md as a
required binary; correct the stale "not committed to git" note.
- Guard startDownload against a missing ffmpeg.exe/ffprobe.exe up front so the
failure is a clear AeroFetch error, not a cryptic yt-dlp postprocessing one
(new getFfprobePath() in binaries.ts).
- Expand test/real-download.integration.test.ts from 2 to 8 live cases: crop,
sponsorblock-remove, audio opus re-encode, mkv+vp9 merge, subtitle+chapter
embed, restrict-filenames, download-archive skip, and Phase C extra-args.
All 8 pass against live yt-dlp + ffmpeg.
- ROADMAP: mark the Phase A/B/C smoke-test caveats done. CODE-AUDIT: add C1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds theme presets (Toffee/Slate/Evergreen/Lavender) with a "follow system"
mode and high-contrast awareness, a first-run welcome screen, and the
Windows analog of Android's share sheet for a Win32 app: an aerofetch://
protocol handler plus an Explorer "Send to AeroFetch" entry, both routed
through a single-instance lock so a second launch hands its link to the
already-running window instead of opening a duplicate.
Co-Authored-By: Claude Sonnet 4.6 <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>
Implements the remaining Phase B (Access & networking) items from
ROADMAP.md: cookie sources (browser cookie-store import, or a built-in
sign-in window that exports a Netscape cookie file via a persisted Electron
session partition), the aria2c external downloader, proxy/rate-limit,
restrict-filenames, and a download-archive to skip repeats.
Wires download.ts to the buildArgs() module added in the previous commit
(it wasn't hooked up yet) and renames its options param
NetworkOptions -> AccessOptions to reflect the wider scope now that cookies
and filename/archive flags joined proxy/rate-limit/aria2c.
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>