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>
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>
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>
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>
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>
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>
- L118: decouple URL-field focus. The DownloadBar registers a focuser (its own
input ref) in the nav store; App calls useNav.getState().focusUrlField() on
launch / "New download" instead of document.getElementById('aerofetch-url').
The hardcoded id is removed — App no longer reaches into DownloadBar's markup.
- L155: the accent swatches are now a real role="radiogroup" with aria-checked
role="radio" swatches, roving tabindex, ←/→/↑/↓/Home/End arrow-key navigation,
and the shared focus ring — matching the app's SegmentedControl radio pattern.
Redundant "(selected)" aria-label text dropped (aria-checked conveys it).
- UX21: keyboard-shortcut discoverability — an Onboarding "Press Ctrl+K anytime"
tip, and the command palette's Settings action surfaces its "Ctrl ," shortcut.
- UX22 / L67: onboarding is revisitable — a "Show welcome tips again" button in
Settings → About re-opens the welcome screen.
Verified & documented (no code needed): UX17 (Fetch/Paste already have aria-label
+ focus-reachable Hint tooltips — a11y met; a permanent visible label is a compact-
layout choice), UI24 (context menus "none by design" — row actions are all visible
inline buttons + keyboard, and Fluent's Menu is portal-based, which the app avoids
for the dev-GPU flicker reason).
Verified: typecheck (node+web) + 279 tests + eslint + production build green;
touched files prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A new in-app toast surface is the batch's spine, making the silent-failure and
global-status gaps fixable:
- Toast infra: store/toasts.ts (useToasts + toast(); auto-dismiss) + ui/Toaster.tsx
(non-portal bottom-center stack, avoids the Fluent-portal GPU flicker). Unit-
tested (test/toasts.test.ts). Mounted at the app root beside LiveRegion.
- UX6: shared renderer reveal.ts revealFile('open'|'folder') awaits the IPC result
and toasts the main-process reason instead of silently no-oping on a moved/typed-
out file. safeShowInFolder upgraded to return an error string like safeOpenPath
(preload/mock/Api types updated).
- UX9 / UI25: a Fluent CounterBadge on the sidebar Downloads nav item shows the
active (downloading + queued) count from any tab, via a count-only App selector
that doesn't re-render on progress ticks.
- UX15: cancelAll store action + a "Cancel all (N)" header button.
- UX24: the disabled "Check watched" button uses disabledFocusable + a Hint that
explains it needs a watched source.
- UX12: the Terminal gate gains an inline "Enable custom commands" button, so it's
no longer a dead-end pointing at another screen.
- L144: the DownloadBar duplicate guard now also checks history — a URL downloaded
earlier warns "Already downloaded: …" (dupInHistory) vs "Already in your queue: …".
Deferred with rationale (CODE-AUDIT.md): UX7/UX8 (Settings IA redesign — visual),
UX18/UX26 (subjective/visual polish), L131 (arguably by-design, needs live taskbar),
L142 (history/templates/sources IPC-return reconciliation — a focused own PR).
Verified: typecheck (node+web) + 279 tests + eslint + production build green;
touched files prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The contained consistency consolidations; the sweeping ones are deferred with
their standard documented (they're the audit's own post-1.0 refactors, and
several would be reckless to do unattended).
Landed:
- src/main/lib/lineBuffer.ts (createLineBuffer): one newline line-splitter for
streamed child-process output, replacing the duplicated buffer loop in
download.ts and terminal.ts (CC3/CC4/SIMP5). Pure + unit-tested
(test/lineBuffer.test.ts, 7 cases: partial lines, CRLF, empty lines, flush,
multi-line chunks). download.ts keeps byte-identical marker parsing and no
close-flush (yt-dlp template lines are always newline-terminated).
- ytdlp.ts: getYtdlpVersion/updateYtdlp now run stderr through cleanError, the
same `cleanError(r.stderr) || r.error?.message || ''` idiom already used by
download/probe/indexer (CC6) — a failed check shows the trailing error line.
- CL6 resolved: the one real redundancy was L15; clearDir/openFile/showInFolder
are deliberate view-model wrappers (CC13), left as-is.
Deferred with documented standard + rationale (CODE-AUDIT.md): CC1 (breaking
persisted-key rename → migration), CC5+CL4 (security-critical updater
net.request, not live-verifiable here), CC7 (cosmetic arg-order), CC9 (zod dep),
CC10 (electron-store↔jsonStore split is deliberate for DPAPI), CC11 (config.ts
move), CC12 (file-tree reorg), CC13→L93, CC14→L142.
Verified: typecheck (node+web) + 275 tests + eslint + production build green;
touched files prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shared UI primitives to end the "two ways to do X" drift on surfaces:
- EmptyState (ui/EmptyState.tsx): one centered empty block — optional focal
icon/badge + Body1 message + optional muted hint, with a `compact` variant
for in-content placeholders. Adopted by Downloads, History, Library (L116),
and — compact — the Terminal log, Diagnostics, and TemplateManager (L117).
- Banner (ui/Banner.tsx) + LinkSuggestion (ui/LinkSuggestion.tsx): one tinted
notification row (icon + truncating/wrap content + actions, tone brand/warning)
replacing five hand-rolled style blocks — the DownloadBar copied-link
suggestion / channel nudge / duplicate warning and the Library copied-link
suggestion (UI19).
- useTextStyles().truncate: shared single-line-ellipsis utility, adopted in
Library rows + the playlist panel + Banner; `title` now also sets minWidth:0
so it shrinks in a flex row (L126).
- L102: Settings "Default format" now uses the same SegmentedControl (Video/
Audio) + quality Select as the DownloadBar — one mental model; the combined
`kind|quality` string encoding is gone.
Resolved by verified convention (documented in code / CODE-AUDIT.md):
- L103 busy = in-button icon→Spinner everywhere (done in L134; no adjacent-
spinner pattern survives).
- L115 list-row actions icon-only+tooltip vs card-toolbar actions labelled.
- L120 Fluent <Card> for static content cards; tokenized surface recipe for
interactive/list-item cards.
- UI20 toggles = solid brand, list-selection = brand-tint (rule noted on
SegmentedControl); the cited Library pill is gone via UI18.
L128 (build-time DCE of preview seeds) deferred to Batch 14 with PERF8.
Verified: typecheck (node+web) + 268 tests + eslint + production build green;
touched files prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clean config/copy items (Phase 7):
- L170: build.sourcemap 'hidden' on main/preload/renderer — emit .map for offline
crash symbolication (of the CC8 logger's raw stacks) without exposing them.
- L34: add `test:integration` npm script (AEROFETCH_REAL_DOWNLOAD=1) for the opt-in
real-download suite; stays out of `npm test` (needs network + live yt-dlp).
- L87: HistoryEntry carries the original `options`; redownload replays them so a
re-download reproduces the same post-processing instead of the current defaults.
- L96: folder "Browse" buttons -> "Browse…" (they open the OS picker).
- L97: PO-token placeholder "(none)" -> "Optional -- paste a token" (consistent).
- Ticked already-resolved: L99 (M28 aria-label), L113 (UX2 tooltip=aria-label),
L95 (usage follows verb/modifier rule), L172 (skipLibCheck accepted).
typecheck + 268 tests + eslint + prettier green (touched files LF/clean).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An App mount effect focuses #aerofetch-url when the Downloads tab is active
(double-rAF for paint), so a user can paste + type immediately instead of
clicking first.
typecheck + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- W2/UX19: windowState.ts persists the window's normal bounds + maximized flag to
<userData>/window-state.json (no new dep); createWindow restores from it with a
screen.getAllDisplays() visibility guard so it never reopens off a disconnected
monitor. Debounced save on resize/move + save on close (which may hide to tray).
- W10: notify() flashFrame(true)s the taskbar when a completion/failure lands while
the window is unfocused/minimized (Windows clears the flash on focus).
- W11: already implemented (badge.ts overlay dot + setOverlayIcon) — ticked.
typecheck + 268 tests + eslint + prettier green. (Window restore + flash want a
live quit/relaunch confirm.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the post-enqueue reset (URL + probe + one-shot trim/schedule/advanced/
incognito/options) into resetForm(), called by both download() and addPlaylist().
clearProbe() stays the lighter probe-only reset for onUrlChange. Also fixes a
latent inconsistency: addPlaylist previously left the trim/schedule/advanced
panels open after enqueuing a playlist.
typecheck + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- L138: gate the clipboard check on the settings `loaded` flag (not just
clipboardWatch), so it never reads using the pre-load fallback; the effect
re-runs when loaded flips true so a pre-launch copied link is still offered.
- L145: lastSeen is module-level, so a dismissed clipboard link stays suppressed
across the hook's remount on a Downloads<->Library tab switch.
- M14: SettingsView renders each card in a React-owned wrapper and drives its
visibility from a `hidden` state array — the search no longer writes display:none
onto a card's own DOM node. Matching still reads textContent (full label coverage,
no per-card keyword upkeep).
typecheck + 268 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- L94/PERF4: add queueSummaryOf — a 1-entry items-ref memo over summarizeQueue.
The store hands out a new items array per change, so App's taskbar subscription
and DownloadsView's render compute the aggregate once per change (second caller
hits the cache) instead of twice per tick. summarizeQueue stays pure/tested.
- PERF5: hoist VirtualList estimateSize/getKey/renderItem to stable module-level
functions so the virtualizer doesn't churn its measure/key cache.
- L163: one useShallow selection replaces QueueItem's 10 individual useDownloads
subscriptions (stable actions -> never re-renders the row).
typecheck + 268 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The queue/scheduler lived only in renderer memory, so saved/scheduled and other
pending downloads were silently lost on quit (audit M4). Persist them:
- main: queue.ts store (proven cached-atomic createJsonStore, queue.json) +
queue:list / queue:save IPC; QUEUE_MAX cap.
- shared: PersistedQueueItem = the durable subset of DownloadItem (runtime-only
progress/speed/eta/sizeLabel/finishing dropped).
- renderer: mirror the persistable items on every queue change (a signature over
the subset means progress ticks don't re-save; main's jsonStore coalesces the
writes) and rehydrate on launch via an App useEffect. Pure mappers extracted
to store/queuePersist.ts and unit-tested.
Restore semantics: downloading -> queued (yt-dlp continues the .part), saved +
scheduledFor survives so the promoter fires when due, paused/error return
actionable; completed/canceled are never persisted. A queueHydrated gate stops a
launch-time store change from wiping queue.json before it's read.
Reconciles ROADMAP.md. typecheck + 268 tests + eslint + prettier green.
(OS-level quit/relaunch cycle is worth a manual smoke test.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shouldAutoCheckYtdlp compared (now - lastCheck) >= interval; a backward system-
clock correction leaves lastCheck in the future so the difference is negative and
the daily check never comes due again. Treat a future lastCheck as skewed and run
the check. The renderer scheduled-item promoter is inherently fire-once (promotion
flips the item out of 'saved'), so it can't double-fire on a backward jump — a
forward jump firing early is documented and accepted as minor.
Unit-tested. typecheck + 263 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Incognito lived only in the renderer (history skip), so a "private" download
still leaked in main: errorlog entries (title+URL), OS toasts showing the title,
and the signed-in cookies attached — the UI promised "no logging, no history, no
cookies" but only history held.
Plumb `incognito` through StartDownloadOptions and enforce it in main:
- logFailure skips the errorlog write (and the pre-spawn errorlog in ipc.ts).
- notify uses a generic outcome title and drops the detail body, so no title/URL
lands in the Windows Action Center.
- both cookie sources (--cookies login jar, --cookies-from-browser) are withheld,
so a private download can't be tied to the user's identity.
typecheck + 262 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- L83: nsis.differentialPackage: false — the custom updater does full
downloads and never consumes the differential blockmap, so don't generate it
(NsisTarget gates blockmap generation on differentialPackage !== false).
- L84: add clean:dist script (fs.rmSync) run at the front of build:win so dist/
no longer accumulates old-version installers (~3 GB observed). dist/out were
already gitignored, so this is local-disk hygiene.
Config only; TS gate unaffected. 137 -> 135 open audit items.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tick items whose root cause already landed, and fix two doc-drift items:
- L82: align ROADMAP-PINCHFLAT Phase F Source/MediaItem sketch to the shipped
shared/ipc.ts shape (videoId/itemCount/watched/feedUrl + required
url/playlistTitle/playlistIndex).
- L89: note H8's afterAllArtifactBuild checksum hook fixes release-artifact
consistency going forward; stale dist artifacts handled by L84.
- Tick the wire-or-remove trio (M5/M6/UX1 — all wired in the DownloadBar
Advanced panel) and MAX_ENQUEUE_BATCH (M33 fixed the false comment).
- Tick UX ref-duplicates resolved by their roots (UX10=H5, UX13=M22, UX14=L76,
UX25=L78), the (ref) a11y line (UI33/UI30/UI28/UI29 all done), and the
(OK)/(non-goal) Windows-conventions line.
Docs only; no code touched. 149 -> 137 open audit items.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI31 — the native <Select> wrapper now takes a `disabled` prop that passes to
the <select> and applies disabled foreground/background/stroke tokens +
not-allowed cursor, so it can show a disabled state like Fluent controls.
UI32 — dropped the datetime-local's explicit colorScheme:'light dark' so it
inherits the resolved in-app scheme from the app root; the native calendar popup
now follows the app's Light/Dark theme, consistent with the native <Select>,
instead of tracking the OS preference.
W16 — the icon-only row-action clusters (QueueItem + HistoryView) enforce a
>=40x40px hit target via a `& button` min-size rule (glyph unchanged, subtle
button just gains padding). Labels were already non-hover-only: the shared Hint
shows on :focus-within and every action has an aria-label. LibraryView item rows
have no icon-only actions, so nothing there needed changing.
W18 — defended the two color-meaning surfaces against forced-colors: accent
swatches set forced-color-adjust:none (they're color previews; otherwise all
flatten to one system color), and the SegmentedControl active segment paints
with system Highlight/HighlightText under @media (forced-colors: active) so the
checked state stays visible. Status chips (Fluent Badge) and thumbnail tints
correctly let the system palette win.
typecheck + 253 tests + eslint + prettier + production build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI33 — render titles as real headings so Narrator heading-navigation works:
the shared ScreenHeader title is now <Subtitle2 as="h1"> (one h1 per screen in
one place — Downloads/History/Library/Settings/Terminal); DownloadsView "Queue
(N)" and all 11 settings cards are <Subtitle2 as="h2"> sections; Onboarding
"Welcome" is <Title2 as="h1">. A minimal h1–h6 margin reset in base.css zeroes
the UA heading margin (Fluent's typography classes out-specify the element
selector, so the visual ramp is unchanged) so layout matches the former spans.
UI27 — give the command palette proper combobox/listbox semantics: the input is
role="combobox" with aria-controls/aria-activedescendant/aria-autocomplete, the
list is role="listbox", and each row is role="option" with aria-selected, so the
active row is announced to screen readers via active-descendant. Focus stays on
the input (standard combobox pattern), so the rows became non-focusable divs;
onMouseEnter is kept to unify pointer + keyboard highlight on one sel state.
Also corrected UI30's checkbox in the audit (already implemented via the shared
SegmentedControl radiogroup; only the marker lagged).
typecheck + 253 tests + eslint + prettier + production build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes H4 and the remaining formatter half of H2.
- New @shared/format.ts is the single home for fmtBytes/fmtSpeed/fmtEta, imported
by both main and renderer. main/lib/formatters.ts re-exports them.
- DownloadProgress now carries raw speedBytesPerSec/etaSeconds instead of
pre-formatted speed/eta strings. main's parseProgress emits the raw numbers.
- The renderer stores the raw numbers on DownloadItem; QueueItem formats them for
per-item display, and summarizeQueue sums/maxes them directly. The lossy
string->number->string round-trip in queueStats (parseSpeed / parseEtaSeconds /
a local formatSpeed / formatEta) is deleted, along with a duplicate fmtEta in
store/downloads.ts.
- Per-item and aggregate speed now use the same 1024-based scale; the aggregate
previously used a 1000-based formatter (a latent inconsistency).
Tests updated for the raw-number contract (parseProgress, summarizeQueue).
typecheck + 248 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consolidates the scattered YouTube-URL parsing (the clean, renderer-contained
half of H2):
- Move the complete youtubeId (watch/shorts/embed/live/youtu.be) into
lib/urlHelpers.ts as the single home.
- thumb.ts, queueStats.ts (sameVideo), and store/downloads.ts (titleFromUrl)
now import it; the three ad-hoc reimplementations are removed.
- titleFromUrl gains correctness: it previously labeled ANY host with a ?v=
param as "YouTube video (…)" and missed youtu.be/shorts; it's now host-correct
and covers every YouTube shape. sameVideo now also dedupes /shorts/ID against
/watch?v=ID.
- Unit-tested in test/clipboardLink.test.ts (+4 cases, 247 pass).
The formatter half of H2 is deferred to travel with H4: the renderer re-parses
speed/eta strings only because raw numbers aren't carried across the IPC boundary
(H4), and fmtBytes (1024) vs formatSpeed (1000) differ intentionally.
typecheck + 247 tests + eslint + prettier green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Housekeeping: the command-preview / incognito / per-download-options features
were implemented and self-reviewed in the previous commits but their audit
checkboxes were still open. Marks M5, M6, UX1 done with fix notes. Completes the
SHOULD-fix-for-1.0 release-gate tier.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
The auto-best format option claimed ext:'mp4', but the real output container
for the best pick depends on the user's videoContainer setting (mp4/mkv/webm),
so it mislabeled mkv/webm outputs. ext is optional on FormatOption and the
option's label is the fixed "Best available", so the field is simply omitted.
hasAudio:true is kept (the best pick always merges an audio track).
typecheck + 242 tests + eslint green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
writeJsonAtomic always pretty-printed (JSON.stringify(value, null, 2)), which
needlessly inflates size and write time for the media-items store (up to
MAX_ITEMS=20000 rows). Added an optional `pretty` flag (default true, so
history/sources/templates/errorlog stay hand-inspectable) threaded through
createJsonStore, and set it false for media-items.json.
typecheck + 242 tests + eslint green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
M23: replaceMediaItems wrote [...newItems, ...others] and the JSON store sliced
the combined list to MAX_ITEMS, so re-indexing one large source could
silently evict ANOTHER source's tail. Now other sources are always kept in
full and only the source being (re)indexed is capped to the remaining
budget (MAX_ITEMS - others.length).
M27: enqueueItems forced settings.defaultKind for every item, so a channel
couldn't be queued as audio without flipping the global default. Added an
optional `kind` override (falls back to the default) and a video/audio
SegmentedControl in the Library action row, seeded from defaultKind so
existing behavior is unchanged until toggled.
typecheck + 242 tests + eslint green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The audio quality preset "Best (MP3)" named a format that's wrong whenever the
audio format is opus/flac/wav. Renamed it to "Best" so the label never
contradicts the chosen audioFormat (which is picked separately). Bitrate presets
stay as bitrates and are already ignored for lossless formats (M21).
- AUDIO_QUALITY_OPTIONS[0]: 'Best (MP3)' -> 'Best' (buildArgs already had a
`case 'Best'` returning '0', so arg generation is unchanged).
- Updated the main + renderer defaults and the preview/mock/test fixtures.
- Added a one-line migration in getSettings() that rewrites a stored legacy
'Best (MP3)' to 'Best' so existing users' dropdowns match.
typecheck + 242 tests + eslint green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LibraryView mapped url -> status by last-write, so when a URL had multiple
queue entries ("Download anyway"), the row reflected whichever item was
iterated last. Added a STATUS_PRIORITY ranking (completed > downloading >
queued > saved > paused > error > canceled) and keep the highest-priority
status per URL, so a row shows the most meaningful state of that video.
typecheck + 242 tests + eslint green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The completion side-effect (add to history unless private + markDownloaded on
the source MediaItem) was duplicated in applyEvent('done') and the preview
fake-ticker, and had already drifted (the preview copy omitted formatId/
formatHasAudio). Extracted one recordCompletion(item) helper that both call.
Also tightened a latent bug: the old applyEvent ran markDownloaded for any
'done' event with a mediaItemId, even when the item was canceled (the reducer
keeps canceled items as 'canceled'). recordCompletion now runs only when
status === 'completed', so a canceled collection item is no longer wrongly
marked downloaded (and will be retried on the next re-sync).
typecheck + 242 tests + eslint green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L119: Standardized empty-state copy on the "No X yet." pattern. Terminal
"Output will appear here." -> "No output yet."; Diagnostics
"No errors logged." -> "No errors yet."
L151: Range-dash convention is ASCII hyphen everywhere. Changed the lone
en-dash prose range ("2-3 is a good balance") to a hyphen, matching the
time-range placeholders (1:30-2:00).
L153: The three "Dismiss" close buttons now name what they dismiss --
"Dismiss suggested link" (DownloadBar + Library suggestion banners) and
"Dismiss duplicate warning" (DownloadBar dup row).
L95 left open: the Cookies-card "sign in"/"sign-in" usage is already correct
English (verb vs modifier); no change warranted.
typecheck + 242 tests + eslint green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L106: Established the convention — ArrowClockwise = retry/redo a local action
(retry-all, re-download, retry queue item); ArrowSync = network sync/
update (sync sources, reindex, check/apply updates). Flipped the two
misclassified sites (LibraryView "check for new" sync, SettingsView
"Update yt-dlp") to ArrowSync and dropped the now-unused imports.
L107: QueueItem queued-row Spinner was size="extra-tiny"; every other spinner
is "tiny". Standardized to "tiny".
L122: The app-update and yt-dlp-update buttons showed "Checking…"/"Updating…"
in the label AND a separate sibling Spinner. Folded the spinner into the
button icon (icon swaps to Spinner while busy), matching DownloadBar/
LibraryView, and removed the redundant sibling Spinner.
typecheck + 242 tests + eslint green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>