# Toast Notifications Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Ajouter un systeme de toast notifications (success/error) en bas a droite pour donner du feedback apres les actions utilisateur. **Architecture:** Composant singleton `Toast` avec file d'attente, monte dans le DOM par `App.ts`. Chaque composant appelle `Toast.show()` apres ses actions. Les `alert()` natifs sont remplaces par des toasts error. **Tech Stack:** TypeScript, CSS animations, Material Symbols Outlined --- ### Task 1: Creer le composant Toast.ts **Files:** - Create: `src/presentation/components/Toast.ts` **Step 1: Creer le fichier Toast.ts** ```typescript export type ToastType = 'success' | 'error'; interface ToastItem { id: number; type: ToastType; message: string; } const ICON: Record = { success: 'check_circle', error: 'error', }; const DURATION: Record = { success: 3000, error: 5000, }; const MAX_VISIBLE = 3; let nextId = 0; let container: HTMLElement | null = null; const items: ToastItem[] = []; export const Toast = { mount(parent: HTMLElement): void { container = document.createElement('div'); container.id = 'toast-container'; parent.appendChild(container); }, show(type: ToastType, message: string): void { if (!container) return; const id = nextId++; items.push({ id, type, message }); // Evincer les plus anciens si > MAX_VISIBLE while (items.length > MAX_VISIBLE) { const old = items.shift()!; const oldEl = container.querySelector(`[data-toast-id="${old.id}"]`); if (oldEl) oldEl.remove(); } const el = document.createElement('div'); el.className = `toast toast-${type}`; el.dataset['toastId'] = String(id); el.innerHTML = `${ICON[type]}${message}`; container.appendChild(el); // Trigger animation requestAnimationFrame(() => el.classList.add('toast-visible')); setTimeout(() => { el.classList.remove('toast-visible'); el.classList.add('toast-exit'); el.addEventListener('animationend', () => { el.remove(); const idx = items.findIndex(i => i.id === id); if (idx !== -1) items.splice(idx, 1); }, { once: true }); }, DURATION[type]); }, }; ``` **Step 2: Run tests** Run: `npm test` (depuis Windows) Expected: PASS (aucun test casse, nouveau fichier sans test pour l'instant) **Step 3: Commit** ```bash git add src/presentation/components/Toast.ts git commit -m "feat(toast): creer le composant Toast singleton" ``` --- ### Task 2: Ajouter les styles CSS **Files:** - Modify: `src/presentation/styles/obsidienne.css` **Step 1: Ajouter les styles toast a la fin de obsidienne.css** Ajouter avant le dernier `}` ou a la fin du fichier : ```css /* ────────────────────���──────────────────────────── TOAST NOTIFICATIONS ───────────────────────���───────────────────────── */ #toast-container { position: fixed; bottom: 24px; right: 24px; z-index: 9999; display: flex; flex-direction: column-reverse; gap: 8px; pointer-events: none; } .toast { display: flex; align-items: center; gap: 10px; padding: 12px 20px; border-radius: 12px; background: rgba(30, 20, 50, 0.85); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08); color: #e0e0e0; font-size: 0.92rem; font-family: var(--font-body); box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4); pointer-events: auto; opacity: 0; transform: translateX(100%); transition: none; min-width: 260px; max-width: 400px; } .toast-visible { animation: toast-in 0.3s ease forwards; } .toast-exit { animation: toast-out 0.3s ease forwards; } .toast-icon { font-size: 1.3rem; flex-shrink: 0; } .toast-msg { flex: 1; line-height: 1.3; } .toast-success { border-left: 3px solid #4caf50; } .toast-success .toast-icon { color: #4caf50; } .toast-error { border-left: 3px solid #f44336; } .toast-error .toast-icon { color: #f44336; } @keyframes toast-in { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } @keyframes toast-out { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100%); } } ``` **Step 2: Verifier visuellement** Run: `npm start` (depuis Windows) Verifier que l'app demarre sans erreur CSS. **Step 3: Commit** ```bash git add src/presentation/styles/obsidienne.css git commit -m "style(toast): ajouter les styles glassmorphism pour les toasts" ``` --- ### Task 3: Monter le conteneur Toast dans App.ts **Files:** - Modify: `src/presentation/components/App.ts:1-74` **Step 1: Ajouter l'import Toast** Apres la ligne `import { UpdateBanner } from './UpdateBanner';` (ligne 14), ajouter : ```typescript import { Toast } from './Toast'; ``` **Step 2: Monter le conteneur dans render()** Apres le montage du update banner (apres la ligne `this.updateBanner.render(bannerRoot);`, environ ligne 73), ajouter : ```typescript // Mount toast container const appShell = this.root.querySelector('.app-shell') as HTMLElement; Toast.mount(appShell); ``` **Step 3: Run tests** Run: `npm test` (depuis Windows) Expected: PASS **Step 4: Commit** ```bash git add src/presentation/components/App.ts git commit -m "feat(toast): monter le conteneur Toast dans App.ts" ``` --- ### Task 4: Remplacer les alert() dans WorkflowsView.ts **Files:** - Modify: `src/presentation/components/WorkflowsView.ts:349,653,665` **Step 1: Ajouter l'import** En haut de `WorkflowsView.ts`, ajouter : ```typescript import { Toast } from './Toast'; ``` **Step 2: Remplacer les alert() par des toasts error** Ligne ~653 — remplacer : ```typescript alert('Aucun plan valide trouvé dans le fichier.'); ``` par : ```typescript Toast.show('error', 'Aucun plan valide trouvé dans le fichier.'); ``` Ligne ~665 — remplacer : ```typescript alert('Le fichier sélectionné n\'est pas un JSON valide.'); ``` par : ```typescript Toast.show('error', 'Le fichier sélectionné n\'est pas un JSON valide.'); ``` **Step 3: Ajouter un toast success apres import reussi** Apres la ligne `this.commandBus.execute({ type: 'import-workflows', ... })` (ligne ~660), avant `this.dirty = true;`, ajouter : ```typescript Toast.show('success', `${valid.length} plan(s) importé(s) avec succès.`); ``` **Step 4: Ajouter un toast success apres suppression workflow** Ligne ~350, apres `this.commandBus.execute({ type: 'delete-workflow', ... })`, ajouter : ```typescript Toast.show('success', `Plan "${name}" supprimé.`); ``` **Step 5: Run tests** Run: `npm test` (depuis Windows) Expected: PASS **Step 6: Commit** ```bash git add src/presentation/components/WorkflowsView.ts git commit -m "feat(toast): remplacer alert() par toasts dans WorkflowsView" ``` --- ### Task 5: Ajouter les toasts dans les autres composants **Files:** - Modify: `src/presentation/components/App.ts:176` (delete-enclos) - Modify: `src/presentation/components/EnclosView.ts:206,274` (clear-enclos, nouvelle-fournee) - Modify: `src/presentation/components/Dashboard.ts:229` (reset-stats) - Modify: `src/presentation/components/AccouplementView.ts:434` (register-accouplement) - Modify: `src/presentation/components/DragodindeCard.ts:216` (remove-dragodinde) **Step 1: App.ts — toast apres suppression enclos** Ajouter l'import `Toast` (deja fait en Task 3). Apres `this.commandBus.execute({ type: 'delete-enclos', enclosId: id });` (ligne ~176), ajouter : ```typescript Toast.show('success', 'Enclos supprimé.'); ``` **Step 2: EnclosView.ts — toasts clear et nouvelle fournee** Ajouter l'import en haut : ```typescript import { Toast } from './Toast'; ``` Apres `this.commandBus.execute({ type: 'clear-enclos', enclosId: eId });` (ligne ~206), ajouter : ```typescript Toast.show('success', 'Enclos vidé.'); ``` Apres `this.commandBus.execute({ type: 'nouvelle-fournee', enclosId: eId });` (ligne ~274), ajouter : ```typescript Toast.show('success', 'Nouvelle fournée lancée.'); ``` **Step 3: Dashboard.ts — toast reset stats** Ajouter l'import : ```typescript import { Toast } from './Toast'; ``` Apres `this.commandBus.execute({ type: 'reset-stats' });` (ligne ~229), ajouter : ```typescript Toast.show('success', 'Statistiques réinitialisées.'); ``` **Step 4: AccouplementView.ts — toast enregistrement** Ajouter l'import : ```typescript import { Toast } from './Toast'; ``` Apres le `this.commandBus.execute({ type: 'register-accouplement', ... })` (ligne ~434), ajouter : ```typescript Toast.show('success', 'Accouplement enregistré.'); ``` **Step 5: DragodindeCard.ts — toast suppression DD** Ajouter l'import : ```typescript import { Toast } from './Toast'; ``` Apres `this.commandBus.execute({ type: 'remove-dragodinde', ... })` (ligne ~216), ajouter : ```typescript Toast.show('success', 'Dragodinde retirée.'); ``` **Step 6: Run tests** Run: `npm test` (depuis Windows) Expected: PASS **Step 7: Commit** ```bash git add src/presentation/components/App.ts src/presentation/components/EnclosView.ts src/presentation/components/Dashboard.ts src/presentation/components/AccouplementView.ts src/presentation/components/DragodindeCard.ts git commit -m "feat(toast): ajouter toasts success sur toutes les actions importantes" ``` --- ### Task 6: Ajouter un toast pour l'export workflows **Files:** - Modify: `src/presentation/components/WorkflowsView.ts` **Step 1: Trouver la methode d'export** Chercher la methode qui gere le clic sur le bouton d'export (probablement `exportWorkflows` ou similaire avec `showSaveDialog`). Apres l'ecriture reussie du fichier, ajouter : ```typescript Toast.show('success', 'Plans exportés avec succès.'); ``` **Step 2: Run tests** Run: `npm test` (depuis Windows) Expected: PASS **Step 3: Commit** ```bash git add src/presentation/components/WorkflowsView.ts git commit -m "feat(toast): toast success apres export workflows" ``` --- ### Task 7: Tests E2E pour les toasts **Files:** - Modify: `tests/e2e/breeding.spec.ts` **Step 1: Ajouter un test E2E pour le toast d'enregistrement** Dans le test "Definir nombre de couples et bebes puis enregistrer", apres le clic sur Enregistrer, ajouter une verification : ```typescript // Verifier que le toast success apparait await expect(page.locator('.toast-success')).toBeVisible({ timeout: 3000 }); await expect(page.locator('.toast-msg')).toContainText('Accouplement enregistré'); ``` **Step 2: Rebuild et run E2E** Run: `npm run build && npx playwright test tests/e2e/breeding.spec.ts` (depuis Windows) Expected: PASS **Step 3: Commit** ```bash git add tests/e2e/breeding.spec.ts git commit -m "test(toast): test E2E toast apres enregistrement accouplement" ```