@@ -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
GPU-flicker reason). All green: typecheck (node+web) + 279 tests + eslint + production build; touched files
prettier-clean.
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
**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
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,
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
- [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
string (no holder/year); the `files` exclude `tsconfig.tsbuildinfo` never matches the real
`tsconfig.web.tsbuildinfo` .
`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
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"`
- [x] **L6 — Control height mismatch. ** `Select` is a fixed 32px sitting beside `size="large"`
(~40px) Input/Buttons in DownloadBar.
(~40px) Input/Buttons in DownloadBar.
- [x] **L7 — `canceled` and `paused` share the 'warning' badge color ** (QueueItem) — visually
- [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] **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
- [x] **L20 — `youtubePlayerClient` is free text ** with no allowlist/validation; a typo passes
straight to yt-dlp's `--extractor-args` .
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
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
- [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.).
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
- [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.
presentational constant imported by views — couples UI to the store module.
- [x] **L30 — Redundant `loading="lazy"` ** on `MediaThumb` images already inside a virtualized
- [x] **L30 — Redundant `loading="lazy"` ** on `MediaThumb` images already inside a virtualized
list (the virtualizer only mounts visible rows).
list (the virtualizer only mounts visible rows).
- [ ] **L31 — Two theme switchers. ** Sidebar shows a 3-way radio when expanded but a cycle button
- [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.
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,
- [x] **L32 — `paletteActions` rebuilt every render ** in `App.tsx` (not memoized); harmless today,
but it's passed straight into a child.
but it's passed straight into a child.
- [x] **L33 — `idCounter` fallback resets per launch. ** The non-crypto `newId` fallback
- [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
- [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` .
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.
- [ ] **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] **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
- [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.
(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.
mislabels when the chosen container is mkv/webm.
- [x] **L63 — `audioQuality()` unknown label → silent `'0'` (best) ** — the audio analog of H5.
- [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.
- [ ] **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.
- [ ] **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] **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
- [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]`
- [x] **L91 — Unsafe tuple cast / fragile encoding. ** `value.split('|') as [MediaKind, string]`
([SettingsView.tsx ](src/renderer/src/components/SettingsView.tsx ):411) trusts a `'|'` -joined
([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.
`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` ,
- [x ] **L92 — Preload names diverge from main fn names. ** `markSourceItemDownloaded` ↔`setMediaItemDownloaded` ,
`syncSources` ↔`syncWatchedSources` . Align the names across the boundary.
`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
- [ ] **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
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 )
- [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
(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
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
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.*
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` .
- [ ] **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
- [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.
[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
*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] **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>` ).
- [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;
- [ ] **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
- [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
`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
`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`;
*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
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`).*
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.
"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
- [ ] **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).
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
*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:*
*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
- [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.
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` /
- [x] **L150 — "PO token" capitalization varies** — ` PO Token` (ipc.ts/ROADMAP) vs ` PO token` /
` Proof-of-Origin token` (SettingsView). Pick one.
` 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
- [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.
placeholders ("1:30-2:00"). Choose one convention.
- [ ] **L152 — Search-field sizing differs per screen.** History ` minWidth 180 / maxWidth 320`, Settings
- [x ] **L152 — Search-field sizing differs per screen.** History ` minWidth 180 / maxWidth 320`, Settings
inline ` width: 100%`, DownloadBar ` flexGrow`. No shared search-input width.
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,
- [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.
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
- [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}`.
now (extends L57). Add ` min={now}`.
- [ ] **L157 — ` removeSource` doesn't cancel in-flight downloads.** Removing a source while its videos are
- [ ] **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.
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.
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
- [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.
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
- [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.)*
still L5.)*
- [ ] **L161 — ` height: 100%` screens inside a padded scroll container.** Downloads/Terminal set
- [ ] **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
` 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
- [ ] **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.
- [x] **L163 — QueueItem makes 9 separate ` useDownloads` selector calls** instead of one destructured
- [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
` 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
[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.*
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`
(` 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,
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
- [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 —
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
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.*
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
- [ ] **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.
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.
(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
### 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
(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
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.
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;
- [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 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."
right-click. **Standard:** add consistent right-click menus mirroring row actions, or note "none by design."