Files
Lesstime/docs/superpowers/plans/2026-05-19-mail-integration-master-plan.md

13 KiB

Mail Integration — Master Plan

Master plan : ce document décrit le découpage en phases. Chaque phase aura son propre plan détaillé (rédigé par un subagent rédacteur) puis sera implémentée par un subagent codeur, en cycle.

Spec source : docs/superpowers/specs/2026-05-19-mail-integration-design.md

Goal : Ajouter à Lesstime un client mail intégré pour une boîte partagée OVH (IMAP/SMTP), avec lecture inbox/dossiers et création/lien tâche depuis un mail.

Stratégie : 7 phases séquentielles, dépendances claires, chaque phase = working software testable. Cycle par phase : rédacteur → codeur → review humaine → phase suivante.


Cartographie des phases

Phase 1 (Backend foundations)   ──┐
                                  ├─→ Phase 2 (IMAP provider + sync)  ──┐
                                  │                                     ├─→ Phase 3 (API backend)  ──┐
                                  │                                     │                            │
                                  └─→─────────────────────────────────────────────────────────────────┤
                                                                                                     │
Phase 4 (Frontend services + store)  ←──────────────────────────────────────────────────────────────┘
        │
        ├─→ Phase 5 (UI principale 3 colonnes)
        │
        ├─→ Phase 6 (Intégration tâches : modals, onglet TaskDrawer)
        │
        └─→ Phase 7 (Admin config + sidebar + polish)

Chaque phase produit du logiciel fonctionnel (testable, mergeable) sans casser les précédentes.


Phase 1 — Backend Foundations

Plan détaillé attendu : docs/superpowers/plans/2026-05-19-mail-phase1-foundations.md

Scope :

  • Entité MailConfiguration (singleton, fields complets de la spec, encryptedPassword via TokenEncryptor)
  • Entité MailFolder
  • Entité MailMessage
  • Entité TaskMailLink (avec unique constraint)
  • Repositories : MailConfigurationRepository::findSingleton(), MailFolderRepository, MailMessageRepository, TaskMailLinkRepository
  • Migration Doctrine unique créant les 4 tables (raw SQL)
  • DTOs sous src/Mail/Dto/ : MailFolderDto, MailMessageHeaderDto, MailMessageDetailDto, MailAttachmentDto
  • Interface App\Mail\MailProviderInterface (signatures uniquement, pas d'impl)
  • Exception App\Mail\Exception\MailProviderException
  • Tests unitaires repositories (au moins le pattern singleton)

Critère d'acceptation :

  • make migration-migrate passe sans erreur
  • php bin/console doctrine:schema:validate OK
  • make test vert (au moins les tests créés)
  • Fixture MailConfiguration désactivée (OVH defaults) ajoutée

Dépendances : aucune (point d'entrée).


Phase 2 — IMAP Provider + Sync

Plan détaillé attendu : docs/superpowers/plans/2026-05-19-mail-phase2-imap-sync.md

Scope :

  • Ajout dépendance Composer webklex/php-imap (vérifier compat PHP 8.4)
  • Implémentation App\Mail\ImapMailProvider implements MailProviderInterface
    • Lecture config via MailConfigurationRepository::findSingleton()
    • Déchiffrement password via TokenEncryptor
    • listFolders, listMessages, fetchMessage, markRead, markFlagged, moveMessage, fetchAttachment
    • Wrapping erreurs en MailProviderException
  • App\Service\MailSyncService
    • syncAll(): MailSyncReport
    • syncFolder(string $folderPath): MailSyncReport
    • syncFolderStructure(): void
    • Algorithme exact de la spec (UID FETCH lastUid+1:*, resync flags N=200 derniers, detect suppressions avec garde 50%)
  • DTO MailSyncReport (count créés / mis à jour / supprimés / errors)
  • Symfony Lock (mail.sync, TTL 10 min)
  • Commande console app:mail:sync (avec option --folder=...)
  • Documentation cron OS + cible Makefile make mail-sync
  • Tests : ImapMailProvider mocké via fixture serveur ou interface, MailSyncService avec provider mocké

Critère d'acceptation :

  • php bin/console app:mail:sync --dry-run fonctionne contre une fake config
  • Tests make test verts
  • make mail-sync documentée dans Makefile

Dépendances : Phase 1.


Phase 3 — API Backend

Plan détaillé attendu : docs/superpowers/plans/2026-05-19-mail-phase3-api.md

Scope :

  • API Platform ressources :
    • GET /api/mail/configuration (ROLE_ADMIN) — singleton provider
    • PATCH /api/mail/configuration (ROLE_ADMIN) — processor (jamais retourner password en clair, accepter nouveau password à chiffrer)
  • Custom controllers (priority: 1) :
    • POST /api/mail/configuration/test (ROLE_ADMIN) — test connexion
    • GET /api/mail/folders (ROLE_USER, refus ROLE_CLIENT explicite) — arbre + unreadCount depuis BDD
    • GET /api/mail/folders/{path}/messages?page&limit — pagination cursor sentAt DESC, id DESC
    • GET /api/mail/messages/{id} — fetch live IMAP + cache Symfony mail_body_{messageId} TTL 5 min
    • POST /api/mail/messages/{id}/read (body { read: bool })
    • POST /api/mail/messages/{id}/flag
    • POST /api/mail/messages/{id}/create-task (body { projectId, taskGroupId?, priority? })
    • POST /api/mail/messages/{id}/link-task (body { taskId })
    • DELETE /api/mail/messages/{id}/link-task/{taskId}
    • GET /api/tasks/{id}/mails
    • GET /api/mail/attachments/{id} — stream, Content-Disposition: attachment, jamais inline
    • POST /api/mail/sync — async via Messenger
  • Message + Handler Symfony Messenger MailSyncRequested
  • Sécurité : #[IsGranted('IS_AUTHENTICATED_FULLY')] + check ROLE_USER && !ROLE_CLIENT explicite
  • Tests fonctionnels endpoints (auth, format réponses, ROLE_CLIENT refusé)

Critère d'acceptation :

  • Tous endpoints répondent corrects status/format
  • Tests make test verts
  • ROLE_CLIENT refusé sur 100% des endpoints mail
  • Password jamais leak dans les réponses

Dépendances : Phase 1, Phase 2.


Phase 4 — Frontend Services + Store

Plan détaillé attendu : docs/superpowers/plans/2026-05-19-mail-phase4-frontend-services.md

Scope :

  • Install npm dompurify + types
  • frontend/services/dto/mail.ts : tous les types TS
  • frontend/services/mail.ts : méthodes API (suivre pattern tasks.ts)
    • listFolders, listMessages, getMessage, markRead, markFlagged
    • createTaskFromMail, linkTask, unlinkTask, listMailsForTask
    • triggerSync
    • getConfiguration, updateConfiguration, testConfiguration
    • downloadAttachment (retourne Blob)
  • Store Pinia frontend/stores/useMailStore.ts
    • State : folders, selectedFolderPath, messages[], selectedMessageId, selectedMessageDetail, loading, syncing, globalUnreadCount
    • Actions correspondantes
    • Polling pollUnreadCount() toutes les 30s (start/stop)
  • Sanitization helper frontend/utils/sanitizeMailHtml.ts (DOMPurify avec config bloquante : script/iframe/object/embed/on*/javascript:, strip ou placeholder pour <img src="http(s)://..."> distants)

Critère d'acceptation :

  • cd frontend && npx tsc --noEmit OK
  • Test manuel d'un appel mail.listFolders() depuis devtools renvoie 401 si pas authentifié, 200 sinon

Dépendances : Phase 3 (les endpoints doivent exister).


Phase 5 — UI principale (page /mail)

Plan détaillé attendu : docs/superpowers/plans/2026-05-19-mail-phase5-ui-main.md

Scope :

  • Page frontend/pages/mail.vue — layout 3 colonnes (dossiers / liste / lecteur), responsive
  • Composants frontend/components/mail/ :
    • MailFolderTree.vue — arbre récursif avec badges unread, sélection
    • MailMessageList.vue — liste paginée (infinite scroll), indicateurs lu/étoilé/PJ, formatage relatif des dates
    • MailMessageViewer.vue — header (de/à/cc/date) + body sanitizé via DOMPurify + liste PJ téléchargeables + actions (Créer tâche / Lier / Marquer lu/non-lu / Étoiler)
    • MailRefreshButton.vue — bouton sync manuel, désactivé pendant syncing
  • i18n clés mail.* dans frontend/i18n/locales/fr.json (et en.json si présent) : titres, vides, actions, erreurs
  • Mapping noms dossiers système (INBOX, Sent, Drafts, Archive, Trash, Junk) → labels traduits
  • Gestion query param ?messageId=X pour deep-link vers un mail (selection auto à l'ouverture)
  • Refus visuel pour ROLE_CLIENT (le middleware backend bloque déjà, mais ajouter check côté router/middleware Nuxt)

Critère d'acceptation :

  • Page accessible à /mail pour ROLE_USER/ROLE_ADMIN
  • ROLE_CLIENT redirigé vers /portal
  • Pas d'XSS via body mail (test manuel avec un mail contenant <script>alert(1)</script>)
  • Pixels tracking distants remplacés par placeholder

Dépendances : Phase 4.


Phase 6 — Intégration Tâches

Plan détaillé attendu : docs/superpowers/plans/2026-05-19-mail-phase6-task-integration.md

Scope :

  • frontend/components/mail/MailCreateTaskModal.vue — wrapper du TaskDrawer existant pré-rempli :
    • Titre = subject
    • Description = body plain text
    • Picker projet + groupe + priorité
    • À la création : appelle POST /api/mail/messages/{id}/create-task, ferme modal, redirige ou affiche succès
  • frontend/components/mail/MailLinkTaskModal.vue — autocomplete sur tâches existantes (filter par projet, statut non-archivé)
  • Onglet "Mails" sur TaskDrawer.vue :
    • Nouvelle section affichée à côté Documents / Time tracking / etc.
    • Liste MailMessage liés à la tâche (via GET /api/tasks/{id}/mails)
    • Item cliquable → router.push('/mail?messageId=' + id)
    • Bouton "Lier un mail" → ouvre un picker mail (TBD selon ergonomie : modal recherche ou redirige vers /mail)
  • Tests manuels : créer tâche depuis mail, lier mail à tâche existante, voir mail depuis onglet tâche

Critère d'acceptation :

  • Workflow complet : mail → "Créer tâche" → tâche créée et liée → visible dans onglet "Mails" du TaskDrawer
  • Workflow : tâche existante → "Lier mail" → mail apparaît dans onglet

Dépendances : Phase 5.


Phase 7 — Admin Config + Sidebar + Polish

Plan détaillé attendu : docs/superpowers/plans/2026-05-19-mail-phase7-admin-polish.md

Scope :

  • frontend/components/admin/AdminMailTab.vue (calqué sur AdminZimbraTab.vue) :
    • Form : protocol (imap pour MVP), imapHost/Port/Encryption, smtpHost/Port/Encryption, username, password (write-only, hasPassword: true côté GET), sentFolderPath, enabled toggle
    • Bouton "Tester la connexion" → POST /api/mail/configuration/test
    • Indicateur OVH defaults pré-remplis (ssl0.ovh.net:993/465)
  • Ajout onglet AdminMailTab dans la page admin (selon pattern existant)
  • Lien sidebar dans le layout default :
    • Icône material-symbols:mail-outline
    • Label traduit
    • Badge unread (count useMailStore.globalUnreadCount)
    • Visible uniquement pour ROLE_USER && !ROLE_CLIENT
  • Lifecycle polling 30s : start dans app.vue ou layout default, stop au logout
  • Documentation finale :
    • README ou docs/ : section "Mail integration" (cron OS, variables config, sécurité)
    • Makefile : make mail-sync documentée
  • Vérification finale tracking pixels (relire sanitizeMailHtml.ts + tester)
  • QA passe : workflow end-to-end depuis vraie boîte OVH (si dispo) ou IMAP test (greenmail/dovecot local)

Critère d'acceptation :

  • Admin peut configurer la boîte, tester, activer
  • Sidebar affiche badge unread temps réel (30s polling)
  • Doc d'install à jour
  • Aucun warning console front, aucun ERROR PHP dans make logs-dev

Dépendances : Phase 5 (sidebar utilise le store), Phase 3 (admin API).


Conventions communes à toutes les phases

  • TDD : test rouge → code → test vert → commit
  • Strict types PHP (declare(strict_types=1)) en tête de chaque fichier
  • PHP CS Fixer : make php-cs-fixer-allow-risky avant chaque commit
  • Commits : format <type>(mail) : <message> (espace avant :)
  • Branche : feat/mail-integration (créée au début de Phase 1)
  • Pas de jamais logger : bodies, password, attachments
  • Review humaine entre chaque phase : le user valide avant lancement phase suivante

Cycle d'exécution

Pour chaque phase N :

  1. Spawn subagent rédacteur (feature-dev:code-architect)

    • Input : ce master plan + spec + scope phase N
    • Output : docs/superpowers/plans/2026-05-19-mail-phaseN-*.md au format writing-plans (tasks bite-sized, fichiers exacts, code complet, commandes test)
  2. Spawn subagent codeur (ruflo-core:coder)

    • Input : plan détaillé phase N
    • Output : code + tests + commits (TDD strict)
  3. Review humaine : user valide ou demande corrections

  4. Phase suivante uniquement si OK