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>
441 lines
11 KiB
Markdown
Executable File
441 lines
11 KiB
Markdown
Executable File
# 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"
|
||
```
|