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>
This commit is contained in:
2026-06-30 20:25:49 -04:00
parent 6c19899f75
commit 7e3e5af52d
20 changed files with 596 additions and 413 deletions
+61
View File
@@ -568,6 +568,60 @@ export interface Settings {
updateToken: string
}
/**
* The canonical default Settings — the single source of truth shared by the main
* electron-store defaults (main/settings.ts), the renderer's pre-load FALLBACK
* (store/settings.ts), and the browser-preview mock (mockApi.ts), so the full
* Settings object is no longer hand-maintained in three places (C1). Preview-only
* tweaks (placeholder folders, skip onboarding, demo channel) are layered on top
* of this at each call site.
*/
export const DEFAULT_SETTINGS: Settings = {
// Both blank by default → downloads land in Documents\Video / Documents\Audio
// (see getDefaultMediaDir). A non-empty value is an explicit per-kind override.
videoDir: '',
audioDir: '',
defaultKind: 'video',
defaultVideoQuality: 'Best available',
defaultAudioQuality: 'Best',
maxConcurrent: 2,
filenameTemplate: '%(title)s.%(ext)s',
// Follow the OS theme on first launch (SR1) so a dark-mode Windows user isn't
// greeted by a bright white window despite full system-theme support.
theme: 'system',
accentColor: 'teal',
clipboardWatch: true,
downloadOptions: DEFAULT_DOWNLOAD_OPTIONS,
proxy: '',
rateLimit: '',
useAria2c: false,
cookieSource: 'none',
cookiesBrowser: 'chrome',
youtubePlayerClient: '',
youtubePoToken: '',
restrictFilenames: false,
downloadArchive: false,
// yt-dlp is self-managed: a writable copy under userData, auto-updated on the
// configured channel so a stale binary can't silently cause YouTube 403s.
autoUpdateYtdlp: true,
// Default to stable so a nightly regression doesn't break downloads for
// everyone out of the box (SR2). Users who want the latest YouTube fixes
// can switch to nightly in Settings → Software.
ytdlpChannel: 'stable',
ytdlpLastUpdateCheck: 0,
customCommandEnabled: false,
defaultTemplateId: null,
notifyOnComplete: true,
// Default off so adding a watched channel doesn't silently fill the user's
// disk on first use (SR3). Enable explicitly once they know what it does.
autoDownloadNew: false,
hasCompletedOnboarding: false,
minimizeToTray: false,
launchAtStartup: false,
sidebarCollapsed: false,
updateToken: ''
}
/**
* A named, reusable set of extra yt-dlp CLI flags (Phase C "custom commands"),
* e.g. name: 'Write thumbnail + no mtime', args: '--write-thumbnail --no-mtime'.
@@ -633,6 +687,13 @@ export interface HistoryEntry {
completedAt: number
}
/**
* Cap on persisted history rows. Single-sourced here so the main store (history.ts)
* and the renderer's optimistic in-memory list (store/history.ts, L141) share one
* value instead of two hand-synced copies.
*/
export const HISTORY_MAX_ENTRIES = 500
/**
* A failed download or download-start attempt, persisted to errorlog.json
* (newest-first) so the report survives the queue item being cleared —