Commit Graph

158 Commits

Author SHA1 Message Date
debont80 6812939848 Merge pull request 'docs(audit): fix stale Deferred entries for metadata editing + PO-token auto-mint' (#10) from docs/code-audit-stale-deferred into main
Reviewed-on: #10
2026-07-02 17:10:10 -04:00
debont80 3623418fe3 docs(audit): fix stale Deferred entries for metadata editing + PO-token auto-mint
Both shipped (ROADMAP.md Phase L/P) but CODE-AUDIT.md's Deferred section
still listed them as pending.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 17:08:20 -04:00
debont80 d8df5ea7c8 Merge pull request 'feat(playlist): exact-format-per-item selection' (#9) from feat/playlist-per-item-format into main
Reviewed-on: #9
2026-07-02 17:06:44 -04:00
debont80 15c2229539 fix(playlist): drop stale per-item formats on re-probe
Peer-review of the exact-format-per-item feature: re-clicking "Preview
formats" on an already-loaded playlist reset info/playlist but left
itemFormats/itemFormatOptions/formatExpanded keyed by position, so a
stale exact-format pick could silently reattach to a different video
landing at the same index in the new probe result and flow into
addPlaylist(). Extracted resetPlaylistItemState() and call it from
both clearProbe() and fetchFormats().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 17:05:29 -04:00
debont80 4c2b54a599 feat(playlist): exact-format-per-item selection
Each video row in the playlist panel gets an exact-format button that
lazily probes just that entry's URL (reusing the existing single-video
probe IPC, no new main-process code) and shows a native Select of its
real formats. The choice flows through addMany -> formatId per queue
item. Probing is on-demand per row, never all up front, since a
playlist can hold thousands of entries and per-entry probes are
rate-limited.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 16:55:58 -04:00
debont80 5c88d9c8e0 feat(network): request-throttle controls to avoid YouTube rate-limiting
Re-implements the sleepRequests/sleepInterval/maxSleepInterval feature
(--sleep-requests / --sleep-interval / --max-sleep-interval) from the
stale feat/binary-management-and-library-scale branch, ported onto
current main's structure (zod settingsSchema, decomposed NetworkCard).
All default to 0/off, preserving current behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 16:34:34 -04:00
debont80 61d5261e33 docs: flip stale PINCHFLAT output-path checkbox (implemented Batch 26)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:16:45 -04:00
debont80 a8dc89d6fc Merge Batch 28 — Retention & quality-upgrade 2026-07-02 16:14:24 -04:00
debont80 0956bd536a feat(pinchflat): Retention & quality-upgrade per Library source
On-demand maintenance panel (SourceRetention) on the source detail —
not a persisted schedule (roadmap's niche-for-desktop scope).

Prune: keep the newest N downloaded files, delete the rest's files
(download archive prevents silent re-download); dry-run count + two-step
confirm before any deletion. Upgrade: re-enqueue items whose recorded
downloadedQuality ranks below the source's current target (profile or
global). Selection is pure + injected-now in @shared/retention.ts
(selectPruneCandidates/selectUpgradeCandidates + qualityRank), unit-
tested; pruneMediaItems (main) deletes best-effort and clears state.
MediaItem.downloadedQuality now recorded through the completion path.
Live-verified the panel renders. 366 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:14:23 -04:00
debont80 15cc96dd10 Merge Batch 27 — Media Profiles 2026-07-02 16:02:09 -04:00
debont80 ed1daf8bef feat(pinchflat): Media Profiles — named download presets per Library source
A MediaProfile bundles { kind, quality, options, outputTemplate, extraArgs }
that a Source points at via Source.profileId, overriding the global defaults
field-by-field for that source's downloads.

Backend mirrors CommandTemplate: profiles.ts (createJsonStore, PROFILES_MAX,
isProfileLike + sanitize), CRUD IPC (profiles:list/save/remove) + preload +
useProfiles store; Source.profileId + sources:set-profile. Pure resolution
in @shared/profileResolve.ts (profile → global, per field; unit-tested).
Applied in the sources-store enqueueItems; a per-download collectionOutput-
Template override threads to buildArgs so a profile's outputTemplate wins.

UI: ProfileManager (mirrors TemplateManager) in a new Settings → Advanced →
Media profiles card, plus a per-source Profile picker in the Library source
detail. Both live-verified in the preview; 354 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:02:07 -04:00
debont80 a02953f6d1 Merge Batch 26 — roadmap surfacing + output-path setting 2026-07-02 15:44:44 -04:00
debont80 42b7b2e01f feat(roadmap): surface stale checkboxes + collection output-path setting
Command preview + incognito toggle were already wired end-to-end in the
DownloadBar (Show command button, Incognito checkbox) — flipped the two
stale ROADMAP.md checkboxes with notes; verified live in the preview.

Output-path setting (PINCHFLAT): new Settings.collectionOutputTemplate — a
raw yt-dlp -o template overriding the default Channel/Playlist/NNN-Title
layout for Library downloads (empty = default). collectionOutputTemplate()
takes an optional custom template (joined under outDir, left for yt-dlp to
expand); download.ts passes the setting; zod schema validates it as a safe
relative template; Settings -> Filenames field. Tests in buildArgs +
settingsSchema; live-verified the field renders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:44:44 -04:00
debont80 72feaa62f8 Merge Batch 25 — project organization (CC12, CC7, CC10) 2026-07-02 15:37:28 -04:00
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 f45acb645b Merge Batch 24 — zod validation layer (CC9) 2026-07-02 12:41:01 -04:00
debont80 8f6b2a761b feat(audit): Batch 24 — one validate-and-coerce schema layer for settings (CC9)
zod field schemas in settingsSchema.ts replace setSettings' bespoke
per-key switch: parse → skip-on-fail → store, side effects (secret
encryption, launch-at-startup sync) kept in settings.ts. sanitizeOptions
extracted to settingsOptions.ts to break the import cycle; backup import
flows through the same schema. Field-by-field parity pinned in
test/settingsSchema.test.ts (15 tests); full suite unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:41:00 -04:00
debont80 6abed2064d Merge Batch 23 — naming & config (CC1, CC11) 2026-07-02 12:34:05 -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 545cfeed5e Merge Batch 22 — perf close-out 2026-07-02 12:25:22 -04:00
debont80 384870ee4a feat(audit): Batch 22 — perf close-out (PERF3 benchmarked, PERF6/L162 by-design)
PERF3: pumpBench.test.ts pins pump()'s pipeline cost — 0.43ms @5k items,
1.47ms @20k (median) — negligible at a few events/sec, so the
count+pointer rewrite of the core queue is declined as risk without
reward; the benchmark stays as a 5ms regression tripwire.
PERF6/L162: closed as documented-by-design (virtualization bounds
mounted thumbs; a theme prop conflicts with PERF5's stable renderer).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:25:21 -04:00
debont80 ea52735b12 docs(audit): check off CL4 + CC5 (Batch 21)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:21:57 -04:00
debont80 e9d41b00ea Merge Batch 21 — updater refactor (CL4, CC5) 2026-07-02 12:21:29 -04:00
debont80 34fbf36610 feat(audit): Batch 21 — updater refactor under behavior pins (CL4, closes CC5)
14 pinning tests lock downloadAppUpdate's security invariants (redirect
re-validation per hop, checksum refusal paths, SHA-256 verify + cleanup,
truncation, idle-timeout, cancel in both phases, progress) with a
scripted net.request fake; then the nested streaming block is extracted
verbatim into streamInstallerToFile. downloadAppUpdate is now a linear
pre-flight; B6 cancel-slot ownership stays with its module state via
onCancelReady/onSettled. Pins green before and after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:21:28 -04:00
debont80 d1e3168449 Merge Batch 20 — Windows/main-process (UI23, W12, W15, L131) 2026-07-02 12:15:52 -04:00
debont80 3b0d668f6c feat(audit): Batch 20 — Windows/main-process (UI23, W12, W15, L131)
UI23: cookie sign-in + PO-token windows open with the app's resolved
theme background (passed from the IPC layer), no more white flash.
W12: multi-resolution fallback tray icon (16/24/32/48 via
addRepresentation dataURL, embedded in trayFallbackIcons.ts); decode +
scale factors verified in an Electron probe.
W15: hardwareAcceleration setting (default false = software rendering);
index.ts gates disableHardwareAcceleration on it after the portable
redirect; Appearance card switch with restart note; boot path verified.
L131: resolved by-design — clearing the queue acknowledges failures;
flashFrame (W10) covers attention, Diagnostics persists the record.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:15:51 -04:00
debont80 f54d9a1965 docs(audit): check off UX7/UX8/UX23 (Batch 19)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:01:15 -04:00
debont80 fc2db1e1b6 Merge Batch 19 — Settings IA (UX7, UX8, UX23) 2026-07-02 12:00:47 -04:00
debont80 a5885739d2 feat(audit): Batch 19 — Settings information architecture (UX7, UX8, UX23)
UX7: 11 cards grouped into five sections (Downloads, Appearance,
Network & accounts, Advanced, App & maintenance) with section headings
and a jump nav under the search box; search hides empty sections and
the nav; heading hierarchy corrected (h1 page, h2 section, h3 card).
UX8: post-processing card co-located under the Downloads card and
renamed 'Post-processing' with a defaults-vs-advanced hint.
UX23: closed as stale duplicate of the already-fixed L71 (editable,
validated, reconciled folder inputs) — verified in the preview probe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:00:47 -04:00
debont80 93f0091104 Merge Batch 18 — watched visual pass 2026-07-02 11:55:45 -04:00
debont80 cdb6eea437 feat(audit): Batch 18 — watched visual pass (UI3, UI12, UI16, UI21, UX18, L109, UX26)
UI3: remaining list-row/control paddings on the SPACE scale (History row,
Downloads summary, Library head/detail, Terminal gate/log, template rows).
UI21: active-nav rail now shows collapsed too (moved into navItemActive).
UX18: stable 'Quality' label + brand '• fetched' tag instead of the
label/control morph after Fetch.
L109/UX26: skeleton lines for the About yt-dlp/ffmpeg boot load; skeleton
cards in Settings until the store's loaded flag flips.
UI12/UI16: deferred eyeball deltas verified via Electron-probe screenshots
(light+dark, collapsed sidebar, fetch flow); UI16 convention codified in
ui/tokens.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:55:45 -04:00
debont80 747cecd1e0 Merge Batch 17 — layout & virtualization (L161, L104) 2026-07-02 11:43:36 -04:00
debont80 17d42ed4fe feat(audit): Batch 17 — screen scroll contract + History virtualization (L161, L104)
L161: <main> is now a non-scrolling flex slot; each screen owns its scroll
via shared shells in ui/Screen.tsx — 'page' (Library/Settings/empty History
scroll the shell) and 'fill' (Downloads/Terminal/History scroll their own
list/log, shell scrolls only as short-window fallback). height:100%
couplings replaced with flexGrow+minHeight:0; list regions floored at
160px so they can't collapse on short windows.

L104: HistoryView renders through VirtualList; Ctrl+A re-homed onto a
wrapper around the scroller, per-row Delete unchanged on rows.

Verified with an Electron probe against the vite preview at 1100x720 and
800x520 (probe metrics + screenshots): one active scroller per screen,
main/body never scroll, floors hold. Adds .claude/launch.json for the
preview server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:43:34 -04:00
debont80 dbbb0c70f9 chore(format): endOfLine auto so format:check is stable under git autocrlf
Prettier's LF default fought git's CRLF checkout on Windows, failing
format:check on every file with no real diff. 'auto' keeps whatever the
checkout uses; content style is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:13:07 -04:00
debont80 71b88e2304 Merge Batch 16 — store discipline (L142, L93, CC13, CC14) 2026-07-02 11:10:58 -04:00
debont80 2ef19ead71 feat(audit): Batch 16 — store discipline (L142 reconciliation, L93 view-model hooks)
L142 (+CC14): keyed latest-wins reconciler (lib/reconcile.ts, unit-tested)
applies main's authoritative IPC returns in the history/templates/sources
stores, extending the M34 settings pattern; loadSources shares the key so
a slow list read can't clobber a newer mutation result.

L93 (+CC13): settings cards, TerminalView, and LibraryView now reach IPC
only through colocated view-model hooks (useAboutCard/useBackupCard/
useCookiesCard/useSoftwareUpdateCard/useNetworkCard/useTerminalRun) or
store actions (openHighContrastSettings, scheduled-sync on sources) —
no window.api left in components.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:10:49 -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 b3e5e4f309 Merge audit chain (Batches 6-15) into main
Reconciles two lines of history that both descended from the same
feat/tray-background-clipboard branch (background running, clipboard-link
detection, updater auth): main picked it up as a single squash commit
(PR #7, d112bc4), while the audit chain (p1-docs..batch-15) branched off
that feature branch's pre-squash tip and continued with its own granular
history, including the H1 decomposition of DownloadBar.tsx/SettingsView.tsx.

12 files conflicted, all resolved by verifying the audit chain already
carries PR #7's functionality (confirmed per-file against the original
d112bc4 diff) and keeping the chain's newer/relocated version:
- shared/ipc.ts, main/settings.ts: DEFAULT_SETTINGS single-sourced (C1)
- main/download.ts, main/tray.ts: trivial, chain is a superset
- main/index.ts: ipcMain.handle wiring extracted to main/ipc.ts (L2);
  removed a silent duplicate notifyBackgroundOnce that 3-way merge
  produced with no conflict markers
- renderer/main.tsx, store/settings.ts: mock/fallback settings
  single-sourced (C1/mockApi.ts)
- LibraryView.tsx: old inline clipboard-suggestion banner replaced by
  the shared LinkSuggestion primitive (Batch 9/UI19); removed dead
  icon imports + styles left behind by the auto-merge
- DownloadBar.tsx, SettingsView.tsx: taken wholesale from the chain
  (H1-decomposed shells) after confirming their extracted hook/cards
  already contain PR #7's additions (useClipboardLink.offer(),
  launchAtStartup, updateToken)
- useClipboardLink.ts, test/clipboardLink.test.ts (add/add): chain's
  version is a strict refinement of the same code (L40/L138/L145)

Verified post-merge: typecheck (node+web), 279 tests, eslint, and the
production build all pass; touched files are prettier-clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 18:35:40 -04:00
debont80 a2c8992650 refactor(audit): peer-review fixes — mergeClasses + import placement
Self-review of the Batches 9-15 changes surfaced two style inconsistencies (no
behavior change):
- Toaster.tsx: combine the icon + tone classes with mergeClasses instead of a
  template-literal string, matching how the rest of the codebase composes Griffel
  classes.
- App.tsx: move the PERF8 React.lazy() view declarations below the import block
  (they were interleaved between import statements).

Verified: typecheck (node+web) + 279 tests + eslint + production build green
(code-split chunks intact); touched files prettier-clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:00:21 -04:00
debont80 4d1c33a081 feat(audit): Batch 15 — download-engine lifecycle (safe part; rest live-verify)
This is the batch the run reserves for a WATCHED LIVE SESSION, so only the safe,
non-file-deleting item is implemented here; the rest are staged with per-item
live-verify rationale in CODE-AUDIT.md.

Landed:
- L157: removeSource cancels its in-flight downloads. removeSource emits a
  `sourceRemoved` event on the coordinator bus (C2); the downloads store cancels
  every active (downloading/queued) item whose mediaItemId starts with
  `${sourceId}:` — MediaItem ids are `${sourceId}:${videoId}` (indexerCore), so the
  prefix reliably identifies the source's downloads. Reuses the proven cancel()
  path (process kill + pump). Type/lint/test-green; process-kill worth a live confirm.

Resolved by prior work:
- UX11: M4 persists the queue, so a scheduled item fires on next launch once due —
  no longer "never fires if quit".
- UX20: L68 resets notifiedBackground on window show, so every close-to-tray
  re-fires the "still running in the tray" notice, not just the first.

Deferred to the watched live pass (each needs a real download to verify):
R4 (delete .part on cancel — file-deletion data-safety), L65 (graceful Ctrl-C
pause), L139 (spawn↔auto-update interlock — timing race), B2 (index AbortSignal +
Cancel), B6 (app-update cancel — ties to CL4's deferred updater), L143 (in-window
quit-anyway).

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 17:41:48 -04:00
debont80 0f784847a3 feat(audit): Batch 14 — perf (code-split + seed DCE) + first-run default
- PERF8: code-split the non-default tabs. Library/History/Terminal/Settings are
  React.lazy chunks behind a Suspense; Downloads (the launch tab) stays eager. The
  production build now emits separate SettingsView (~90kB), LibraryView (~29kB),
  HistoryView (~16kB), TerminalView (~8kB) chunks — ~140kB out of the initial
  bundle. The stores these views use are imported eagerly elsewhere, so lazy-
  loading the views never delays store startup/persistence (per the C2 note).
- L128: gate the three preview seed arrays (downloadSeed / sources seeds / history
  seed) on `import.meta.env.DEV && PREVIEW`. In the packaged build DEV is
  statically false, so Rollup constant-folds and dead-code-eliminates the seed
  literals; the browser dev preview (DEV true) still gets them.
- SR4: clipboardWatch now defaults false for new installs (privacy — no clipboard
  read on focus until opted in). Existing installs keep their saved value
  (electron-store fills only missing keys); the onboarding tip points to the
  setting. NOTE: this flips a signature convenience off-by-default for new users —
  flagged for the maintainer, trivially reversible.

Deferred with rationale (CODE-AUDIT.md): PERF3 (core-queue micro-opt on a bounded
list, risky unattended), PERF6/L162 (bounded by virtualization + conflicts with
PERF5's hoisted renderItem), SR5 (MP3 = deliberate compatibility default; the label
bug was M18), W12 (fallback-of-a-fallback icon), W15 (GPU software-rendering
workaround — external driver dependency), L104 (History virtualization needs the
L161 layout rework + live verification; bounded by the 500-cap).

Verified: typecheck (node+web) + 279 tests + eslint + production build green
(code-split chunks confirmed in output); touched files prettier-clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 17:35:40 -04:00
debont80 71f0b2b68f feat(audit): Batch 13 — copy polish + misc-L cleanup
Contained fixes:
- L158: drag-highlight flicker. useDownloadBar tracks a dragDepth counter —
  onDragEnter increments, onDragLeave decrements, the highlight clears only when
  depth returns to 0 (the drag truly left the card). onDragOver just
  preventDefaults; onDrop resets. No more blink when crossing child elements.
- L92: preload method names aligned with main — markSourceItemDownloaded →
  setMediaItemDownloaded, syncSources → syncWatchedSources (callers + mock
  updated; compiler-enforced via the derived Api type).
- L149: the three-sentence "Keep running in the tray" hint is now one line.
- L171: one MOCK_CURRENT_VERSION backs both the preview mock's getAppVersion and
  checkForAppUpdate.currentVersion, so they agree.
- L5 (representative): the About card's repeated monospace inline styles → shared
  mono/monoBlock/monoPre classes; convention set (makeStyles for static, inline
  style only for data-driven values).

Resolved by verification/convention (CODE-AUDIT.md): L21 (moot after the H1
SettingsView decomposition), L31 (dual theme switcher is width-driven), L130
(lowercase fragments vs shown-raw sentences), L152 (search sizes are role-
appropriate), UI22 (brand/entity/section icon-emphasis set now defined).

Deferred with rationale: L51/L64 (features), L93 (CC13 view-model pass), L104
(moved to Batch 14 perf), L109/UI21 (visual), L161/UI23 (layout/live-verify).

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 17:26:01 -04:00
debont80 d131ee67d5 feat(audit): Batch 12 — a11y & discoverability
- L118: decouple URL-field focus. The DownloadBar registers a focuser (its own
  input ref) in the nav store; App calls useNav.getState().focusUrlField() on
  launch / "New download" instead of document.getElementById('aerofetch-url').
  The hardcoded id is removed — App no longer reaches into DownloadBar's markup.
- L155: the accent swatches are now a real role="radiogroup" with aria-checked
  role="radio" swatches, roving tabindex, ←/→/↑/↓/Home/End arrow-key navigation,
  and the shared focus ring — matching the app's SegmentedControl radio pattern.
  Redundant "(selected)" aria-label text dropped (aria-checked conveys it).
- UX21: keyboard-shortcut discoverability — an Onboarding "Press Ctrl+K anytime"
  tip, and the command palette's Settings action surfaces its "Ctrl ," shortcut.
- UX22 / L67: onboarding is revisitable — a "Show welcome tips again" button in
  Settings → About re-opens the welcome screen.

Verified & documented (no code needed): UX17 (Fetch/Paste already have aria-label
+ focus-reachable Hint tooltips — a11y met; a permanent visible label is a compact-
layout choice), UI24 (context menus "none by design" — row actions are all visible
inline buttons + keyboard, and Fluent's Menu is portal-based, which the app avoids
for the dev-GPU flicker reason).

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 17:15:10 -04:00
debont80 661a4e7572 feat(audit): Batch 11 — UX behavior gaps (toast surface, sidebar badge)
A new in-app toast surface is the batch's spine, making the silent-failure and
global-status gaps fixable:

- Toast infra: store/toasts.ts (useToasts + toast(); auto-dismiss) + ui/Toaster.tsx
  (non-portal bottom-center stack, avoids the Fluent-portal GPU flicker). Unit-
  tested (test/toasts.test.ts). Mounted at the app root beside LiveRegion.
- UX6: shared renderer reveal.ts revealFile('open'|'folder') awaits the IPC result
  and toasts the main-process reason instead of silently no-oping on a moved/typed-
  out file. safeShowInFolder upgraded to return an error string like safeOpenPath
  (preload/mock/Api types updated).
- UX9 / UI25: a Fluent CounterBadge on the sidebar Downloads nav item shows the
  active (downloading + queued) count from any tab, via a count-only App selector
  that doesn't re-render on progress ticks.
- UX15: cancelAll store action + a "Cancel all (N)" header button.
- UX24: the disabled "Check watched" button uses disabledFocusable + a Hint that
  explains it needs a watched source.
- UX12: the Terminal gate gains an inline "Enable custom commands" button, so it's
  no longer a dead-end pointing at another screen.
- L144: the DownloadBar duplicate guard now also checks history — a URL downloaded
  earlier warns "Already downloaded: …" (dupInHistory) vs "Already in your queue: …".

Deferred with rationale (CODE-AUDIT.md): UX7/UX8 (Settings IA redesign — visual),
UX18/UX26 (subjective/visual polish), L131 (arguably by-design, needs live taskbar),
L142 (history/templates/sources IPC-return reconciliation — a focused own PR).

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 17:05:26 -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 3d4e574916 feat(audit): Batch 9 — surface primitives (empty-state, banner, truncation)
Shared UI primitives to end the "two ways to do X" drift on surfaces:

- EmptyState (ui/EmptyState.tsx): one centered empty block — optional focal
  icon/badge + Body1 message + optional muted hint, with a `compact` variant
  for in-content placeholders. Adopted by Downloads, History, Library (L116),
  and — compact — the Terminal log, Diagnostics, and TemplateManager (L117).
- Banner (ui/Banner.tsx) + LinkSuggestion (ui/LinkSuggestion.tsx): one tinted
  notification row (icon + truncating/wrap content + actions, tone brand/warning)
  replacing five hand-rolled style blocks — the DownloadBar copied-link
  suggestion / channel nudge / duplicate warning and the Library copied-link
  suggestion (UI19).
- useTextStyles().truncate: shared single-line-ellipsis utility, adopted in
  Library rows + the playlist panel + Banner; `title` now also sets minWidth:0
  so it shrinks in a flex row (L126).
- L102: Settings "Default format" now uses the same SegmentedControl (Video/
  Audio) + quality Select as the DownloadBar — one mental model; the combined
  `kind|quality` string encoding is gone.

Resolved by verified convention (documented in code / CODE-AUDIT.md):
- L103 busy = in-button icon→Spinner everywhere (done in L134; no adjacent-
  spinner pattern survives).
- L115 list-row actions icon-only+tooltip vs card-toolbar actions labelled.
- L120 Fluent <Card> for static content cards; tokenized surface recipe for
  interactive/list-item cards.
- UI20 toggles = solid brand, list-selection = brand-tint (rule noted on
  SegmentedControl); the cited Library pill is gone via UI18.

L128 (build-time DCE of preview seeds) deferred to Batch 14 with PERF8.

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:37:32 -04:00
debont80 bd7ba8726e feat(audit): Batch 6-7 — design tokens + type/color roles, full migration
Landed complete design-token foundation (Batch 6) and type/color standardization (Batch 7):
- New src/renderer/src/components/ui/tokens.ts: SPACE, RADIUS, ELEVATION, Z, ICON, MOTION, SCRIM, META_SEP scales
- New src/renderer/src/components/ui/text.ts: useTextStyles hook + type-role convention (Subtitle2/Text/Body1/Caption1)
- Migrated scattered literals in 17 renderer files to named tokens
- Adopted unified muted color and row-title component across 7 list views
- All tile glyphs snapped to ICON tiers (no literal px remain)
- base.css reduced-motion gate + prefers-color-scheme alignment
- Verified tile-glyph visual deltas (≤2px) for watched pass; UI3/UI12 implementation confirmed, awaiting visual check
- CODE-AUDIT.md reconciled: Batch 6 complete (12 items checked, UI3+UI12 flagged); Batch 7 complete (6 items checked)

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

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-01 15:24:00 -04:00
debont80 e6b4f343e0 feat(config+ux): batch #5 — source maps, integration script, copy, redownload options
Clean config/copy items (Phase 7):
- L170: build.sourcemap 'hidden' on main/preload/renderer — emit .map for offline
  crash symbolication (of the CC8 logger's raw stacks) without exposing them.
- L34: add `test:integration` npm script (AEROFETCH_REAL_DOWNLOAD=1) for the opt-in
  real-download suite; stays out of `npm test` (needs network + live yt-dlp).
- L87: HistoryEntry carries the original `options`; redownload replays them so a
  re-download reproduces the same post-processing instead of the current defaults.
- L96: folder "Browse" buttons -> "Browse…" (they open the OS picker).
- L97: PO-token placeholder "(none)" -> "Optional -- paste a token" (consistent).
- Ticked already-resolved: L99 (M28 aria-label), L113 (UX2 tooltip=aria-label),
  L95 (usage follows verb/modifier rule), L172 (skipLibCheck accepted).

typecheck + 268 tests + eslint + prettier green (touched files LF/clean).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 12:06:06 -04:00
debont80 cc4fb132e4 feat(ux): UX16 — autofocus the URL field on launch
An App mount effect focuses #aerofetch-url when the Downloads tab is active
(double-rAF for paint), so a user can paste + type immediately instead of
clicking first.

typecheck + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 11:35:44 -04:00