Files
AeroFetch/src/renderer/src/store/toasts.ts
T
debont80 9134e7d216 feat(audit): Batch 25 — project reorg (CC12), leading DI args (CC7), CC10 by-design
CC12: renderer views/ (5 screens + Onboarding + settings cards) +
lib/ (theme/thumb/datetime/id/qualityOptions/useClipboardLink/queueStats);
main core/ (buildArgs/validation/indexerCore/ytdlpPolicy). git mv preserved
history; all src+test imports updated. Build emits view chunks from views/,
344 tests + typecheck green, live probe rendered all screens.
CC7: wc/onProgress is the leading arg everywhere — downloadAppUpdate(wc,url),
indexSource(onProgress,url,signal), indexSourceCancelable(onProgress,url).
CC10: closed by-design — jsonStore backs all records; settings stay in
electron-store for DPAPI secret encryption (a deliberate two-store split).
Also closes the UI24/W4 context-menu cross-reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:37:28 -04:00

46 lines
1.5 KiB
TypeScript

import { create } from 'zustand'
import { newId } from '../lib/id'
/**
* A tiny transient-notification store (audit UX6/UX9). The app had no way to tell
* the user about a background failure — a swallowed `openPath` error, a file that
* moved — so those actions silently did nothing. This backs a lightweight,
* non-blocking toast stack rendered by `Toaster`; any store or component can raise
* one via {@link toast}.
*/
export type ToastTone = 'info' | 'error' | 'success'
export interface Toast {
id: string
message: string
tone: ToastTone
}
interface ToastState {
toasts: Toast[]
show: (message: string, tone?: ToastTone) => void
dismiss: (id: string) => void
}
/** How long a toast lingers before auto-dismissing. */
const AUTO_DISMISS_MS = 5000
export const useToasts = create<ToastState>((set, get) => ({
toasts: [],
show: (message, tone = 'info') => {
const id = newId('toast')
set((s) => ({ toasts: [...s.toasts, { id, message, tone }] }))
// Auto-dismiss so the stack self-clears; the user can also dismiss early.
setTimeout(() => get().dismiss(id), AUTO_DISMISS_MS)
},
dismiss: (id) => set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) }))
}))
/**
* Imperative shortcut for non-component callers (the Zustand stores), so a store
* action can surface an error without wiring the hook through a component.
*/
export function toast(message: string, tone?: ToastTone): void {
useToasts.getState().show(message, tone)
}