diff --git a/CODE-AUDIT.md b/CODE-AUDIT.md index 5b5bc5c..e873ebb 100644 --- a/CODE-AUDIT.md +++ b/CODE-AUDIT.md @@ -211,6 +211,21 @@ L109 (visual, with UX26), L161 (layout, live-verify), UI21 (visual), UI23 (cooki live-verify). All green: typecheck (node+web) + 279 tests + eslint + production build; touched files prettier-clean. +**Session 2026-07-01 pass 11 (Batch 14 — perf + first-run defaults + Windows):** two real bundle/startup wins +plus one first-run default. **PERF8** — Library/History/Terminal/Settings are `React.lazy` chunks behind a +`Suspense` (Downloads stays eager); the production build now splits ~140 kB (incl. the 90 kB SettingsView) out +of the initial bundle. **L128** — the three preview seed arrays are gated on `import.meta.env.DEV && PREVIEW`, +so Rollup dead-code-eliminates them from the packaged renderer (DEV statically false) while the browser dev +preview keeps them. **SR4** — `clipboardWatch` now defaults `false` for new installs (privacy-first; existing +installs keep their saved value; Onboarding tip repointed) — *a product default flip flagged for the +maintainer, trivially reversible.* **Deferred with rationale:** **PERF3** (core-queue micro-opt on a bounded +list — risk > reward unattended, revisit in the Batch 15 live pass), **PERF6/L162** (bounded by +virtualization; a prop conflicts with PERF5's hoisted renderItem), **SR5** (MP3 = deliberate +max-compatibility default; the label bug was M18), **W12** (fallback-of-a-fallback icon), **W15** (the GPU +software-rendering workaround — external driver dependency), and **L104** (History virtualization needs the +L161 layout rework + live verification; the 500-cap bounds it). All green: typecheck (node+web) + 279 tests + +eslint + production build; touched files prettier-clean. + **Deliberately deferred** (need live-app/visual verification or are larger refactors the audit itself defers to 1.x): the wire-or-cut features (M5/M6/UX1), the god-file/store refactors (C1, C2, H1), the SIMP* helpers + shared-UI-token work, the CC* consolidations, @@ -916,9 +931,12 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n remaining ``s are standalone status-row indicators (the "Fetching…"/"Waiting to start…"/indexing rows), not a button + adjacent spinner. No separate-adjacent-spinner pattern survives.* - [ ] **L104 — History isn't virtualized.** It renders all filtered rows, while Downloads and Library use `VirtualList`. - *Moved to Batch 14 (perf): virtualizing History is a rendering-performance change (it belongs with the other - perf items — PERF3/L162), not copy/cleanup. Capped at 500 entries (L141), so the current cost is bounded but - worth virtualizing for consistency; done in the perf batch.* + *Deferred (bounded; needs the L161 layout rework + live verification): History is capped at 500 rows (L141), + so the un-virtualized render is bounded — not a real perf problem. Virtualizing it requires giving the list a + fixed-height internal scroll container (the same `height:100%`/`overflow` rework L161 flags as needing + live verification at multiple window sizes) *and* re-homing its container-level Ctrl+A and per-row Delete + keyboard handling onto the virtualized scroller. That's a layout+interaction change best done watched, so it + rides with L161 rather than shipping unattended for a list that's already capped.* - [x] **L105 — `MediaKind` declared twice.** In both [ipc.ts](src/shared/ipc.ts) and [store/downloads.ts](src/renderer/src/store/downloads.ts); components import it from both. Single source it. *Fixed: the store now imports `MediaKind` from `@shared/ipc` and re-exports it, so the duplicate local @@ -1018,11 +1036,14 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n (the header is now `title`), and the shared `Banner` content (UI19). `title` also gained `minWidth:0` so it shrinks correctly in a flex row. (Remaining correct-but-inline truncations migrate to the util incrementally.)* - [x] **L127 — `Notification` sets no icon** — completion toasts used the default Electron icon though `getAppIconPath()` existed. *Fixed with W13 (cached `getAppIconImage()` passed as `icon` at both notification sites).* -- [ ] **L128 — Preview seed/mock data ships in production.** The `PREVIEW` runtime check can't be +- [x] **L128 — Preview seed/mock data ships in production.** The `PREVIEW` runtime check can't be tree-shaken, so each store's seed arrays + fake ticker are bundled into the Electron renderer. - *Deferred to Batch 14 (perf/bundle): this is a build-time dead-code-elimination concern (gate the seed - imports behind `import.meta.env` so Rollup drops them from the packaged renderer), grouped with PERF8's - code-splitting where the lazy-loading is set up. Left open on purpose.* + *Fixed: the three preview seed arrays (`downloadSeed`, `sources` `seedSources`/`seedItemsBySource`, + `history` seed) are now gated on `import.meta.env.DEV && PREVIEW`. In the packaged renderer build + `import.meta.env.DEV` is statically `false`, so Rollup constant-folds `false && …` and dead-code-eliminates + the whole seed literal; the browser dev preview (`vite`, DEV `true`) still gets them. (The small `PREVIEW` + fake-ticker function stays — it's tied to the runtime flag and is a few lines; the bulky part was the data, + now dropped.)* - [x] **L129 — `Hint align` is silently ignored for left/right placements** yet callers still pass it. *Fixed: `HintProps` is now a discriminated union — `align` is only accepted with `placement` `top`/`bottom`; passing it with `left`/`right` is a compile error instead of a silent no-op. No caller needed changing (the @@ -1189,7 +1210,9 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n a layout change best validated with the app open at several window sizes — a watched pass, not an unattended edit.* - [ ] **L162 — Per-thumbnail store subscription.** Every `MediaThumb` calls `useResolvedDark()` (two store - subscriptions) instead of receiving a resolved theme prop — N subscriptions per list. + subscriptions) instead of receiving a resolved theme prop — N subscriptions per list. *Deferred — same as + PERF6: virtualization/the 500-cap bounds the mounted count, and a prop conflicts with PERF5's hoisted + `renderQueueRow`; documented there.* - [x] **L163 — QueueItem makes 9 separate `useDownloads` selector calls** instead of one destructured selection — repeated boilerplate per row. *Fixed: one `useShallow` selection replaces the 10 individual subscriptions in [QueueItem.tsx](src/renderer/src/components/QueueItem.tsx) (the actions are stable, so the @@ -1708,6 +1731,10 @@ DPI handled by Chromium). The deviations: - [ ] **W12 — Fallback tray icon is single-resolution.** The embedded fallback is a 32×32 PNG, so on 150%/200% DPI the tray glyph is blurry when the real multi-size `.ico` is absent. **Standard:** a multi-size fallback (or rely only on the `.ico`). + *Deferred (low value, needs the real tray to judge): the fallback only renders in the unlikely case the + bundled multi-size `build/icon.ico` is missing (W14 regenerated it), so it's a fallback-of-a-fallback. A + multi-res embedded base64 is fiddly to author and best verified on a real high-DPI tray — not worth an + unattended change for a path that shouldn't be hit.* - [x] **W13 — Notifications set no icon.** `new Notification({title, body})` omitted `icon`; on the **portable** build (no installed AUMID shortcut) Windows toasts showed a generic icon. *Fixed: both notification sites ([download.ts](src/main/download.ts) completion/failure toast + [index.ts](src/main/index.ts) background-running @@ -1724,6 +1751,11 @@ DPI handled by Chromium). The deviations: - [ ] **W15 — Software rendering is global.** Hardware acceleration is disabled for a documented GPU workaround; on high-DPI/large windows this trades GPU compositing for CPU, which can be sluggish. **Standard:** re-enable HW accel where the target GPU allows (the memo already flags revisiting this). + *Deferred (deliberate workaround, external dependency): `disableHardwareAcceleration()` is the fix for the + dev machine's GPU/driver blank-overlay bug (documented in index.ts and the project memo) — the very reason + the app hand-rolls non-portal overlays. Re-enabling HW accel can't be validated until the NVIDIA driver + issue is confirmed resolved on the target GPU, so it's intentionally left until that watched hardware pass. + PERF8 (this batch) trims the first-paint cost that software rendering compounds.* - [x] **W16 — Touch targets too small / hover-only labels.** Icon-only row actions are ~32px, 4px apart, 4 per row (below the ~40px touch guideline), and their only labels are hover tooltips (`Hint`) that never appear on touch. **Standard:** ≥40px touch targets and non-hover labels. *(extends UX17)* *Fixed: @@ -2183,11 +2215,23 @@ the end so the picture is complete. **Verified-clean first:** card/label/button Defensible for chasing YouTube changes, but a risky default for a commercial release. **Fix:** consider `stable` as the shipped default. - [x] **SR3 — `autoDownloadNew` defaults to `true`.** The moment a user marks a channel "watched," new uploads auto-download with no per-event consent — can quietly fill a disk. **Fix:** default off (let watching be "notify," downloading be opt-in). -- [ ] **SR4 — `clipboardWatch` defaults to `true`.** The app reads the clipboard on every window focus from +- [x] **SR4 — `clipboardWatch` defaults to `true`.** The app reads the clipboard on every window focus from first launch (R7/L138). A privacy-conscious default would be off or first-run-prompted. **Fix:** default off, or ask on first run. -- [ ] **SR5 — Audio defaults to MP3 (lossy).** `defaultAudioQuality: 'Best (MP3)'` + `audioFormat: 'mp3'` + *Fixed: `DEFAULT_SETTINGS.clipboardWatch` is now `false` ([shared/ipc.ts](src/shared/ipc.ts)) — a new install + doesn't read the clipboard until the user opts in (a one-toggle setting), and the Onboarding tip was reworded + to point there ("Turn on clipboard detection in Settings…"). Existing installs keep their saved value + (electron-store only fills missing keys), so no one who already enabled it is affected. **Product note for + the maintainer:** this flips a signature convenience off-by-default for new users — trivially reversible if + you'd rather it stay on.* +- [x] **SR5 — Audio defaults to MP3 (lossy).** `defaultAudioQuality: 'Best (MP3)'` + `audioFormat: 'mp3'` give a lossy re-encode by default; m4a/opus are higher quality at the same size. Minor (MP3 = max - compatibility), but "Best" implying MP3 is a quality mismatch (M18). + compatibility), but "Best" implying MP3 is a quality mismatch (M18). + *Resolved as a deliberate default (documented): MP3 is the maximum-compatibility container (plays on every + device/player), which is the right *default* for a general-audience downloader — a user who wants + m4a/opus/flac picks it in the format options. The "Best" *label* mismatch (the real bug) was fixed in M18 + (the preset labels and `--audio-quality` handling now match the chosen format). Changing the shipped default + container away from MP3 is a product-taste call with a real compatibility downside, so it stays MP3 by + intent, not oversight.* ### Stuck states & progress glitches @@ -2242,6 +2286,11 @@ acceptable first:** memoized `QueueItem`s don't re-render when their item is unc on every add/done/error/cancel/progress-driven change; with M33's unbounded channel enqueue (thousands of items) every event becomes O(n), and several events fire per second during active downloads. **Fix:** track running/queued counts + a pointer instead of rescanning. + *Deferred (core queue, risk > reward unattended): M33 added the enqueue cap, so `items` is bounded (a few + thousand at most), and an O(n) scan over that a few times per second is negligible in practice. Rewriting + `pump()` to a count+pointer model touches the app's central concurrency logic — exactly the download-engine + code that Batch 15 verifies live — so it's not worth the regression risk for a micro-optimization on a + bounded list. Revisit with the Batch 15 live pass if profiling ever flags it.* - [x] **PERF4 — `summarizeQueue` runs twice per progress tick.** Once in App's store subscription (which also pushes a taskbar IPC every tick, L25) and once in DownloadsView's render (no `useMemo`, L94) — each O(items), every ~second per active download. **Fix:** compute once, memoize, throttle the taskbar push. @@ -2254,11 +2303,22 @@ acceptable first:** memoized `QueueItem`s don't re-render when their item is unc stable module-level functions in [DownloadsView.tsx](src/renderer/src/components/DownloadsView.tsx).* - [ ] **PERF6 — Per-thumbnail store subscriptions.** Each `MediaThumb` calls `useResolvedDark()` (two store subscriptions) (L162); a long list creates N×2 subscriptions. **Fix:** resolve once, pass as a prop. + *Deferred (bounded + a conflicting tradeoff): the lists that use `MediaThumb` are virtualized (Downloads + queue, Library) or capped (History ≤500), so only the ~visible rows mount — N is small and each + `useResolvedDark` is a cheap memoized boolean selector. Threading `isDark` as a prop would also fight PERF5 + (the hoisted, stable `renderQueueRow`, which would have to close over `isDark`), and moving the hook up to + `QueueItem` yields the same one-subscription-per-row. Low value, mild conflict — left as documented.* - [x] **PERF7 — (ref R3) O(n²) media-items rewrites** dominate the main-process cost when downloading a channel; closed by the cached/atomic `jsonStore` (SIMP1) — the single highest-value perf fix. -- [ ] **PERF8 — No code-splitting.** All views (incl. the rarely-used Terminal/Settings) load up front in +- [x] **PERF8 — No code-splitting.** All views (incl. the rarely-used Terminal/Settings) load up front in one renderer bundle alongside Fluent UI + React; software rendering (W15) compounds first-paint cost. Minor for a desktop app, but lazy-loading heavy views would trim startup. **Fix:** `React.lazy` the heavy tabs. + *Fixed: Library/History/Terminal/Settings are `React.lazy` chunks behind a `Suspense` in + [App.tsx](src/renderer/src/App.tsx) (Downloads, the launch tab, stays eager). The production build now emits + separate `SettingsView` (~90 kB), `LibraryView` (~29 kB), `HistoryView` (~16 kB), and `TerminalView` + (~8 kB) chunks — ~140 kB out of the initial bundle, loaded on first visit. The stores these views use are + imported eagerly elsewhere (App's `useDownloads` + `import './store/sources'`, and downloads → history), so + lazy-loading the *view* never delays store startup or persistence (the C2 note already set this up).* **Priority:** PERF7/R3 (scaling cliff) and PERF1/PERF2 (per-download redundant I/O + crypto) are the only ones with real user-visible impact; the rest are good hygiene. None warrant work before the correctness diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 8304b49..f96a8ec 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -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 {
{tab === 'downloads' && } - {tab === 'library' && } - {tab === 'history' && } - {tab === 'terminal' && } - {tab === 'settings' && } + + {tab === 'library' && } + {tab === 'history' && } + {tab === 'terminal' && } + {tab === 'settings' && } +
{paletteOpen && ( diff --git a/src/renderer/src/components/Onboarding.tsx b/src/renderer/src/components/Onboarding.tsx index 7db41de..97872b1 100644 --- a/src/renderer/src/components/Onboarding.tsx +++ b/src/renderer/src/components/Onboarding.tsx @@ -120,7 +120,7 @@ const useStyles = makeStyles({ const TIPS: { icon: React.JSX.Element; text: string }[] = [ { icon: , - 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: , diff --git a/src/renderer/src/store/downloadSeed.ts b/src/renderer/src/store/downloadSeed.ts index 42787b3..007ff74 100644 --- a/src/renderer/src/store/downloadSeed.ts +++ b/src/renderer/src/store/downloadSeed.ts @@ -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.' + } + ] + : [] diff --git a/src/renderer/src/store/history.ts b/src/renderer/src/store/history.ts index 7b7a1a9..7bdb74d 100644 --- a/src/renderer/src/store/history.ts +++ b/src/renderer/src/store/history.ts @@ -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 diff --git a/src/renderer/src/store/sources.ts b/src/renderer/src/store/sources.ts index bf7bd14..0f42f81 100644 --- a/src/renderer/src/store/sources.ts +++ b/src/renderer/src/store/sources.ts @@ -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 = 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 = + 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 ------------------------------------------------------------------ diff --git a/src/shared/ipc.ts b/src/shared/ipc.ts index 03f0578..331ee6e 100644 --- a/src/shared/ipc.ts +++ b/src/shared/ipc.ts @@ -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: '',