Commit Graph

32 Commits

Author SHA1 Message Date
debont80 ab085f2bfe Fix L26: consolidate drag-drop URL extraction into shared urlHelpers
firstUrl() in DownloadBar was a local duplicate of looksLikeUrl iteration
(scanning multi-line text for the first valid URL). Promoted to
firstUrlInText() in src/renderer/src/lib/urlHelpers.ts alongside the other
URL utilities, re-exported from useClipboardLink.ts. DownloadBar now imports
firstUrlInText from there instead of maintaining a local copy.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:50:39 -04:00
debont80 9abd2c4441 Fix L67/L86: Onboarding Skip button; clear hidden selections on filter change
L67: Onboarding now has a 'Skip' button alongside 'Get started', both in a
     right-aligned flex row. Both call update({hasCompletedOnboarding:true})
     so the behavior is the same — Skip just reads as optional for users who
     want to get started immediately without reading the tips.

L86: HistoryView now clears any selected IDs that are no longer visible when
     the filter (query or kind) changes. A useEffect on `filtered` intersects
     the current selection with the new visible set and calls setSelected only
     if the size actually changed (avoids re-renders when the filter doesn't
     affect the selection).

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:48:51 -04:00
debont80 6c7caa269c Fix L85: move launch.json from .claude/ to .vscode/ where VS Code reads it
.claude/launch.json was committed but VS Code reads launch configs from
.vscode/launch.json, so no tool was consuming it. Moved to the correct
location and removed the misplaced file from git tracking.

Also added .vscode/settings.json to .gitignore so local VS Code user
preferences don't end up tracked.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:47:03 -04:00
debont80 5241abbea9 Fix W18/W19: high-contrast support + window title already reflects state
W18: Added explicit forced-color-adjust:auto on the universal selector so
     the browser's HC mode can override all custom token colors. Added an
     @media(forced-colors:active) rule that replaces the CommandPalette
     backdrop's rgba(0,0,0,0.32) with the Canvas system color + a ButtonText
     border, so the palette remains perceivable under Windows High Contrast.

W19: Already fixed in the SR8 commit (window title now reflects download
     state via mainWindow.setTitle in the taskbarProgress handler). Marking
     done here with the companion CSS change.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:45:42 -04:00
debont80 fa4b41e0a5 Fix L48: strip ERROR: prefix from errorlog seed data to match production
cleanError() strips leading 'ERROR:' from yt-dlp stderr before sending
errors to the UI and error log. The browser-preview seed entry in
store/errorlog.ts had the raw 'ERROR: [youtube]...' prefix, making the
preview inconsistent with what production shows. Removed the prefix from
the seed string so the preview and production rendering match.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:44:18 -04:00
debont80 036ef74362 Fix SR8: update window title to reflect active download state
The window title was always 'AeroFetch' regardless of activity, so screen
readers and taskbar previews couldn't tell whether downloads were running.

The taskbarProgress IPC handler (which already fires on each progress tick)
now calls mainWindow.setTitle() alongside setProgressBar/setOverlayIcon:
- Idle: 'AeroFetch'
- Downloading: 'AeroFetch -- N downloads active'
- Error: 'AeroFetch -- Error'

This reuses the same overlay badge label string so the text is consistent
between the taskbar tooltip and the title bar.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:41:15 -04:00
debont80 04109d8220 Fix W8/W9: add Ctrl+, Settings shortcut; restore focus on palette close
W8: Added Ctrl+, as a standard Settings accelerator alongside the existing
    Ctrl+K palette shortcut. The key handler now handles both in one listener
    so there's a single window.addEventListener lifecycle.

W9: Command palette now restores focus to the previously focused element when
    it closes. prePaletteRef stores document.activeElement at open time;
    onClose fires a requestAnimationFrame focus-restore after unmounting the
    palette, so the focus returns to the trigger (a sidebar nav button, the
    URL input, etc.) rather than leaving focus stranded at the document body.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:39:51 -04:00
debont80 53aaed5fef Fix M15: replace role=button divs with native button elements in LibraryView
Both interactive headers in LibraryView used role="button" on divs, which is
invalid when they contain child interactive elements (group header had nested
All/None and Download buttons).

- Group header (groupHead): split into an outer flex div + a native <button>
  (groupToggle style) for the toggle/expand action + sibling Fluent Buttons
  for All/None and Download. The toggle button gets aria-expanded and a
  composite aria-label (title + item count). e.stopPropagation() removed
  from the sibling buttons since they are no longer inside the toggle.

- Source card header (cardHead): converted from a role="button" div to a
  native <button> with width:100%, border:none, and background:transparent.
  aria-label set to source.title. tabIndex/onKeyDown removed (native button
  handles keyboard by default).

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:37:33 -04:00
debont80 2f59a6ce63 Fix M10: consolidate .url Internet Shortcut parsing into shared utility
Both DownloadBar (renderer, drag-drop) and deeplink.ts (main, argv) had their
own regex to extract URL= from a Windows Internet Shortcut file -- with subtly
different patterns (/^\s*URL\s*=\s*(\S+)/ vs /^URL=(.+)$/).

Added parseUrlShortcutContent() to @shared/ipc. Both callers now delegate to
it and apply their own http/https guard (looksLikeUrl in renderer, asHttpUrl
in main). The local parseUrlFile wrapper in DownloadBar is kept as the
validation compositing point; it's now a one-liner.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:35:00 -04:00
debont80 a4fd0feb6b Fix L29/L52: QUALITY_OPTIONS moved to UI module; installer spawn via child_process
L29: QUALITY_OPTIONS was a presentational constant living in store/downloads.ts,
     coupling views to the store. Moved to src/renderer/src/qualityOptions.ts,
     which assembles it directly from @shared/ipc. store/downloads.ts no longer
     imports VIDEO/AUDIO_QUALITY_OPTIONS. Both consumers (DownloadBar, SettingsView)
     updated to import from the new module.

L52: Replaced the fixed `setTimeout(app.quit, 1500)` installer handoff with a
     detached spawn. The installer is spawned with stdio:'ignore' and unref'd so
     it outlives AeroFetch even on a slow machine. app.quit() fires on setImmediate
     so the IPC response returns to the renderer before the process exits.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:32:50 -04:00
debont80 1f2465576b Fix L19/L73: replace setTimeout focus hack with double-rAF; unify probe verb
L19: App.tsx replaced `setTimeout(() => focus(), 60)` in the "New download"
     palette action with `requestAnimationFrame(() => requestAnimationFrame(...))`
     to wait exactly one React render + paint cycle rather than an arbitrary 60ms
     timer that could race on slow machines.

L73: DownloadBar's probe button label changed from "Fetch" to "Check URL" (hint
     and aria-label both updated). Placeholder text updated to match. This
     distinguishes it from LibraryView's "Index" action, which catalogs an entire
     channel/playlist, making both labels clearly meaningful in context.

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:29:07 -04:00
debont80 3ae4a5357c Fix L46/L71/L74: unify yt-dlp missing message, editable dir inputs, swatch a11y
L46: Introduced YTDLP_MISSING_MSG constant in binaries.ts. All four callers
     (download, indexer, probe, ytdlp) now emit the same user-facing string:
     "yt-dlp.exe is missing. Open Settings -> Software update to re-download it."
     This removes newline-embedded messages and four divergent phrasings.

L71: Removed readOnly from videoDir/audioDir folder inputs in SettingsView.
     Added onChange handler so users can paste or type a path directly without
     being forced to use the Browse button. The path still flows through
     applySettings() in main, which validates it.

L74: Accent color swatches were triple-labeled (aria-pressed + aria-label +
     title). Replaced with a single aria-label that embeds "(selected)" state,
     plus title for the hover tooltip. Removed the incorrect aria-pressed
     (these are selection buttons, not toggles).

typecheck + 242 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:27:02 -04:00
debont80 b035a88873 Fix L6/L12: Select size=large variant, command palette scroll-into-view
L6: Select.tsx adds a `size` prop ('medium' | 'large'). When size='large',
    the select is 40px tall (matching Fluent size='large' Input/Button) with
    slightly larger padding and font. DownloadBar quality/format selects now
    use size='large' so they align with the row's large Input and buttons.

L12: CommandPalette keyboard navigation now scrolls the highlighted item into
     view on each ArrowUp/ArrowDown press. Uses a listRef + data-sel attribute
     so no DOM imperative tracking of individual buttons is needed.

typecheck + 242 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:22:50 -04:00
debont80 6a8452d6c3 Fix L9/L28/M11: shared thumb sizes, live defaults, clipboard IPC
L9: Extract four thumbnail box dimensions into src/renderer/src/thumbSizes.ts
    (THUMB_LG 120x68, THUMB_MD 108x64, THUMB_SM 72x44, THUMB_XS 60x34).
    DownloadBar, QueueItem, HistoryView, and LibraryView all import and use
    them instead of hard-coded strings -- one place to change the 16:9 sizing.

L28: DownloadBar now reflects Settings changes to defaultKind/defaultQuality
     in real time, but only while the URL field is empty (idle state). A URL
     typed or probed in progress is never discarded by a settings change.

M11: Clipboard reads now consistently use window.api.readClipboard (IPC) in
     the paste button, matching the clipboard-watcher hook. navigator.clipboard
     is no longer used for reads anywhere in the renderer.

typecheck + 242 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:20:21 -04:00
debont80 3df32d0505 Fix L22/L24/L27: kind-aware form, shell openUrl, Enter-to-download
L22: DownloadOptionsForm accepts optional `kind` prop. When set, hides
     audio-specific fields (Audio format) for video downloads and vice versa
     (Video container, Preferred codec) for audio downloads. Settings passes
     defaultKind so the defaults card shows only relevant options.

L24: Replace lone window.open('htmlUrl', '_blank') with a new openUrl IPC
     channel (shell:open-url) that calls shell.openExternal after validating
     the protocol is http/https. Consistent with how all other external opens
     work in the app. Preload + main handler + mock updated.

L27: DownloadBar URL-field Enter is now smart -- if formats/playlist are
     already loaded (or probe errored), Enter triggers download; otherwise
     it probes. Eliminates the keyboard dead-end where Enter never downloads.

typecheck + 242 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:16:15 -04:00
debont80 c038b94c6f Fix L14/L20/SR10/L150: CSS baseline, client datalist, copy polish
L14: base.css gets * { box-sizing: border-box } (removes ad-hoc swatch rule),
     :focus-visible outline using the Fluent brand token color, and
     -webkit-font-smoothing / -moz-osx-font-smoothing antialiasing.

L20: youtubePlayerClient Input now has a datalist of known client names
     (web, web_safari, web_embedded, mweb, tv, ios, android) so users
     get autocomplete suggestions. Still accepts free text for future clients.

SR10: Sidebar tagline changed from 'yt-dlp frontend' to 'Video downloader'
      (plain-English, not developer shorthand).

L150: Standardized 'PO Token' capitalization -- SettingsView label now says
      'YouTube PO Token (advanced)' consistently.

typecheck + 242 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:11:36 -04:00
debont80 67c0cd8bf4 Fix L8/SR9/M20: list keys, jargon hints, ProgressBar a11y
L8: TerminalView lines now carry a stable auto-increment id (keyed by that
    instead of array index) so front-trimming at MAX_LOG_LINES doesn't cause
    React to reuse wrong DOM nodes. SettingsView error list keyed by id alone
    (not id+occurredAt, which was redundant since IDs are already unique).

SR9: DownloadOptionsForm -- rewrote 'Container for merged video' to a plain
     description; rewrote format-sorting hint to remove raw '-S string' /
     'yt-dlp syntax' jargon.

M20: All ProgressBars now carry aria-label -- QueueItem (downloading +
     paused rows, keyed on title/URL), DownloadsView overall summary bar,
     SettingsView app-update download bar.

typecheck + 242 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:09:36 -04:00
debont80 4e3b141d6d M19: Move sidebarCollapsed from localStorage to electron-store
Sidebar collapsed state was an orphan in localStorage -- not backed up,
lost on portable installs, and inconsistent with every other preference.

Added sidebarCollapsed: boolean to the Settings type and DEFAULT_SETTINGS,
wired it through applySettings' boolean branch, and updated the renderer
store / main.tsx mock defaults. App.tsx now drives the toggle via
updateSettings({ sidebarCollapsed }) instead of localStorage.

typecheck + 242 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:04:30 -04:00
debont80 63a327a31c Fix L7/M13/M37: badge colors, secret masking, dev-jargon hints
L7: Canceled status chip now uses 'subtle' color (neutral) instead of 'warning'
    (amber) -- distinguishing it from paused which remains amber.
M13: Proxy URL and YouTube PO-token inputs now use type="password" to mask
     credentials, matching updateToken. Proxy URLs can carry user:pass; PO tokens
     are opaque secrets.
M37: Rewrite two dev-facing hints -- youtubePlayerClient now says 'how AeroFetch
     identifies itself to YouTube' instead of citing yt-dlp internals; filename
     template says 'Controls how saved files are named' instead of 'yt-dlp output
     template'. Remaining hints (cookie browser, aria2c, etc.) were already clean.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:01:25 -04:00
debont80 94c5723246 Fix L23/L78/L79: diagnostics hint, empty-state copy, SponsorBlock defaults
L23: Diagnostics error list now shows "Showing 20 of N errors." when there
     are more than 20 logged errors.
L78: DownloadsView empty-state copy updated from "Paste a URL above to get
     started" to "Paste a URL above, then click Download" -- matches the UI.
L79: SponsorBlock default categories expanded from ['sponsor'] to
     ['sponsor', 'selfpromo', 'interaction'] -- sponsor-only was silently
     covering almost nothing; the three new defaults are universally unwanted.
     Existing SB tests updated to pass explicit categories instead of using defaults.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:57:53 -04:00
debont80 3086ac469f Fix L16/L75/L77: datetime formatting, update-token visibility, ffmpeg re-check
L16: relTime now caps at weeks/months instead of unbounded 'N d ago' (adds w/mo
     buckets). formatWhen omits the year when the timestamp is in the current year.
L75: Update-token Field in the Software-update card is hidden by default; only
     shown when a token is already configured or an update-check error occurred.
L77: Adds a Re-check button next to the ffmpeg/ffprobe version display so a
     previously-not-found binary can be re-detected without restarting the app.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:54:19 -04:00
debont80 910cf23e61 Fix L17/L18/L56: regex validation, terminal visibility, SponsorBlock warning
L17: TemplateManager urlPattern field now validates the regex on each keystroke;
     an error message appears below the input and Save is blocked until fixed.
L18: Terminal nav item is hidden when customCommandEnabled is false -- no more
     dead-end gated view. Sidebar gains showTerminal prop; App.tsx reads the
     setting and passes it through.
L56: SponsorBlock Categories Field shows a 'warning' validationMessage when the
     toggle is ON but no categories are selected, making the silent no-op visible.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:48:41 -04:00
debont80 ba208de98b Fix L54/L61/L63: thumbnail error fallback, ipcMain.handle, audioQuality label
L54: DownloadBar preview image now tracks failed src in state (same pattern as
     MediaThumb); on load error shows the kind icon fallback instead of broken img.
L61: Convert taskbarProgress from ipcMain.on/ipcRenderer.send to ipcMain.handle/
     ipcRenderer.invoke so all IPC channels use the same request-response idiom.
     App.tsx calls void to explicitly discard the resolved promise.
L63: audioQuality() now has an explicit 'Best' case alongside the bitrate strings;
     unknown labels still fall back to '0' but the default branch is documented.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:45:45 -04:00
debont80 9d816f0c86 Fix L49/L53/L59: error string newlines, dialog null-safety, shared theme constant
L49: Remove newline from missing-binary error message in download.ts; inline
     error spans render it as literal newline, now one sentence with period.
L53: backup.ts exportBackup/importBackup/showMessageBox no longer use win!
     non-null assertions -- each uses a guarded ternary (same pattern index.ts
     already used for chooseFolder).
L59: Extract PAGE_BACKGROUND to src/shared/theme.ts; main/index.ts and
     renderer/src/theme.ts both import from it -- no more "keep in sync" comment.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:42:22 -04:00
debont80 72462cab1e Fix H3/L30/L44/L60/L90/L25/L32 polish: dependencies, naming, consistency
L30: Remove redundant loading=lazy from MediaThumb images inside the
     virtualizer which only mounts visible rows anyway.
L44: electron-builder.yml now excludes the actual eslint.config.js and
     .prettierrc rather than non-existent legacy .eslintrc/.prettierrc.yaml.
L60: IpcChannels.externalUrl renamed from external-url to app:external-url
     to match every other channel name category prefix.
L90: settings.ts sanitizeOptions now uses (ARR as readonly string[]).includes
     pattern consistently with setSettings; removes the as-never hack.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:37:05 -04:00
debont80 69d68e1854 Fix H3/M33/L25/L32: fix probe dependency, correct ipc comment, throttle taskbar, memoize palette
H3: probe.ts now imports fmtBytes from lib/formatters instead of download.ts,
    breaking the circular probe->download dependency direction.
M33: ipc.ts comment corrected to reflect that no batch cap exists; notes the
     known M33 issue for future implementation.
L25: App.tsx taskbar subscriber skips IPC when fraction/mode/badge unchanged,
     eliminating redundant roundtrips on every progress tick.
L32: paletteActions wrapped in useMemo so CommandPalette sees a stable array
     reference; only rebuilds when isDark changes.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:32:29 -04:00
debont80 8ab85da67e Fix H5/H6/M21/L37-40/L47/L55/L68/L72/L76/L91/L98/L108/L165/L166 audit items
H5: HistoryEntry now stores formatId+formatHasAudio; redownload passes them
    back to addFromUrl so the original yt-dlp format is reused, not guessed.
    Compound quality labels stripped to the preset prefix for old history.
L72: thumbnail now passed in redownload so re-queued rows keep their art.
H6: TerminalView log capped at MAX_LOG_LINES=2000 to prevent unbounded growth.
M21: --audio-quality omitted for lossless audioFormats (flac/wav).
L166: fmtEta hour rollover fixed in all 3 locations -- 2h00:00 not 120:00.
L165: queueStats formatSpeed precision aligned with fmtBytes.
L55: QueueItem suppresses sizeLabel when already in probed-format quality label.
L47: probeMeta null sends empty meta event so Resolving clears via SR6.
L68: notifiedBackground resets on window show for subsequent close-while-downloading.
L76: dup warning falls back to URL when title is still a placeholder.
L91: SettingsView onFormatSelect uses indexOf instead of unsafe tuple cast.
L98: Embed chapters field gets a hint text.
L108: BrowserWindow created with explicit title AeroFetch.
L37: pure formatters extracted to src/main/lib/formatters.ts and unit-tested.
L38: buildArgs test covers webm thumbnail suppression.
L39: CROP_SQUARE_PPA test is structural not exact-string.
L40: looksLikeUrl/looksLikeSingleVideo extracted to src/renderer/src/lib/urlHelpers.ts.

typecheck + 242 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:29:35 -04:00
debont80 1376c2dee8 Harden audit findings: correctness, type-safety, Windows conventions & polish
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified —
typecheck + 234 tests + eslint + prettier green).

Correctness / bugs:
- B3: match the release checksum to the asset's filename line (no wrong-hash verify)
- B4: newline-safe metadata probe (one --print with a unit-separator delimiter)
- B5 / L88: guard the meta event against canceled items; progress no longer promotes
  a queued item outside pump()
- B7: cookie-login promise always resolves (handles destroy-without-close)
- L146: trim parser rejects >2 colon-group times; M36: Library selection counts only
  actionable rows
- L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message,
  schedule picker min, dead-code/comment cleanup

Type safety:
- Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed)

Resilience / Windows / metadata:
- R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth)
- W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window;
  L147 dead macOS branches removed
- CL1: shared stdout markers; package/builder metadata (license, homepage, repository,
  copyright, tsbuildinfo glob)

Copy / docs / tests:
- M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled
- New unit tests for L35 (isValidMediaItem) and L36 (compareVersions)

This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard
work it builds on: background running + auto-download, library clipboard detection,
tray, binary management & library scale, credential encryption at rest, the shared
jsonStore and ui/ primitives, and the eslint/prettier tooling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:02:54 -04:00
debont80 a6a8c5f578 Update CODE-AUDIT.md: living checklist with stable IDs
Reformat from a one-off security report into a living checklist.
Adds the 2026-06-29 architectural review findings and a second
polish pass; all original security findings marked completed.
Items carry stable IDs for tracking across sessions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 13:47:45 -04:00
debont80 81b97ea429 Add placeholder app icon and wire it into the Windows build
Generate build/icon.svg — a white download glyph on the teal brand square,
mirroring the in-app brand mark (Onboarding.tsx) — and rasterise it to a
multi-size build/icon.ico (256/128/64/48/32/16) via ImageMagick. Uncomment
win.icon in electron-builder.yml so installers stop falling back to the default
Electron icon. Marks CODE-AUDIT.md M3 placeholder-done; a designed asset is
still wanted before v1.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:10:54 -04:00
debont80 18e1d9a24d Bundle ffprobe.exe and complete the real-download smoke-test pass
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>
2026-06-23 10:57:12 -04:00
debont80 b08d76a986 Apply all CODE-AUDIT.md findings (S1-S5, P1-P3, M1-M3)
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>
2026-06-23 10:22:43 -04:00