Files
AeroFetch/src/renderer/src/store/history.ts
T
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

103 lines
3.3 KiB
TypeScript

import { create } from 'zustand'
import { HISTORY_MAX_ENTRIES, type HistoryEntry } from '@shared/ipc'
import { isPreview as PREVIEW } from '../isPreview'
import { logError } from '../reportError'
// Mock history so the History tab has content during UI design.
const seed: HistoryEntry[] = PREVIEW
? [
{
id: 'h1',
title: 'How yt-dlp Works Under the Hood',
channel: 'Open Source Weekly',
url: 'https://youtube.com/watch?v=zzz9999',
kind: 'video',
quality: '720p · mp4 · 184 MB',
sizeLabel: '184 MB',
filePath: 'C:\\Users\\you\\Downloads\\How yt-dlp Works Under the Hood.mp4',
completedAt: Date.now() - 1000 * 60 * 42
},
{
id: 'h2',
title: 'Deep Focus — Ambient Mix',
channel: 'ChillStudio',
url: 'https://youtube.com/watch?v=aaa1111',
kind: 'audio',
quality: 'Best',
sizeLabel: '142 MB',
filePath: 'C:\\Users\\you\\Downloads\\Deep Focus — Ambient Mix.mp3',
completedAt: Date.now() - 1000 * 60 * 60 * 26
},
{
id: 'h3',
title: 'Conference Keynote 2026 (Full)',
channel: 'TechConf',
url: 'https://youtube.com/watch?v=bbb2222',
kind: 'video',
quality: '1080p · mp4 · 1.2 GB',
sizeLabel: '1.2 GB',
filePath: 'C:\\Users\\you\\Downloads\\Conference Keynote 2026 (Full).mp4',
completedAt: Date.now() - 1000 * 60 * 60 * 24 * 3
}
]
: []
// Cap the optimistic in-memory list at the same shared limit main persists to
// (HISTORY_MAX_ENTRIES) so a long session can't grow it past what a reload would
// show (L141). De-duping by url as well as id keeps it consistent with main's add
// (M35), so the optimistic list matches the reload.
interface HistoryState {
entries: HistoryEntry[]
add: (entry: HistoryEntry) => void
remove: (id: string) => void
removeMany: (ids: string[]) => void
clear: () => void
openFile: (id: string) => void
showInFolder: (id: string) => void
}
export const useHistory = create<HistoryState>((set, get) => ({
entries: seed,
add: (entry) => {
set((s) => ({
entries: [entry, ...s.entries.filter((e) => e.id !== entry.id && e.url !== entry.url)].slice(
0,
HISTORY_MAX_ENTRIES
)
}))
if (!PREVIEW) window.api.addHistory(entry).catch(logError('addHistory'))
},
remove: (id) => {
set((s) => ({ entries: s.entries.filter((e) => e.id !== id) }))
if (!PREVIEW) window.api.removeHistory(id).catch(logError('removeHistory'))
},
removeMany: (ids) => {
const remove = new Set(ids)
set((s) => ({ entries: s.entries.filter((e) => !remove.has(e.id)) }))
if (!PREVIEW) window.api.removeManyHistory(ids).catch(logError('removeManyHistory'))
},
clear: () => {
set({ entries: [] })
if (!PREVIEW) window.api.clearHistory().catch(logError('clearHistory'))
},
openFile: (id) => {
const e = get().entries.find((x) => x.id === id)
if (!PREVIEW && e?.filePath) window.api.openPath(e.filePath)
},
showInFolder: (id) => {
const e = get().entries.find((x) => x.id === id)
if (!PREVIEW && e?.filePath) window.api.showInFolder(e.filePath)
}
}))
// Load persisted history on startup.
if (!PREVIEW) {
window.api.listHistory().then((entries) => useHistory.setState({ entries }))
}