# 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<ToastType, string> = {
  success: 'check_circle',
  error: 'error',
};

const DURATION: Record<ToastType, number> = {
  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 = `<span class="toast-icon material-symbols-outlined">${ICON[type]}</span><span class="toast-msg">${message}</span>`;
    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"
```
