dd-timer/docs/plans/2026-04-05-toast-notifications-plan.md
POL Mickaël 3e485fd09b chore: normalise fins de ligne CRLF → LF dans tout le repo
Applique .gitattributes sur tous les fichiers existants.
Élimine les différences fantômes entre WSL et Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 08:55:10 +02:00

441 lines
11 KiB
Markdown
Executable File
Raw Permalink Blame History

# 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
/* ────────────────────<E29480><E29480><EFBFBD>────────────────────────────
TOAST NOTIFICATIONS
───────────────────────<E29480><E29480><EFBFBD>───────────────────────── */
#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"
```