Background running, clipboard detection, updater auth + 1.0 release-gate audit fixes #8
Reference in New Issue
Block a user
Delete Branch "feat/tray-background-clipboard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Brings the
feat/tray-background-clipboardbranch (48 commits) up tomain.Highlights
DEFAULT_SETTINGSin@shared, one typedmockApi.ts, centralizedisPreview, broke the downloads-to-sources dependency cycle with a typed event bus.fmtBytes/fmtSpeed/fmtEtasingle-homed insrc/shared/format.ts;DownloadProgresscarries raw speed/eta numbers (no string-to-number re-parse); one canonicalyoutubeId.Testing
248 tests passing.
Still deferred to 1.x (not in this PR)
H1 (god-file decomposition), CC8 (electron-log sink), M4 (queue persistence), M14 (Settings search DOM), SIMP refactors, and the Low tail.
Generated with Claude Code
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>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>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>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>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>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>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>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>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>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>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>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>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>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>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>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>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>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>W8: Added Ctrl+, as a standard Settings accelerator alongside the existing Ctrl+K palette shortcut. The key handler now handles both in one listener so there's a single window.addEventListener lifecycle. W9: Command palette now restores focus to the previously focused element when it closes. prePaletteRef stores document.activeElement at open time; onClose fires a requestAnimationFrame focus-restore after unmounting the palette, so the focus returns to the trigger (a sidebar nav button, the URL input, etc.) rather than leaving focus stranded at the document body. typecheck + 242 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>W18: Added explicit forced-color-adjust:auto on the universal selector so the browser's HC mode can override all custom token colors. Added an @media(forced-colors:active) rule that replaces the CommandPalette backdrop's rgba(0,0,0,0.32) with the Canvas system color + a ButtonText border, so the palette remains perceivable under Windows High Contrast. W19: Already fixed in the SR8 commit (window title now reflects download state via mainWindow.setTitle in the taskbarProgress handler). Marking done here with the companion CSS change. typecheck + 242 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>L67: Onboarding now has a 'Skip' button alongside 'Get started', both in a right-aligned flex row. Both call update({hasCompletedOnboarding:true}) so the behavior is the same — Skip just reads as optional for users who want to get started immediately without reading the tips. L86: HistoryView now clears any selected IDs that are no longer visible when the filter (query or kind) changes. A useEffect on `filtered` intersects the current selection with the new visible set and calls setSelected only if the size actually changed (avoids re-renders when the filter doesn't affect the selection). typecheck + 242 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Addressing issues found reviewing this session's audit commits: REGRESSIONS FIXED - updater.ts (L52): reverted the spawn(detached) installer launch. Raw CreateProcess/spawn fails with ERROR_ELEVATION_REQUIRED if the NSIS build flips to perMachine (the config comment invites this), and it silently dropped shell.openPath's launch-error detection. Restored shell.openPath (ShellExecute honors the elevation manifest) and kept the L52 goal by replacing the 1500ms timer with setImmediate(app.quit). - LibraryView.tsx (M15): native <button> doesn't inherit color (UA sets ButtonText), so the source-card chevron (currentColor) would render wrong in dark mode. Added color: colorNeutralForeground1 to cardHead. INEFFECTIVE CHANGE REVERTED - base.css (W18): forced-color-adjust:auto on * is the CSS default (no-op), and [class*="backdrop"] never matches Fluent/Griffel's hashed atomic class names. Reverted; W18 unmarked (needs real High-Contrast testing). WEAK FIX REVERTED - Onboarding.tsx (L67): "Skip" and "Get started" called the identical handler on a single-screen onboarding. Removed the duplicate button; L67 unmarked (real ask is a "show tips again" revisit affordance). STYLE / CORRECTNESS - Stripped UTF-8 BOMs accidentally added to Select/CommandPalette/App/ Onboarding by the PowerShell sanitizer; left pre-existing BOMs untouched. - DownloadBar.tsx: merged the duplicate @shared/ipc import; removed a stray double blank line. - qualityOptions.ts (L29): restored the original `satisfies Record<MediaKind, readonly string[]>` constraint + the noUncheckedIndexedAccess rationale comment (the extraction had dropped both for a weaker `as const`). - downloads.ts: removed double blank line left by the QUALITY_OPTIONS move. - binaries.ts: corrected YTDLP_MISSING_MSG doc comment ("at startup" -> the actual call sites). typecheck + 242 tests + eslint all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>L106: Established the convention — ArrowClockwise = retry/redo a local action (retry-all, re-download, retry queue item); ArrowSync = network sync/ update (sync sources, reindex, check/apply updates). Flipped the two misclassified sites (LibraryView "check for new" sync, SettingsView "Update yt-dlp") to ArrowSync and dropped the now-unused imports. L107: QueueItem queued-row Spinner was size="extra-tiny"; every other spinner is "tiny". Standardized to "tiny". L122: The app-update and yt-dlp-update buttons showed "Checking…"/"Updating…" in the label AND a separate sibling Spinner. Folded the spinner into the button icon (icon swaps to Spinner while busy), matching DownloadBar/ LibraryView, and removed the redundant sibling Spinner. typecheck + 242 tests + eslint green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>L119: Standardized empty-state copy on the "No X yet." pattern. Terminal "Output will appear here." -> "No output yet."; Diagnostics "No errors logged." -> "No errors yet." L151: Range-dash convention is ASCII hyphen everywhere. Changed the lone en-dash prose range ("2-3 is a good balance") to a hyphen, matching the time-range placeholders (1:30-2:00). L153: The three "Dismiss" close buttons now name what they dismiss -- "Dismiss suggested link" (DownloadBar + Library suggestion banners) and "Dismiss duplicate warning" (DownloadBar dup row). L95 left open: the Cookies-card "sign in"/"sign-in" usage is already correct English (verb vs modifier); no change warranted. typecheck + 242 tests + eslint green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>The completion side-effect (add to history unless private + markDownloaded on the source MediaItem) was duplicated in applyEvent('done') and the preview fake-ticker, and had already drifted (the preview copy omitted formatId/ formatHasAudio). Extracted one recordCompletion(item) helper that both call. Also tightened a latent bug: the old applyEvent ran markDownloaded for any 'done' event with a mediaItemId, even when the item was canceled (the reducer keeps canceled items as 'canceled'). recordCompletion now runs only when status === 'completed', so a canceled collection item is no longer wrongly marked downloaded (and will be retried on the next re-sync). typecheck + 242 tests + eslint green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>LibraryView mapped url -> status by last-write, so when a URL had multiple queue entries ("Download anyway"), the row reflected whichever item was iterated last. Added a STATUS_PRIORITY ranking (completed > downloading > queued > saved > paused > error > canceled) and keep the highest-priority status per URL, so a row shows the most meaningful state of that video. typecheck + 242 tests + eslint green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>M23: replaceMediaItems wrote [...newItems, ...others] and the JSON store sliced the combined list to MAX_ITEMS, so re-indexing one large source could silently evict ANOTHER source's tail. Now other sources are always kept in full and only the source being (re)indexed is capped to the remaining budget (MAX_ITEMS - others.length). M27: enqueueItems forced settings.defaultKind for every item, so a channel couldn't be queued as audio without flipping the global default. Added an optional `kind` override (falls back to the default) and a video/audio SegmentedControl in the Library action row, seeded from defaultKind so existing behavior is unchanged until toggled. typecheck + 242 tests + eslint green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Fixes found reviewing the previous commit: - M5 preview was inaccurate: loadCommandPreview omitted formatId/formatHasAudio and trim, so the "exact command" differed from what download() actually spawns whenever a probe-selected format or a trim range was set. toggleCommandPreview now mirrors download()'s StartDownloadOptions (format + trim included). - Stale preview: a shown command only cleared on URL change. Now it also clears on kind, quality, format, trim, download-options change, and on probe — so the displayed command is never out of sync with the form. - Command button is now a real toggle (Show command / Hide command) instead of a no-op "Command · shown" label. - Per-download overrides are now one-shot: incognito + downloadOptions reset to defaults after a download, matching trim/schedule and preventing incognito from silently carrying into the next download. - Style: replaced two inline style={{}} usages with makeStyles classes (commandError), hardcoded 'monospace' -> tokens.fontFamilyMonospace, dropped an unnecessary fragment, and let long command lines wrap (pre-wrap/break-all). typecheck + 243 tests + eslint + prettier green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>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>Pull request closed