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>
11 KiB
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
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
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 :
/* ────────────────────<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
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 :
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 :
// 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
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 :
import { Toast } from './Toast';
Step 2: Remplacer les alert() par des toasts error
Ligne ~653 — remplacer :
alert('Aucun plan valide trouvé dans le fichier.');
par :
Toast.show('error', 'Aucun plan valide trouvé dans le fichier.');
Ligne ~665 — remplacer :
alert('Le fichier sélectionné n\'est pas un JSON valide.');
par :
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 :
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 :
Toast.show('success', `Plan "${name}" supprimé.`);
Step 5: Run tests
Run: npm test (depuis Windows)
Expected: PASS
Step 6: Commit
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 :
Toast.show('success', 'Enclos supprimé.');
Step 2: EnclosView.ts — toasts clear et nouvelle fournee
Ajouter l'import en haut :
import { Toast } from './Toast';
Apres this.commandBus.execute({ type: 'clear-enclos', enclosId: eId }); (ligne ~206), ajouter :
Toast.show('success', 'Enclos vidé.');
Apres this.commandBus.execute({ type: 'nouvelle-fournee', enclosId: eId }); (ligne ~274), ajouter :
Toast.show('success', 'Nouvelle fournée lancée.');
Step 3: Dashboard.ts — toast reset stats
Ajouter l'import :
import { Toast } from './Toast';
Apres this.commandBus.execute({ type: 'reset-stats' }); (ligne ~229), ajouter :
Toast.show('success', 'Statistiques réinitialisées.');
Step 4: AccouplementView.ts — toast enregistrement
Ajouter l'import :
import { Toast } from './Toast';
Apres le this.commandBus.execute({ type: 'register-accouplement', ... }) (ligne ~434), ajouter :
Toast.show('success', 'Accouplement enregistré.');
Step 5: DragodindeCard.ts — toast suppression DD
Ajouter l'import :
import { Toast } from './Toast';
Apres this.commandBus.execute({ type: 'remove-dragodinde', ... }) (ligne ~216), ajouter :
Toast.show('success', 'Dragodinde retirée.');
Step 6: Run tests
Run: npm test (depuis Windows)
Expected: PASS
Step 7: Commit
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 :
Toast.show('success', 'Plans exportés avec succès.');
Step 2: Run tests
Run: npm test (depuis Windows)
Expected: PASS
Step 3: Commit
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 :
// 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
git add tests/e2e/breeding.spec.ts
git commit -m "test(toast): test E2E toast apres enregistrement accouplement"