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>
Security:
- S1 (backup.ts): require explicit confirmation before enabling custom-command
templates from a backup file; import templates in a disabled state if declined
- S2 (cookies.ts): deny non-http/https popups in cookie login window, matching
the main window's setWindowOpenHandler defence
- S3 (download.ts): main-process concurrency cap — startDownload now rejects
spawns when active.size >= maxConcurrent (defence-in-depth; renderer already
enforces this but should not be the only gate)
- S4 (settings.ts, validation.ts): reject filenameTemplate values containing
path traversal (.. segments or absolute paths); validate outputDir is absolute
- S5 (history.ts, errorlog.ts, templates.ts, validation.ts): per-field
validation on JSON reads — invalid entries dropped rather than blindly trusted
Performance:
- P1 (settings.ts): getSettings() now only writes to disk when sanitization
actually changed a value; removes synchronous file I/O on every hot-path read
- P2 (ipc.ts, download.ts, downloads.ts): renderer forwards its pre-probed
metadata (title/channel/duration) via StartDownloadOptions.meta; main uses
it directly instead of spawning a redundant second yt-dlp probe
Maintainability:
- M1 (log.ts, download.ts, probe.ts): extract duplicated cleanError() to
src/main/log.ts; both callers import from the shared module
- M2 (buildArgs.ts): document parseExtraArgs escape-sequence limitations
- M3 (electron-builder.yml): clarify icon TODO as a pre-release action
- P3 (downloads.ts): document that lowering maxConcurrent mid-flight does
not pause active downloads (intended behaviour, now written down)
Tests:
- Add test/validation.test.ts covering isSafeFilenameTemplate, isSafeOutputDir,
isValidHistoryEntry, isValidErrorLogEntry, isTemplateLike (76 tests pass)
Co-Authored-By: Claude Sonnet 4.6 <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>
Extracts yt-dlp argv construction out of download.ts into its own
electron-free module (src/main/buildArgs.ts) so it can be exercised by
vitest without spinning up Electron, plus a real-download integration
test (skipped by default; opt in with AEROFETCH_REAL_DOWNLOAD=1) that
spawns the bundled yt-dlp.exe/ffmpeg.exe against its argv. download.ts
isn't wired up to it yet — that lands with the next commit.
This was done in an earlier local session but never actually committed,
even though download.ts has imported from it since the Phase A commit
(bb5dd6c) — the build had been relying on an uncommitted local file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>