# AeroFetch Roadmap — feature parity with Seal AeroFetch is the Windows/Electron counterpart to [Seal](https://github.com/JunkFood02/Seal), the Android Material-You frontend for [yt-dlp](https://github.com/yt-dlp/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](https://github.com/JunkFood02/Seal) · [Seal on F-Droid](https://f-droid.org/packages/com.junkfood.seal/) · [It's FOSS overview](https://itsfoss.com/news/seal/). > **See also:** [ROADMAP-PINCHFLAT.md](ROADMAP-PINCHFLAT.md) — a parallel track that extends > AeroFetch from a one-shot downloader into a [Pinchflat](https://github.com/kieraneglin/pinchflat)-style > media manager (index entire channels → organize into `Channel / Playlist / Title` folders → > 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](test/real-download.integration.test.ts). That pass found and fixed a real shipping bug — the bundled `ffprobe.exe` was missing (see CODE-AUDIT C1). - [x] **Playlist downloads.** Probe now uses `yt-dlp -J --flat-playlist` and 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. - [x] **Subtitles.** Download + embed (`--write-subs`, `--write-auto-subs`, `--sub-langs`, `--embed-subs`, `--convert-subs srt`); language field + auto-caption toggle. - [x] **SponsorBlock.** `--sponsorblock-mark` / `--sponsorblock-remove` with a category multi-select (sponsor, intro, outro, selfpromo, music_offtopic…) + `--force-keyframes-at-cuts`. - [x] **More audio formats.** Was mp3-only. Now best / mp3 / m4a / opus / flac / wav / aac via `--audio-format`. - [x] **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. - [x] **Embed chapters.** `--embed-chapters` toggle. (`--split-chapters` still TODO.) - [x] **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 `--ppa` crop recipe survives yt-dlp's shlex split + ffmpeg's filtergraph.* - [x] **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. Threads `options` through `addFromUrl → DownloadItem → startDownload` and 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.* - [x] **Cookies.** Settings → Cookies has a source picker: - *From a browser's cookie store* → `--cookies-from-browser ` (chrome/edge/ firefox/brave/opera/vivaldi — `COOKIE_BROWSERS` in `src/shared/ipc.ts`). - *Sign-in window (built into AeroFetch)* → opens an Electron `BrowserWindow` on a persisted session partition (`persist:aerofetch-login`, see `src/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`) which `buildArgs` passes via `--cookies`. Settings → Cookies shows last-saved time + a "Clear saved cookies" button (also clears the partition's storage). - [x] **aria2c external downloader.** Settings → Network has a "Use aria2c downloader" toggle; when on and `resources/bin/aria2c.exe` exists, `buildArgs` emits `--downloader --downloader-args` (`ARIA2C_ARGS` in `src/main/buildArgs.ts`: `-x 16 -s 16 -k 1M`). Missing binary falls back silently to yt-dlp's own downloader (checked in `src/main/download.ts`). Binary is optional and not committed — see `resources/bin/README.md`. - [x] **Proxy** (`--proxy`) and **rate limit** (`--limit-rate`). Settings → Network fields (`Settings.proxy` / `Settings.rateLimit`), threaded through `buildArgs`'s `AccessOptions` param. - [x] **Restrict filenames** toggle (`--restrict-filenames`) + **download archive** (`--download-archive`, skip already-downloaded) — both in Settings → Filenames. The archive file lives at a fixed `userData/download-archive.txt` (`getDownloadArchivePath()` in `src/main/settings.ts`), not user-configurable. ## Phase C — Custom commands & yt-dlp management ✅ COMPLETE Named `CommandTemplate`s (`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. - [x] **Custom command templates.** Named, editable templates of extra yt-dlp args (`CommandTemplate`), CRUD'd inline via `TemplateManager.tsx`; persisted to `templates.json`. **Pick a default** via `Settings.defaultTemplateId` (a Select in the Custom commands card). The **"run custom command" mode** is `Settings.customCommandEnabled`, applied to every new download unless overridden per-download in the download bar's Custom command panel. - [x] **Command preview.** `IpcChannels.commandPreview` → `previewCommand()` (`src/main/download.ts`) builds the exact argv via the same `buildCommand()` path a real download would take, then renders it with `formatCommandLine`. Surfaced as a **Preview command** button in the download bar, with a Copy button. - [x] **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). - [x] **History search + multi-select + filter** (video/audio) + bulk delete + **re-download**. `HistoryView.tsx` gained a search box, an All/Video/Audio filter, a "Select" mode with per-row checkboxes + select-all + **Delete selected** (new `historyRemoveMany` IPC, `src/main/history.ts`), and a per-row re-download button that re-queues the entry's URL/kind/quality via the existing `addFromUrl`. - [x] **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. - [x] **Private / incognito mode** — a download bar toggle (`DownloadBar.tsx`, sticky like an incognito tab) sets `DownloadItem.incognito`, which `store/downloads.ts` checks before ever calling `useHistory().add(...)`. Queue items show an eye-off badge when private. - [x] **Backup / restore** settings + templates (export / import JSON). New `src/main/backup.ts` writes/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**. - [x] **Error log / copy error report** view (Seal's debug report). New `src/main/errorlog.ts` persists 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 Some items are Android-specific in Seal and adapted to Windows here. - [x] **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 `BrandVariants` ramp sharing toffee's lightness curve at a different hue (`src/renderer/src/theme.ts`). "Follow system" reads Electron's `nativeTheme.shouldUseDarkColors` in the main process (`src/main/index.ts`'s `resolveBackgroundMode`/`registerSystemThemeBridge`, pushed to the renderer over a new `system-theme:update` channel into `src/renderer/src/store/systemTheme.ts`) so the native window background stays in sync too, not just the in-page theme. High contrast: `nativeTheme.shouldUseHighContrastColors` surfaces 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 native `forced-colors` handling since nothing in the app sets `forced-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 test` clean): default Toffee, switching to Slate/Evergreen/Lavender, Light/Dark/Follow-system, and the sidebar toggle's break-out-of-system behavior all checked visually. - [ ] **i18n / language setting** (Seal ships ~40 languages). Large but optional — deferred, since shipping ~40 real translations isn't something to fake; doing this properly means wiring up an i18n library + extracting every string now, with translation content arriving incrementally from contributors. - [x] **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=`) — registered at install time via `electron-builder.yml`'s `protocols` field (NSIS), and at runtime via `app.setAsDefaultProtocolClient` in `src/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`'s `registerSendToShortcut()` writes a `.lnk` into `%APPDATA%\Microsoft\Windows\SendTo` via Electron's `shell.writeShortcutLink` (no native deps). Sending a `.url` Internet Shortcut file (what Explorer/browsers create from "Create shortcut"/dragging a tab to the desktop) passes its path as argv; `extractIncomingUrl()` parses the `URL=` 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.* - [x] **Onboarding / welcome screen** on first run. `Settings.hasCompletedOnboarding` (default `false`, persisted) gates a full-screen `Onboarding.tsx` rendered in place of the sidebar + main content in `App.tsx` — not a Fluent `Dialog`, since this app avoids Fluent's portal-based overlays entirely (GPU/driver blank-overlay issue noted in `src/main/index.ts` and `Select.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's `loaded` flag 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 test` clean): 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](ROADMAP-PINCHFLAT.md) (Phases F–K ✅) — this final track collects what's left, from two sources: 1. A **feature study of [YTDLnis](https://github.com/deniscerri/ytdlnis)** (the other major Android yt-dlp frontend), keeping only the features AeroFetch doesn't already have. 2. 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](https://github.com/deniscerri/ytdlnis) · [YTDLnis docs](https://ytdlnis.org/) · [YTDLnis changelogs](https://ytdlnis.org/changelogs/) · [F-Droid listing](https://f-droid.org/packages/com.deniscerri.ytdl/). ### 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.** Download only part of a video, or remove segments — by **timestamp** or by **chapter**, with multiple cuts allowed. → yt-dlp `--download-sections "*START-END"` (repeatable) + `--force-keyframes-at-cuts`. Chapters come free from the probe's `yt-dlp -J` `chapters[]` array (`src/main/probe.ts`). New fields on `DownloadOptions` (or a per-download override), surfaced as a **Trim** panel in `src/renderer/src/components/DownloadBar.tsx`. *v1 can skip a scrubber/preview player — timestamp inputs + checkboxes of the probed chapter list is enough. Smoke-test the exact multi-section output behaviour (one file vs. per-section) the same way Phases A–C smoke-tested live yt-dlp.* - [ ] **Split by chapters into separate files.** → `--split-chapters` (already flagged as a TODO in Phase A above). One toggle on `DownloadOptions` + `src/renderer/src/components/DownloadOptionsForm.tsx`; pairs naturally with trim. Uses an `--output chapter:` template for the per-chapter filenames. - [ ] **Metadata editing before download.** Change title / author / artist pre-download (YTDLnis: "modify metadata such as title and author"). → `--parse-metadata` / `--ppa "Metadata:-metadata title=…"`; fits the audio path of `DownloadOptions`. Good for building a clean music library where the source title is messy. ## Phase M — Queue & daily-use UX 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 `.part` files via `--continue` (already the default). Pause = kill the child (existing `taskkill /pid /T /F` path) but keep the partial + mark the item `paused`; resume = relaunch the same item, which continues. New `paused` state in the queue store. - [ ] **Reorder / prioritize the queue.** Drag a queued item up. The renderer scheduler (`pump()` promotes oldest-queued-first) already treats the queued array as the order, so this is reordering that array. - [ ] **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. - [ ] **Drag-and-drop a link or `.url` file onto the window.** A window `drop` handler that accepts `text/uri-list` and Internet-Shortcut files, reusing `extractIncomingUrl()` from `src/main/deeplink.ts` (already validates http(s)-only). - [ ] **Retry all failed.** One action that re-enqueues every entry in `src/main/errorlog.ts`. - [ ] **Duplicate detection.** Warn ("you already have this") when a URL/video-id is already in history or the active queue before enqueuing. - [ ] **Per-download scheduling + "save for later."** YTDLnis lets you "schedule by date and time" and park items. Add `scheduledFor?: number` + a `saved` state to `DownloadItem`; `pump()` gains a time gate before promotion. Distinct from the existing background `--sync` schedule (Phase J), which is for *sources*, not one-off items. ## Phase N — Power-user surface YTDLnis leans hard into this; AeroFetch has the building blocks (command preview, templates, per-item `options`) but not the surfaces. - [ ] **Built-in terminal mode.** An interactive raw-yt-dlp console with live stdout/stderr — YTDLnis's "terminal." AeroFetch already has command *preview* (`previewCommand` + `formatCommandLine` in `src/main/download.ts`) and a streaming spawn path; a terminal is "spawn arbitrary argv → stream to a log pane → input box." New view + one IPC channel. - [ ] **Per-playlist-item editing.** Edit each playlist entry separately — different format/type per item, multiple audio formats, batch-update type in one click (YTDLnis). Extend the playlist selection panel in `src/renderer/src/components/DownloadBar.tsx`; the queue already carries per-item `DownloadItem.options`, so the model supports it. - [ ] **Weighted format sorting ("format aspect importance").** Rank codec / container / quality and auto-pick by weighted priority. Extends today's single codec preference (`-S res,fps,vcodec:…`) into a fuller `-S` builder. - [ ] **URL-regex template auto-matching.** A `CommandTemplate` gains an optional URL pattern; `buildCommand` auto-applies the matching template (e.g. always use template X for `soundcloud.com`). Small extension to the existing `defaultTemplateId` logic in `src/main/templates.ts`. - [ ] **Command palette + keyboard shortcuts.** A Ctrl+K palette and a few global shortcuts (paste-and-download, focus URL, switch tabs). Today keyboard handling is just Enter in a couple of inputs. ## 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 `Tray` with 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.tsx` is ~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-login` in `src/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 `.exe` triggers SmartScreen "Run anyway" — the single biggest first-run friction for anyone you share it with. electron-builder `win` signing (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`, `CommandTemplate`s, `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.