Collaboration

Référence technique du domaine Collaboration — commentaires, threads, mentions, notes internes, réactions, inbox et notifications.

Collaboration

Périmètre : commentaires sur entités, threads (réponses imbriquées), mentions @user, réactions emoji, notes internes vs externes, attachements et inbox de notifications. Le composant CommentThread est réutilisé sur toutes les fiches (asset, contrat, opportunité, facture, maintenance…). Source de cette page : docs/14-domain-collab.md.

Les routes API vivent sous /api/workspace/comments et /api/workspace/inbox. Le composant central est CommentThread, inclus dans chaque fiche de domaine.

Vocabulaire & entités

TermeEntité / ChampDéfinition
CommentaireCommentMessage texte attaché à une entité (asset, contrat, opportunité, facture…).
ThreadComment.parentCommentIdRéponses imbriquées sous un commentaire parent.
MentionCommentMentionRéférence @userId dans le texte — génère une notification.
Note interneComment.isInternalVisible uniquement par les membres Pulse, jamais exposée aux portails externes.
RéactionCommentReactionEmoji (👍 ❤️ ✅ ⚠️ 👀) posé sur un commentaire.
ÉpinglageComment.pinnedAtUn commentaire épinglé au sommet du thread (1 max par thread).
WatchEntityWatchAbonnement d'un utilisateur aux nouveaux commentaires d'une entité.
InboxVue agrégée /workspace/inbox : mentions + threads suivis, filtrables.

Schéma Prisma détaillé dans docs/04-data-model.md §11.

Écrans

ÉcranRoute / ComposantContenu
Composant CommentThreadIntégré dans chaque ficheListe chronologique paginée (50/page), composer markdown, toggle notes internes, réactions, reply, pin.
Inbox/workspace/inboxTous les commentaires me mentionnant + threads que je watch ; filtres non lu / mention / période.

Composant CommentThread — détail

Liste

Chronologique (plus ancien en haut). Pagination si > 50 commentaires (bouton « Voir plus anciens »). Toggle « Notes internes » en filtre.

Composer

Markdown léger : gras / italique / liste / mention / lien. Drag-drop ou picker pour ajouter des pièces jointes (FileLink). Autocomplete @... debounce 200 ms, 10 résultats max.

Actions par commentaire

Réactions emoji · Reply (thread enfant) · Édition (15 min) · Suppression (soft) · Pin (modérateur).

Modèle de données (points clés)

  • Comment.targetType + Comment.targetId : clé polymorphique vers l'entité parente. Index (targetType, targetId) pour les requêtes de récupération de thread.
  • Comment.parentCommentId nullable : NULL = commentaire racine, non NULL = réponse.
  • Comment.isInternal : filtré côté serveur à chaque lecture ; les portails externes ne reçoivent jamais les notes internes (le filtre n'est jamais délégué au client).
  • Comment.deletedAt : suppression soft — le contenu est effacé, l'auteur et la date sont préservés, affichage « Commentaire supprimé ».
  • Attachements via FileLink (cf. docs/13-domain-files.md).

API

Toutes les routes exigent une session authentifiée. Les permissions comment:* sont vérifiées côté API.

MéthodeRoutePermission
GET/api/workspace/comments?targetType=X&targetId=Ycomment:read
POST/api/workspace/commentscomment:create
PATCH/api/workspace/comments/[id]comment:update_own ou comment:moderate
DELETE/api/workspace/comments/[id]comment:delete_own ou comment:moderate
POST/api/workspace/comments/[id]/reactionscomment:create
DELETE/api/workspace/comments/[id]/reactions/[emoji]comment:create
POST/api/workspace/comments/[id]/pincomment:moderate
GET/api/workspace/inbox(authentifié)
POST/api/workspace/commentsAuth

Crée un commentaire sur une entité. Si le texte contient des mentions @userId, des entrées CommentMention sont créées et les notifications correspondantes sont émises.

Corps (JSON)

targetType
string required
Type de l'entité cible (asset, contract, opportunity, invoice, maintenance…).
targetId
string required
Identifiant de l'entité cible.
body
string required
Contenu markdown du commentaire.
isInternal
boolean
true pour une note interne (défaut : false).
parentCommentId
string
Identifiant du commentaire parent (réponse dans un thread).

Requête

curl -s -X POST "$API/api/workspace/comments" \
  -H "Content-Type: application/json" -H "Cookie: $SESSION" \
  -d '{"targetType":"opportunity","targetId":"opp_…","body":"@alice peux-tu valider ?","isInternal":false}'

Réponse

{
  "id": "cmt_…",
  "targetType": "opportunity",
  "targetId": "opp_…",
  "body": "@alice peux-tu valider ?",
  "isInternal": false,
  "authorId": "usr_…",
  "createdAt": "2026-06-16T10:00:00Z",
  "mentions": [{ "userId": "usr_alice" }]
}

Workflows

Mention @user

Saisie @nom
  → Autocomplete (debounce 200 ms, 10 résultats) remplace @nom → @userId
  → À la soumission : parsing du body
  → INSERT CommentMention (commentId, userId)
  → Notification in-app + email (selon préférences) : "X vous a mentionné dans [entité]"

Réponse à un commentaire

User B répond à un commentaire de User A (B ≠ A)
  → Comment créé avec parentCommentId = comment de A
  → Notification in-app à A : "B a répondu à votre commentaire dans [entité]"

Watch d'entité

User clique "Suivre" sur une entité
  → INSERT EntityWatch (userId, targetType, targetId)
  → Tout nouveau commentaire sur cette entité → notification in-app au watcher
  → Filtrable selon les préférences de notification de l'utilisateur

Notes internes vs externes

Composer → toggle "Note interne"
  → isInternal = true  : serveur filtre à la lecture, portails externes exclus
  → isInternal = false : visible par tous les participants ayant accès à l'entité
                         (ex : client voit les commentaires de son contrat dans son portail)

Notifications

ÉvénementCanalDestinataire
Mention dans un commentaireIn-app + emailUtilisateur mentionné
Réponse à mon commentaireIn-appAuteur du commentaire parent
Nouveau commentaire sur entité suivieIn-appWatchers de l'entité

Règles métier

  • Fenêtre d'édition : 15 minutes après la création. Au-delà, le commentaire est figé sauf pour un modérateur (comment:moderate).
  • Suppression soft : deletedAt posé, contenu effacé, auteur et date préservés ; affichage « Commentaire supprimé ».
  • Isolation notes internes : filtre appliqué côté serveur à chaque lecture — jamais délégué au client ou au portail externe.
  • Mention vers utilisateur désactivé : badge « Utilisateur inconnu » affiché, la CommentMention reste en base.
  • Épinglage : 1 commentaire épinglé maximum par thread (le précédent est désépinglé automatiquement).
  • Attachements : via FileLink (cf. domaine Files — docs/13-domain-files.md).