dd-timer/docs/plans/2026-04-05-toast-notifications-plan.md
POL Mickaël 2893013093 docs: README, CLAUDE.md, changelog, plans de conception
- README : fonctionnalités, installation, build, tests (302 + 20 E2E),
  couverture 94%, workflow mise à jour latest.yml, changelog v1.1.6
- CLAUDE.md : règles de collaboration, architecture, conventions
- Plans de conception : DDD, electron-updater, accouplement, toast

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 05:43:38 +02:00

11 KiB
Raw 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

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"