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>
This commit is contained in:
@@ -1,12 +1,26 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react'
|
||||
import { useState, useEffect, useMemo, useRef, lazy, Suspense } from 'react'
|
||||
import { FluentProvider, makeStyles, tokens } from '@fluentui/react-components'
|
||||
import { Sidebar } from './components/Sidebar'
|
||||
import { DownloadsView } from './components/DownloadsView'
|
||||
import { LibraryView } from './components/LibraryView'
|
||||
import { HistoryView } from './components/HistoryView'
|
||||
import { TerminalView } from './components/TerminalView'
|
||||
import { SettingsView } from './components/SettingsView'
|
||||
import { Onboarding } from './components/Onboarding'
|
||||
|
||||
// Code-split the non-default tabs (PERF8): Downloads is the launch tab and stays
|
||||
// eager, but Library/History/Terminal/Settings load their chunks on first visit so
|
||||
// they (and their slice of Fluent) aren't in the initial bundle. The stores these
|
||||
// views use are imported eagerly elsewhere (App's `useDownloads` + `./store/sources`,
|
||||
// and downloads → history), so lazy-loading the *view* never delays store startup.
|
||||
const LibraryView = lazy(() =>
|
||||
import('./components/LibraryView').then((m) => ({ default: m.LibraryView }))
|
||||
)
|
||||
const HistoryView = lazy(() =>
|
||||
import('./components/HistoryView').then((m) => ({ default: m.HistoryView }))
|
||||
)
|
||||
const TerminalView = lazy(() =>
|
||||
import('./components/TerminalView').then((m) => ({ default: m.TerminalView }))
|
||||
)
|
||||
const SettingsView = lazy(() =>
|
||||
import('./components/SettingsView').then((m) => ({ default: m.SettingsView }))
|
||||
)
|
||||
import { CommandPalette, type PaletteAction } from './components/CommandPalette'
|
||||
import { LiveRegion } from './components/LiveRegion'
|
||||
import { Toaster } from './components/ui/Toaster'
|
||||
@@ -238,10 +252,12 @@ function App(): React.JSX.Element {
|
||||
|
||||
<main className={styles.content}>
|
||||
{tab === 'downloads' && <DownloadsView />}
|
||||
{tab === 'library' && <LibraryView />}
|
||||
{tab === 'history' && <HistoryView />}
|
||||
{tab === 'terminal' && <TerminalView />}
|
||||
{tab === 'settings' && <SettingsView />}
|
||||
<Suspense fallback={null}>
|
||||
{tab === 'library' && <LibraryView />}
|
||||
{tab === 'history' && <HistoryView />}
|
||||
{tab === 'terminal' && <TerminalView />}
|
||||
{tab === 'settings' && <SettingsView />}
|
||||
</Suspense>
|
||||
</main>
|
||||
|
||||
{paletteOpen && (
|
||||
|
||||
@@ -120,7 +120,7 @@ const useStyles = makeStyles({
|
||||
const TIPS: { icon: React.JSX.Element; text: string }[] = [
|
||||
{
|
||||
icon: <ClipboardPasteRegular />,
|
||||
text: 'Copy a video link and AeroFetch offers to fetch it as soon as you switch back.'
|
||||
text: 'Turn on clipboard detection in Settings and AeroFetch will offer to fetch links you copy.'
|
||||
},
|
||||
{
|
||||
icon: <HistoryRegular />,
|
||||
|
||||
@@ -3,57 +3,60 @@ import { newId } from '../id'
|
||||
import { type DownloadItem } from './downloadTypes'
|
||||
|
||||
// Mock seed data so every visual state is visible in the UI preview. Empty in a
|
||||
// real run -- the queue starts blank.
|
||||
export const seed: DownloadItem[] = PREVIEW
|
||||
? [
|
||||
{
|
||||
id: newId('item'),
|
||||
url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
title: 'Building a Desktop App with Electron -- Full Course',
|
||||
channel: 'DevChannel',
|
||||
durationLabel: '1:42:08',
|
||||
kind: 'video',
|
||||
quality: '1080p',
|
||||
status: 'downloading',
|
||||
progress: 0.42,
|
||||
speedBytesPerSec: 4.7 * 1024 * 1024,
|
||||
etaSeconds: 72,
|
||||
sizeLabel: '512 MB'
|
||||
},
|
||||
{
|
||||
id: newId('item'),
|
||||
url: 'https://youtube.com/watch?v=abcd1234',
|
||||
title: 'Lo-fi beats to code to (1 hour mix)',
|
||||
channel: 'ChillStudio',
|
||||
durationLabel: '1:00:21',
|
||||
kind: 'audio',
|
||||
quality: 'Best',
|
||||
status: 'queued',
|
||||
progress: 0
|
||||
},
|
||||
{
|
||||
id: newId('item'),
|
||||
url: 'https://youtube.com/watch?v=zzz9999',
|
||||
title: 'How yt-dlp Works Under the Hood',
|
||||
channel: 'Open Source Weekly',
|
||||
durationLabel: '14:03',
|
||||
kind: 'video',
|
||||
quality: '720p',
|
||||
status: 'completed',
|
||||
progress: 1,
|
||||
sizeLabel: '184 MB',
|
||||
filePath: 'C:\\Users\\you\\Downloads\\How yt-dlp Works Under the Hood.mp4'
|
||||
},
|
||||
{
|
||||
id: newId('item'),
|
||||
url: 'https://youtube.com/watch?v=err0r',
|
||||
title: 'Private video',
|
||||
channel: undefined,
|
||||
kind: 'video',
|
||||
quality: 'Best available',
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
error: 'Video unavailable: this video is private.'
|
||||
}
|
||||
]
|
||||
: []
|
||||
// real run -- the queue starts blank. Gated on `import.meta.env.DEV` as well as the
|
||||
// runtime PREVIEW flag so Rollup dead-code-eliminates this whole array from the
|
||||
// packaged (production) renderer build, where DEV is statically false (L128).
|
||||
export const seed: DownloadItem[] =
|
||||
import.meta.env.DEV && PREVIEW
|
||||
? [
|
||||
{
|
||||
id: newId('item'),
|
||||
url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
title: 'Building a Desktop App with Electron -- Full Course',
|
||||
channel: 'DevChannel',
|
||||
durationLabel: '1:42:08',
|
||||
kind: 'video',
|
||||
quality: '1080p',
|
||||
status: 'downloading',
|
||||
progress: 0.42,
|
||||
speedBytesPerSec: 4.7 * 1024 * 1024,
|
||||
etaSeconds: 72,
|
||||
sizeLabel: '512 MB'
|
||||
},
|
||||
{
|
||||
id: newId('item'),
|
||||
url: 'https://youtube.com/watch?v=abcd1234',
|
||||
title: 'Lo-fi beats to code to (1 hour mix)',
|
||||
channel: 'ChillStudio',
|
||||
durationLabel: '1:00:21',
|
||||
kind: 'audio',
|
||||
quality: 'Best',
|
||||
status: 'queued',
|
||||
progress: 0
|
||||
},
|
||||
{
|
||||
id: newId('item'),
|
||||
url: 'https://youtube.com/watch?v=zzz9999',
|
||||
title: 'How yt-dlp Works Under the Hood',
|
||||
channel: 'Open Source Weekly',
|
||||
durationLabel: '14:03',
|
||||
kind: 'video',
|
||||
quality: '720p',
|
||||
status: 'completed',
|
||||
progress: 1,
|
||||
sizeLabel: '184 MB',
|
||||
filePath: 'C:\\Users\\you\\Downloads\\How yt-dlp Works Under the Hood.mp4'
|
||||
},
|
||||
{
|
||||
id: newId('item'),
|
||||
url: 'https://youtube.com/watch?v=err0r',
|
||||
title: 'Private video',
|
||||
channel: undefined,
|
||||
kind: 'video',
|
||||
quality: 'Best available',
|
||||
status: 'error',
|
||||
progress: 0,
|
||||
error: 'Video unavailable: this video is private.'
|
||||
}
|
||||
]
|
||||
: []
|
||||
|
||||
@@ -4,44 +4,46 @@ import { isPreview as PREVIEW } from '../isPreview'
|
||||
import { logError } from '../reportError'
|
||||
import { revealFile } from '../reveal'
|
||||
|
||||
// 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
|
||||
}
|
||||
]
|
||||
: []
|
||||
// Mock history so the History tab has content during UI design. Gated on
|
||||
// `import.meta.env.DEV` too so Rollup drops it from the production build (L128).
|
||||
const seed: HistoryEntry[] =
|
||||
import.meta.env.DEV && 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
|
||||
|
||||
@@ -21,40 +21,44 @@ function seedItems(sourceId: string, playlist: string, n: number, downloadedUpTo
|
||||
}))
|
||||
}
|
||||
|
||||
const seedSources: Source[] = PREVIEW
|
||||
? [
|
||||
{
|
||||
id: 'src-dev',
|
||||
url: 'https://www.youtube.com/@DevChannel',
|
||||
kind: 'channel',
|
||||
title: 'DevChannel',
|
||||
channel: 'DevChannel',
|
||||
addedAt: Date.now() - 86_400_000,
|
||||
lastIndexedAt: Date.now() - 3_600_000,
|
||||
itemCount: 9
|
||||
},
|
||||
{
|
||||
id: 'src-mix',
|
||||
url: 'https://www.youtube.com/playlist?list=PLmix',
|
||||
kind: 'playlist',
|
||||
title: 'Lo-fi Coding Mixes',
|
||||
channel: 'ChillStudio',
|
||||
addedAt: Date.now() - 2 * 86_400_000,
|
||||
lastIndexedAt: Date.now() - 7_200_000,
|
||||
itemCount: 5
|
||||
}
|
||||
]
|
||||
: []
|
||||
// Gated on `import.meta.env.DEV` too so Rollup drops these preview seeds from the
|
||||
// packaged production renderer (DEV is statically false there) (L128).
|
||||
const seedSources: Source[] =
|
||||
import.meta.env.DEV && PREVIEW
|
||||
? [
|
||||
{
|
||||
id: 'src-dev',
|
||||
url: 'https://www.youtube.com/@DevChannel',
|
||||
kind: 'channel',
|
||||
title: 'DevChannel',
|
||||
channel: 'DevChannel',
|
||||
addedAt: Date.now() - 86_400_000,
|
||||
lastIndexedAt: Date.now() - 3_600_000,
|
||||
itemCount: 9
|
||||
},
|
||||
{
|
||||
id: 'src-mix',
|
||||
url: 'https://www.youtube.com/playlist?list=PLmix',
|
||||
kind: 'playlist',
|
||||
title: 'Lo-fi Coding Mixes',
|
||||
channel: 'ChillStudio',
|
||||
addedAt: Date.now() - 2 * 86_400_000,
|
||||
lastIndexedAt: Date.now() - 7_200_000,
|
||||
itemCount: 5
|
||||
}
|
||||
]
|
||||
: []
|
||||
|
||||
const seedItemsBySource: Record<string, MediaItem[]> = PREVIEW
|
||||
? {
|
||||
'src-dev': [
|
||||
...seedItems('src-dev', 'Full Electron Course', 6, 2),
|
||||
...seedItems('src-dev', 'Uploads', 3)
|
||||
],
|
||||
'src-mix': seedItems('src-mix', 'Lo-fi Coding Mixes', 5, 1)
|
||||
}
|
||||
: {}
|
||||
const seedItemsBySource: Record<string, MediaItem[]> =
|
||||
import.meta.env.DEV && PREVIEW
|
||||
? {
|
||||
'src-dev': [
|
||||
...seedItems('src-dev', 'Full Electron Course', 6, 2),
|
||||
...seedItems('src-dev', 'Uploads', 3)
|
||||
],
|
||||
'src-mix': seedItems('src-mix', 'Lo-fi Coding Mixes', 5, 1)
|
||||
}
|
||||
: {}
|
||||
|
||||
// --- Store ------------------------------------------------------------------
|
||||
|
||||
|
||||
+4
-1
@@ -638,7 +638,10 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
// greeted by a bright white window despite full system-theme support.
|
||||
theme: 'system',
|
||||
accentColor: 'teal',
|
||||
clipboardWatch: true,
|
||||
// Off on first launch (SR4): reading the clipboard on every window focus is a
|
||||
// privacy surprise for a brand-new user. It's a one-toggle opt-in in Settings
|
||||
// (the onboarding tip points there). Existing installs keep their saved value.
|
||||
clipboardWatch: false,
|
||||
downloadOptions: DEFAULT_DOWNLOAD_OPTIONS,
|
||||
proxy: '',
|
||||
rateLimit: '',
|
||||
|
||||
Reference in New Issue
Block a user