- Built-in yt-dlp terminal: src/main/terminal.ts streams raw yt-dlp runs (binary fixed, gated on customCommandEnabled), new TerminalView + sidebar tab - Command palette (Ctrl/Cmd+K), portal-free CommandPalette.tsx wired in App - Per-playlist-item editing: per-row video/audio toggle + All video/All audio batch; addPlaylist enqueues via addMany with per-item kind - Weighted format sorting: DownloadOptions.formatSort -> raw -S (overrides codec) - URL-regex template auto-matching: CommandTemplate.urlPattern + selectExtraArgs matchesUrl, threaded through resolveExtraArgs; field in TemplateManager typecheck + test (192) + electron-vite build all clean. Roadmap: Phase N COMPLETE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
30 KiB
AeroFetch Roadmap — feature parity with Seal
AeroFetch is the Windows/Electron counterpart to Seal, the Android Material-You frontend for yt-dlp. The core loop (probe → format pick → queue → download → history) is already in place. This document tracks the remaining gap: everything Seal does that AeroFetch doesn't yet.
Sources: Seal GitHub · Seal on F-Droid · It's FOSS overview.
See also: ROADMAP-PINCHFLAT.md — a parallel track that extends AeroFetch from a one-shot downloader into a Pinchflat-style media manager (index entire channels → organize into
Channel / Playlist / Titlefolders → keep in sync), reusing the existing queue/history/options rather than replacing them.Post-parity work (YTDLnis-derived editing features + Windows-native/UX polish + reliability) is tracked at the end of this document under Post-parity (Phases L onward).
Already at parity ✅
Video + audio download · audio → mp3 with embedded thumbnail/metadata · interactive format picker (probe) · quality presets · queue + concurrency cap + cancel · download history (open / show-in-folder) · clipboard auto-detect · light/dark theme · output folder + filename template · yt-dlp version check · NSIS installer + portable build.
Phase A — Core download power ✅ COMPLETE
A shared DownloadOptions model (src/shared/ipc.ts) carries the post-processing
choices end-to-end. Persisted defaults are editable in Settings → Format &
post-processing via a reusable DownloadOptionsForm component, which the download
bar also hosts in a collapsible per-download Options panel. The main process emits
the flags in buildArgs (src/main/download.ts). All items verified in the UI preview
(typecheck clean). Real-download smoke test ✅ (2026-06-23): crop-to-square cover,
audio re-encode (opus), video container + codec preference (mkv + vp9 merge), subtitle
embed (→ mov_text), chapter embed, and SponsorBlock-remove (output duration shrinks by the
cut segments) are now all verified against live yt-dlp + ffmpeg in
real-download.integration.test.ts. That pass
found and fixed a real shipping bug — the bundled ffprobe.exe was missing (see CODE-AUDIT C1).
- Playlist downloads. Probe now uses
yt-dlp -J --flat-playlistand returns either a single video (with formats) or a flat playlist. The download bar shows a selection panel (scrollable checkbox list, select-all/none, live count) and enqueues each chosen entry as its own single-video download — reusing the existing queue/concurrency/history. - Subtitles. Download + embed (
--write-subs,--write-auto-subs,--sub-langs,--embed-subs,--convert-subs srt); language field + auto-caption toggle. - SponsorBlock.
--sponsorblock-mark/--sponsorblock-removewith a category multi-select (sponsor, intro, outro, selfpromo, music_offtopic…) +--force-keyframes-at-cuts. - More audio formats. Was mp3-only. Now best / mp3 / m4a / opus / flac / wav / aac
via
--audio-format. - Video container + codec preference. Container choice (mp4/mkv/webm) +
preferred codec (
-S res,fps,vcodec:…) to nudge toward av1 / vp9 / h264 without overriding the requested resolution. - Embed chapters.
--embed-chapterstoggle. (--split-chaptersstill TODO.) - Embed thumbnail crop-to-square for audio (Seal's "crop artwork") via thumbnail
post-processor args. Smoke-tested ✅: the embedded cover comes out a perfect square
(360×360) — the
--ppacrop recipe survives yt-dlp's shlex split + ffmpeg's filtergraph. - Per-download overrides. Collapsible Options panel in the download bar (shared
DownloadOptionsForm) so one download — or one playlist batch — can deviate from the persisted defaults; "Reset to defaults" clears it. ThreadsoptionsthroughaddFromUrl → DownloadItem → startDownloadand is carried on retry.
Phase B — Access & networking ✅ COMPLETE
buildArgs's former NetworkOptions param is now AccessOptions (still in
src/main/buildArgs.ts) — it grew past pure networking once cookies and
filename/archive flags joined proxy/rate-limit/aria2c. Smoke-tested ✅ (2026-06-23):
--restrict-filenames (spaces → underscores) and --download-archive (a second run of
the same URL is skipped with no fresh download) are verified end-to-end. Still untested
live: the sign-in window's cookie export — it needs an interactive login against a gated
video, which can't run unattended, so it stays reasoning + typecheck only. aria2c
(optional, binary not bundled) and proxy (needs a live proxy server) are likewise not
smoke-tested.
- Cookies. Settings → Cookies has a source picker:
- From a browser's cookie store →
--cookies-from-browser <name>(chrome/edge/ firefox/brave/opera/vivaldi —COOKIE_BROWSERSinsrc/shared/ipc.ts). - Sign-in window (built into AeroFetch) → opens an ElectronBrowserWindowon a persisted session partition (persist:aerofetch-login, seesrc/main/cookies.ts); OAuth/SSO popups are allowed through onto the same partition. Closing the window exports its cookies to a Netscape-format file (userData/cookies.txt) whichbuildArgspasses via--cookies. Settings → Cookies shows last-saved time + a "Clear saved cookies" button (also clears the partition's storage). - aria2c external downloader. Settings → Network has a "Use aria2c downloader"
toggle; when on and
resources/bin/aria2c.exeexists,buildArgsemits--downloader <path> --downloader-args(ARIA2C_ARGSinsrc/main/buildArgs.ts:-x 16 -s 16 -k 1M). Missing binary falls back silently to yt-dlp's own downloader (checked insrc/main/download.ts). Binary is optional and not committed — seeresources/bin/README.md. - Proxy (
--proxy) and rate limit (--limit-rate). Settings → Network fields (Settings.proxy/Settings.rateLimit), threaded throughbuildArgs'sAccessOptionsparam. - Restrict filenames toggle (
--restrict-filenames) + download archive (--download-archive, skip already-downloaded) — both in Settings → Filenames. The archive file lives at a fixeduserData/download-archive.txt(getDownloadArchivePath()insrc/main/settings.ts), not user-configurable.
Phase C — Custom commands & yt-dlp management ✅ COMPLETE
Named CommandTemplates (src/shared/ipc.ts) persist to templates.json
(src/main/templates.ts, mirrors history.ts's plain-JSON pattern) and are managed
inline in Settings → Custom commands via TemplateManager.tsx — no modal, matching
the rest of the app's no-portal-overlay convention (see the Hint/Select comments re:
this dev machine's GPU/driver). A template's extra yt-dlp flags are shell-split
(parseExtraArgs, src/main/buildArgs.ts) and appended to the generated argv
immediately before the -- URL terminator, so a template flag can override anything
chosen earlier. Settings.customCommandEnabled + defaultTemplateId apply a template
to every new download; the download bar's own Custom command panel lets a single
download override that default or opt out entirely (extraArgs on
StartDownloadOptions, mirroring the existing per-download options override).
buildCommand() (src/main/download.ts) now centralises settings/access/
options/extraArgs resolution, shared by both the real startDownload spawn and the new
preview path. All items verified in the UI preview + npm run typecheck +
npm run test (11 new unit tests covering parseExtraArgs, formatCommandLine, and
extraArgs ordering). Smoke-tested ✅ (2026-06-23): parseExtraArgs('--write-info-json --no-mtime') splits into discrete tokens and the flags reach a live yt-dlp run — the
.info.json sidecar is written, confirming custom-command args take effect end-to-end.
- Custom command templates. Named, editable templates of extra yt-dlp args
(
CommandTemplate), CRUD'd inline viaTemplateManager.tsx; persisted totemplates.json. Pick a default viaSettings.defaultTemplateId(a Select in the Custom commands card). The "run custom command" mode isSettings.customCommandEnabled, applied to every new download unless overridden per-download in the download bar's Custom command panel. - Command preview.
IpcChannels.commandPreview→previewCommand()(src/main/download.ts) builds the exact argv via the samebuildCommand()path a real download would take, then renders it withformatCommandLine. Surfaced as a Preview command button in the download bar, with a Copy button. - In-app yt-dlp updater.
updateYtdlp(channel)(src/main/ytdlp.ts) runs yt-dlp's own--update-to stable|nightly. Surfaced in Settings → About, right next to the existing version check, with a channel picker + result output.
Phase D — Library, UX & notifications ✅ COMPLETE
History stays plain JSON (history.json, 500-entry cap) — search/filter/select are done
client-side over the already-small array, so the planned better-sqlite3 migration wasn't
needed for this phase and was deliberately skipped to avoid a native-module build step for
no functional gain at this scale. Failed downloads (pre-spawn rejections and in-flight
yt-dlp failures alike) now persist to a new errorlog.json (src/main/errorlog.ts,
200-entry cap) independent of the queue, so a report survives Clear finished. Native
notifications and the backup file both reuse existing settings/templates plumbing
(getSettings/setSettings, listTemplates/the new replaceTemplates) rather than adding
parallel storage. All items verified in the UI preview (typecheck + npm run test clean).
- History search + multi-select + filter (video/audio) + bulk delete + re-download.
HistoryView.tsxgained a search box, an All/Video/Audio filter, a "Select" mode with per-row checkboxes + select-all + Delete selected (newhistoryRemoveManyIPC,src/main/history.ts), and a per-row re-download button that re-queues the entry's URL/kind/quality via the existingaddFromUrl. - Native OS notifications on completion / failure (Electron
Notification,src/main/download.ts). Gated by a new Notify when downloads finish switch (Settings.notifyOnComplete, default on); clicking a notification refocuses the window. - Private / incognito mode — a download bar toggle (
DownloadBar.tsx, sticky like an incognito tab) setsDownloadItem.incognito, whichstore/downloads.tschecks before ever callinguseHistory().add(...). Queue items show an eye-off badge when private. - Backup / restore settings + templates (export / import JSON). New
src/main/backup.tswrites/reads{ settings, templates }via a save/open dialog;replaceTemplates()(src/main/templates.ts) does a full restore rather than a merge-by-id. Surfaced as Settings → Backup & restore. - Error log / copy error report view (Seal's debug report). New
src/main/errorlog.tspersists every failure (pre-spawn rejection, spawn error, or non-zero yt-dlp exit); Settings → Diagnostics lists recent entries with Copy full report and Clear log.
Phase E — Theming & platform polish ✅ COMPLETE
Some items are Android-specific in Seal and adapted to Windows here.
-
Theme presets + "follow system" and high-contrast. Settings → Appearance has a Theme select (Light / Dark / Follow system) and four accent presets — Toffee (original), Slate, Evergreen, Lavender — each a 16-stop
BrandVariantsramp sharing toffee's lightness curve at a different hue (src/renderer/src/theme.ts). "Follow system" reads Electron'snativeTheme.shouldUseDarkColorsin the main process (src/main/index.ts'sresolveBackgroundMode/registerSystemThemeBridge, pushed to the renderer over a newsystem-theme:updatechannel intosrc/renderer/src/store/systemTheme.ts) so the native window background stays in sync too, not just the in-page theme. High contrast:nativeTheme.shouldUseHighContrastColorssurfaces as a status row in Settings → Appearance with a shortcut to Windows' contrast settings (ms-settings:easeofaccess-highcontrast); actual color overrides are left to Chromium's nativeforced-colorshandling since nothing in the app setsforced-color-adjust: none. The sidebar's quick toggle still works under "system" — it sets an explicit light/dark away from whatever's currently resolved, breaking out of auto. Verified in the UI preview (typecheck +npm run testclean): default Toffee, switching to Slate/Evergreen/Lavender, Light/Dark/Follow-system, and the sidebar toggle's break-out-of-system behavior all checked visually. -
Windows "open with" / share integration. Both mechanisms named above, since a Win32 (non-MSIX) app can't register as an actual Share Target: -
aerofetch://protocol (aerofetch://download?url=<encoded>) — registered at install time viaelectron-builder.yml'sprotocolsfield (NSIS), and at runtime viaapp.setAsDefaultProtocolClientinsrc/main/index.ts(covers the portable build and dev, which have no installer step to do it for them). - Explorer "Send to AeroFetch" —src/main/deeplink.ts'sregisterSendToShortcut()writes a.lnkinto%APPDATA%\Microsoft\Windows\SendTovia Electron'sshell.writeShortcutLink(no native deps). Sending a.urlInternet Shortcut file (what Explorer/browsers create from "Create shortcut"/dragging a tab to the desktop) passes its path as argv;extractIncomingUrl()parses theURL=line.Both paths funnel through the same `extractIncomingUrl()` (only ever forwards http(s) targets — same restriction as the existing external-link window-open handler) and a new `external-url` IPC push channel. The renderer surfaces the incoming link via `DownloadBar.tsx`'s existing clipboard-suggestion banner (now labeled "Link received:" for this source instead of "Use copied link?") rather than a new UI element. Added `app.requestSingleInstanceLock()` + a `second-instance` handler so a second invocation (the OS relaunching us for the protocol/SendTo case) hands its link to the already-running window instead of opening a duplicate. `extractIncomingUrl()`'s parsing/validation logic has 10 new unit tests (`test/deeplink.test.ts`); typecheck and the full suite are clean. *Caveat: the OS-level wiring (protocol registration, second-instance routing, the SendTo shortcut) is main-process/Electron-only and can't be exercised in the Vite browser preview used elsewhere in this phase — worth a real install + a manual `aerofetch://download?url=...` / "Send to" smoke test, same spirit as Phases A–C's real-download caveats.* -
Onboarding / welcome screen on first run.
Settings.hasCompletedOnboarding(defaultfalse, persisted) gates a full-screenOnboarding.tsxrendered in place of the sidebar + main content inApp.tsx— not a FluentDialog, since this app avoids Fluent's portal-based overlays entirely (GPU/driver blank-overlay issue noted insrc/main/index.tsandSelect.tsx). Shows the brand mark, a one-line pitch, the download-folder picker (same field as Settings → Downloads), three tip bullets, and a "Get started" button that flips the flag. Gated on the settings store'sloadedflag so a returning user's real settings can never get clobbered by a one-frame flash of the default-false state. Verified in the UI preview (typecheck +npm run testclean): screen renders, folder field/browse work, "Get started" dismisses into the normal app.
Not portable from Seal (noted for completeness)
- SAF directory picker (Android storage) → already covered by the native folder dialog.
- Android share-sheet / quick-download-on-share → mapped to Phase E's
aerofetch://protocol- Explorer "Send to" integration (a true Share Target needs an MSIX package, out of scope for this app's NSIS/portable distribution).
- F-Droid distribution → N/A (AeroFetch ships NSIS + portable).
Post-parity — editing, native polish & reliability
With both parity tracks complete — Seal (Phases A–E above) and the Pinchflat media-manager roadmap (Phases F–K ✅) — this final track collects what's left, from two sources:
- A feature study of YTDLnis (the other major Android yt-dlp frontend), keeping only the features AeroFetch doesn't already have.
- A Windows-native + daily-use UX gap analysis of AeroFetch itself (things neither Seal nor Pinchflat cover, but that a desktop app should do).
Nothing here is built yet — every box is unchecked. Phases continue the existing lettering
(A–E Seal, F–K Pinchflat) from L onward, and each item carries the yt-dlp flag and the
AeroFetch file it touches so it can be driven straight from the existing buildArgs /
DownloadOptions / queue plumbing rather than new infrastructure.
Sources: YTDLnis GitHub · YTDLnis docs · YTDLnis changelogs · F-Droid listing.
Already at parity with YTDLnis (do not rebuild)
Format/quality/container picking · SponsorBlock (remove/mark + embed-as-chapters) · embed subtitles/metadata/chapters · cookies + login · custom command templates · backup/restore · Observe Sources (= our Phase J watched sources + RSS) · incognito mode · history with filter + bulk re-download · filename templating · self-managing yt-dlp + ffmpeg binaries. AeroFetch is even with, or ahead of, YTDLnis on all of these.
Phase L — Media editing (YTDLnis headline features)
YTDLnis's signature differentiator. The keyframe plumbing already exists in AeroFetch
(--force-keyframes-at-cuts is emitted today for SponsorBlock-remove in
src/main/buildArgs.ts), so this is cheaper than it looks.
- Trim / cut downloads. Keep only chosen time ranges (multiple allowed). →
parseTrimSections(src/main/buildArgs.ts) normalises raw user text into yt-dlp--download-sections "*START-END"specs (+--force-keyframes-at-cuts, deduped against SponsorBlock-remove). Threaded asStartDownloadOptions.trimthroughstore/downloads.ts, surfaced as a collapsible Trim panel inDownloadBar.tsx(single downloads only). Implemented + unit-tested (buildArgs.test.ts— 7 new tests; typecheck + test green). Live smoke test pending, plus two follow-ups: timestamp-range only so far (chapter-based cuts via the probechapters[]array, and a scrubber/preview player, still TODO). - Split by chapters into separate files. →
--split-chapters(already flagged as a TODO in Phase A above). One toggle onDownloadOptions+src/renderer/src/components/DownloadOptionsForm.tsx; pairs naturally with trim. yt-dlp keeps the full file and writes per-chapter files via its defaultchapter:template. Implemented + unit-tested (buildArgs.test.ts, typecheck + test green); live smoke test still pending. - Metadata editing before download. Change title / author / artist pre-download
(YTDLnis: "modify metadata such as title and author"). →
--parse-metadata/--replace-in-metadata; fits the audio path. Good for building a clean music library where the source title is messy. Held: the exact set-a-literal recipe is fragile (--parse-metadata FROM:TOsplits on a colon that titles often contain;--replace-in-metadata FIELD REGEX REPLACEhas empty-field + regex-replacement edge cases) — confirm against a live yt-dlp run before wiring it, rather than guessing the flag.
Phase M — Queue & daily-use UX ✅ COMPLETE
Neither Seal nor Pinchflat covers these; they're what makes the queue pleasant to live in.
Today src/renderer/src/store/downloads.ts has cancel + retry only.
- Pause / resume individual downloads. → yt-dlp resumes
.partfiles via--continue(already the default). Done:pauseDownload()(src/main/download.ts) reuses thetaskkill /T /Ftree-kill via a newkillTreehelper but flags the recordpausedso the close/error handlers stay silent (no error event/log/notification); newdownload:pauseIPC + preload bridge. Rendererpause/resume(store/downloads.ts) + apausedstatus: pause frees a concurrency slot, resume re-queues WITHOUT resetting progress so the relaunch continues the partial. Pause/Resume buttons + a frozen progress row inQueueItem.tsx; paused items survive "Clear finished". typecheck + test green; live resume-continues-the-.part still wants a smoke test. - Reorder / prioritize the queue. Done as a "Download next" action rather than
drag: since the queue renders newest-first while
pump()promotes oldest-first, a reposition primitive is clearer than drag.prioritize(id)(store/downloads.ts) moves a queued item to the front of the promotion order; an up-arrow button on queued rows (QueueItem.tsx). - Aggregate progress + total ETA + combined speed. A header strip on the Downloads
view summing bytes/speed across active items. Feeds straight into Phase O's taskbar
progress bar. Done: pure
summarizeQueue(store/queueStats.ts) → a strip inDownloadsView.tsx(overall %, combined speed, longest-ETA "time left"); unit-tested (test/queueStats.test.ts). A true remaining-bytes combined ETA is still out of reach — we don't track per-item sizes — so "time left" is the longest single ETA. - Drag-and-drop a link or
.urlfile onto the window. Done: the download card (DownloadBar.tsx) handlesonDragOver/onDropwith a dashed-outline drag highlight, acceptingtext/uri-list/text/plain(firstUrl) and dropped Windows.urlInternet-Shortcut files (parseUrlFile, read viaFile.text()); both http(s)-validated by the existinglooksLikeUrl. A dropped link fills the URL field. - Retry all failed. One action that re-enqueues every failed item. Done:
retryAll()instore/downloads.tsre-queues allerroritems + a "Retry all failed (N)" button inDownloadsView.tsx. (Re-queues live queue items rather than readingerrorlog.json— same effect for the on-screen failures.) - Duplicate detection. Warn ("you already have this") before enqueuing a URL already in
the active queue. Done: pure
sameVideo()(store/queueStats.ts, YouTube-id aware + normalised-URL fallback) drives a "Already in your queue — Download anyway?" banner inDownloadBar.tsx; unit-tested. Checks the live queue (which still holds recently-completed items); cross-checkinghistory.jsonfor older downloads is a possible extension. - Per-download scheduling + "save for later." Done: a
savedstatus + optionalscheduledFor(store/downloads.ts);pump()ignoressaved(the time gate), and a 15s ticker promotes due scheduled items toqueued. UI: a Schedule panel with a nativedatetime-localinput inDownloadBar.tsx(future time parks the new download as scheduled), plus per-row Save for later / Add to queue actions inQueueItem.tsx; saved items survive "Clear finished". Distinct from the Phase J--syncschedule (which is for sources). Limitation: the queue isn't persisted, so a schedule only fires while AeroFetch is running — noted in the UI hint and the code.
Phase N — Power-user surface ✅ COMPLETE
YTDLnis leans hard into this; AeroFetch had the building blocks (command preview, templates,
per-item options) but not the surfaces. typecheck + test + npm run build clean.
- Built-in terminal mode. Done:
src/main/terminal.tsruns the bundled yt-dlp with raw user args (binary fixed; gated oncustomCommandEnabled, enforced in main), streaming stdout/stderr line-by-line over a newterminal:outputchannel (terminal:run/cancelIPC + preload). NewTerminalView.tsx(args box, Run/Stop/Clear, live log, Ctrl+Enter) and a Terminal sidebar tab. - Per-playlist-item editing. Done: the playlist panel in
DownloadBar.tsxgained a per-row video/audio toggle + All video / All audio batch buttons;addPlaylistenqueues viaaddManywith each entry's own kind (quality falls back to that kind's default). Per-item exact-format selection (needs probing each entry) left as a further extension. - Weighted format sorting ("format aspect importance"). Done: a
formatSortfield onDownloadOptions(raw yt-dlp-Sstring); when set it emits-S <value>, overriding the codec tiebreaker (buildArgs.ts); an "advanced" input inDownloadOptionsForm. Unit-tested. - URL-regex template auto-matching. Done:
CommandTemplate.urlPattern(regex);selectExtraArgsauto-applies a matching template ahead of the global default (matchesUrl, bad patterns never match), URL threaded viaresolveExtraArgs. urlPattern field inTemplateManager+ persisted intemplates.ts. Unit-tested. - Command palette + keyboard shortcuts. Done: a portal-free
CommandPalette.tsx(filter, ↑/↓, Enter, Esc) opened with Ctrl/Cmd+K fromApp.tsx; actions = navigate tabs, new download (focuses theaerofetch-urlinput), toggle theme.
Phase O — Windows-native integration & discoverability
Grepping src/main confirms none of these exist yet (no setProgressBar, Tray,
setThumbarButtons, setJumpList, globalShortcut). All live in the main process, mostly in
src/main/index.ts.
- Taskbar progress bar.
win.setProgressBar(fraction)driven by Phase M's aggregate progress. ~20 lines, very high perceived-quality return. - System tray + minimize-to-tray. A
Traywith show/quit/pause-all. This is also the coherent home for the watched-source background sync (Phase J), which today just "launches the normal window unobtrusively." - Thumbnail toolbar buttons (
win.setThumbarButtons) — pause/cancel from the taskbar preview — and a jump list (app.setJumpList) with "Paste & download." - Taskbar overlay badge (
win.setOverlayIcon) showing active-download count / error state. - Settings discoverability.
src/renderer/src/components/SettingsView.tsxis ~1,000 lines across ~10 cards. Add a settings search box or a Basic / Advanced split so first-time users aren't faced with the full power-user wall.
Phase P — Reliability, longevity & trust
The least glamorous, highest-leverage track: make sure what's already shipped actually works, keep YouTube downloads working as the platform tightens, and remove first-run friction.
- PO Token generation. YTDLnis mints YouTube tokens in a WebView to dodge throttling
and "Sign in to confirm you're not a bot." This is becoming required for YouTube to
keep working at all. → reuse the existing persisted Electron
BrowserWindow(persist:aerofetch-logininsrc/main/cookies.ts) to host a token-minting script, or support a PO-token provider, passed via--extractor-args youtube:po_token=…. A longevity hedge more than a feature. - Verify the shipped-but-untested OS wiring (flagged across both parity tracks as
"reasoning + typecheck only"). A single real NSIS-install smoke-test pass over: the
cookies sign-in window export · the
aerofetch://protocol + Explorer "Send to" · the scheduled--sync+ RSS fast-check (Phase J) · aria2c · proxy. Converts a lot of "probably works" into "verified." - Code signing. The unsigned
.exetriggers SmartScreen "Run anyway" — the single biggest first-run friction for anyone you share it with. electron-builderwinsigning (OV/EV cert); EV builds SmartScreen reputation fastest. - i18n / language setting. Carried over from Phase E (deferred). YTDLnis and Seal both ship many languages. Large but optional — needs an i18n library + string extraction, with translations arriving incrementally.
Post-parity — what we reuse vs. build
Reuse unchanged: buildArgs/buildCommand flag emission, DownloadOptions +
DownloadOptionsForm, the renderer queue scheduler + concurrency cap + per-item options,
the persisted cookies BrowserWindow, the probe's chapters[]/format data, errorlog.ts,
CommandTemplates, extractIncomingUrl(), and the self-managing binary updater.
Build new: the trim/split UI + --download-sections/--split-chapters wiring (L), the
pause/reorder/schedule queue states (M), the terminal view + per-item playlist editor (N), the
main-process taskbar/tray/jumplist integration (O), and the PO-token provider (P).
Post-parity — suggested order
L is the high-value headline — trim + split-chapters share one UI surface and the keyframe
plumbing already exists, so they land cheaply and give AeroFetch YTDLnis's marquee capability.
Then P's PO-token + verify-wiring as a reliability block (keeps YouTube working and de-risks
shipped features). M and O are the daily-use polish and pair well (aggregate progress →
taskbar bar). N is the power-user surface, each item independently shippable. Each phase is
self-contained and rebuild-gated (npm run typecheck + npm run test + npm run build) the
same way Phases A–K were.