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,encryptedPasswordviaTokenEncryptor) - 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-migratepasse sans erreurphp bin/console doctrine:schema:validateOKmake testvert (au moins les tests créés)- Fixture
MailConfigurationdé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
- Lecture config via
App\Service\MailSyncServicesyncAll(): MailSyncReportsyncFolder(string $folderPath): MailSyncReportsyncFolderStructure(): 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-runfonctionne contre une fake config- Tests
make testverts make mail-syncdocumenté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 providerPATCH /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 connexionGET /api/mail/folders(ROLE_USER, refus ROLE_CLIENT explicite) — arbre + unreadCount depuis BDDGET /api/mail/folders/{path}/messages?page&limit— pagination cursorsentAt DESC, id DESCGET /api/mail/messages/{id}— fetch live IMAP + cache Symfonymail_body_{messageId}TTL 5 minPOST /api/mail/messages/{id}/read(body{ read: bool })POST /api/mail/messages/{id}/flagPOST /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}/mailsGET /api/mail/attachments/{id}— stream,Content-Disposition: attachment, jamais inlinePOST /api/mail/sync— async via Messenger
- Message + Handler Symfony Messenger
MailSyncRequested - Sécurité :
#[IsGranted('IS_AUTHENTICATED_FULLY')]+ checkROLE_USER && !ROLE_CLIENTexplicite - Tests fonctionnels endpoints (auth, format réponses, ROLE_CLIENT refusé)
Critère d'acceptation :
- Tous endpoints répondent corrects status/format
- Tests
make testverts - 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 TSfrontend/services/mail.ts: méthodes API (suivre patterntasks.ts)listFolders,listMessages,getMessage,markRead,markFlaggedcreateTaskFromMail,linkTask,unlinkTask,listMailsForTasktriggerSyncgetConfiguration,updateConfiguration,testConfigurationdownloadAttachment(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)
- State :
- 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 --noEmitOK- 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électionMailMessageList.vue— liste paginée (infinite scroll), indicateurs lu/étoilé/PJ, formatage relatif des datesMailMessageViewer.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é pendantsyncing
- i18n clés
mail.*dansfrontend/i18n/locales/fr.json(eten.jsonsi présent) : titres, vides, actions, erreurs - Mapping noms dossiers système (
INBOX,Sent,Drafts,Archive,Trash,Junk) → labels traduits - Gestion query param
?messageId=Xpour 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 à
/mailpour 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 duTaskDrawerexistant 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
MailMessageliés à la tâche (viaGET /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é surAdminZimbraTab.vue) :- Form : protocol (imap pour MVP), imapHost/Port/Encryption, smtpHost/Port/Encryption, username, password (write-only,
hasPassword: truecôté GET), sentFolderPath, enabled toggle - Bouton "Tester la connexion" →
POST /api/mail/configuration/test - Indicateur OVH defaults pré-remplis (
ssl0.ovh.net:993/465)
- Form : protocol (imap pour MVP), imapHost/Port/Encryption, smtpHost/Port/Encryption, username, password (write-only,
- Ajout onglet
AdminMailTabdans 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
- Icône
- Lifecycle polling 30s : start dans
app.vueou layout default, stop au logout - Documentation finale :
- README ou
docs/: section "Mail integration" (cron OS, variables config, sécurité) - Makefile :
make mail-syncdocumentée
- README ou
- 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-riskyavant 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 :
-
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-*.mdau formatwriting-plans(tasks bite-sized, fichiers exacts, code complet, commandes test)
-
Spawn subagent codeur (
ruflo-core:coder)- Input : plan détaillé phase N
- Output : code + tests + commits (TDD strict)
-
Review humaine : user valide ou demande corrections
-
Phase suivante uniquement si OK