The release repo is private, so the auto-updater previously required each
user to paste a Gitea token into Settings before it could see or download
updates. Inject a read-only token at build time (AEROFETCH_UPDATE_TOKEN env
var or a gitignored .update-token file) via electron.vite's `define`, and
fall back to it in authHeader() when the user hasn't set their own. The token
value lives only in the built bundle, never in source or git.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ROADMAP/ROADMAP-PINCHFLAT docs were removed; this drops the now-dead
"see ROADMAP-PINCHFLAT.md" breadcrumbs (and one "the roadmap" mention)
from source comments, preserving the surrounding Pinchflat-style context.
Comment-only; typecheck (node+web) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ROADMAP files are fully shipped/retrospective and the smoke-test and
signing guides are retired at the maintainer's request. resources/bin/README.md
is kept (documents bundled-binary setup + license obligations).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The audit is fully closed (0 open / 372 done); all findings shipped to main across Batches 6-28. Removing the 2,558-line tracking doc now that it holds no open items. The preview-probe harness (.claude/launch.json) is retained.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both shipped (ROADMAP.md Phase L/P) but CODE-AUDIT.md's Deferred section
still listed them as pending.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Peer-review of the exact-format-per-item feature: re-clicking "Preview
formats" on an already-loaded playlist reset info/playlist but left
itemFormats/itemFormatOptions/formatExpanded keyed by position, so a
stale exact-format pick could silently reattach to a different video
landing at the same index in the new probe result and flow into
addPlaylist(). Extracted resetPlaylistItemState() and call it from
both clearProbe() and fetchFormats().
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each video row in the playlist panel gets an exact-format button that
lazily probes just that entry's URL (reusing the existing single-video
probe IPC, no new main-process code) and shows a native Select of its
real formats. The choice flows through addMany -> formatId per queue
item. Probing is on-demand per row, never all up front, since a
playlist can hold thousands of entries and per-entry probes are
rate-limited.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Re-implements the sleepRequests/sleepInterval/maxSleepInterval feature
(--sleep-requests / --sleep-interval / --max-sleep-interval) from the
stale feat/binary-management-and-library-scale branch, ported onto
current main's structure (zod settingsSchema, decomposed NetworkCard).
All default to 0/off, preserving current behaviour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On-demand maintenance panel (SourceRetention) on the source detail —
not a persisted schedule (roadmap's niche-for-desktop scope).
Prune: keep the newest N downloaded files, delete the rest's files
(download archive prevents silent re-download); dry-run count + two-step
confirm before any deletion. Upgrade: re-enqueue items whose recorded
downloadedQuality ranks below the source's current target (profile or
global). Selection is pure + injected-now in @shared/retention.ts
(selectPruneCandidates/selectUpgradeCandidates + qualityRank), unit-
tested; pruneMediaItems (main) deletes best-effort and clears state.
MediaItem.downloadedQuality now recorded through the completion path.
Live-verified the panel renders. 366 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A MediaProfile bundles { kind, quality, options, outputTemplate, extraArgs }
that a Source points at via Source.profileId, overriding the global defaults
field-by-field for that source's downloads.
Backend mirrors CommandTemplate: profiles.ts (createJsonStore, PROFILES_MAX,
isProfileLike + sanitize), CRUD IPC (profiles:list/save/remove) + preload +
useProfiles store; Source.profileId + sources:set-profile. Pure resolution
in @shared/profileResolve.ts (profile → global, per field; unit-tested).
Applied in the sources-store enqueueItems; a per-download collectionOutput-
Template override threads to buildArgs so a profile's outputTemplate wins.
UI: ProfileManager (mirrors TemplateManager) in a new Settings → Advanced →
Media profiles card, plus a per-source Profile picker in the Library source
detail. Both live-verified in the preview; 354 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Command preview + incognito toggle were already wired end-to-end in the
DownloadBar (Show command button, Incognito checkbox) — flipped the two
stale ROADMAP.md checkboxes with notes; verified live in the preview.
Output-path setting (PINCHFLAT): new Settings.collectionOutputTemplate — a
raw yt-dlp -o template overriding the default Channel/Playlist/NNN-Title
layout for Library downloads (empty = default). collectionOutputTemplate()
takes an optional custom template (joined under outDir, left for yt-dlp to
expand); download.ts passes the setting; zod schema validates it as a safe
relative template; Settings -> Filenames field. Tests in buildArgs +
settingsSchema; live-verified the field renders.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zod field schemas in settingsSchema.ts replace setSettings' bespoke
per-key switch: parse → skip-on-fail → store, side effects (secret
encryption, launch-at-startup sync) kept in settings.ts. sanitizeOptions
extracted to settingsOptions.ts to break the import cycle; backup import
flows through the same schema. Field-by-field parity pinned in
test/settingsSchema.test.ts (15 tests); full suite unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CC1: customCommandEnabled→enableCustomCommands, downloadArchive→
useDownloadArchive, clipboardWatch→watchClipboard, sidebarCollapsed→
isSidebarCollapsed, hardwareAcceleration→useHardwareAcceleration,
videoDir/audioDir→videoFolder/audioFolder (+ chooseDir/clearDir store
actions → chooseFolder/clearFolder). Rename map in settingsMigration.ts
(pure, unit-tested incl. pre-rename backup fixture), applied as an
idempotent on-disk shim at store open and on backup import so old
settings.json and old backups both keep working.
CC11: update host/owner/repo + release API moved to src/main/config.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PERF3: pumpBench.test.ts pins pump()'s pipeline cost — 0.43ms @5k items,
1.47ms @20k (median) — negligible at a few events/sec, so the
count+pointer rewrite of the core queue is declined as risk without
reward; the benchmark stays as a 5ms regression tripwire.
PERF6/L162: closed as documented-by-design (virtualization bounds
mounted thumbs; a theme prop conflicts with PERF5's stable renderer).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 pinning tests lock downloadAppUpdate's security invariants (redirect
re-validation per hop, checksum refusal paths, SHA-256 verify + cleanup,
truncation, idle-timeout, cancel in both phases, progress) with a
scripted net.request fake; then the nested streaming block is extracted
verbatim into streamInstallerToFile. downloadAppUpdate is now a linear
pre-flight; B6 cancel-slot ownership stays with its module state via
onCancelReady/onSettled. Pins green before and after.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UI23: cookie sign-in + PO-token windows open with the app's resolved
theme background (passed from the IPC layer), no more white flash.
W12: multi-resolution fallback tray icon (16/24/32/48 via
addRepresentation dataURL, embedded in trayFallbackIcons.ts); decode +
scale factors verified in an Electron probe.
W15: hardwareAcceleration setting (default false = software rendering);
index.ts gates disableHardwareAcceleration on it after the portable
redirect; Appearance card switch with restart note; boot path verified.
L131: resolved by-design — clearing the queue acknowledges failures;
flashFrame (W10) covers attention, Diagnostics persists the record.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UX7: 11 cards grouped into five sections (Downloads, Appearance,
Network & accounts, Advanced, App & maintenance) with section headings
and a jump nav under the search box; search hides empty sections and
the nav; heading hierarchy corrected (h1 page, h2 section, h3 card).
UX8: post-processing card co-located under the Downloads card and
renamed 'Post-processing' with a defaults-vs-advanced hint.
UX23: closed as stale duplicate of the already-fixed L71 (editable,
validated, reconciled folder inputs) — verified in the preview probe.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UI3: remaining list-row/control paddings on the SPACE scale (History row,
Downloads summary, Library head/detail, Terminal gate/log, template rows).
UI21: active-nav rail now shows collapsed too (moved into navItemActive).
UX18: stable 'Quality' label + brand '• fetched' tag instead of the
label/control morph after Fetch.
L109/UX26: skeleton lines for the About yt-dlp/ffmpeg boot load; skeleton
cards in Settings until the store's loaded flag flips.
UI12/UI16: deferred eyeball deltas verified via Electron-probe screenshots
(light+dark, collapsed sidebar, fetch flow); UI16 convention codified in
ui/tokens.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
L161: <main> is now a non-scrolling flex slot; each screen owns its scroll
via shared shells in ui/Screen.tsx — 'page' (Library/Settings/empty History
scroll the shell) and 'fill' (Downloads/Terminal/History scroll their own
list/log, shell scrolls only as short-window fallback). height:100%
couplings replaced with flexGrow+minHeight:0; list regions floored at
160px so they can't collapse on short windows.
L104: HistoryView renders through VirtualList; Ctrl+A re-homed onto a
wrapper around the scroller, per-row Delete unchanged on rows.
Verified with an Electron probe against the vite preview at 1100x720 and
800x520 (probe metrics + screenshots): one active scroller per screen,
main/body never scroll, floors hold. Adds .claude/launch.json for the
preview server.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prettier's LF default fought git's CRLF checkout on Windows, failing
format:check on every file with no real diff. 'auto' keeps whatever the
checkout uses; content style is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
L142 (+CC14): keyed latest-wins reconciler (lib/reconcile.ts, unit-tested)
applies main's authoritative IPC returns in the history/templates/sources
stores, extending the M34 settings pattern; loadSources shares the key so
a slow list read can't clobber a newer mutation result.
L93 (+CC13): settings cards, TerminalView, and LibraryView now reach IPC
only through colocated view-model hooks (useAboutCard/useBackupCard/
useCookiesCard/useSoftwareUpdateCard/useNetworkCard/useTerminalRun) or
store actions (openHighContrastSettings, scheduled-sync on sources) —
no window.api left in components.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two small self-contained features from the audit's deferred list:
- L51: the Task Scheduler daily sync is no longer pinned to 09:00. New
Settings.syncTime (24h HH:MM) with a native time input beside the Library
"Daily sync" switch — persists on change, re-registers the task on blur
(schtasks /Create /F overwrite is the time-change mechanism). The value
reaches the schtasks /ST argv, so a shared isValidSyncTime guards it at
both the settings write and in schedule.ts (unit-tested incl.
injection-shaped input). Live-verified against the machine's real task.
- L64: aria2c connection count (-x/-s) is user-tunable. New
Settings.aria2cConnections (1-16, aria2c's own ceiling) exposed as a
SpinButton in Settings -> Network while the aria2c toggle is on.
ARIA2C_ARGS became the pure aria2cArgs(n?) with a defensive clamp;
threaded through AccessOptions. The -k 1M split floor stays fixed — a
free-text field would bypass the custom-command consent gate.
typecheck + 309 tests + eslint + production build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The watched live session for the six deferred lifecycle items, each verified
against real yt-dlp/Electron behavior on Windows:
- R4: cancel now deletes the download's orphaned partials. The engine captures
the final output path via a new `--print before_dl:dest|%(filename)s`
(empirically %(filepath)s is still 'NA' that early) and the close handler
sweeps only unambiguous intermediates (.part / .part-Frag* / .ytdl /
<stem>.fNNN.<ext>) via the pure, unit-tested lib/partials.ts — never a bare
<stem>.<ext> or another download's files. Live-verified with decoys.
- L65: verified-acceptable, no code change — a taskkill /F pause leaves the
.part byte-intact and yt-dlp --continue resumes at the exact byte offset.
- L139: spawn↔self-update interlock (new ytdlpLock.ts). The updater refuses
while any yt-dlp spawn is live; download/terminal IPC handlers hold (await)
behind an in-progress exe rewrite instead of failing.
- B2: source indexing is cancellable — AbortSignal through the probe walk
(kills the in-flight child, live-verified via tasklist), sources:index-cancel
IPC, and a Cancel button in the Library add-source row.
- B6: app-update download is cancellable — app:update-cancel routes through
the existing finish() teardown (abort + destroy + unlink, live-verified on
Electron net.request); Cancel button under the update progress bar. The
checksum phase is cancelable too.
- L143: closing the window mid-download (non-tray mode) now offers a native
tray-independent "Quit anyway / Keep downloading" dialog, replacing the
passive "use the tray to quit" notification.
Peer-review pass caught and fixed: a non-reentrant update lock (concurrent
begin clobbered the settle promise), a dead Cancel window during the B6
checksum fetch + a canceller slot-ownership bug (L140 shape), and a
persist-after-cancel hole in the index walk.
typecheck + 304 tests + eslint + production build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reconciles two lines of history that both descended from the same
feat/tray-background-clipboard branch (background running, clipboard-link
detection, updater auth): main picked it up as a single squash commit
(PR #7, d112bc4), while the audit chain (p1-docs..batch-15) branched off
that feature branch's pre-squash tip and continued with its own granular
history, including the H1 decomposition of DownloadBar.tsx/SettingsView.tsx.
12 files conflicted, all resolved by verifying the audit chain already
carries PR #7's functionality (confirmed per-file against the original
d112bc4 diff) and keeping the chain's newer/relocated version:
- shared/ipc.ts, main/settings.ts: DEFAULT_SETTINGS single-sourced (C1)
- main/download.ts, main/tray.ts: trivial, chain is a superset
- main/index.ts: ipcMain.handle wiring extracted to main/ipc.ts (L2);
removed a silent duplicate notifyBackgroundOnce that 3-way merge
produced with no conflict markers
- renderer/main.tsx, store/settings.ts: mock/fallback settings
single-sourced (C1/mockApi.ts)
- LibraryView.tsx: old inline clipboard-suggestion banner replaced by
the shared LinkSuggestion primitive (Batch 9/UI19); removed dead
icon imports + styles left behind by the auto-merge
- DownloadBar.tsx, SettingsView.tsx: taken wholesale from the chain
(H1-decomposed shells) after confirming their extracted hook/cards
already contain PR #7's additions (useClipboardLink.offer(),
launchAtStartup, updateToken)
- useClipboardLink.ts, test/clipboardLink.test.ts (add/add): chain's
version is a strict refinement of the same code (L40/L138/L145)
Verified post-merge: typecheck (node+web), 279 tests, eslint, and the
production build all pass; touched files are prettier-clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Self-review of the Batches 9-15 changes surfaced two style inconsistencies (no
behavior change):
- Toaster.tsx: combine the icon + tone classes with mergeClasses instead of a
template-literal string, matching how the rest of the codebase composes Griffel
classes.
- App.tsx: move the PERF8 React.lazy() view declarations below the import block
(they were interleaved between import statements).
Verified: typecheck (node+web) + 279 tests + eslint + production build green
(code-split chunks intact); touched files prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This is the batch the run reserves for a WATCHED LIVE SESSION, so only the safe,
non-file-deleting item is implemented here; the rest are staged with per-item
live-verify rationale in CODE-AUDIT.md.
Landed:
- L157: removeSource cancels its in-flight downloads. removeSource emits a
`sourceRemoved` event on the coordinator bus (C2); the downloads store cancels
every active (downloading/queued) item whose mediaItemId starts with
`${sourceId}:` — MediaItem ids are `${sourceId}:${videoId}` (indexerCore), so the
prefix reliably identifies the source's downloads. Reuses the proven cancel()
path (process kill + pump). Type/lint/test-green; process-kill worth a live confirm.
Resolved by prior work:
- UX11: M4 persists the queue, so a scheduled item fires on next launch once due —
no longer "never fires if quit".
- UX20: L68 resets notifiedBackground on window show, so every close-to-tray
re-fires the "still running in the tray" notice, not just the first.
Deferred to the watched live pass (each needs a real download to verify):
R4 (delete .part on cancel — file-deletion data-safety), L65 (graceful Ctrl-C
pause), L139 (spawn↔auto-update interlock — timing race), B2 (index AbortSignal +
Cancel), B6 (app-update cancel — ties to CL4's deferred updater), L143 (in-window
quit-anyway).
Verified: typecheck (node+web) + 279 tests + eslint + production build green;
touched files prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
Contained fixes:
- L158: drag-highlight flicker. useDownloadBar tracks a dragDepth counter —
onDragEnter increments, onDragLeave decrements, the highlight clears only when
depth returns to 0 (the drag truly left the card). onDragOver just
preventDefaults; onDrop resets. No more blink when crossing child elements.
- L92: preload method names aligned with main — markSourceItemDownloaded →
setMediaItemDownloaded, syncSources → syncWatchedSources (callers + mock
updated; compiler-enforced via the derived Api type).
- L149: the three-sentence "Keep running in the tray" hint is now one line.
- L171: one MOCK_CURRENT_VERSION backs both the preview mock's getAppVersion and
checkForAppUpdate.currentVersion, so they agree.
- L5 (representative): the About card's repeated monospace inline styles → shared
mono/monoBlock/monoPre classes; convention set (makeStyles for static, inline
style only for data-driven values).
Resolved by verification/convention (CODE-AUDIT.md): L21 (moot after the H1
SettingsView decomposition), L31 (dual theme switcher is width-driven), L130
(lowercase fragments vs shown-raw sentences), L152 (search sizes are role-
appropriate), UI22 (brand/entity/section icon-emphasis set now defined).
Deferred with rationale: L51/L64 (features), L93 (CC13 view-model pass), L104
(moved to Batch 14 perf), L109/UI21 (visual), L161/UI23 (layout/live-verify).
Verified: typecheck (node+web) + 279 tests + eslint + production build green;
touched files prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>