feat(download): show a toast + button check when a file is saved

The desktop (Tauri) app has no native download UI, so FileSaver.saveAs saved
files silently — no visual or audio confirmation. Users re-clicked because
nothing said it worked (one report: 5 copies of the same file). Add a small
useSaveFile() hook that saves AND raises a 'Downloaded <filename>' toast, and
route every download call site through it (file attachments, image viewer, PDF
viewer, plus the recovery-key / key-backup exports). The file-message download
button also shows a green check on success.

Toast system extended with an optional iconSrc so system toasts render an icon
instead of an avatar/initials, and an empty roomName is no longer rendered.

Tests: createDownloadToast covered; 701/701 pass; typecheck + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 22:30:57 -04:00
co-authored by Claude Opus 4.8
parent f12175e76f
commit fd9e4a9802
10 changed files with 91 additions and 23 deletions
+15
View File
@@ -1,8 +1,10 @@
import { atom } from 'jotai';
import type { IconSrc } from 'folds';
export type ToastNotif = {
id: string;
avatarUrl?: string;
iconSrc?: IconSrc; // folds Icon src for a "system" toast (shown instead of an avatar/initials)
displayName: string;
body: string;
roomName: string;
@@ -12,6 +14,19 @@ export type ToastNotif = {
sticky?: boolean; // when true, does not auto-dismiss — use for action toasts that require a click
};
// Build a "download complete" system toast. Kept folds-free here (the icon src is
// passed in) so this stays a pure, testable builder. roomId is empty + onClick is
// set so a click only dismisses (never navigates to a room).
export const createDownloadToast = (filename: string, iconSrc?: IconSrc): ToastNotif => ({
id: `download-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
displayName: 'Downloaded',
body: filename,
roomName: '',
roomId: '',
iconSrc,
onClick: () => undefined,
});
const baseAtom = atom<ToastNotif[]>([]);
// Write-only setter used in ClientNonUIFeatures