# Code Audit Living checklist of audit findings for AeroFetch. Security & correctness were reviewed 2026-06-23 (all fixed — see Completed). The architectural review (2026-06-29) added the structural items; a second polish pass (2026-06-29) added the smaller inconsistencies. Items carry stable IDs so we can check them off as they land this session. Severity = structural leverage / risk of future bugs, not "app is broken" (it isn't). **Session 2026-06-30 (correctness/safety/strictness pass):** landed B3 B4 B5 B7 (checksum- filename match, newline-safe meta probe, canceled-event guards, cookie-login never-resolve), L88 L146 (queue/trim edge cases), **L168 + L169** (`noUncheckedIndexedAccess` + `noFallthroughCasesInSwitch` now on — 0 errors after 8 real edge-case fixes), R5 (settings write failure handled), W1 W5 W6 (min window size, seeded folder picker, parented sign-in window), CL1 (shared stdout markers), L147 (dead macOS branches removed), M8 (StatusChip already unified the status→label map), M36 (Library selection only counts actionable rows), L11 (Queue header counts the live queue), L50 (no "saved" for 0 cookies), L156 + L57 (schedule picker `min`), L159 L15 L3 (dead-code/comment cleanup), package/builder metadata (L4 L41 L42 L43 L45 L58), user-facing copy (L66 L154; partial M37/SR9 jargon), doc reconciliation (M25 M26 L80 L81), and new unit tests (L35 isValidMediaItem, L36 compareVersions). All verified: typecheck + 234 tests + eslint + prettier green. **Session 2026-06-30 pass 2 (quality/format/UX polish):** H5 (history re-download now stores formatId/formatHasAudio + strips compound quality labels; thumbnail also passed — L72), H6 (TerminalView log capped at 2000 lines), M21 (skip --audio-quality for lossless flac/wav), L166 (fmtEta hour rollover fixed in download.ts, store/downloads.ts, queueStats.ts — "2:00:00" not "120:00"), L165 (formatSpeed precision aligned with fmtBytes), L55 (QueueItem suppresses sizeLabel when already in probed-format quality label), L47 (probeMeta null sends empty meta event to clear "Resolving…"), L68 (notifiedBackground resets on window show), L76 (dup warning falls back to URL when title is placeholder), L91 (SettingsView unsafe tuple cast fixed), L98 (embed-chapters hint added), L108 (window title set), L37 (formatters extracted to lib/formatters.ts; fmtBytes/fmtEta/parseProgress unit-tested including NA paths), L38 (webm thumbnail exclusion test), L39 (CROP_SQUARE_PPA structural test), L40 (looksLikeUrl/looksLikeSingleVideo extracted to lib/urlHelpers.ts; test imports pure module directly). All verified: typecheck + 242 tests + eslint + prettier green. **Session 2026-06-30 pass 3 (reliability — B/R/L races & silent failures):** L140 + L148 (the `active`-map teardown race — `cancelDownload`/`pauseDownload` release the concurrency slot synchronously and an identity-guarded `releaseActive` + per-spawn cookie jar make same-id retry/resume safe), L141 (renderer history capped at 500 + url de-dup to mirror main), R6 (`writeJsonAtomic` reports + logs write failures and keeps the data dirty for retry instead of an empty catch — log half; toast → CC8), R7 (`encryptSecret` warns before the plaintext fallback). All verified: typecheck + 243 tests + eslint green; the 5 touched files are prettier-clean. *Deferred this pass (need UI/live-app or larger design): B2/B6 (cancel buttons + abort wiring), R4 (can't safely identify a download's `.part` files at cancel time — needs the yt-dlp "Destination" path tracked), L139 (needs a spawn↔auto-update interlock), L142 (multi-site IPC-return reconciliation, generalises M34), L157 (cross-store cancel-on-remove, ties to C2), R9 (clock-skew — "note for awareness").* **Session 2026-06-30 pass 4 (Critical — store architecture):** **C1** (single source of truth for the preview mock + Settings defaults — `DEFAULT_SETTINGS` in `@shared`, one typed `mockApi.ts`, centralized `isPreview`) and **C2** (broke the `downloads ↔ sources` circular import via a pure typed event bus, `store/coordinator.ts` — neither store imports the other now). All verified: typecheck + 243 tests + eslint green; the touched files are prettier-clean (3 pre-existing format violations in DownloadOptionsForm/LibraryView/SettingsView are untouched and out of scope). **Session 2026-07-01 (main-process structure — the H1-adjacent decompose batch):** **L2** (the ~200-line flat IPC block moved into one `src/main/ipc.ts` `registerIpcHandlers(getMainWindow)`; index.ts is now lifecycle + window creation and imports the shared theme helpers from ipc.ts), **L69** (path helpers split into `src/main/paths.ts`; settings.ts is now purely the electron-store layer), **L10** (a main-process `src/main/constants.ts` centralizes the spawn timeouts, execFile buffers, stderr tail, and the three store caps — nine modules migrated off bare literals), **CL2** (`buildArgs` → single `BuildArgsInput` object), **CL3** (`startDownload`'s stream/close/error/watchdog wiring extracted into `wireChildProcess`), and **L1** (the scheduled-download promoter tick moved from store-module load into an App `useEffect` so a store import no longer starts a stray timer). All verified: typecheck (node+web) + 253 tests + eslint + production build green; the touched files are prettier-clean (the 8 remaining prettier warnings are all pre-existing, untouched files — DownloadOptionsForm/SettingsView/settings/*Card). **Session 2026-07-01 pass 2 (the "finish the audit" batch — phase-by-phase PRs):** landed **Phase 1** (doc/stale-checkbox reconciliation — L82, L89, plus ticks for wire-or-remove M5/M6/UX1, MAX_ENQUEUE_BATCH, and the UX/ref duplicates whose roots already shipped), **Phase 2** (build hygiene — L83 `.blockmap` off via `nsis.differentialPackage:false`, L84 `dist/` clean step), and **Phase 3 (main foundation)**: **CC8** (in-house file-backed leveled logger [logger.ts](src/main/logger.ts) + renderer `log:write` sink), the **`execFileAsync`** consolidation ([lib/exec.ts](src/main/lib/exec.ts) — the five hand-rolled spawn-and-read wrappers in probe/ytdlp×2/ffmpeg/indexer/download.probeMeta; SIMP2, and the foundation for CC4/CC5), **L136** (incognito is now enforced in main — no errorlog entry, no title/URL in the OS toast, no login/ browser cookies), and **R9** (the yt-dlp update throttle no longer skips forever after a backward clock correction). Each phase is its own branch/PR stacked on `refactor/h1-decompose-god-files`. All green: typecheck + 263 tests + eslint + prettier. **Next:** M4 (queue persistence — its own focused PR), the download-engine lifecycle items (R4/L65/L139/L157/B2/B6 — need a live download to verify), the CC4/CC5 tails (SIMP5 line-buffer + SIMP16 net.request), then Phases 4–8 (renderer tokens+primitives, logic/perf, UX/Windows, copy/a11y). Per the run's policy, subjective-visual and live-verify-only items are implemented but left **unchecked** with a flagged list for a watched pass. **Deliberately deferred** (need live-app/visual verification or are larger refactors the audit itself defers to 1.x): the wire-or-cut features (M5/M6/UX1), the god-file/store refactors (C1, C2, H1), the SIMP* helpers + shared-UI-token work, the CC* consolidations, PERF8 code-splitting, and the visual UI/UX polish long-tail. These are best done as their own PRs with the app running so the UI can actually be checked. --- # 1.0 Release-Readiness Audit (lead-engineer synthesis) *A pre-release synthesis of the ~385 catalogued findings below — root causes, a release gate, and the consequential issues per dimension (severity · reasoning · fix · benefit). The detailed, ID'd backlog follows. No rewrite required; everything here is incremental.* ## Verdict **Not 1.0-ready as-is, but close — roughly 3–4 focused PRs away.** The foundation is genuinely strong: disciplined security model (context isolation, sandbox, argv-injection defense, host-pinned signed-checksum updater), a clean pure/impure architecture, and good unit coverage of the pure logic. What blocks a *commercial* 1.0 is a thin layer of (a) real reliability bugs, (b) naive persistence, (c) built-but-unwired features, (d) first-run/perceived-quality gaps, and (e) the absence of enforcement/observability tooling. None require rearchitecting. ## Root-cause themes (the 385 findings collapse to 8) 1. **Built-but-unwired features** — command preview (M5), incognito (M6), per-download options/extraArgs (UX1) are plumbed end-to-end with no UI; the ROADMAP even marks them ✅ (M25). *Decide wire-or-cut.* 2. **"Two ways to do X" with no enforcement** — persistence (M1), validation (CC9), async (CC5), errors (CC6), UI primitives (UI14/UI18/UI19), formatters (M9) — and no ESLint/Prettier to hold any line (CC2). 3. **Renderer-optimistic state never reconciles + a store cycle** — M34/L142 (settings show unsaved values), C2 (downloads↔sources circular import). 4. **Naive JSON persistence** — non-atomic writes (R1), corrupt→silent total data loss (R2), O(n²) full-file rewrites per completion (R3/PERF7), no cache. 5. **Download-engine reliability gaps** — no stall timeout (B1), reverse-order batches (M32), orphan `.part` on cancel (R4), retry-during-teardown race (L140). 6. **First-run / perceived quality** — light-default theme (SR1), nightly-default yt-dlp (SR2), placeholder icon (W14), stuck "Resolving…" (SR6), progress bar visibly restarting (SR7), unsigned build (SIGNING.md). 7. **Accessibility & Windows-native feel** — focus rings (UI28/29), no list keyboard nav (W7), title-bar theme (W3), text-field context menu (W4), no `aria-live` (W17), input labels (M28). 8. **No enforcement / observability** — no lint (CC2), `noImplicitAny:false` (M38), no logging (CC8/M29), no production source maps (L170). ## Release gate **MUST fix before 1.0 (blockers)** — correctness, data safety, security, trust: B1 · M32 · M35 · M34 · R1 · R2 · R3 · H7 · H8 · SR1 · SR6 · SR7 · W14 — **all resolved as of this session.** Code signing was dropped from the gate: it's a deliberate non-goal (no certificate will be purchased; the SmartScreen prompt on the unsigned build is accepted). **SHOULD fix for 1.0 (high):** wire-or-cut the dead features (M5/M6/UX1) · a11y cluster (UI28/29, W4, W7, W17, M28) · W3 native theming · lint + `noImplicitAny` (CC2/M38) · dev-jargon copy (M37/SR9) · destructive confirmations (UX4) · PERF1/PERF2 (per-download redundant work). **DEFER to 1.x:** the ~150 Low items · the simplification refactors (SIMP*, do incrementally) · the UI token system + shared primitives (UI/SIMP — high value, larger effort) · i18n · sqlite migration. ## By dimension (severity · reasoning · fix · benefit) ### Reliability - **[Critical] No download stall timeout (B1).** *Reasoning:* `spawn` has no timeout + no `--socket-timeout`; a dead connection hangs forever and permanently consumes a concurrency slot. *Fix:* `--socket-timeout` + an idle watchdog that kills+errors via the existing `killTree`. *Benefit:* downloads self-recover on flaky networks. - **[Critical] Non-atomic writes + silent corruption→data-loss (R1/R2).** *Reasoning:* `writeFileSync` isn't atomic; a crash mid-write corrupts the store and the next read returns `[]`, silently wiping history/ sources. *Fix:* atomic write (temp+rename) + back up a corrupt file before resetting, in one `jsonStore`. *Benefit:* user data survives crashes; no silent loss. - **[High] Reverse-order batch downloads (M32)** & **history duplicates (M35).** *Reasoning:* `addMany`+`pump` promote highest-index first; `redownload` mints a new id defeating dedup. *Fix:* reverse the batch enqueue; dedup history by URL. *Benefit:* playlists download 1→N; clean history. - **[High] Optimistic state never reconciles (M34).** *Reasoning:* renderer keeps a value main rejected. *Fix:* apply the validated `Settings` the IPC returns. *Benefit:* the UI never lies about what's saved. ### Performance - **[High] O(n²) media-items rewrite (R3/PERF7).** *Reasoning:* every completion re-reads+rewrites the whole (≤20k-item) file synchronously. *Fix:* in-memory cache + batched atomic writes (the same `jsonStore`). *Benefit:* channel downloads stop hitching the main process. - **[Medium] Per-download redundant work (PERF1/PERF2).** *Reasoning:* `templates.json` read + settings decrypt on every spawn even when unused. *Fix:* gate `listTemplates()` on the consent flag; cache decrypted settings. *Benefit:* lower latency/IO per download. - **[Low] `summarizeQueue`/`pump` O(n) per tick (PERF3/PERF4).** *Fix:* memoize + count-based pump. *Benefit:* scales to large queues. ### Maintainability - **[High] Triplicated Settings/`Api` + the preview mock (C1).** *Reasoning:* a 4th touch-point per IPC method; drift-prone. *Fix:* `DEFAULT_SETTINGS` in `@shared`; one typed `mockApi`. *Benefit:* one place to change. - **[High] Store circular dependency (C2).** *Fix:* a coordinator/event bus. *Benefit:* removes a latent init crash. - **[Medium] Duplicated persistence/validation/async/formatters (M1/CC9/CC5/M9).** *Fix:* the `lib/` helpers in SIMP1–SIMP5. *Benefit:* ~−500 LOC and fewer divergence bugs. - **[Medium] God files (H1).** *Fix:* SettingsView via ``/`` (SIMP10). *Benefit:* 1104→~600 lines, reviewable. ### Consistency - **[Medium] Two ways to do X** across persistence, status chips, segmented controls, buttons, errors (M1/UI18/UI14/UI15/CC6). *Fix:* one shared primitive/standard each (see "Recommended single style"). *Benefit:* the app reads as one product. - **[Low] Naming/copy drift** (CC1, L95/L150, separators L164). *Fix:* the conventions in CC1. *Benefit:* professional finish. ### Readability - **[Medium] Magic strings/numbers + long signatures + deep nesting (CL1/CL2/CL4, L10).** *Fix:* `constants.ts`, options-objects, extracted helpers. *Benefit:* faster comprehension, fewer transpose bugs (CL2). ### Polish (perceived quality) - **[High] First-run defaults (SR1/SR2/SR3).** *Reasoning:* light-on-dark first launch, nightly yt-dlp by default, auto-download-new on. *Fix:* `theme:'system'`, `ytdlpChannel:'stable'`, `autoDownloadNew:false`. *Benefit:* the first 10 seconds feel native and safe. - **[High] Stuck "Resolving…" (SR6) & restarting progress bar (SR7).** *Fix:* clear placeholder on error; weight the two merge phases. *Benefit:* nothing looks hung/glitchy. - **[High] Placeholder icon (W14).** *Fixed:* a designed mark (teal gradient square + top sheen, bold rounded download glyph over a landing shelf) replaces the flat placeholder; multi-size `.ico` regenerated. *Benefit:* brand credibility. *(Unsigned build is a non-goal — no cert; the SmartScreen prompt is accepted.)* ### User experience - **[High] Per-download options are unreachable (UX1).** *Reasoning:* must change global settings to tweak one download. *Fix:* wire the existing `DownloadOptionsForm` into the bar (plumbing exists). *Benefit:* reclaims a whole feature. - **[High] Destructive actions have no confirmation/undo (UX4).** *Fix:* confirm Clear/Remove/Delete. *Benefit:* prevents data loss. - **[Medium] Silent failures on Open/Show (UX6)** and **no global download status off the Downloads tab (UX9).** *Fix:* surface errors; sidebar badge. *Benefit:* the app feels responsive and honest. ### Accessibility & Windows-native (UX-critical for a Windows product) - **[High] Fragmented/invisible focus + no list keyboard nav (UI28/29, W7).** *Fix:* one focus-ring + roving list focus + Delete/Ctrl+A. *Benefit:* keyboard- and Narrator-usable. - **[Medium] Title bar ignores in-app theme (W3); text fields have no context menu (W4); no `aria-live` (W17); inputs lack accessible names (M28).** *Fix:* sync `themeSource`; add an editing context menu; live regions; `aria-label`s. *Benefit:* feels like a native, accessible Windows app. ### Developer experience - **[High] No enforcement tooling (CC2) + `noImplicitAny:false` (M38).** *Reasoning:* style/type safety rely on discipline; implicit `any` is allowed. *Fix:* Prettier + typescript-eslint + CI; re-enable `noImplicitAny`. *Benefit:* the conventions stay true; regressions caught pre-merge. - **[Medium] No logging + no prod source maps (CC8/M29/L170).** *Fix:* one leveled file logger at every catch; hidden source maps. *Benefit:* field crashes become diagnosable instead of invisible. - **[Low] Stale roadmap/docs (M25/M26, L80–L82); release checksum is manual (H8).** *Fix:* reconcile docs; generate `.sha256` in `build:win`. *Benefit:* trustworthy docs; updates don't silently fail to install. ## Suggested PR sequence to 1.0 1. **Correctness & data safety:** B1, M32, M35, M34, and the cached/atomic `jsonStore` (R1/R2/R3/PERF7). 2. **Security & trust:** H7 (encrypt cookies), H8 (auto-generate checksums). *(Code signing is a non-goal — no cert.)* 3. **First-run polish:** SR1/SR2/SR3 defaults, SR6/SR7 status, W14 icon, W3 title-bar theme. 4. **Wire-or-cut + a11y + tooling:** UX1 (per-download panel) or remove M5/M6; focus/keyboard (UI28/29, W7, W4); Prettier+ESLint+`noImplicitAny` (CC2/M38). 5. **Incremental cleanup (post-1.0):** the SIMP refactors and the Low/UI/UX long tail, behind the new lint gate. --- ## Critical - [x] **C1 — Single source of truth for the IPC mock + Settings defaults.** `const PREVIEW` redeclared in 8 files; `main.tsx` reimplements the entire `Api` (~180 lines); the full `Settings` object is hand-maintained in 3 places (`main/settings.ts` `DEFAULTS`, `renderer/store/settings.ts` `FALLBACK`, `main.tsx` `MOCK_SETTINGS`). Add `DEFAULT_SETTINGS` to `shared/ipc.ts`, extract the preview mock into one `mockApi.ts` typed as `Api`, centralize `isPreview`. *Fixed: `DEFAULT_SETTINGS` is now the canonical default in [shared/ipc.ts](src/shared/ipc.ts); main's `DEFAULTS`, the renderer `FALLBACK` ([store/settings.ts](src/renderer/src/store/settings.ts)), and the preview `MOCK_SETTINGS` all derive from it (preview-only field tweaks layered on top) instead of three hand-kept copies. The whole browser mock moved into one [mockApi.ts](src/renderer/src/mockApi.ts) typed `Window['api']` (so a missing/mistyped method is a compile error), and `main.tsx` shrank to `window.api = mockApi`. The `PREVIEW` check is single-sourced in [isPreview.ts](src/renderer/src/isPreview.ts) and imported (as `PREVIEW`) by all 8 sites.* - [x] **C2 — Break the `downloads ↔ sources` store circular dependency.** `downloads.ts` imports `useSources`; `sources.ts` imports `useDownloads`. Works only via lazy `.getState()`. Introduce a coordinator/event bus that owns cross-store reactions. *Fixed: a tiny typed event bus [store/coordinator.ts](src/renderer/src/store/coordinator.ts) (no runtime deps — the `AddEntry` import is type-only) now owns the two cross-store reactions. `sources` emits `enqueueDownloads` (consumed by `downloads.addMany`); `downloads` emits `downloadCompleted` (consumed by `sources.markDownloaded`). Each store imports the bus, not the other, so the cycle is gone. Because `downloads` no longer pulls in `sources`, `App` now eagerly imports the `sources` store for side-effects (`import './store/sources'`) so its startup load + scheduled- `--sync` kickoff and the `downloadCompleted` subscription still run at launch — robust even once the views are lazy-loaded (PERF8).* ## High - [x] **H1 — Decompose god files.** `SettingsView.tsx` (1104, ~11 cards, ~30 selector subs + ~20 useState), `DownloadBar.tsx` (858, ~17 state hooks), `store/downloads.ts` (614). *Done on `refactor/h1-decompose-god-files`: SettingsView → `components/settings/*.tsx` (one component per card + shared `settingsStyles.ts`), leaving a ~90-line shell (search box + display-toggle filter); each card still renders one `` DOM node so the filter is unchanged. DownloadBar → a `downloadBar/useDownloadBar.ts` hook (all state + handlers, wrapper handlers keep the preview-invalidation side-effects) + `downloadBar/styles.ts` + `downloadBar/PlaylistPanel.tsx`, leaving a render-only shell. store/downloads.ts → `downloadTypes.ts` (types, re-exported so importers are untouched) + `downloadItem.ts` (buildItem/titleFromUrl/RESOLVING) + `downloadSeed.ts` (preview seed), leaving the store + recordCompletion + wiring. Behaviour unchanged; full typecheck + lint + production build + 248 tests green.* - [x] **H2 — Consolidate scattered utilities.** `youtubeId` ×2; 4 YouTube-URL-parsing variants; byte/speed/eta/duration formatting across 4 modules. *URL half: one canonical `youtubeId` now lives in [urlHelpers.ts](src/renderer/src/lib/urlHelpers.ts) (the complete watch/shorts/embed/live/youtu.be parse); `thumb.ts`, `queueStats.ts` (`sameVideo`), and `store/downloads.ts` (`titleFromUrl`, which also fixes an old bug where any host with a `?v=` param was mislabeled "YouTube video") all import it — the three ad-hoc reimplementations are gone. Formatter half (done with H4): `fmtBytes`/`fmtSpeed`/`fmtEta` now have a single home in [@shared/format.ts](src/shared/format.ts) imported by both sides; the main formatters module re-exports them and the renderer's three private copies (a byte formatter, an ETA formatter, and a speed string→number re-parser in `queueStats`) are deleted. Per-item and aggregate speed now use the same 1024-based scale (the old aggregate used 1000, a latent inconsistency). Unit-tested in `test/clipboardLink.test.ts` + `test/download.test.ts` + `test/queueStats.test.ts`.* - [x] **H3 — Fix `probe.ts → download.ts` dependency direction** (`fmtBytes` import); resolves with H2's shared formatter. - [x] **H4 — Carry raw numbers across the progress boundary.** `DownloadProgress` ships formatted `speed`/`eta` strings; `queueStats.ts` re-parses them back to numbers. *Fixed: `DownloadProgress` now carries `speedBytesPerSec`/`etaSeconds` as raw numbers (main's `parseProgress` no longer formats them); the renderer stores the raw numbers on `DownloadItem`, `QueueItem` formats them for display via [@shared/format](src/shared/format.ts), and `summarizeQueue` sums/maxes the numbers directly — the lossy string→number→string round-trip (and `queueStats`' `parseSpeed`/`parseEtaSeconds`) is gone. Done together with the formatter half of H2.* - [x] **H5 — History re-download silently changes quality.** `HistoryView.redownload` passes the stored `quality` (for format-picker downloads this is a *label* like "720p · mp4 · 184 MB") back as `quality` with no `formatId`; `buildArgs.videoFormat()` has no matching case and falls back to `bv*+ba/b` (Best). The queued item shows "720p…" while actually fetching Best. - [x] **H6 — TerminalView log grows unbounded.** `setLines((ls) => [...ls, …])` with no cap; a verbose run (`-F`, `--verbose`) streams thousands of lines into React state. Every other log/list in the app is capped — cap this too (and/or virtualize). - [x] **H7 — `cookies.txt` written in plaintext with no restrictive perms.** [cookies.ts](src/main/cookies.ts) `writeFileSync(getCookiesFilePath(), …)` stores live auth/session cookies unencrypted under `userData`. In the **portable build** that's `AeroFetch-data/` next to the exe (USB stick / shared Downloads folder), so anyone with folder access can read a logged-in session. Settings secrets are DPAPI-encrypted (settings.ts) but cookies are not. Encrypt at rest or document the exposure for the portable/shared-PC scenario the app explicitly targets. - [x] **H8 — Release checksum is a manual step the updater hard-requires.** [updater.ts](src/main/updater.ts) sets `REQUIRE_CHECKSUM = true` and refuses any update lacking a `.sha256`, but `build:win` (`electron-vite build && electron-builder --win`) never generates one. Evidence in `dist/`: only `0.4.1` has `.sha256` files (hand-made); the current `0.5.0` build does not. If a release is published without manually adding the checksum, **every client's in-app update fails** ("This release has no checksum … refusing to install"). Generate the `.sha256` in the build/release script. ## Medium - [x] **M1 — Unify JSON persistence.** `sources.ts` has generic `readJsonArray`/`writeJson`; `history.ts`/`errorlog.ts`/`templates.ts` reimplement it inline. - [x] **M2 — Remove dead code: `getDefaultFolder` / `download:default-folder`** (channel + preload + handler + mock, zero callers). - [x] **M3 — Remove duplicated completion side-effect in `store/downloads.ts`** (history write + `markDownloaded` in both `applyEvent('done')` and the preview ticker). Subsumed by C2. - [x] **M4 — Decide queue persistence explicitly.** Scheduler/queue is renderer-memory only; `saved`/scheduled items don't survive a quit. Persist or document as a non-goal in code. *Fixed (persist, per the run decision): a main [queue.ts](src/main/queue.ts) store (the proven cached-atomic `createJsonStore`, `queue.json`) plus `queue:list`/`queue:save` IPC. The renderer mirrors its durable items on every queue change (a `PersistedQueueItem` = the stable subset of `DownloadItem`; runtime-only progress/speed/eta are dropped, and a signature over the persistable subset means progress ticks don't re-save) and rehydrates on launch via an `App` `useEffect`. On restore a `downloading` item (its process died) normalizes back to `queued` (yt-dlp continues the `.part`), `saved`+`scheduledFor` survives so the promoter fires it when due, `paused`/`error` return actionable; `completed`/`canceled` are never persisted. A `queueHydrated` gate stops a launch-time store change wiping the file before it's read. Pure mappers extracted to [queuePersist.ts](src/renderer/src/store/queuePersist.ts) and unit-tested (`test/queuePersist.test.ts`); plumbing is typecheck-verified. **Live-verify note:** the OS-level quit→relaunch cycle itself wasn't smoke-tested here — worth a manual confirm (schedule/park an item, quit, relaunch).* - [x] **M5 — Command-preview feature is fully built but unwired.** `CommandPreviewResult` type + `command:preview` channel + `previewCommand` (download.ts) + `formatCommandLine`/ `quoteForDisplay` (buildArgs) + preload method all exist, but no renderer component calls `window.api.previewCommand` (only the `main.tsx` mock references it). Wire it into the DownloadBar Options panel, or remove the dead chain. *Fixed: a "Show command / Hide command" toggle in [DownloadBar.tsx](src/renderer/src/components/DownloadBar.tsx) calls `window.api.previewCommand` and renders the exact yt-dlp argv (monospace). The preview mirrors what `download()` spawns — probe-selected `formatId`/`formatHasAudio`, `quality`, per-download `options`, and `trim` — and is invalidated whenever any of those inputs (or the URL / a re-probe) changes, so the shown command never goes stale. Wired with M6/UX1 in the same panel.* - [x] **M6 — Private/incognito mode is plumbed but unreachable.** `incognito` flows through `AddOptions` → `buildItem` → history-skip → the QueueItem "Private" badge, but nothing in the UI ever sets `incognito: true`. Add the toggle or remove the dead plumbing. *Fixed: an "Incognito mode (no logging, no history, no cookies)" checkbox in the DownloadBar Advanced panel sets `incognito` on the enqueued item (via `AddOptions`), lighting up the existing QueueItem "Private" badge. Reset to off after each download so it never silently carries over to the next.* - [x] **M7 — `newId()` duplicated 3×** (`store/downloads.ts`, `TerminalView.tsx`, `TemplateManager.tsx`) with divergent fallback prefixes (`item-`/`t-`/`tpl-`). Extract one helper. *Fixed: one [`newId(prefix)`](src/renderer/src/id.ts) helper; the three copies import it and pass their prefix. The fallback gained a monotonic counter so same-millisecond ids can't collide (subsumes L33/L70). Unit-tested in `test/id.test.ts`.* - [x] **M8 — Two download-status→label maps.** `STATUS_BADGE` (QueueItem) and `STATUS_LABEL` (LibraryView) independently map the same statuses (completed = "Completed" vs "Downloaded"). One shared map. - [x] **M9 — Three time formatters.** `relTime` (LibraryView), `formatWhen` (HistoryView), `fmtSchedule` (QueueItem) — consolidate into a date util. *Fixed: all three moved to [`datetime.ts`](src/renderer/src/datetime.ts) and imported by their views; unit-tested in `test/datetime.test.ts`.* - [x] **M10 — `.url` shortcut parsing duplicated** — `parseUrlFile` (DownloadBar) vs `readUrlShortcut` (main/deeplink). Different `URL=` extractors for the same file format. - [x] **M11 — Inconsistent clipboard access.** Reads use `navigator.clipboard.readText` (paste button) *and* `window.api.readClipboard` (suggestion watcher); writes use `navigator.clipboard.writeText` (copy report). Pick one strategy. - [x] **M12 — Shared `errorText` style.** `tokens.colorPaletteRedForeground1` is applied inline 12× across 5 files instead of one class. *Partially fixed: added [`useErrorTextStyles`](src/renderer/src/components/ui/errorText.ts) (`error` / `errorPre` for pre-wrap multi-line output) and adopted it in `SettingsView.tsx` — the biggest offender — replacing all 6 inline `style={{ color: … }}` error spans + its local `errorRowText` class. The remaining red references live in per-component `makeStyles` classes (DownloadBar/LibraryView/QueueItem/TerminalView), not inline; migrate those to the shared hook incrementally.* - [x] **M13 — Inconsistent secret-field masking.** `updateToken` uses `type="password"`; `proxy` (may carry `user:pass@`) and `youtubePoToken` (a token) are plain-text Inputs. - [x] **M14 — Settings search mutates React-owned DOM.** The search toggles each card's `el.style.display` directly; fragile (breaks if a card ever gets a conditional `style`) and matches on `textContent` incl. hidden text. Prefer state-driven filtering. *Fixed: each card renders inside a React-owned wrapper whose visibility is driven by a `hidden` state array in [SettingsView.tsx](src/renderer/src/components/SettingsView.tsx) — the filter no longer writes `display` onto a card's own node, so a card setting its own inline style can't break it. Matching still reads the wrapper's `textContent` (so search covers every visible label without hand-maintained per-card keywords); the minor "matches hidden descendant text" point is unchanged and left as-is.* - [x] **M15 — Nested interactive controls in `role="button"` (a11y).** LibraryView's group header is a `role="button"` div containing `