Commit Graph

31 Commits

Author SHA1 Message Date
debont80 9134e7d216 feat(audit): Batch 25 — project reorg (CC12), leading DI args (CC7), CC10 by-design
CC12: renderer views/ (5 screens + Onboarding + settings cards) +
lib/ (theme/thumb/datetime/id/qualityOptions/useClipboardLink/queueStats);
main core/ (buildArgs/validation/indexerCore/ytdlpPolicy). git mv preserved
history; all src+test imports updated. Build emits view chunks from views/,
344 tests + typecheck green, live probe rendered all screens.
CC7: wc/onProgress is the leading arg everywhere — downloadAppUpdate(wc,url),
indexSource(onProgress,url,signal), indexSourceCancelable(onProgress,url).
CC10: closed by-design — jsonStore backs all records; settings stay in
electron-store for DPAPI secret encryption (a deliberate two-store split).
Also closes the UI24/W4 context-menu cross-reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:37:28 -04:00
debont80 134c6a167c feat(audit): Batch 23 — settings key renames with migration (CC1) + config.ts (CC11)
CC1: customCommandEnabled→enableCustomCommands, downloadArchive→
useDownloadArchive, clipboardWatch→watchClipboard, sidebarCollapsed→
isSidebarCollapsed, hardwareAcceleration→useHardwareAcceleration,
videoDir/audioDir→videoFolder/audioFolder (+ chooseDir/clearDir store
actions → chooseFolder/clearFolder). Rename map in settingsMigration.ts
(pure, unit-tested incl. pre-rename backup fixture), applied as an
idempotent on-disk shim at store open and on backup import so old
settings.json and old backups both keep working.

CC11: update host/owner/repo + release API moved to src/main/config.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:34:05 -04:00
debont80 96800d5a17 feat(audit): L51 + L64 — daily-sync time picker + aria2c connection tuning
Two small self-contained features from the audit's deferred list:

- L51: the Task Scheduler daily sync is no longer pinned to 09:00. New
  Settings.syncTime (24h HH:MM) with a native time input beside the Library
  "Daily sync" switch — persists on change, re-registers the task on blur
  (schtasks /Create /F overwrite is the time-change mechanism). The value
  reaches the schtasks /ST argv, so a shared isValidSyncTime guards it at
  both the settings write and in schedule.ts (unit-tested incl.
  injection-shaped input). Live-verified against the machine's real task.

- L64: aria2c connection count (-x/-s) is user-tunable. New
  Settings.aria2cConnections (1-16, aria2c's own ceiling) exposed as a
  SpinButton in Settings -> Network while the aria2c toggle is on.
  ARIA2C_ARGS became the pure aria2cArgs(n?) with a defensive clamp;
  threaded through AccessOptions. The -k 1M split floor stays fixed — a
  free-text field would bypass the custom-command consent gate.

typecheck + 309 tests + eslint + production build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 22:57:20 -04:00
debont80 e19f988c72 feat(audit): Batch 15 live-verify — download-engine lifecycle (R4, L65, L139, B2, B6, L143)
The watched live session for the six deferred lifecycle items, each verified
against real yt-dlp/Electron behavior on Windows:

- R4: cancel now deletes the download's orphaned partials. The engine captures
  the final output path via a new `--print before_dl:dest|%(filename)s`
  (empirically %(filepath)s is still 'NA' that early) and the close handler
  sweeps only unambiguous intermediates (.part / .part-Frag* / .ytdl /
  <stem>.fNNN.<ext>) via the pure, unit-tested lib/partials.ts — never a bare
  <stem>.<ext> or another download's files. Live-verified with decoys.
- L65: verified-acceptable, no code change — a taskkill /F pause leaves the
  .part byte-intact and yt-dlp --continue resumes at the exact byte offset.
- L139: spawn↔self-update interlock (new ytdlpLock.ts). The updater refuses
  while any yt-dlp spawn is live; download/terminal IPC handlers hold (await)
  behind an in-progress exe rewrite instead of failing.
- B2: source indexing is cancellable — AbortSignal through the probe walk
  (kills the in-flight child, live-verified via tasklist), sources:index-cancel
  IPC, and a Cancel button in the Library add-source row.
- B6: app-update download is cancellable — app:update-cancel routes through
  the existing finish() teardown (abort + destroy + unlink, live-verified on
  Electron net.request); Cancel button under the update progress bar. The
  checksum phase is cancelable too.
- L143: closing the window mid-download (non-tray mode) now offers a native
  tray-independent "Quit anyway / Keep downloading" dialog, replacing the
  passive "use the tray to quit" notification.

Peer-review pass caught and fixed: a non-reentrant update lock (concurrent
begin clobbered the settle promise), a dead Cancel window during the B6
checksum fetch + a canceller slot-ownership bug (L140 shape), and a
persist-after-cancel hole in the index walk.

typecheck + 304 tests + eslint + production build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:36:22 -04:00
debont80 814ecac287 feat(audit): Batch 10 — CC conventions (shared line-buffer, cleanError)
The contained consistency consolidations; the sweeping ones are deferred with
their standard documented (they're the audit's own post-1.0 refactors, and
several would be reckless to do unattended).

Landed:
- src/main/lib/lineBuffer.ts (createLineBuffer): one newline line-splitter for
  streamed child-process output, replacing the duplicated buffer loop in
  download.ts and terminal.ts (CC3/CC4/SIMP5). Pure + unit-tested
  (test/lineBuffer.test.ts, 7 cases: partial lines, CRLF, empty lines, flush,
  multi-line chunks). download.ts keeps byte-identical marker parsing and no
  close-flush (yt-dlp template lines are always newline-terminated).
- ytdlp.ts: getYtdlpVersion/updateYtdlp now run stderr through cleanError, the
  same `cleanError(r.stderr) || r.error?.message || ''` idiom already used by
  download/probe/indexer (CC6) — a failed check shows the trailing error line.
- CL6 resolved: the one real redundancy was L15; clearDir/openFile/showInFolder
  are deliberate view-model wrappers (CC13), left as-is.

Deferred with documented standard + rationale (CODE-AUDIT.md): CC1 (breaking
persisted-key rename → migration), CC5+CL4 (security-critical updater
net.request, not live-verifiable here), CC7 (cosmetic arg-order), CC9 (zod dep),
CC10 (electron-store↔jsonStore split is deliberate for DPAPI), CC11 (config.ts
move), CC12 (file-tree reorg), CC13→L93, CC14→L142.

Verified: typecheck (node+web) + 275 tests + eslint + production build green;
touched files prettier-clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:48:54 -04:00
debont80 ab825488b8 feat(win): W2/UX19 persist window placement, W10 taskbar flash; tick W11
- W2/UX19: windowState.ts persists the window's normal bounds + maximized flag to
  <userData>/window-state.json (no new dep); createWindow restores from it with a
  screen.getAllDisplays() visibility guard so it never reopens off a disconnected
  monitor. Debounced save on resize/move + save on close (which may hide to tray).
- W10: notify() flashFrame(true)s the taskbar when a completion/failure lands while
  the window is unfocused/minimized (Windows clears the flash on focus).
- W11: already implemented (badge.ts overlay dot + setOverlayIcon) — ticked.

typecheck + 268 tests + eslint + prettier green. (Window restore + flash want a
live quit/relaunch confirm.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 11:33:11 -04:00
debont80 701c73a889 fix(main): L136 — enforce incognito in main (no errorlog / toast / cookie leak)
Incognito lived only in the renderer (history skip), so a "private" download
still leaked in main: errorlog entries (title+URL), OS toasts showing the title,
and the signed-in cookies attached — the UI promised "no logging, no history, no
cookies" but only history held.

Plumb `incognito` through StartDownloadOptions and enforce it in main:
- logFailure skips the errorlog write (and the pre-spawn errorlog in ipc.ts).
- notify uses a generic outcome title and drops the detail body, so no title/URL
  lands in the Windows Action Center.
- both cookie sources (--cookies login jar, --cookies-from-browser) are withheld,
  so a private download can't be tied to the user's identity.

typecheck + 262 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:14:49 -04:00
debont80 3baba3b7bc refactor(main): execFileAsync helper — unify the 5 spawn-and-read sites
Replace the hand-rolled `new Promise((resolve) => execFile(…, cb))` wrappers in
probe / ytdlp (×2) / ffmpeg / indexer.probeFlat / download.probeMeta with one
lib/exec.ts execFileAsync returning a normalized
{ok,stdout,stderr,timedOut,error} that never rejects. Each caller keeps its own
error mapping (timedOut -> "Timed out …" vs stderr) and JSON parsing; behaviour
is unchanged. windowsHide defaults on; per-call timeout/maxBuffer preserved.

Foundation for CC4/CC5 — the stdout line-buffer half lands with SIMP5, the
net.request half with SIMP16, at which point those checkboxes close.

Unit-tested (exec.test.ts) against the node binary: ok / stderr+nonzero-exit /
missing-binary / timeout. typecheck + 262 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:06:08 -04:00
debont80 36699531cf Format code with Prettier (8 modified files from H1 decomposition)
Aligns with CC2 enforcement: ESLint + Prettier + noImplicitAny now enforced.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-01 07:54:02 -04:00
debont80 4492eee054 Self-review: drop the formatter re-export shim, import @shared/format directly
The prior commit kept a pass-through `export { fmtBytes, fmtSpeed, fmtEta }` in
main/lib/formatters.ts purely so existing importers wouldn't have to change —
which re-exported fmtSpeed (zero importers, dead) and left download.ts re-
exporting fmtBytes/fmtEta with no consumer at all, undercutting the "one home"
goal.

Now the real consumers import from the canonical module directly:
- probe.ts and the download.test import from @shared/format.
- formatters.ts keeps only its internal `import { fmtBytes }` (for parseProgress)
  and no longer re-exports.
- download.ts re-exports only parseProgress (its own function); the dead
  fmtBytes/fmtEta re-export is removed.

No behavior change. typecheck + 248 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:10:11 -04:00
debont80 7e3e5af52d Fix C1/C2 + reliability cluster (L140/L141/L148, R6/R7)
Critical:
- C1: single source of truth for IPC mock + Settings defaults. DEFAULT_SETTINGS
  in @shared/ipc; main DEFAULTS, renderer FALLBACK, and preview MOCK_SETTINGS all
  derive from it. Whole browser mock collapsed into one typed mockApi.ts; main.tsx
  shrinks to window.api = mockApi. PREVIEW check single-sourced in isPreview.ts.
- C2: break the downloads<->sources circular import via a typed event bus
  (store/coordinator.ts). App eagerly imports sources for side-effects so startup
  load + scheduled --sync + downloadCompleted subscription still run.

Reliability:
- L140/L148: cancel/pause release the active slot synchronously; identity-guarded
  releaseActive; per-spawn cookie jar so a same-id retry/resume is never rejected
  by a stale entry nor run over the concurrency cap.
- L141: renderer history capped at 500 + url de-dup to match main.
- R6: writeJsonAtomic reports/logs write failures and keeps data dirty for retry
  instead of an empty catch.
- R7: encryptSecret warns before the plaintext fallback.

typecheck + 243 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:26:18 -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 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 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
wayne f167c02946 Background running for downloads/auto-download, library clipboard detect, updater auth + token
Closing the window no longer kills in-progress downloads, the library gains the
same copied-link suggestion the downloads tab has, and the in-app updater can now
authenticate to a sign-in-required Gitea.

Background / tray:
- The window's close handler now hides to the tray (instead of quitting) whenever
  a download is in flight, even if "Keep running in the tray" is off — quitting
  was killing the spawned yt-dlp processes. A one-time notification explains the
  app is still running. (download.ts exposes hasActiveDownloads().)
- tray.ts now falls back to an embedded icon when no build/icon.ico ships, so the
  tray actually appears — previously an empty icon meant the tray was skipped and
  a minimized window could be stranded with no way back.
- New "Start with Windows" setting (launchAtStartup) wired to
  app.setLoginItemSettings, synced at startup and on toggle. Useful with
  auto-download so watched channels stay current in the background.
- The "Keep running in the tray" hint now explains it also enables background
  auto-download of new uploads.

Library clipboard detection:
- Extracted the downloads tab's clipboard watcher into a shared useClipboardLink
  hook and used it in the library's add-source field, so a copied channel/playlist
  link is offered there too.

Updater fix (works on a private / sign-in-required instance):
- Added an optional updateToken setting. When set, the updater sends it as a Gitea
  Authorization header on the release check, the checksum fetch, and the installer
  download — so "Check for updates" works where anonymous access is blocked (the
  previous "could not reach the update server" case). Blank = anonymous, unchanged.
  No token is ever shipped; it's only ever sent to the host-pinned update host.
  Settings gains a masked "Update access token" field.

Typecheck clean; 187 tests pass; electron-vite build clean. New UI verified in the
browser preview (tray/startup toggles, token field, library copied-link banner).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:22:19 -04:00
debont80 24c4c807a3 feat: Phase P reliability (YouTube extractor-args plumbing, signing + smoke-test docs)
- YouTube reliability: Settings.youtubePlayerClient + youtubePoToken ->
  one --extractor-args "youtube:player_client=...;po_token=..." group
  (accessArgs, unit-tested); advanced fields in Settings -> Network.
  Automatic WebView PO-token minting deferred (documented).
- docs/SMOKE-TEST.md: release-gate checklist for all shipped-but-untested
  OS wiring (cookies window, protocol/Send-to, scheduled sync, aria2c, proxy,
  tray/taskbar/pause-resume/terminal) — needs a human to run.
- docs/SIGNING.md: code-signing guide (CSC_LINK/CSC_KEY_PASSWORD, OV/EV,
  CI, HSM hook, verification). Build is signing-ready; needs a cert.
- i18n remains deferred (not faked).

typecheck + test (195) + build all clean. Roadmap: Phase P code shipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:25:57 -04:00
debont80 da37690b42 feat: Phase N power-user surface (terminal, palette, per-item, sort, url-templates)
- Built-in yt-dlp terminal: src/main/terminal.ts streams raw yt-dlp runs
  (binary fixed, gated on customCommandEnabled), new TerminalView + sidebar tab
- Command palette (Ctrl/Cmd+K), portal-free CommandPalette.tsx wired in App
- Per-playlist-item editing: per-row video/audio toggle + All video/All audio
  batch; addPlaylist enqueues via addMany with per-item kind
- Weighted format sorting: DownloadOptions.formatSort -> raw -S (overrides codec)
- URL-regex template auto-matching: CommandTemplate.urlPattern + selectExtraArgs
  matchesUrl, threaded through resolveExtraArgs; field in TemplateManager

typecheck + test (192) + electron-vite build all clean. Roadmap: Phase N COMPLETE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:10:22 -04:00
debont80 9ac11ceb6c feat: queue UX (Phase M) + media editing (split-chapters, trim)
Phase L (media editing):
- Split by chapters (--split-chapters) as a DownloadOptions toggle
- Trim/cut by time range: parseTrimSections -> --download-sections "*A-B"
  + --force-keyframes-at-cuts (deduped vs SponsorBlock), per-download Trim panel

Phase M (queue & daily-use UX), all 7:
- Pause/resume: main-side killTree + paused flag (silent close), download:pause
  IPC, store pause/resume + paused status, QueueItem controls
- Reorder via "Download next" (prioritize)
- Aggregate progress strip (pure summarizeQueue) + combined speed/ETA
- Drag-and-drop links / .url files onto the download card
- Retry all failed
- Duplicate detection (sameVideo) with "Download anyway" guard
- Per-download scheduling (datetime-local) + save-for-later (saved status,
  15s promotion ticker); session-only (queue not persisted)

New pure modules unit-tested (queueStats); buildArgs trim/split tested.
typecheck + test green (178 passed). Roadmap updated: Phase M COMPLETE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:55:43 -04:00
debont80 4854fcc947 feat: self-managing yt-dlp + ffmpeg version display
yt-dlp is now a managed, auto-updating binary rather than a static bundled
one. On launch AeroFetch seeds a writable copy under userData from the
bundled seed, self-heals it if it goes missing, and — throttled to once a
day — self-updates it to the configured channel (default: nightly). This
keeps the binary current so YouTube changes don't silently cause 403s, and
an app reinstall (or portable re-extraction) can no longer roll it back.
Settings shows an auto-update toggle, the live version, last-checked time,
and the channel selector.

Also surface the bundled ffmpeg/ffprobe versions in Settings (display only:
they have no self-update path and, unlike yt-dlp, don't break as sites
change, so there is no auto-update for them).

- binaries: split the read-only bundled seed from the managed userData copy
- ytdlp: ensureManagedYtdlp (seed + self-heal), startup auto-update runner
- ytdlpPolicy: pure once-a-day throttle decision (unit-tested)
- ffmpeg: parse ffmpeg/ffprobe -version for the Settings panel
- settings/ipc/preload/renderer: new settings, IPC channels, types, and UI

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 07:27:43 -04:00
debont80 e8ab8b9c73 Merge feat/documents-folders-and-ui: Documents folders, collapsible sidebar, theme switch
# Conflicts:
#	src/main/download.ts
#	src/renderer/src/components/DownloadBar.tsx
2026-06-24 11:26:52 -04:00
debont80 3536626a8a security: harden command exec, IPC, deep-link, cookies, and persistence
Seven-tier security audit of the main process, each finding fixed with a
regression test. Typecheck (node + web) clean; unit tests 106 -> 140.

- Tier 1 (command exec/argv): allowlist the yt-dlp --update-to channel
  (blocks arbitrary-binary-install RCE); gate per-download extraArgs behind
  the customCommandEnabled consent flag in main (blocks --exec RCE); resolve
  taskkill/schtasks by absolute System32 path; validate per-download
  outputDir; normalize the URL in assertHttpUrl and use it at every spawn.
- Tier 2 (input validation): fix isSafeFilenameTemplate drive-relative
  ('C:foo') and Windows dotted-'..' traversal bypasses; percent-encode the
  untrusted id in entryUrl; catch reserved device names with extensions in
  sanitizeDirSegment.
- Tier 3 (fs/backup): drop malformed template rows in importBackup;
  normalize deep-link URLs via assertHttpUrl.
- Tier 4 (cookies): confine the sign-in window's navigations/popups to web
  URLs (recursively) and deny all web permissions on its session.
- Tier 5 (deep-link/argv): bound the .url file read to 64 KB; match the
  aerofetch:// scheme case-insensitively.
- Tier 6 (Electron window): deny camera/mic/geolocation/USB/HID/serial/
  Bluetooth permissions on the app window.
- Tier 7 (network/persistence): restrict the watched-source RSS fetch to
  youtube.com feed URLs (SSRF guard); complete isValidSource validation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 08:04:19 -04:00
wayne 37687f9870 Separate per-kind video/audio folders, defaulting to Documents\Video and Documents\Audio
Existing installs had outputDir persisted to the Downloads folder (auto-filled by
earlier versions), so the v0.3.0 per-kind routing never triggered — downloads kept
going to Downloads. Replace the single outputDir setting with independent videoDir
and audioDir settings:

- Settings model: drop Settings.outputDir; add videoDir + audioDir (both blank by
  default). A blank value routes that kind into Documents\Video / Documents\Audio;
  a chosen folder overrides it. The stale outputDir key in old settings files is
  simply ignored, so existing users now get the Documents defaults.
- buildCommand routes by kind: per-download override → per-kind folder → default.
- The renderer no longer sends a global outputDir, so main always routes by kind.
- Settings: the single "Download folder" field becomes separate "Video folder" and
  "Audio folder" pickers, each with a Browse + Reset and a Documents\… placeholder.
  Store gains chooseDir(target) / clearDir(target).
- Onboarding: replace the folder picker with a short note about the two default
  folders (changeable in Settings).
- Bump version to 0.3.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 06:11:15 -04:00
wayne a2763f10b4 Route downloads into Documents\Video / Documents\Audio and simplify the home page
- Create Documents\Video and Documents\Audio on startup (ensureMediaDirs), and
  route each download into them by kind when no explicit output folder is set.
  An empty outputDir now means "sort by type"; a chosen folder still overrides
  for both kinds.
- Settings/Onboarding "Download folder" gains a placeholder + hint describing
  the blank = route-by-type behaviour.
- Trim the home download bar to the essentials: URL box, Search and Paste
  buttons, format/quality, and the Download button. Removed the private-mode
  toggle, per-download Options panel, custom-command panel, command preview, and
  the saving-to-folder line.
- Bump version to 0.3.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 05:17:16 -04:00
debont80 47da3e6746 Add Pinchflat-style media manager: index channels into playlist folders
Index an entire YouTube channel or playlist once, then download it into
organized <Channel>/<Playlist>/<NNN> - <Title> folders, with incremental
re-sync. Built in six rebuild-gated phases (F-K); see ROADMAP-PINCHFLAT.md.

- F: channel-walk indexer (/playlists + /videos) -> persisted Source +
  MediaItem JSON stores; pure classify/dedup logic in indexerCore.ts
- G: Windows-safe folder paths + dir sanitizer (collectionOutputTemplate)
- H: Library tab + source tree + "Download pending" into the existing queue
- I: state-preserving re-index merge + persist-downloaded-on-complete
- J: watched sources, RSS fast-check, Task Scheduler + --sync launch
  (the OS-level scheduling/RSS need a real-install smoke test)
- K: .info.json / thumbnail / .description sidecars for media servers

Also gitignore .gitea-token. 106 unit tests pass; typecheck + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 20:50:13 -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
debont80 a822a2bb52 Complete Phase C (custom commands & yt-dlp updater) and Phase D (library, UX & notifications)
Phase C had been implemented in the working tree but never committed:
named custom-command templates (CRUD + per-download override), command
preview, and the in-app yt-dlp self-updater.

Phase D adds: history search/kind-filter/multi-select/bulk-delete/
re-download; native OS notifications on completion/failure; a private/
incognito download mode that skips history; settings+template backup/
restore via JSON; and a persistent error log surfaced as a Diagnostics
report in Settings.

See ROADMAP.md for the full per-item breakdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 06:19:45 -04:00
debont80 67133f13b5 Add Phase B: cookies, aria2c, proxy/rate-limit, restrict-filenames & archive
Implements the remaining Phase B (Access & networking) items from
ROADMAP.md: cookie sources (browser cookie-store import, or a built-in
sign-in window that exports a Netscape cookie file via a persisted Electron
session partition), the aria2c external downloader, proxy/rate-limit,
restrict-filenames, and a download-archive to skip repeats.

Wires download.ts to the buildArgs() module added in the previous commit
(it wasn't hooked up yet) and renames its options param
NetworkOptions -> AccessOptions to reflect the wider scope now that cookies
and filename/archive flags joined proxy/rate-limit/aria2c.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 20:53:18 -04:00
debont80 bb5dd6c438 Add Seal-parity download options + playlist support (Phase A)
Phase A of the Seal feature-parity roadmap: configurable post-processing via a
shared DownloadOptions model (src/shared/ipc.ts), editable as persisted defaults
in Settings and overridable per-download in the download bar.

- audio formats (mp3/m4a/opus/flac/wav/aac), video container (mp4/mkv/webm),
  preferred-codec sort (-S vcodec)
- subtitles (download/embed/langs/auto), SponsorBlock (remove/mark + categories
  + force-keyframes-at-cuts), embed chapters/metadata/thumbnail, square-crop
  audio artwork
- playlist downloads: probe via `-J --flat-playlist`, inline selection UI
  (checkbox list, select-all/none, live count); each entry enqueued as its own
  single-video download, reusing the existing queue/concurrency/history
- reusable DownloadOptionsForm shared by Settings and the download bar so the
  defaults and per-download override never drift
- ROADMAP.md tracking full Seal parity (phases A-E)

Also includes in-tree security hardening that landed alongside: preload sandbox
(CJS preload output), http(s)-only window-open + blocked renderer navigation,
argv-injection guard for URLs (src/main/url.ts), extension-allowlisted file
open/reveal (src/main/reveal.ts), yt-dlp version-check timeout, and a minimal
window.electron presence marker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:37:22 -04:00
debont80 1a2270c95e Initial commit: AeroFetch — yt-dlp/YouTube downloader for Windows
Electron + React (Fluent UI) desktop frontend for yt-dlp:
- Download queue with live progress, concurrency cap, cancel/retry
- Format/quality picker via yt-dlp probe; audio extraction to MP3
- Settings + history persistence (electron-store / JSON)
- Clipboard link auto-detect; persisted light/dark theme
- NSIS + portable packaging (binaries bundled at build time, not in git)

UI: "Studio" sidebar layout with a toffee-brown theme (light + neutral dark).
Dropdowns use a native <select> and tooltips a CSS-only Hint, to avoid a
dev-machine GPU overlay flicker/blank issue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 18:17:41 -04:00