Commit Graph

25 Commits

Author SHA1 Message Date
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