@@ -195,6 +195,22 @@ is a visible inline button + keyboard, and Fluent's context `Menu` is portal-bas
GPU-flicker reason). All green: typecheck (node+web) + 279 tests + eslint + production build; touched files
prettier-clean.
**Session 2026-07-01 pass 10 (Batch 13 — copy polish + misc-L cleanup): ** the contained fixes landed; the
feature/visual/larger items are documented with rationale. **Fixed: ** **L158 ** (drag-highlight flicker — a
`dragDepth` enter/leave counter in useDownloadBar so the dashed outline no longer blinks crossing child
elements), **L92 ** (preload methods renamed to match main — `setMediaItemDownloaded` /`syncWatchedSources` ,
compiler-enforced through the `Api` type), **L149 ** (the three-sentence tray hint is now one line), **L171 **
(one `MOCK_CURRENT_VERSION` so the preview mock's `getAppVersion` /`currentVersion` agree), and **L5 **
(representative pass — the About card's monospace inlines → `mono*` classes; convention set: makeStyles for
static, inline `style` only for data-driven values). **Resolved by verification/convention: ** **L21 **
(SettingsView's "two patterns in one file" is moot post-H1 decomposition), **L31 ** (dual theme switcher is
width-driven, not inconsistent), **L130 ** (lowercase = interpolated fragment, capitalized = shown-raw
sentence), **L152 ** (search sizes are role-appropriate), **UI22 ** (the brand/entity/section icon-emphasis set
is now defined). **Deferred with rationale: ** L51/L64 (features), L93 (CC13 pass), **L104 ** →Batch 14 (perf),
L109 (visual, with UX26), L161 (layout, live-verify), UI21 (visual), UI23 (cookie sign-in window theming,
live-verify). 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,
@@ -618,9 +634,15 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
- [x] **L4 — `electron-builder.yml` nits. ** `copyright: yt-dlp frontend` is not a copyright
string (no holder/year); the `files` exclude `tsconfig.tsbuildinfo` never matches the real
`tsconfig.web.tsbuildinfo` .
- [ ] **L5 — Inline `style={{}}` vs makeStyles ** (25× across 8 files; SettingsView 14× ). Notably
- [x ] **L5 — Inline `style={{}}` vs makeStyles ** (25× across 8 files; SettingsView 14× ). Notably
Sidebar's active-nav inset shadow and DownloadOptionsForm's hint caption use inline styles
where a class exists elsewhere.
where a class exists elsewhere. *Addressed incrementally + convention set: the About card's four
repeated `style={{ fontFamily: fontFamilyMonospace }}` inlines are now `mono` /`monoBlock` /`monoPre`
classes in [settingsStyles.ts ](src/renderer/src/components/settings/settingsStyles.ts ). The **convention ** :
makeStyles for static styling; inline `style` stays **only ** for genuinely data-driven values (a swatch's
`backgroundColor: opt.swatch` , a thumbnail's dynamic dimensions, the active-nav inset shadow keyed to
selection) — those can't be static classes and are correct as-is. Remaining static inlines migrate
opportunistically under this rule.*
- [x] **L6 — Control height mismatch. ** `Select` is a fixed 32px sitting beside `size="large"`
(~40px) Input/Buttons in DownloadBar.
- [x] **L7 — `canceled` and `paused` share the 'warning' badge color ** (QueueItem) — visually
@@ -659,9 +681,13 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
- [x] **L19 — `App.tsx` focus hack ** — `setTimeout(() => …focus(), 60)` after a tab switch.
- [x] **L20 — `youtubePlayerClient` is free text ** with no allowlist/validation; a typo passes
straight to yt-dlp's `--extractor-args` .
- [ ] **L21 — Inconsistent in-component error handling. ** SettingsView funnels the app-update
- [x ] **L21 — Inconsistent in-component error handling. ** SettingsView funnels the app-update
not-ok result into a dedicated `appUpdError` slot but keeps not-ok duals for
version/update/export/import — two patterns in one file.
version/update/export/import — two patterns in one file. *Resolved by the H1 decomposition: SettingsView is
no longer one file — app-update lives in `SoftwareUpdateCard` , version/update in `AboutCard` , backup
export/import in its own card, each holding its own result state internally. The "two patterns in one file"
no longer exists; each card's error handling is self-contained and consistent within itself. (The remaining
`{ ok, error }` vs `null` signaling styles are the CC6 cross-cutting item, not this per-file one.)*
- [x] **L22 — DownloadOptionsForm shows both audio + video controls ** regardless of the chosen
kind in the per-download override panel (audio format shown for a video download, etc.).
- [x] **L23 — Diagnostics shows first 20 errors ** with no "showing 20 of N" hint (the copied
@@ -682,8 +708,12 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
presentational constant imported by views — couples UI to the store module.
- [x] **L30 — Redundant `loading="lazy"` ** on `MediaThumb` images already inside a virtualized
list (the virtualizer only mounts visible rows).
- [ ] **L31 — Two theme switchers. ** Sidebar shows a 3-way radio when expanded but a cycle button
when collapsed — minor dual-UX for the same setting.
- [x ] **L31 — Two theme switchers. ** Sidebar shows a 3-way radio when expanded but a cycle button
when collapsed — minor dual-UX for the same setting. *Resolved as intentional (documented): the two are
responses to width, not a real inconsistency — the expanded sidebar has room for the 3-segment
`SegmentedControl` , while the 60px collapsed rail can only fit a single icon button, so it cycles
Light→Dark→Auto (with a tooltip naming the current mode). Both drive the exact same `theme` setting; a full
3-way control can't fit collapsed. Left as-is by design.*
- [x] **L32 — `paletteActions` rebuilt every render ** in `App.tsx` (not memoized); harmless today,
but it's passed straight into a child.
- [x] **L33 — `idCounter` fallback resets per launch. ** The non-crypto `newId` fallback
@@ -725,6 +755,8 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
- [x] **L50 — "Cookies saved" shown for 0 cookies. ** Closing the sign-in window without logging in
still writes the file and reports `ok` with `cookieCount: 0` .
- [ ] **L51 — Scheduled daily sync time hardcoded to 09:00 ** (schedule.ts) — on/off toggle only, no time picker.
*Deferred (feature): a time picker is a small new feature (UI + a Task Scheduler time argument), not a
cleanup — worth its own change; 09:00 is a reasonable default meanwhile.*
- [x] **L52 — Installer handoff is a fixed `setTimeout(app.quit, 1500)` ** (updater.ts) — a race on slow machines.
- [x] **L53 — `dialog.show*Dialog(win!, …)` non-null assertions ** on a possibly-null window
(chooseFolder, backup export/import) — can throw if the sender window is gone.
@@ -744,6 +776,8 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
mislabels when the chosen container is mkv/webm.
- [x] **L63 — `audioQuality()` unknown label → silent `'0'` (best) ** — the audio analog of H5.
- [ ] **L64 — `ARIA2C_ARGS` connection params hardcoded ** (`-x16 -s16 -k1M` ), no user control.
*Deferred (feature): exposing aria2c tuning is a new advanced setting, not a cleanup; the defaults are
sensible and aria2c itself is already opt-in.*
- [ ] **L65 — Pause uses `taskkill /F` ** like cancel — a forced kill risks an unflushed `.part` tail vs a graceful stop.
- [x] **L66 — Sidebar caption flips ** "yt-dlp frontend" → "v<x>" once the version loads (text shift on boot).
- [x] **L67 — Onboarding has no Skip and can't be revisited ** (no "show tips again"). *Fixed the revisit
@@ -828,11 +862,18 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
- [x] **L91 — Unsafe tuple cast / fragile encoding. ** `value.split('|') as [MediaKind, string]`
([SettingsView.tsx ](src/renderer/src/components/SettingsView.tsx ):411) trusts a `'|'` -joined
`kind|quality` string; a value without `'|'` yields `undefined` quality. Use a typed pair, not a string.
- [ ] **L92 — Preload names diverge from main fn names. ** `markSourceItemDownloaded` ↔`setMediaItemDownloaded` ,
`syncSources` ↔`syncWatchedSources` . Align the names across the boundary.
- [x ] **L92 — Preload names diverge from main fn names. ** `markSourceItemDownloaded` ↔`setMediaItemDownloaded` ,
`syncSources` ↔`syncWatchedSources` . Align the names across the boundary. *Fixed: the preload methods are
renamed to match main — `setMediaItemDownloaded` and `syncWatchedSources`
([preload/index.ts ](src/preload/index.ts )); the renderer callers ([store/sources.ts ](src/renderer/src/store/sources.ts ))
and the preview mock ([mockApi.ts ](src/renderer/src/mockApi.ts )) updated to suit. The `Api` type derives from
the preload, so the rename is enforced end-to-end by the compiler.*
- [ ] **L93 — SettingsView bypasses the store layer. ** It calls `window.api` directly for
cookies/ffmpeg/yt-dlp/app-update/backup while using stores for settings/templates/errorlog — mixed
data-access in one component.
data-access in one component. *Deferred (CC13's concrete instance): these one-off, card-local IPC reads
(version checks, backup export/import, cookie sign-in) have no shared state to own, so a store per call is
arguably overkill — but the "no `window.api` in components" convention argues for thin hooks. Best done as
the focused CC13 view-model pass, not folded into this copy/cleanup batch.*
- [x] **L94 — `summarizeQueue` recomputed every render ** in [DownloadsView.tsx ](src/renderer/src/components/DownloadsView.tsx )
(no `useMemo` ) — and again on every store change in App's taskbar effect. Memoize/share. *Fixed: a 1-entry
items-ref memo `queueSummaryOf` in [queueStats.ts ](src/renderer/src/store/queueStats.ts ) — the store hands
@@ -875,6 +916,9 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
remaining `<Spinner>` 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.*
- [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
@@ -884,7 +928,9 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
- [x] **L107 — Spinner sizes mixed ** — `"tiny"` almost everywhere, `"extra-tiny"` for the QueueItem queued row.
- [x] **L108 — Main window sets no explicit `title` ** (relies on the document `<title>` ).
- [ ] **L109 — Versions auto-load with no indicator. ** yt-dlp/ffmpeg versions in About just appear;
only the manual "Check" button shows a spinner.
only the manual "Check" button shows a spinner. *Deferred (visual, with UX26): a boot loading state worth
eyeballing against the real sub-second load; joins the watched visual pass with the other skeleton/spinner
polish.*
- [x] **L110 — One muted-text token, many class aliases. ** `colorNeutralForeground3` is re-declared as
`hint` /`sub` /`meta` /`srcSub` /`stats` /`count` /`emptyHint` /`previewMeta` across components. *Fixed: one
`useTextStyles().muted` in [ui/text.ts ](src/renderer/src/components/ui/text.ts ). Adopted for the pure-color
@@ -981,8 +1027,13 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
*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
left/right hints already omit ` align`).*
- [ ] **L130 — Error-string style is mixed** — internal errors are lowercase/no-period ("timed out",
- [x ] **L130 — Error-string style is mixed** — internal errors are lowercase/no-period ("timed out",
"interrupted"); user-facing ones are capitalized with periods. Fine when interpolated, jarring if shown raw.
*Resolved (convention documented): the split is intentional — lowercase fragments are *interpolated* into a
larger sentence ("… timed out"), while standalone user-facing strings are capitalized with a period. The
audit's own note ("Fine when interpolated") confirms it; every string that reaches the UI raw (via a toast,
a status line, ` cleanError`) is already a complete capitalized sentence. Standard: complete sentences for
anything shown raw; lowercase only for fragments meant to be composed.*
- [ ] **L131 — Taskbar ` error` state is ephemeral** — it's driven by live ` error` items, so "Clear
finished" flips the red taskbar bar back to normal even though failures occurred (out of sync with the persisted error log).
*Deferred (arguably by-design, needs the live taskbar): the taskbar reflects the *live queue*, so clearing
@@ -1089,14 +1140,20 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
*Round 7 (2026-06-29) — copy, a11y patterns & micro-behavior:*
- [ ] **L149 — Hint length wildly inconsistent.** Most field hints are one short line, but "Keep running
in the tray" is a three-sentence paragraph with a parenthetical. Normalize hint length/voice.
- [x ] **L149 — Hint length wildly inconsistent.** Most field hints are one short line, but "Keep running
in the tray" is a three-sentence paragraph with a parenthetical. Normalize hint length/voice. *Fixed: the
tray hint is now one line — "Closing the window minimizes to the system tray instead of quitting, so
downloads and watched-channel syncing keep running." — matching the one-line voice of its sibling hints.*
- [x] **L150 — "PO token" capitalization varies** — ` PO Token` (ipc.ts/ROADMAP) vs ` PO token` /
` Proof-of-Origin token` (SettingsView). Pick one.
- [x] **L151 — Mixed range dashes.** En-dash in prose ("2– 3 is a good balance") vs hyphen in time-range
placeholders ("1:30-2:00"). Choose one convention.
- [ ] **L152 — Search-field sizing differs per screen.** History ` minWidth 180 / maxWidth 320`, Settings
inline ` width: 100%`, DownloadBar ` flexGrow`. No shared search-input width.
- [x ] **L152 — Search-field sizing differs per screen.** History ` minWidth 180 / maxWidth 320`, Settings
inline ` width: 100%`, DownloadBar ` flexGrow`. No shared search-input width. *Resolved as role-appropriate
(documented): the three are different controls, not one — History's is a bounded filter in a busy header
row (cap it so it doesn't crowd the actions), the Settings search spans its card (full width), and the
DownloadBar field is the screen's primary input (flex-grow to fill). A single shared width would be wrong
for at least two of them; each sizing matches its layout role.*
- [x] **L153 — Generic "Dismiss" aria-labels.** Three close buttons (DownloadBar suggestion + dup,
Library suggestion) all read just "Dismiss" — ambiguous to a screen reader. Name what's dismissed.
- [x] **L154 — Instructional text in an aria-label.** Sidebar theme-cycle button is
@@ -1112,8 +1169,13 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
now (extends L57). Add ` min={now}`.
- [ ] **L157 — ` removeSource` doesn't cancel in-flight downloads.** Removing a source while its videos are
downloading lets them finish into the removed source's folders; ` markDownloaded` then no-ops on the gone item.
- [ ] **L158 — Drag highlight flickers.** ` onDragLeave={() => setDragActive(false)}` fires when the cursor
- [x ] **L158 — Drag highlight flickers.** ` onDragLeave={() => setDragActive(false)}` fires when the cursor
crosses child elements (no enter/leave depth counter), so the dashed-outline blinks during a drag.
*Fixed: [useDownloadBar.ts](src/renderer/src/components/downloadBar/useDownloadBar.ts) now tracks a
` dragDepth` counter — ` onDragEnter` increments, ` onDragLeave` decrements, and the highlight only clears when
depth returns to 0 (the drag has truly left the card). ` onDragOver` just ` preventDefault`s (keeps it a valid
drop target); ` onDrop` resets the counter. The DownloadBar wires ` onDragEnter`/` onDragLeave` instead of the
raw leave-toggles it.*
- [x] **L159 — Dead fallback branch.** ` copyErrorReport` falls back to "No errors logged." but its button
is disabled when there are no entries, so the fallback is unreachable.
- [x] **L160 — One-off inline margins.** ` marginTop` literals (Onboarding 2px, QueueItem 4px, SettingsView
@@ -1122,7 +1184,10 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
still L5.)*
- [ ] **L161 — ` height: 100%` screens inside a padded scroll container.** Downloads/Terminal set
` height: 100%` while App's ` <main>` is ` overflowY: auto` + 24/28px padding — a fragile coupling that can
double-scroll or misalign the virtualized list's viewport.
double-scroll or misalign the virtualized list's viewport. *Deferred (layout, needs live verification): it
works today (the virtualized queue scrolls correctly), and reworking the ` <main>` scroll/height contract is
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.
- [x] **L163 — QueueItem makes 9 separate ` useDownloads` selector calls** instead of one destructured
@@ -1162,10 +1227,16 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
` build.sourcemap: 'hidden'` on all three bundles (main / preload / renderer) in
[electron.vite.config.ts](electron.vite.config.ts) — ` .map` files are emitted for offline symbolication
of the raw stacks the logger now records (CC8), with no ` sourceMappingURL` reference in the shipped bundle.*
- [ ] **L171 — ` main.tsx` mock drift.** The preview mock's version strings disagree
- [x ] **L171 — ` main.tsx` mock drift.** The preview mock's version strings disagree
(` getAppVersion`→'0.4.0-preview' vs ` checkForAppUpdate.currentVersion`→'0.4.0'), and its ` setSettings`
skips the real validation/sanitization — so the browser preview can accept settings the app rejects,
masking M34-class issues during design.
masking M34-class issues during design. *Fixed the version drift: one ` MOCK_CURRENT_VERSION ` constant in
[mockApi.ts](src/renderer/src/mockApi.ts) now backs both ` getAppVersion` and
` checkForAppUpdate.currentVersion`, so they agree (kept below the mock "latest" so the updater card still
demos an available update). The ` setSettings`-skips-validation point is left as-is on purpose: the mock is a
browser-only design aid with no access to main's ` settings.ts` validators; replicating them in the mock
would be a second source of truth to drift. M34/M24 already make the real app authoritative, so the design
preview being permissive doesn't affect shipped behavior.*
- [x] **L172 — ` skipLibCheck: true`** (toolkit) hides type errors in dependency ` .d.ts` files — standard
practice, low risk, noted for awareness (a dep type regression won't fail typecheck). *Acknowledged —
kept as-is (it IS standard practice and speeds typecheck); recorded so the trade-off (a dependency
@@ -1292,8 +1363,16 @@ are an undefined spacing/radius/type scale and a drift between Fluent components
gone — UI18 replaced it with the shared ` StatusChip`, so there's no third treatment left to reconcile.*
- [ ] **UI21 — Sidebar active-nav rail is expanded-only.** The inset brand box-shadow shows only when
expanded; collapsed relies on tint alone. **Standard:** a consistent active indicator in both states.
- [ ] **UI22 — Brand-icon tiles differ.** ` mark ` (on-brand on solid brand) vs ` srcIcon `
*Deferred (subjective visual): whether the collapsed rail needs its ow n inset indicator (and how it reads
against the new active-download ` CounterBadge` added in Batch 11) is an eyeball call; joins the watched
visual pass. The tint already marks the active item in both states.*
- [x] **UI22 — Brand-icon tiles differ.** ` mark` (on-brand on solid brand) vs ` srcIcon`
(brandForeground2 on brand-tint) vs ` sectionIcon` (compoundBrand, no tile). **Standard:** a defined icon-emphasis set.
*Resolved (the set is now defined + documented): three deliberate emphasis levels — **brand mark** (` mark`,
on-brand glyph on solid brand: the app/onboarding identity, highest emphasis), **entity tile** (` srcIcon`,
brandForeground2 on brand-tint: a Library source, medium), and **section glyph** (` sectionIcon`,
compoundBrand, no tile: a settings-card header, lowest). They *should* differ — one per role; the levels are
now named rather than incidental, and each is applied consistently.*
### Dialogs, menus, status, navigation
@@ -1302,6 +1381,11 @@ are an undefined spacing/radius/type scale and a drift between Fluent components
(Onboarding), and an **unstyled separate ` BrowserWindow`** (cookie sign-in) that shows none of the
app's theme/chrome. **Standard:** keep native for file/confirm; unify in-app overlays under one
themed primitive; theme the sign-in window's title/background to match.
*Deferred (larger, needs the live windows): the in-app overlays are already fairly unified — CommandPalette
and the new Toaster share the non-portal ` Z`/` SCRIM`/` ELEVATION` tokens (Batch 6/11). The actionable
remainder is theming the separate cookie **sign-in ` BrowserWindow`** (its own HTML/background) to match the
app's Light/Dark chrome — a main-process window change best verified with a real login flow open, so it's
left for a watched session. Native file/confirm dialogs stay native by design.*
- [x] **UI24 — No context menus anywhere.** Every row exposes actions only as inline buttons;
right-click does nothing on any screen, despite many per-row actions that conventionally also live on
right-click. **Standard:** add consistent right-click menus mirroring row actions, or note "none by design."