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:
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { useToasts, toast } from '../src/renderer/src/store/toasts'
|
||||
|
||||
describe('toasts store', () => {
|
||||
beforeEach(() => {
|
||||
useToasts.setState({ toasts: [] })
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('show() adds a toast with the given message and tone', () => {
|
||||
toast('could not open file', 'error')
|
||||
const ts = useToasts.getState().toasts
|
||||
expect(ts).toHaveLength(1)
|
||||
expect(ts[0]?.message).toBe('could not open file')
|
||||
expect(ts[0]?.tone).toBe('error')
|
||||
})
|
||||
|
||||
it('defaults the tone to info', () => {
|
||||
toast('heads up')
|
||||
expect(useToasts.getState().toasts[0]?.tone).toBe('info')
|
||||
})
|
||||
|
||||
it('dismiss() removes only the matching toast', () => {
|
||||
toast('a')
|
||||
toast('b')
|
||||
const firstId = useToasts.getState().toasts[0]?.id ?? ''
|
||||
useToasts.getState().dismiss(firstId)
|
||||
const ts = useToasts.getState().toasts
|
||||
expect(ts).toHaveLength(1)
|
||||
expect(ts[0]?.message).toBe('b')
|
||||
})
|
||||
|
||||
it('auto-dismisses after the timeout elapses', () => {
|
||||
vi.useFakeTimers()
|
||||
toast('temporary')
|
||||
expect(useToasts.getState().toasts).toHaveLength(1)
|
||||
vi.advanceTimersByTime(5000)
|
||||
expect(useToasts.getState().toasts).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user