feat(audit): Batch 11 — UX behavior gaps (toast surface, sidebar badge)
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>
This commit is contained in:
+71
-9
@@ -169,6 +169,20 @@ electron-store split is deliberate for DPAPI), **CC11** (constants centralized;
|
||||
carry a documented standard + deferral rationale. All green: typecheck (node+web) + 275 tests + eslint +
|
||||
production build; touched files prettier-clean.
|
||||
|
||||
**Session 2026-07-01 pass 8 (Batch 11 — UX behavior gaps):** a new in-app **toast** surface
|
||||
([store/toasts.ts](src/renderer/src/store/toasts.ts) + [ui/Toaster.tsx](src/renderer/src/components/ui/Toaster.tsx),
|
||||
non-portal bottom-center, +unit-tested) is the batch's spine — it makes **UX6** real: a shared
|
||||
[reveal.ts](src/renderer/src/reveal.ts) `revealFile` now surfaces open/reveal failures ("File not found — it
|
||||
may have been moved…") instead of the old silent no-op, and `safeShowInFolder` was upgraded to return an error
|
||||
string like `safeOpenPath`. Also landed **UX9/UI25** (a `CounterBadge` on the sidebar Downloads nav item shows
|
||||
the active count from any tab, via a count-only selector that doesn't re-render on ticks), **UX15** (`cancelAll`
|
||||
+ a "Cancel all (N)" header button), **UX24** (`disabledFocusable` + `Hint` explains the disabled "Check
|
||||
watched"), **UX12** (an inline "Enable custom commands" button on the Terminal gate), and **L144** (the dup
|
||||
guard now checks history → "Already downloaded: …"). **Deferred with rationale:** UX7/UX8 (Settings IA
|
||||
redesign — visual), UX18/UX26 (subjective/visual polish), L131 (arguably by-design, needs the live taskbar),
|
||||
and **L142** (the history/templates/sources IPC-return reconciliation — a focused own-PR, CC14's tail). 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,
|
||||
@@ -950,6 +964,10 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
"interrupted"); user-facing ones are capitalized with periods. Fine when interpolated, jarring if shown raw.
|
||||
- [ ] **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
|
||||
finished items legitimately returns it to idle — the persisted record lives in Diagnostics. Whether the red
|
||||
bar should linger after a clear is a debatable UX call best judged against the real Windows taskbar; left
|
||||
for the watched Windows pass (with Batch 14/15).*
|
||||
- [x] **L132 — `clearProbe` + form-reset logic is duplicated** in DownloadBar (`onUrlChange`, `download`, `addPlaylist`).
|
||||
*Fixed: a shared `resetForm()` in [useDownloadBar.ts](src/renderer/src/components/downloadBar/useDownloadBar.ts)
|
||||
now owns the post-enqueue reset (URL + probe + the one-shot trim/schedule/advanced/incognito/options),
|
||||
@@ -1017,11 +1035,20 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
- [ ] **L142 — IPC mutation return values are discarded everywhere.** history/templates/sources/settings
|
||||
IPC calls return the authoritative (validated/capped/sanitized) state, but every renderer caller does
|
||||
optimistic-only updates and ignores it — client and persisted state can silently diverge until reload (generalizes M34).
|
||||
*Deferred (targeted, own PR): settings already reconcile (M34). Extending "apply the returned authoritative
|
||||
state" to history/templates/sources touches each store's mutation path and each corresponding IPC return
|
||||
shape, and the divergence it guards against is rare (only when main caps/sanitizes differently than the
|
||||
optimistic update) and self-heals on reload. Best done as one focused reconciliation PR with each store
|
||||
verified, not folded into this UX batch. Standard already recorded under CC14.*
|
||||
- [ ] **L143 — No in-window "quit anyway."** With a download active (or tray mode), closing only hides;
|
||||
quitting requires the tray menu. If the tray ever fails to create, the only exit is Task Manager
|
||||
(mitigated today by the embedded fallback tray icon).
|
||||
- [ ] **L144 — Duplicate guard checks the queue only, not history.** Re-adding a URL downloaded earlier
|
||||
- [x] **L144 — Duplicate guard checks the queue only, not history.** Re-adding a URL downloaded earlier
|
||||
(already cleared from the queue) gives no "already downloaded" hint (roadmap notes this as a possible extension).
|
||||
*Fixed: the DownloadBar dup guard ([useDownloadBar.ts](src/renderer/src/components/downloadBar/useDownloadBar.ts))
|
||||
now checks history when a URL isn't in the live queue — if it was downloaded before, the warning banner reads
|
||||
"Already downloaded: …" (vs "Already in your queue: …"), with the same "Download anyway" confirm. A new
|
||||
`dupInHistory` flag drives the wording.*
|
||||
- [x] **L145 — `useClipboardLink` re-offers a dismissed link after a tab switch.** `lastSeen` is
|
||||
per-hook-instance; switching Downloads↔Library remounts it, so a previously-dismissed clipboard link is offered again.
|
||||
*Fixed: `lastSeen` is now a module-level variable (not a per-instance `useRef`), so a dismissed/accepted
|
||||
@@ -1253,9 +1280,12 @@ are an undefined spacing/radius/type scale and a drift between Fluent components
|
||||
- [ ] **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."
|
||||
- [ ] **UI25 — No in-app global status surface.** Queue progress shows only on the Downloads tab's
|
||||
- [x] **UI25 — No in-app global status surface.** Queue progress shows only on the Downloads tab's
|
||||
summary strip; on any other tab there's no in-app sign downloads are running (only the OS taskbar).
|
||||
**Standard:** a persistent affordance (e.g. a sidebar "Downloads" badge with the active count).
|
||||
*Fixed (with UX9): a `CounterBadge` on the sidebar Downloads nav item shows the active (downloading +
|
||||
queued) count from every tab, and a new in-app toast surface covers transient status. The exact
|
||||
suggested affordance.*
|
||||
|
||||
### Animations & transitions
|
||||
|
||||
@@ -1388,35 +1418,60 @@ root cause is already filed.
|
||||
|
||||
### Medium — confusion, feedback & organization
|
||||
|
||||
- [ ] **UX6 — Silent failures on file actions.** `openFile`/`showInFolder` in the stores call
|
||||
- [x] **UX6 — Silent failures on file actions.** `openFile`/`showInFolder` in the stores call
|
||||
`window.api.openPath(...)` and ignore the returned error string; [reveal.ts](src/main/reveal.ts)
|
||||
refuses missing/moved files or disallowed types and returns an error nobody surfaces. Clicking
|
||||
"Open file" on a moved download does nothing, with no message. **Fix:** surface open/reveal errors.
|
||||
*Fixed: a shared renderer helper [reveal.ts](src/renderer/src/reveal.ts) `revealFile('open'|'folder', path)`
|
||||
awaits the IPC result and raises the main-process reason ("File not found — it may have been moved…",
|
||||
"Refusing to open this file type.") as an error **toast** — the new transient-notification surface (see
|
||||
UX9). Both the downloads and history stores route through it. `safeShowInFolder` was upgraded to return an
|
||||
error string like `safeOpenPath` (it previously no-op'd silently), so reveal failures surface too.*
|
||||
- [ ] **UX7 — Settings is one long unsegmented scroll** of ~11 cards with no section nav/anchors; the
|
||||
only finder is the hide-cards search (M14). Order is arbitrary and related items are split.
|
||||
**Fix:** group into sub-sections (or a left rail) with a stable, logical order.
|
||||
*Deferred (visual, own pass): a Settings section-nav / left-rail is a layout redesign whose value is
|
||||
entirely in how it looks and scrolls — it wants the app running and eyeballed, not an unattended edit.
|
||||
Grouped with the visual/UX polish pass.*
|
||||
- [ ] **UX8 — "Default format" vs "Format & post-processing" are two places for one concept.** Kind +
|
||||
quality live under Downloads; container/codec/subs/SponsorBlock live in a separate card. **Fix:**
|
||||
co-locate, or clearly label one "defaults" and the other "advanced post-processing."
|
||||
- [ ] **UX9 — No global completion feedback off the Downloads tab** (UI25). If OS notifications are
|
||||
*Deferred (ties to UX7): co-locating or re-labelling these cards is part of the same Settings
|
||||
information-architecture pass; done together there with the app running.*
|
||||
- [x] **UX9 — No global completion feedback off the Downloads tab** (UI25). If OS notifications are
|
||||
off/suppressed (focus assist) and you're on another tab, a finished download gives no in-app sign.
|
||||
**Fix:** a sidebar Downloads badge / lightweight in-app toast.
|
||||
*Fixed (both halves of the suggested fix): a **sidebar Downloads badge** — a Fluent `CounterBadge` on the
|
||||
Downloads nav item shows the active (downloading + queued) count from any tab
|
||||
([Sidebar.tsx](src/renderer/src/components/Sidebar.tsx)), driven by a count-only selector in App so it
|
||||
updates without re-rendering on every progress tick; and a **lightweight in-app toast** surface
|
||||
([Toaster.tsx](src/renderer/src/components/ui/Toaster.tsx) + [store/toasts.ts](src/renderer/src/store/toasts.ts)),
|
||||
a non-portal bottom-center stack that any store/component can raise (now used for UX6's file-action errors).
|
||||
Together this closes **UI25**.*
|
||||
- [x] **UX10 — Re-download silently changes quality** (H5) — a confusing "I asked for 720p, got Best." *Resolved by H5 (re-download stores `formatId`/`formatHasAudio` and strips compound quality labels).*
|
||||
- [ ] **UX11 — Scheduled downloads silently never fire if the app is quit** (M4). The hint is easy to
|
||||
miss; the user returns to find nothing happened. **Fix:** warn at schedule time that it requires the
|
||||
app running (or persist + relaunch).
|
||||
- [ ] **UX12 — Terminal is a dead-end when custom commands are off** (L18). The nav item is always
|
||||
- [x] **UX12 — Terminal is a dead-end when custom commands are off** (L18). The nav item is always
|
||||
visible; clicking it shows a gate pointing to a setting on another screen. **Fix:** hide/disable the
|
||||
nav item, or let the gate enable the setting inline.
|
||||
nav item, or let the gate enable the setting inline. *Fixed (both fixes): the nav item is already hidden
|
||||
when custom commands are off (Sidebar's `showTerminal` filter, L18), and the gate — still reachable via the
|
||||
command palette — now carries an inline **"Enable custom commands"** button that flips the setting on the
|
||||
spot ([TerminalView.tsx](src/renderer/src/components/TerminalView.tsx)), so it's no longer a dead-end that
|
||||
sends you to another screen.*
|
||||
- [x] **UX13 — Backup export warns of nothing; it contains secrets** (M22). A user "backs up settings"
|
||||
and ships proxy creds / tokens in cleartext. **Fix:** warn, or mask/omit secrets. *Resolved by M22
|
||||
(backup strips proxy / PO-token / update-token; the caption states credentials aren't included).*
|
||||
- [x] **UX14 — Duplicate warning can show a placeholder title** (L76): 'Already in your queue:
|
||||
"YouTube video (abc123)"' before metadata resolves looks broken. **Fix:** fall back to the URL.
|
||||
*Resolved by L76 (the dup warning falls back to the URL when the title is still the placeholder).*
|
||||
- [ ] **UX15 — No bulk actions in the Downloads queue.** History has a select-mode + bulk delete;
|
||||
- [x] **UX15 — No bulk actions in the Downloads queue.** History has a select-mode + bulk delete;
|
||||
Downloads has only per-row remove + "Clear finished." Inconsistent. **Fix:** mirror select/bulk, or
|
||||
add "Cancel all."
|
||||
add "Cancel all." *Fixed (the "Cancel all" option): a `cancelAll` store action + a "Cancel all (N)"
|
||||
header button ([DownloadsView.tsx](src/renderer/src/components/DownloadsView.tsx)) that cancels every
|
||||
active (downloading + queued) item at once — killing the live processes and freeing the queue — shown only
|
||||
when there's something cancelable. (A full History-style select-mode is the heavier alternative; "Cancel
|
||||
all" covers the common bulk need.)*
|
||||
|
||||
### Low — polish & smaller friction
|
||||
|
||||
@@ -1427,6 +1482,8 @@ root cause is already filed.
|
||||
- [ ] **UX17 — Icon-only Fetch/Paste buttons** rely on hover tooltips for meaning (UI a11y; new users on touch/keyboard miss them). **Fix:** labels or `aria`-described affordances.
|
||||
- [ ] **UX18 — The quality control morphs after Fetch** (preset list → real formats; label "Quality" →
|
||||
"Quality / format") in place — a surprising transform. **Fix:** keep a stable control with a clear "formats loaded" state.
|
||||
*Deferred (subjective visual): the morph is arguably informative (the label change signals real formats
|
||||
loaded); whether it "surprises" is a judgment best made watching it, so it joins the watched visual pass.*
|
||||
- [x] **UX19 — Window size/position isn't remembered.** [index.ts](src/main/index.ts) opens a fixed
|
||||
920×700 every launch (no bounds persistence). **Fix:** persist + restore window bounds. *Fixed with W2 —
|
||||
[windowState.ts](src/main/windowState.ts) persists + restores bounds/maximized with an off-screen guard.*
|
||||
@@ -1438,13 +1495,18 @@ root cause is already filed.
|
||||
- [ ] **UX22 — Onboarding is shallow and one-shot** (L67): three tips, no folder choice, not
|
||||
revisitable; the clipboard tip requires a focus event a first-timer won't trigger.
|
||||
- [ ] **UX23 — Settings folder paths are read-only** (L71) — no paste; Browse-dialog only.
|
||||
- [ ] **UX24 — "Check N watched for new" is disabled with no explanation** when no source is watched —
|
||||
- [x] **UX24 — "Check N watched for new" is disabled with no explanation** when no source is watched —
|
||||
a user with indexed (but unwatched) sources sees a dead button. **Fix:** tooltip / enable with a hint to watch a source.
|
||||
*Fixed: the button uses `disabledFocusable` (not `disabled`) when nothing is watched, so it stays
|
||||
hover/focus-reachable, and a `Hint` explains why — "Turn on 'Watch' for a channel or playlist below first,
|
||||
then this checks them for new uploads." ([LibraryView.tsx](src/renderer/src/components/LibraryView.tsx)).*
|
||||
- [x] **UX25 — Empty-state copy mismatches the flow** (L78): Downloads says "Paste a URL above to get
|
||||
started," but the actual path is Fetch/Download. *Resolved by L78 (DownloadsView empty-state copy
|
||||
rewritten to match the Fetch/Download flow).*
|
||||
- [ ] **UX26 — yt-dlp/ffmpeg version + initial settings load have no skeleton/spinner** — brief blank
|
||||
states on boot (plus the documented one-frame theme flash). **Fix:** lightweight loading states.
|
||||
*Deferred (low value, needs eyeballing): the blanks are sub-second on a normal launch; adding skeletons is
|
||||
a perceived-polish tweak worth doing while watching the actual boot, so it joins the visual pass.*
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user