# Global Notification System — Sonner Toast Integration

## Context

The adiuvAI Electron app has **52+ user-facing mutations** (create/update/delete for tasks, projects, clients, notes, timeline events, agents, settings, auth) with **no unified feedback system**. Some components show a transient "Saved" button label for 2s via `useState` + `setTimeout`; most mutations are completely silent. Errors are handled inconsistently — some show inline text, many are swallowed.

This plan adds a global toast notification system using **shadcn's sonner component**, replacing all ad-hoc patterns with a single i18n-aware API.

---

## Phase 1: Foundation

### 1.1 Install sonner via shadcn CLI

```bash
cd adiuvAI && npx shadcn@latest add sonner
```

This installs the `sonner` npm package and generates `src/renderer/components/ui/sonner.tsx`.

### 1.2 Fix theme import in generated `sonner.tsx`

The generated file imports `useTheme` from `next-themes` (doesn't exist in this app). Replace with:

```tsx
import { useTheme } from "@/components/theme-provider"
```

The app's `useTheme()` returns `{ theme: "dark" | "light" | "system" }` — same shape sonner expects.

Configure `position="bottom-right"` to avoid sidebar collision. Keep `richColors` enabled for variant-specific coloring.

Full target file:

```tsx
import { useTheme } from "@/components/theme-provider"
import { Toaster as Sonner } from "sonner"

type ToasterProps = React.ComponentProps<typeof Sonner>

const Toaster = ({ ...props }: ToasterProps) => {
  const { theme = "system" } = useTheme()
  return (
    <Sonner
      theme={theme as ToasterProps["theme"]}
      position="bottom-right"
      richColors
      className="toaster group"
      toastOptions={{
        classNames: {
          toast:
            "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
          description: "group-[.toast]:text-muted-foreground",
          actionButton:
            "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
          cancelButton:
            "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
        },
      }}
      {...props}
    />
  )
}

export { Toaster }
```

### 1.3 Place `<Toaster />` in `src/renderer/index.tsx`

Add `<Toaster />` as a sibling of `<RouterProvider />` inside `<ThemeProvider>`:

```tsx
import { Toaster } from '@/components/ui/sonner';
// ...
<ThemeProvider defaultTheme="system" storageKey="adiuvai-theme">
  <trpc.Provider client={trpcClient} queryClient={queryClient}>
    <QueryClientProvider client={queryClient}>
      <LanguageSync />
      <RouterProvider router={router} />
      <Toaster />
    </QueryClientProvider>
  </trpc.Provider>
</ThemeProvider>
```

**Why here?** The `<Toaster />` must render OUTSIDE all conditional rendering in AppShell.tsx (which gates LoginForm / OnboardingFlow / main app). Placing it in `index.tsx` ensures toasts work in all three states.

### 1.4 Create `useNotify()` hook

**New file:** `src/renderer/hooks/useNotify.ts`

```tsx
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';

type ToastVariant = 'success' | 'error' | 'info' | 'warning';

interface NotifyOptions {
  descriptionKey?: string;
  values?: Record<string, string | number>;
  duration?: number;
}

export function useNotify() {
  const { t } = useTranslation();

  function notify(variant: ToastVariant, messageKey: string, options?: NotifyOptions) {
    const message = t(messageKey, options?.values);
    const description = options?.descriptionKey
      ? t(options.descriptionKey, options?.values)
      : undefined;
    const duration = options?.duration;

    switch (variant) {
      case 'success': toast.success(message, { description, duration: duration ?? 3000 }); break;
      case 'error':   toast.error(message, { description, duration: duration ?? Infinity }); break;
      case 'info':    toast.info(message, { description, duration: duration ?? 3000 }); break;
      case 'warning': toast.warning(message, { description, duration: duration ?? 4000 }); break;
    }
  }

  function notifyError(messageKey: string, error?: { message?: string }) {
    toast.error(t(messageKey), { description: error?.message, duration: Infinity });
  }

  function notifyPromise<T>(promise: Promise<T>, keys: { loading: string; success: string; error: string }) {
    toast.promise(promise, { loading: t(keys.loading), success: t(keys.success), error: t(keys.error) });
  }

  return { notify, notifyError, notifyPromise };
}
```

**Design rationale:**
- Error toasts: `duration: Infinity` — persist until dismissed so users can read/copy errors
- Success: 3s auto-dismiss — brief confirmation
- Warning (destructive): 4s — slightly longer for delete confirmations
- `notifyError`: convenience for `onError` callbacks — title from i18n, description from raw error
- `notifyPromise`: wraps `toast.promise()` for long-running ops
- All text goes through `t()` for i18n

### 1.5 Add i18n toast keys

**Files:** `src/renderer/locales/{en,it,es,fr,de}/translation.json`

Add a `toast` top-level key. English:

```json
"toast": {
  "profile": {
    "updated": "Profile updated",
    "updateError": "Failed to update profile"
  },
  "settings": {
    "languageChanged": "Language changed",
    "backendUrlSaved": "Server URL saved",
    "backendUrlError": "Failed to save server URL",
    "formatPrefsSaved": "Display preferences saved",
    "formatPrefsError": "Failed to save display preferences",
    "memorySaved": "Preferences saved",
    "memoryError": "Failed to save preferences"
  },
  "auth": {
    "loginError": "Sign-in failed",
    "registerError": "Registration failed",
    "oauthError": "Google sign-in failed",
    "loggedOut": "Signed out"
  },
  "onboarding": {
    "completed": "Onboarding complete",
    "completedDescription": "Your workspace is personalized",
    "error": "Failed to save onboarding",
    "reset": "Onboarding reset",
    "normalizing": "Personalizing your workspace...",
    "normalized": "Personalization ready"
  },
  "task": {
    "created": "Task created",
    "createError": "Failed to create task",
    "updated": "Task updated",
    "updateError": "Failed to update task",
    "deleted": "Task deleted",
    "deleteError": "Failed to delete task"
  },
  "project": {
    "created": "Project created",
    "createError": "Failed to create project",
    "updated": "Project updated",
    "updateError": "Failed to update project",
    "deleted": "Project deleted",
    "deleteError": "Failed to delete project",
    "archived": "Project archived",
    "unarchived": "Project unarchived",
    "archivedAll": "All projects archived",
    "unarchivedAll": "All projects unarchived"
  },
  "client": {
    "created": "Client created",
    "createError": "Failed to create client",
    "updated": "Client renamed",
    "updateError": "Failed to rename client",
    "deleted": "Client deleted",
    "deleteError": "Failed to delete client"
  },
  "note": {
    "created": "Note created",
    "createError": "Failed to create note",
    "deleted": "Note deleted",
    "deleteError": "Failed to delete note"
  },
  "timeline": {
    "created": "Event created",
    "createError": "Failed to create event",
    "updated": "Event updated",
    "updateError": "Failed to update event",
    "deleted": "Event deleted",
    "deleteError": "Failed to delete event"
  },
  "comment": {
    "created": "Comment added",
    "createError": "Failed to add comment",
    "deleted": "Comment deleted",
    "deleteError": "Failed to delete comment"
  },
  "agent": {
    "created": "Agent created",
    "createError": "Failed to create agent",
    "updated": "Agent configuration saved",
    "updateError": "Failed to save agent configuration",
    "deleted": "Agent deleted",
    "deleteError": "Failed to delete agent",
    "runStarted": "Agent run started",
    "runError": "Failed to start agent"
  }
}
```

**Italian translations:**

```json
"toast": {
  "profile": { "updated": "Profilo aggiornato", "updateError": "Impossibile aggiornare il profilo" },
  "settings": { "languageChanged": "Lingua cambiata", "backendUrlSaved": "URL del server salvato", "backendUrlError": "Impossibile salvare l'URL del server", "formatPrefsSaved": "Preferenze di visualizzazione salvate", "formatPrefsError": "Impossibile salvare le preferenze", "memorySaved": "Preferenze salvate", "memoryError": "Impossibile salvare le preferenze" },
  "auth": { "loginError": "Accesso fallito", "registerError": "Registrazione fallita", "oauthError": "Accesso con Google fallito", "loggedOut": "Disconnesso" },
  "onboarding": { "completed": "Onboarding completato", "completedDescription": "Il tuo workspace e' personalizzato", "error": "Impossibile salvare l'onboarding", "reset": "Onboarding ripristinato", "normalizing": "Personalizzazione in corso...", "normalized": "Personalizzazione pronta" },
  "task": { "created": "Attivita' creata", "createError": "Impossibile creare l'attivita'", "updated": "Attivita' aggiornata", "updateError": "Impossibile aggiornare l'attivita'", "deleted": "Attivita' eliminata", "deleteError": "Impossibile eliminare l'attivita'" },
  "project": { "created": "Progetto creato", "createError": "Impossibile creare il progetto", "updated": "Progetto aggiornato", "updateError": "Impossibile aggiornare il progetto", "deleted": "Progetto eliminato", "deleteError": "Impossibile eliminare il progetto", "archived": "Progetto archiviato", "unarchived": "Progetto ripristinato", "archivedAll": "Tutti i progetti archiviati", "unarchivedAll": "Tutti i progetti ripristinati" },
  "client": { "created": "Cliente creato", "createError": "Impossibile creare il cliente", "updated": "Cliente rinominato", "updateError": "Impossibile rinominare il cliente", "deleted": "Cliente eliminato", "deleteError": "Impossibile eliminare il cliente" },
  "note": { "created": "Nota creata", "createError": "Impossibile creare la nota", "deleted": "Nota eliminata", "deleteError": "Impossibile eliminare la nota" },
  "timeline": { "created": "Evento creato", "createError": "Impossibile creare l'evento", "updated": "Evento aggiornato", "updateError": "Impossibile aggiornare l'evento", "deleted": "Evento eliminato", "deleteError": "Impossibile eliminare l'evento" },
  "comment": { "created": "Commento aggiunto", "createError": "Impossibile aggiungere il commento", "deleted": "Commento eliminato", "deleteError": "Impossibile eliminare il commento" },
  "agent": { "created": "Agente creato", "createError": "Impossibile creare l'agente", "updated": "Configurazione agente salvata", "updateError": "Impossibile salvare la configurazione", "deleted": "Agente eliminato", "deleteError": "Impossibile eliminare l'agente", "runStarted": "Esecuzione agente avviata", "runError": "Impossibile avviare l'agente" }
}
```

**Spanish translations:**

```json
"toast": {
  "profile": { "updated": "Perfil actualizado", "updateError": "Error al actualizar el perfil" },
  "settings": { "languageChanged": "Idioma cambiado", "backendUrlSaved": "URL del servidor guardada", "backendUrlError": "Error al guardar la URL del servidor", "formatPrefsSaved": "Preferencias de visualizacion guardadas", "formatPrefsError": "Error al guardar las preferencias", "memorySaved": "Preferencias guardadas", "memoryError": "Error al guardar las preferencias" },
  "auth": { "loginError": "Error de acceso", "registerError": "Error de registro", "oauthError": "Error de acceso con Google", "loggedOut": "Sesion cerrada" },
  "onboarding": { "completed": "Configuracion completada", "completedDescription": "Tu espacio de trabajo esta personalizado", "error": "Error al guardar la configuracion", "reset": "Configuracion reiniciada", "normalizing": "Personalizando tu espacio...", "normalized": "Personalizacion lista" },
  "task": { "created": "Tarea creada", "createError": "Error al crear la tarea", "updated": "Tarea actualizada", "updateError": "Error al actualizar la tarea", "deleted": "Tarea eliminada", "deleteError": "Error al eliminar la tarea" },
  "project": { "created": "Proyecto creado", "createError": "Error al crear el proyecto", "updated": "Proyecto actualizado", "updateError": "Error al actualizar el proyecto", "deleted": "Proyecto eliminado", "deleteError": "Error al eliminar el proyecto", "archived": "Proyecto archivado", "unarchived": "Proyecto restaurado", "archivedAll": "Todos los proyectos archivados", "unarchivedAll": "Todos los proyectos restaurados" },
  "client": { "created": "Cliente creado", "createError": "Error al crear el cliente", "updated": "Cliente renombrado", "updateError": "Error al renombrar el cliente", "deleted": "Cliente eliminado", "deleteError": "Error al eliminar el cliente" },
  "note": { "created": "Nota creada", "createError": "Error al crear la nota", "deleted": "Nota eliminada", "deleteError": "Error al eliminar la nota" },
  "timeline": { "created": "Evento creado", "createError": "Error al crear el evento", "updated": "Evento actualizado", "updateError": "Error al actualizar el evento", "deleted": "Evento eliminado", "deleteError": "Error al eliminar el evento" },
  "comment": { "created": "Comentario agregado", "createError": "Error al agregar el comentario", "deleted": "Comentario eliminado", "deleteError": "Error al eliminar el comentario" },
  "agent": { "created": "Agente creado", "createError": "Error al crear el agente", "updated": "Configuracion del agente guardada", "updateError": "Error al guardar la configuracion", "deleted": "Agente eliminado", "deleteError": "Error al eliminar el agente", "runStarted": "Ejecucion del agente iniciada", "runError": "Error al iniciar el agente" }
}
```

**French translations:**

```json
"toast": {
  "profile": { "updated": "Profil mis a jour", "updateError": "Impossible de mettre a jour le profil" },
  "settings": { "languageChanged": "Langue modifiee", "backendUrlSaved": "URL du serveur enregistree", "backendUrlError": "Impossible d'enregistrer l'URL du serveur", "formatPrefsSaved": "Preferences d'affichage enregistrees", "formatPrefsError": "Impossible d'enregistrer les preferences", "memorySaved": "Preferences enregistrees", "memoryError": "Impossible d'enregistrer les preferences" },
  "auth": { "loginError": "Echec de la connexion", "registerError": "Echec de l'inscription", "oauthError": "Echec de la connexion Google", "loggedOut": "Deconnecte" },
  "onboarding": { "completed": "Configuration terminee", "completedDescription": "Votre espace de travail est personnalise", "error": "Impossible d'enregistrer la configuration", "reset": "Configuration reinitialisee", "normalizing": "Personnalisation en cours...", "normalized": "Personnalisation terminee" },
  "task": { "created": "Tache creee", "createError": "Impossible de creer la tache", "updated": "Tache mise a jour", "updateError": "Impossible de mettre a jour la tache", "deleted": "Tache supprimee", "deleteError": "Impossible de supprimer la tache" },
  "project": { "created": "Projet cree", "createError": "Impossible de creer le projet", "updated": "Projet mis a jour", "updateError": "Impossible de mettre a jour le projet", "deleted": "Projet supprime", "deleteError": "Impossible de supprimer le projet", "archived": "Projet archive", "unarchived": "Projet restaure", "archivedAll": "Tous les projets archives", "unarchivedAll": "Tous les projets restaures" },
  "client": { "created": "Client cree", "createError": "Impossible de creer le client", "updated": "Client renomme", "updateError": "Impossible de renommer le client", "deleted": "Client supprime", "deleteError": "Impossible de supprimer le client" },
  "note": { "created": "Note creee", "createError": "Impossible de creer la note", "deleted": "Note supprimee", "deleteError": "Impossible de supprimer la note" },
  "timeline": { "created": "Evenement cree", "createError": "Impossible de creer l'evenement", "updated": "Evenement mis a jour", "updateError": "Impossible de mettre a jour l'evenement", "deleted": "Evenement supprime", "deleteError": "Impossible de supprimer l'evenement" },
  "comment": { "created": "Commentaire ajoute", "createError": "Impossible d'ajouter le commentaire", "deleted": "Commentaire supprime", "deleteError": "Impossible de supprimer le commentaire" },
  "agent": { "created": "Agent cree", "createError": "Impossible de creer l'agent", "updated": "Configuration de l'agent enregistree", "updateError": "Impossible d'enregistrer la configuration", "deleted": "Agent supprime", "deleteError": "Impossible de supprimer l'agent", "runStarted": "Execution de l'agent lancee", "runError": "Impossible de lancer l'agent" }
}
```

**German translations:**

```json
"toast": {
  "profile": { "updated": "Profil aktualisiert", "updateError": "Profil konnte nicht aktualisiert werden" },
  "settings": { "languageChanged": "Sprache geaendert", "backendUrlSaved": "Server-URL gespeichert", "backendUrlError": "Server-URL konnte nicht gespeichert werden", "formatPrefsSaved": "Anzeigeeinstellungen gespeichert", "formatPrefsError": "Einstellungen konnten nicht gespeichert werden", "memorySaved": "Einstellungen gespeichert", "memoryError": "Einstellungen konnten nicht gespeichert werden" },
  "auth": { "loginError": "Anmeldung fehlgeschlagen", "registerError": "Registrierung fehlgeschlagen", "oauthError": "Google-Anmeldung fehlgeschlagen", "loggedOut": "Abgemeldet" },
  "onboarding": { "completed": "Einrichtung abgeschlossen", "completedDescription": "Ihr Arbeitsbereich ist personalisiert", "error": "Einrichtung konnte nicht gespeichert werden", "reset": "Einrichtung zurueckgesetzt", "normalizing": "Personalisierung laeuft...", "normalized": "Personalisierung abgeschlossen" },
  "task": { "created": "Aufgabe erstellt", "createError": "Aufgabe konnte nicht erstellt werden", "updated": "Aufgabe aktualisiert", "updateError": "Aufgabe konnte nicht aktualisiert werden", "deleted": "Aufgabe geloescht", "deleteError": "Aufgabe konnte nicht geloescht werden" },
  "project": { "created": "Projekt erstellt", "createError": "Projekt konnte nicht erstellt werden", "updated": "Projekt aktualisiert", "updateError": "Projekt konnte nicht aktualisiert werden", "deleted": "Projekt geloescht", "deleteError": "Projekt konnte nicht geloescht werden", "archived": "Projekt archiviert", "unarchived": "Projekt wiederhergestellt", "archivedAll": "Alle Projekte archiviert", "unarchivedAll": "Alle Projekte wiederhergestellt" },
  "client": { "created": "Kunde erstellt", "createError": "Kunde konnte nicht erstellt werden", "updated": "Kunde umbenannt", "updateError": "Kunde konnte nicht umbenannt werden", "deleted": "Kunde geloescht", "deleteError": "Kunde konnte nicht geloescht werden" },
  "note": { "created": "Notiz erstellt", "createError": "Notiz konnte nicht erstellt werden", "deleted": "Notiz geloescht", "deleteError": "Notiz konnte nicht geloescht werden" },
  "timeline": { "created": "Ereignis erstellt", "createError": "Ereignis konnte nicht erstellt werden", "updated": "Ereignis aktualisiert", "updateError": "Ereignis konnte nicht aktualisiert werden", "deleted": "Ereignis geloescht", "deleteError": "Ereignis konnte nicht geloescht werden" },
  "comment": { "created": "Kommentar hinzugefuegt", "createError": "Kommentar konnte nicht hinzugefuegt werden", "deleted": "Kommentar geloescht", "deleteError": "Kommentar konnte nicht geloescht werden" },
  "agent": { "created": "Agent erstellt", "createError": "Agent konnte nicht erstellt werden", "updated": "Agent-Konfiguration gespeichert", "updateError": "Konfiguration konnte nicht gespeichert werden", "deleted": "Agent geloescht", "deleteError": "Agent konnte nicht geloescht werden", "runStarted": "Agent-Ausfuehrung gestartet", "runError": "Agent konnte nicht gestartet werden" }
}
```

---

## Phase 2: Settings Mutations (replace existing `saved`/`setSaved` patterns)

These 5 components have existing feedback to **remove and replace**:

### 2.1 `src/renderer/components/settings/GeneralSection.tsx`

**Current:** `saved`/`setSaved` state (line 28), `error`/`setError` state (line 29), `setTimeout` (line 44), inline `<p>` error (line 93).

**Changes:**
1. Add `const { notify, notifyError } = useNotify();`
2. **Remove** `const [saved, setSaved] = useState(false);`
3. **Remove** `const [error, setError] = useState('');`
4. In `handleSave` `onSuccess`: remove `setSaved(true); setTimeout(...)` → add `notify('success', 'toast.profile.updated');`
5. In `handleSave` `onError`: replace `setError(err.message)` → `notifyError('toast.profile.updateError', err);`
6. **Remove** inline error `<p>` tag (line 93)
7. **Remove** `setSaved(false)` from `onChange` handlers (lines 83, 89)
8. Button text: `{saved ? t('settings.saved') : t('common.save')}` → `{t('common.save')}`
9. In `handleLanguageChange`: add `notify('info', 'toast.settings.languageChanged');`

### 2.2 `src/renderer/components/settings/ProfileSection.tsx`

**Current:** `profileSaved`/`displaySaved` states, both with `setTimeout`.

**Changes:**
1. Add `useNotify()`, remove both `saved` states and `setTimeout`s
2. Profile save `onSuccess` → `notify('success', 'toast.settings.memorySaved')`
3. Display save `onSuccess` → `notify('success', 'toast.settings.formatPrefsSaved')`
4. Reset onboarding `onSuccess` → `notify('info', 'toast.onboarding.reset')`
5. Both save buttons: replace ternary with `{t('common.save')}`

### 2.3 `src/renderer/components/settings/AccountSection.tsx`

**Current:** `urlSaved`/`setUrlSaved` state, `setTimeout`.

**Changes:**
1. Add `useNotify()`, remove `urlSaved` state and `setTimeout`
2. Backend URL save `onSuccess` → `notify('success', 'toast.settings.backendUrlSaved')`
3. Add `onError` → `notifyError('toast.settings.backendUrlError', err)`
4. Logout `onSuccess` → `notify('info', 'toast.auth.loggedOut')`
5. Button text: replace ternary with `{t('common.save')}`

### 2.4 `src/renderer/components/settings/LocalAgentConfigPanel.tsx`

**Current:** `saved`/`setSaved` state, `setTimeout`.

**Changes:**
1. Add `useNotify()`, remove `saved` state and `setTimeout`
2. Save `onSuccess` → `notify('success', 'toast.agent.updated')`
3. Add `onError` → `notifyError('toast.agent.updateError', err)`
4. Button text: `{t('common.save')}`

### 2.5 `src/renderer/components/settings/CloudAgentConfigPanel.tsx`

Identical pattern to 2.4.

---

## Phase 3: CRUD Operations

### Tasks

| File | Mutation | Toast | Key |
|------|----------|-------|-----|
| `components/tasks/NewTaskDialog.tsx` | `tasks.create` | success | `toast.task.created` |
| `components/tasks/EditTaskDialog.tsx` | `tasks.update` | success | `toast.task.updated` |
| `components/tasks/TaskDetailDialog.tsx` | `taskComments.create` | success | `toast.comment.created` |
| `components/tasks/TaskDetailDialog.tsx` | `taskComments.delete` | warning | `toast.comment.deleted` |
| `routes/tasks.tsx` | `tasks.delete` | warning | `toast.task.deleted` |
| `components/projects/KanbanBoard.tsx` | `tasks.delete` | warning | `toast.task.deleted` |
| `components/projects/ProjectDetail.tsx` | `tasks.delete` | warning | `toast.task.deleted` |
| `components/ai/blocks/ChatEntityBlock.tsx` | `tasks.delete` | warning | `toast.task.deleted` |

**Error-only (no success toast):** Status toggle mutations in `routes/tasks.tsx`, `KanbanBoard.tsx`, `ProjectDetail.tsx`, `ChatEntityBlock.tsx` — visual feedback (badge/card move) IS the confirmation.

### Projects

| File | Mutation | Toast | Key |
|------|----------|-------|-----|
| `components/projects/ProjectSidebar.tsx` | `projects.create` | success | `toast.project.created` |
| `components/projects/ProjectSidebar.tsx` | `projects.update` | success | `toast.project.updated` |
| `components/projects/ProjectSidebar.tsx` | `projects.delete` | warning | `toast.project.deleted` |
| `components/projects/ProjectSidebar.tsx` | `projects.archiveByClient` | warning | `toast.project.archivedAll` / `unarchivedAll` |

### Clients

| File | Mutation | Toast | Key |
|------|----------|-------|-----|
| `components/projects/ProjectSidebar.tsx` | `clients.create` | success | `toast.client.created` |
| `components/projects/ProjectSidebar.tsx` | `clients.update` | success | `toast.client.updated` |
| `components/projects/ProjectSidebar.tsx` | `clients.deleteWithCascade` | warning | `toast.client.deleted` |
| `components/tasks/NewTaskDialog.tsx` | `clients.create` (inline) | success | `toast.client.created` |

### Notes

| File | Mutation | Toast | Key |
|------|----------|-------|-----|
| `components/projects/ProjectDetail.tsx` | `notes.create` | success | `toast.note.created` |
| `routes/notes.$noteId.tsx` | `notes.delete` | warning | `toast.note.deleted` |

### Timeline Events

| File | Mutation | Toast | Key |
|------|----------|-------|-----|
| `components/timeline/AddEventDialog.tsx` | `timelineEvents.create` | success | `toast.timeline.created` |
| `components/timeline/EditEventDialog.tsx` | `timelineEvents.update` | success | `toast.timeline.updated` |
| `routes/timeline.tsx` | `timelineEvents.delete` | warning | `toast.timeline.deleted` |
| `routes/timeline.tsx` | `timelineEvents.update` | success | `toast.timeline.updated` |

### Agents

| File | Mutation | Toast | Key |
|------|----------|-------|-----|
| `components/settings/AgentsSection.tsx` | `agent.*.delete` | warning | `toast.agent.deleted` |
| `components/settings/AgentsSection.tsx` | `agent.runNow` | promise | `toast.agent.runStarted` / `runError` |
| `components/settings/InlineAgentCreationStepper.tsx` | `agent.*.create` | success | `toast.agent.created` |

---

## Phase 4: Auth + Onboarding

| File | Mutation | Toast | Key |
|------|----------|-------|-----|
| `components/auth/LoginForm.tsx` | `auth.login` error | error | `toast.auth.loginError` |
| `components/auth/LoginForm.tsx` | `auth.register` error | error | `toast.auth.registerError` |
| `components/auth/LoginForm.tsx` | `auth.loginWithOAuth` error | error | `toast.auth.oauthError` |
| `components/layout/AppShell.tsx` | `auth.logout` success | info | `toast.auth.loggedOut` |
| `components/onboarding/OnboardingFlow.tsx` | final save success | success | `toast.onboarding.completed` |
| `components/onboarding/OnboardingFlow.tsx` | normalize call | promise | `toast.onboarding.normalizing` / `normalized` |

**Auth note:** Keep inline form errors in LoginForm alongside toast — form-level positional context is valuable.

---

## Explicitly SILENT Mutations (no toast)

| Mutation | Reason |
|----------|--------|
| `notes.update` (auto-save debounced 2s) | Would spam a toast every 2s while typing |
| `tasks.update` status toggle (kanban drag, checkbox, status cycle) | Card movement / badge change IS the feedback |
| `settings.setSidebarCollapsed` | Sidebar animation IS the feedback |
| `ai.chat` / `ai.dailyBrief` | Has own streaming UI |
| `agent.journey.*` | Has own conversational UI |

For these: add `onError` callback only (no success toast).

---

## All Files Modified (Summary)

**New files (2):**
- `src/renderer/components/ui/sonner.tsx` — generated by CLI + theme fix
- `src/renderer/hooks/useNotify.ts` — custom hook

**Modified files (~25):**
- `package.json` — `sonner` dependency (automatic via CLI)
- `src/renderer/index.tsx` — add `<Toaster />`
- 5x `locales/{en,it,es,fr,de}/translation.json` — add `toast` keys
- 5x Settings components — replace saved state with toast
- ~14x CRUD/auth/onboarding components — add toast calls

---

## Verification

1. **All app states:** Trigger toasts during login (error), onboarding (completion), and normal usage (CRUD)
2. **Theme:** Switch dark/light/system — toast backgrounds should follow semantic tokens
3. **i18n:** Switch to Italian → trigger save → toast should read "Profilo aggiornato"
4. **Error persistence:** Error toasts stay until dismissed (test with invalid backend URL)
5. **Silent mutations:** Drag task on kanban, type in notes, toggle sidebar — NO toasts
6. **Removed patterns:** Settings Save buttons stay as "Save" (no "Saved" flash) + toast appears
7. **Position:** Bottom-right, not overlapping sidebar
8. **Lint:** `npm run lint` passes
9. **Build:** `npm run package` succeeds
