gigafibre-fsm/docs/design/communications-coherence.md
louispaulb 512c4a5f1b feat(ops): dispatch auto complet + perf Boîte/rapports + fix session
Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré /
juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis
par compétence + par job, carte des tournées (1 couleur/tech, domicile→arrêts,
sélecteur de jour), fenêtre de dispatch auj.+demain (dates sélectionnées), règle
week-end + placeholder « en attente du quart », clustering + lasso + filtre-date
sur la carte, accès rapide « À assigner » (badge).

Boîte : liste /conversations allégée (45 Mo → ~1 Mo, 17×) + messages chargés à
l'ouverture. Rapports : cache SWR sur revenue-explorer (22×). Session : keep-alive
+ timeout fetch global + authFetch durci → fin des rechargements manuels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:49:35 -04:00

6.3 KiB

Cohérence de l'interface Communications — cap « feeling Missive »

Statut : PLAN (2026-06-18). Refactor d'AFFICHAGE + threading — ne change pas les données stockées. Objectif : que notre Boîte soit aussi cohérente et intuitive que Missive.

Diagnostic : une seule cause méta

Presque toutes les incohérences viennent du même défaut : le même concept est calculé à plusieurs endroits avec des logiques différentes (liste vs détail vs ingestion). Missive est cohérent parce que chaque concept a une source de vérité. → Principe directeur : un resolver unique par concept, importé partout.

Inventaire (causes racines, vérifiées dans le code)

1. « à moi » au lieu de « à support » (attribution du destinataire)

  • Rendu : ConversationPanel.vue:126 (entête, convTo) + :414 (msgTomsgRecipientList, :1163-1186).
  • Cause : meAddr = msg.toEmail || convTo puis if addr === meAddr → 'moi'. « moi » = la boîte qui a REÇU le courriel, PAS l'utilisateur connecté (meEmail, :1395, jamais consulté ici). Aucune table adresse-de-groupe → nom d'équipe.
  • Correctif : table support@→Support, facturation@→Facturation… (depuis SENDER_IDENTITIES / queueLabels) ; « moi » seulement si l'adresse == meEmail.

2. Nom d'expéditeur vide / incohérent

  • Rendu : liste MessageList.vue:104 (finit par « Inconnu », OK) ; détail msgWho (:1577-1582) + avatar (:1569-1570).
  • Cause : fromName='' si le From n'a pas de display-name → retombe sur activeDiscussion.customerName (peut être un AUTRE expéditeur dans un fil multi-personnes) ; avatar staffInitials('') → vide. Et shortAgent (local part minuscule, ex. « gilles.drolet ») vs noteAuthorName (Capitalisé) = même personne affichée de 2 façons.
  • Correctif : un resolver identity() partagé, jamais vide, agents toujours capitalisés (réutiliser noteAuthorName).

3. Sur-regroupement (fils fusionnés à tort)

  • Lieux : ingestion conversation.js:722-734 + re-bucketing liste listConversations (:435-461).
  • Causes : (a) courriel sans sujetfindConversationByEmail:681 fusionne par email seul (garde sautée si un sujet est vide) ; (b) clé :active (:444) fusionne TOUS les fils actifs d'un même baseKey sans borne de date ; (c) SMS/téléphone fusionne par numéro pour toujours (sans sujet ni fenêtre) ; (d) thread_id Gmail pris tel quel (réutilisation de sujet → vieux fil) ; (e) In-Reply-To/References stockés mais PAS utilisés pour regrouper.
  • Correctif : exiger un signal de fil positif (thread_id OU match References) avant de fusionner ; sujet vide/auto = non-concordant ; fenêtre de récence sur la clé :active.

4. Canal incohérent

  • conv.channel (conversation) vs msg.via (message) — vocabulaires différents ; le courriel n'a AUCUN badge (MessageList.vue:102 le masque) ; pas de badge par message.
  • Correctif : msg.via = canal par message (vocabulaire normalisé) ; badge cohérent sur CHAQUE ligne/message, courriel inclus.

5. FILE + identité éclatées

  • FILE : entête activeConv.queue (chargé async) vs liste d.queue → décalage bref. Identité : titre/chip/message utilisent 3 champs différents (customerName / coordAccountName / email) → 3 noms pour le même fil.
  • Correctif : FILE = une source (réconcilier une fois activeConv arrivé) ; « qui » = un seul computed.

6. Finitions incohérentes

  • Deux formatTime/formatDate : local ConversationPanel:2070-2087 (≠ canonique useFormatters) → liste en « DD/MM » (relTime) vs entête de message « mois jour HH:MM » ; le local n'a pas le garde isNaN → « Invalid Date ».
  • Tri : message emailDate||ts vs liste/lastTs = ts → ordre divergent sur fils re-synchronisés.
  • Non-lu / « en train d'écrire » pris du latest seul, pas du fil fusionné entier.
  • Couleurs d'avatar : clés de hash différentes liste vs détail → même personne, couleurs différentes.

Les resolvers (colonne vertébrale du correctif)

Un module partagé (ex. composables/useConversationDisplay.js) exporte un resolver par concept, utilisé par la liste ET le détail (et côté hub, l'ingestion) :

Concept Resolver Règle
Qui identity(p) nom → local part → tél → « Inconnu » ; agents capitalisés ; avatar/couleur/initiales dérivés du MÊME id
Destinataire recipientLabel(addr) adresse de groupe → nom d'équipe ; « moi » seulement si == utilisateur ; sinon nom/adresse
Canal channelOf(msg) depuis via normalisé → badge
Fil threadKey(msg) signal positif requis ; sujet vide = non-match ; fenêtre de récence
File fileOf(conv) une seule source
Temps useFormatters partout supprimer les copies locales

Patterns Missive à adopter (extraits du tour vidéo)

  • Ligne uniforme : avatar + badge de canal + nom + heure + sujet + extrait + chips.
  • 1 fil = 1 sujet (titre visible → toute fausse fusion saute aux yeux).
  • Fil unifié : messages client (blocs pleine largeur) + notes internes (bulles) + tâches inline, avec 2 composeurs (répondre au client / note d'équipe).
  • Assign / Snooze / Close en actions de barre supérieure (dropdowns riches).
  • Toasts Undo sur actions réversibles.

Plan par phases

Phase Contenu Risque
P1 — Cohérence resolvers identity + recipientLabel (« à support ») + temps unifié (tuer les formatTime locaux) + fileOf une source faible (affichage)
P2 — Threading strict signal de fil positif, sujet vide = non-match, fenêtre de récence, In-Reply-To/References utilisés moyen (logique de fil → tester sur fils réels)
P3 — Canal channelOf + badge partout (Messenger/courriel/SMS) faible
P4 — UX Missive fil unifié + 2 composeurs + assign/snooze + undo moyen (UI)

Garde-fous

  • Refactor d'affichage d'abord — ne touche pas aux données stockées.
  • Threading (P2) : préférer sur-séparer que sur-fusionner — un faux split est bénin ; une fuite de conversation entre deux clients ne l'est pas.
  • Tester P2 sur de vrais fils : multi-expéditeur, SMS long, adresse de groupe, sujet réutilisé.
  • Chaque resolver = testable isolément (entrées → libellé attendu) avant branchement UI.