@atlashub/smartstack-cli 1.5.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/.documentation/css/styles.css +2168 -2168
  2. package/.documentation/js/app.js +794 -794
  3. package/config/default-config.json +86 -86
  4. package/config/settings.json +53 -53
  5. package/config/settings.local.example.json +16 -16
  6. package/dist/index.js +0 -0
  7. package/dist/index.js.map +1 -1
  8. package/package.json +88 -88
  9. package/templates/agents/action.md +36 -36
  10. package/templates/agents/efcore/conflicts.md +84 -84
  11. package/templates/agents/efcore/db-deploy.md +51 -51
  12. package/templates/agents/efcore/db-reset.md +59 -59
  13. package/templates/agents/efcore/db-seed.md +56 -56
  14. package/templates/agents/efcore/db-status.md +64 -64
  15. package/templates/agents/efcore/migration.md +85 -85
  16. package/templates/agents/efcore/rebase-snapshot.md +62 -62
  17. package/templates/agents/efcore/scan.md +90 -90
  18. package/templates/agents/efcore/squash.md +67 -67
  19. package/templates/agents/explore-codebase.md +65 -65
  20. package/templates/agents/explore-docs.md +97 -97
  21. package/templates/agents/fix-grammar.md +49 -49
  22. package/templates/agents/gitflow/abort.md +45 -45
  23. package/templates/agents/gitflow/cleanup.md +85 -85
  24. package/templates/agents/gitflow/commit.md +40 -40
  25. package/templates/agents/gitflow/exec.md +48 -48
  26. package/templates/agents/gitflow/finish.md +92 -92
  27. package/templates/agents/gitflow/init.md +139 -139
  28. package/templates/agents/gitflow/merge.md +62 -62
  29. package/templates/agents/gitflow/plan.md +42 -42
  30. package/templates/agents/gitflow/pr.md +78 -78
  31. package/templates/agents/gitflow/review.md +49 -49
  32. package/templates/agents/gitflow/start.md +61 -61
  33. package/templates/agents/gitflow/status.md +32 -32
  34. package/templates/agents/snipper.md +36 -36
  35. package/templates/agents/websearch.md +46 -46
  36. package/templates/commands/_resources/formatting-guide.md +124 -124
  37. package/templates/commands/ai-prompt.md +315 -315
  38. package/templates/commands/apex/1-analyze.md +100 -100
  39. package/templates/commands/apex/2-plan.md +145 -145
  40. package/templates/commands/apex/3-execute.md +171 -171
  41. package/templates/commands/apex/4-examine.md +116 -116
  42. package/templates/commands/apex/5-tasks.md +209 -209
  43. package/templates/commands/apex.md +76 -76
  44. package/templates/commands/application/create.md +362 -362
  45. package/templates/commands/application/templates-backend.md +463 -463
  46. package/templates/commands/application/templates-frontend.md +517 -517
  47. package/templates/commands/application/templates-i18n.md +478 -478
  48. package/templates/commands/application/templates-seed.md +362 -362
  49. package/templates/commands/application.md +303 -303
  50. package/templates/commands/business-analyse/0-orchestrate.md +640 -640
  51. package/templates/commands/business-analyse/1-init.md +269 -269
  52. package/templates/commands/business-analyse/2-discover.md +520 -520
  53. package/templates/commands/business-analyse/3-analyse.md +408 -408
  54. package/templates/commands/business-analyse/4-specify.md +598 -598
  55. package/templates/commands/business-analyse/5-validate.md +326 -326
  56. package/templates/commands/business-analyse/6-handoff.md +746 -746
  57. package/templates/commands/business-analyse/7-doc-html.md +602 -602
  58. package/templates/commands/business-analyse/bug.md +325 -325
  59. package/templates/commands/business-analyse/change-request.md +368 -368
  60. package/templates/commands/business-analyse/hotfix.md +200 -200
  61. package/templates/commands/business-analyse.md +640 -640
  62. package/templates/commands/controller/create.md +216 -216
  63. package/templates/commands/controller/postman-templates.md +528 -528
  64. package/templates/commands/controller/templates.md +600 -600
  65. package/templates/commands/controller.md +337 -337
  66. package/templates/commands/create/agent.md +138 -138
  67. package/templates/commands/create/command.md +166 -166
  68. package/templates/commands/create/hook.md +234 -234
  69. package/templates/commands/create/plugin.md +329 -329
  70. package/templates/commands/create/project.md +507 -507
  71. package/templates/commands/create/skill.md +199 -199
  72. package/templates/commands/create.md +220 -220
  73. package/templates/commands/debug.md +95 -95
  74. package/templates/commands/documentation/module.md +202 -202
  75. package/templates/commands/documentation/templates.md +432 -432
  76. package/templates/commands/documentation.md +190 -190
  77. package/templates/commands/efcore/_env-check.md +153 -153
  78. package/templates/commands/efcore/conflicts.md +186 -186
  79. package/templates/commands/efcore/db-deploy.md +193 -193
  80. package/templates/commands/efcore/db-reset.md +426 -426
  81. package/templates/commands/efcore/db-seed.md +326 -326
  82. package/templates/commands/efcore/db-status.md +226 -226
  83. package/templates/commands/efcore/migration.md +400 -400
  84. package/templates/commands/efcore/rebase-snapshot.md +264 -264
  85. package/templates/commands/efcore/scan.md +198 -198
  86. package/templates/commands/efcore/squash.md +298 -298
  87. package/templates/commands/efcore.md +224 -224
  88. package/templates/commands/epct.md +69 -69
  89. package/templates/commands/explain.md +186 -186
  90. package/templates/commands/explore.md +45 -45
  91. package/templates/commands/feature-full.md +267 -267
  92. package/templates/commands/gitflow/1-init.md +1038 -1038
  93. package/templates/commands/gitflow/10-start.md +768 -768
  94. package/templates/commands/gitflow/11-finish.md +457 -457
  95. package/templates/commands/gitflow/12-cleanup.md +276 -276
  96. package/templates/commands/gitflow/13-sync.md +216 -216
  97. package/templates/commands/gitflow/14-rebase.md +251 -251
  98. package/templates/commands/gitflow/2-status.md +277 -277
  99. package/templates/commands/gitflow/3-commit.md +344 -344
  100. package/templates/commands/gitflow/4-plan.md +145 -145
  101. package/templates/commands/gitflow/5-exec.md +147 -147
  102. package/templates/commands/gitflow/6-abort.md +344 -344
  103. package/templates/commands/gitflow/7-pull-request.md +453 -355
  104. package/templates/commands/gitflow/8-review.md +240 -176
  105. package/templates/commands/gitflow/9-merge.md +451 -365
  106. package/templates/commands/gitflow.md +128 -128
  107. package/templates/commands/implement.md +663 -663
  108. package/templates/commands/init.md +567 -567
  109. package/templates/commands/mcp-integration.md +330 -330
  110. package/templates/commands/notification.md +129 -129
  111. package/templates/commands/oneshot.md +57 -57
  112. package/templates/commands/quick-search.md +72 -72
  113. package/templates/commands/ralph-loop/cancel-ralph.md +18 -18
  114. package/templates/commands/ralph-loop/help.md +126 -126
  115. package/templates/commands/ralph-loop/ralph-loop.md +18 -18
  116. package/templates/commands/review.md +106 -106
  117. package/templates/commands/utils/test-web-config.md +160 -160
  118. package/templates/commands/utils/test-web.md +151 -151
  119. package/templates/commands/validate.md +233 -233
  120. package/templates/commands/workflow.md +193 -193
  121. package/templates/gitflow/config.json +138 -138
  122. package/templates/hooks/ef-migration-check.md +139 -139
  123. package/templates/hooks/hooks.json +25 -25
  124. package/templates/hooks/stop-hook.sh +177 -177
  125. package/templates/skills/ai-prompt/SKILL.md +778 -778
  126. package/templates/skills/application/SKILL.md +563 -563
  127. package/templates/skills/application/templates-backend.md +450 -450
  128. package/templates/skills/application/templates-frontend.md +531 -531
  129. package/templates/skills/application/templates-i18n.md +520 -520
  130. package/templates/skills/application/templates-seed.md +647 -647
  131. package/templates/skills/business-analyse/SKILL.md +191 -191
  132. package/templates/skills/business-analyse/questionnaire.md +283 -283
  133. package/templates/skills/business-analyse/templates-frd.md +477 -477
  134. package/templates/skills/business-analyse/templates-react.md +580 -580
  135. package/templates/skills/controller/SKILL.md +240 -240
  136. package/templates/skills/controller/postman-templates.md +614 -614
  137. package/templates/skills/controller/templates.md +1468 -1468
  138. package/templates/skills/documentation/SKILL.md +133 -133
  139. package/templates/skills/documentation/templates.md +476 -476
  140. package/templates/skills/feature-full/SKILL.md +838 -838
  141. package/templates/skills/notification/SKILL.md +555 -555
  142. package/templates/skills/ui-components/SKILL.md +870 -870
  143. package/templates/skills/workflow/SKILL.md +582 -582
  144. package/templates/test-web/api-health.json +38 -38
  145. package/templates/test-web/minimal.json +19 -19
  146. package/templates/test-web/npm-package.json +46 -46
  147. package/templates/test-web/seo-check.json +54 -54
@@ -1,555 +1,555 @@
1
- ---
2
- name: notification
3
- description: |
4
- Integre le systeme de notifications SmartStack dans le developpement.
5
- Utiliser ce skill quand:
6
- - L'utilisateur veut envoyer des notifications depuis une feature
7
- - L'utilisateur mentionne "notifier", "alerte", "notification", "in-app"
8
- - Creation d'un module qui necessite des notifications utilisateur
9
- - Integration SignalR pour real-time
10
- Types: In-App, Email (via Workflow), Push (futur)
11
- ---
12
-
13
- # Skill Notification SmartStack
14
-
15
- > **Architecture:** Notifications = In-App (DB + SignalR) + Email (via Workflows)
16
- > Les notifications push sont prevues mais pas encore implementees.
17
-
18
- ## QUAND CE SKILL S'ACTIVE
19
-
20
- Claude invoque automatiquement ce skill quand il detecte :
21
-
22
- | Declencheur | Exemple |
23
- |-------------|---------|
24
- | Demande explicite | "Notifie l'utilisateur quand un ticket est cree" |
25
- | Mention notification | "Il faut alerter l'admin si..." |
26
- | Event-driven | "Quand le SLA expire, prevenir l'utilisateur" |
27
- | Real-time | "Afficher en temps reel les nouvelles notifications" |
28
- | Mots-cles | "notifier", "alerte", "notification", "SignalR", "real-time" |
29
-
30
- ---
31
-
32
- ## ARCHITECTURE NOTIFICATIONS
33
-
34
- ```
35
- ┌─────────────────────────────────────────────────────────────────────────────┐
36
- │ NOTIFICATION FLOW │
37
- ├─────────────────────────────────────────────────────────────────────────────┤
38
- │ │
39
- │ [EVENT] ─────┬──────────────────────────────────────────────────────────┐ │
40
- │ │ │ │
41
- │ ▼ │ │
42
- │ ┌─────────────────────────┐ │ │
43
- │ │ INotificationService │ │ │
44
- │ │ SendNotificationAsync │ │ │
45
- │ └────────────┬────────────┘ │ │
46
- │ │ │ │
47
- │ ┌───────┴───────┐ │ │
48
- │ ▼ ▼ │ │
49
- │ ┌─────────┐ ┌───────────────────┐ │ │
50
- │ │ DB │ │ SignalR Hub │ │ │
51
- │ │ (store) │ │ (real-time push) │ │ │
52
- │ └─────────┘ └─────────┬─────────┘ │ │
53
- │ │ │ │
54
- │ ▼ │ │
55
- │ ┌──────────────────┐ │ │
56
- │ │ Frontend Hook │ │ │
57
- │ │ useSignalR() │ │ │
58
- │ └─────────┬────────┘ │ │
59
- │ │ │ │
60
- │ ▼ │ │
61
- │ ┌──────────────────┐ │ │
62
- │ │ NotificationBell │ │ │
63
- │ │ (UI Component) │ │ │
64
- │ └──────────────────┘ │ │
65
- │ │ │
66
- │ [EMAIL] ── IWorkflowService.TriggerAsync() ── EmailTemplate ── SMTP │ │
67
- │ │ │
68
- └─────────────────────────────────────────────────────────────────────────────┘
69
- ```
70
-
71
- ---
72
-
73
- ## TYPES DE NOTIFICATION
74
-
75
- ### Enum NotificationType
76
-
77
- | Type | Declencheur | Usage |
78
- |------|-------------|-------|
79
- | `TicketCreated` | Nouveau ticket | Support |
80
- | `TicketAssigned` | Ticket assigne | Support |
81
- | `TicketStatusChanged` | Changement statut | Support |
82
- | `TicketCommentAdded` | Nouveau commentaire | Support |
83
- | `TicketResolved` | Ticket resolu | Support |
84
- | `RoleAssigned` | Role attribue | Admin |
85
- | `RoleRemoved` | Role retire | Admin |
86
- | `RolePermissionsChanged` | Permissions modifiees | Admin |
87
- | `SystemAnnouncement` | Annonce systeme | Global |
88
- | `AccountUpdated` | Compte modifie | User |
89
- | `SlaWarning` | SLA proche expiration | SLA |
90
- | `SlaResponseBreached` | SLA reponse depasse | SLA |
91
- | `SlaResolutionBreached` | SLA resolution depasse | SLA |
92
-
93
- ### Ajouter un Nouveau Type
94
-
95
- ```csharp
96
- // 1. Domain/Support/Enums/NotificationType.cs
97
- public enum NotificationType
98
- {
99
- // ... existants ...
100
-
101
- // Ajouter le nouveau type
102
- $NEW_TYPE = XX, // Increment depuis le dernier
103
- }
104
-
105
- // 2. Utilisation dans le service
106
- await _notificationService.SendNotificationAsync(
107
- userId,
108
- NotificationType.$NEW_TYPE,
109
- title,
110
- message,
111
- relatedEntityType: "EntityName",
112
- relatedEntityId: entityId,
113
- actionUrl: "/path/to/entity"
114
- );
115
- ```
116
-
117
- ---
118
-
119
- ## WORKFLOW INTEGRATION
120
-
121
- ### ETAPE 1: Identifier le Besoin
122
-
123
- | Question | Reponse → Action |
124
- |----------|------------------|
125
- | L'utilisateur doit etre notifie en temps reel ? | → In-App + SignalR |
126
- | L'utilisateur doit recevoir un email ? | → Workflow + EmailTemplate |
127
- | L'action est liee a une entite ? | → Ajouter relatedEntityType/Id |
128
- | L'utilisateur peut naviguer vers l'entite ? | → Ajouter actionUrl |
129
-
130
- ### ETAPE 2: Injection du Service
131
-
132
- ```csharp
133
- // Dans le Controller ou Service
134
- public class MyController : ControllerBase
135
- {
136
- private readonly INotificationService _notificationService;
137
- private readonly ILogger<MyController> _logger;
138
-
139
- public MyController(
140
- INotificationService notificationService,
141
- ILogger<MyController> logger)
142
- {
143
- _notificationService = notificationService;
144
- _logger = logger;
145
- }
146
- }
147
- ```
148
-
149
- ### ETAPE 3: Envoi de Notification
150
-
151
- ```csharp
152
- // Notification simple
153
- await _notificationService.SendNotificationAsync(
154
- userId: targetUserId,
155
- type: NotificationType.TicketCreated,
156
- title: "Nouveau ticket",
157
- message: $"Le ticket #{ticket.Number} a ete cree",
158
- cancellationToken: ct
159
- );
160
-
161
- // Notification avec entite liee
162
- await _notificationService.SendNotificationAsync(
163
- userId: ticket.AssignedToId.Value,
164
- type: NotificationType.TicketAssigned,
165
- title: "Ticket assigne",
166
- message: $"Le ticket #{ticket.Number} vous a ete assigne",
167
- relatedEntityType: "Ticket",
168
- relatedEntityId: ticket.Id,
169
- actionUrl: $"/support/tickets/{ticket.Id}",
170
- cancellationToken: ct
171
- );
172
-
173
- // Notification a plusieurs utilisateurs
174
- await _notificationService.SendNotificationsAsync(
175
- userIds: teamUserIds,
176
- type: NotificationType.SystemAnnouncement,
177
- title: "Annonce equipe",
178
- message: "Reunion planifiee demain a 10h",
179
- cancellationToken: ct
180
- );
181
-
182
- // Notification a un role
183
- await _notificationService.SendNotificationToRoleAsync(
184
- roleName: "Admin",
185
- type: NotificationType.SystemAnnouncement,
186
- title: "Alerte systeme",
187
- message: "Maintenance planifiee ce soir",
188
- cancellationToken: ct
189
- );
190
- ```
191
-
192
- ### ETAPE 4: Preferences Utilisateur
193
-
194
- ```csharp
195
- // Verifier si l'utilisateur veut etre notifie
196
- var (shouldEmail, shouldInApp, shouldPush) =
197
- await _notificationService.ShouldNotifyAsync(userId, notificationType);
198
-
199
- if (shouldInApp)
200
- {
201
- await _notificationService.SendNotificationAsync(...);
202
- }
203
-
204
- if (shouldEmail)
205
- {
206
- // Declencher workflow email
207
- await _workflowService.TriggerAsync("ticket.created", new Dictionary<string, object>
208
- {
209
- ["ticketNumber"] = ticket.Number,
210
- ["ticketTitle"] = ticket.Title,
211
- ["userEmail"] = user.Email,
212
- ["userName"] = user.DisplayName
213
- });
214
- }
215
- ```
216
-
217
- ---
218
-
219
- ## FRONTEND INTEGRATION
220
-
221
- ### Hook useSignalR
222
-
223
- ```typescript
224
- // hooks/useSignalR.ts
225
- import { useSignalR } from '@/hooks/useSignalR';
226
-
227
- function MyComponent() {
228
- useSignalR({
229
- onNotification: (notification) => {
230
- // Notification recue en real-time
231
- console.log('New notification:', notification);
232
- toast.info(notification.title);
233
- },
234
- onUnreadCountUpdate: (count) => {
235
- // Mise a jour du badge
236
- setUnreadCount(count);
237
- },
238
- onPermissionsChanged: () => {
239
- // Permissions modifiees - recharger
240
- queryClient.invalidateQueries(['permissions']);
241
- },
242
- });
243
- }
244
- ```
245
-
246
- ### API Notifications
247
-
248
- ```typescript
249
- // services/api/notificationsApi.ts
250
- import { apiClient } from './apiClient';
251
-
252
- export const notificationsApi = {
253
- // Recuperer les non-lues
254
- getUnread: (limit = 10) =>
255
- apiClient.get<NotificationDto[]>(`/notifications/unread?limit=${limit}`),
256
-
257
- // Recuperer avec pagination
258
- getAll: (page = 1, pageSize = 20, isRead?: boolean) =>
259
- apiClient.get<PagedResult<NotificationDto>>('/notifications', {
260
- params: { page, pageSize, isRead }
261
- }),
262
-
263
- // Compter les non-lues
264
- getUnreadCount: () =>
265
- apiClient.get<{ count: number }>('/notifications/unread/count'),
266
-
267
- // Marquer comme lue
268
- markAsRead: (id: string) =>
269
- apiClient.post(`/notifications/${id}/read`),
270
-
271
- // Marquer toutes comme lues
272
- markAllAsRead: () =>
273
- apiClient.post('/notifications/read-all'),
274
-
275
- // Supprimer
276
- delete: (id: string) =>
277
- apiClient.delete(`/notifications/${id}`),
278
-
279
- // Supprimer toutes
280
- deleteAll: () =>
281
- apiClient.delete('/notifications'),
282
- };
283
- ```
284
-
285
- ### Composant NotificationBell
286
-
287
- ```tsx
288
- // components/notifications/NotificationBell.tsx
289
- import { Bell } from 'lucide-react';
290
- import { useSignalR } from '@/hooks/useSignalR';
291
- import { notificationsApi } from '@/services/api/notificationsApi';
292
-
293
- export function NotificationBell() {
294
- const [notifications, setNotifications] = useState<NotificationDto[]>([]);
295
- const [unreadCount, setUnreadCount] = useState(0);
296
- const [isOpen, setIsOpen] = useState(false);
297
-
298
- // Real-time updates
299
- useSignalR({
300
- onNotification: (notification) => {
301
- setNotifications(prev => [notification, ...prev]);
302
- setUnreadCount(prev => prev + 1);
303
- },
304
- onUnreadCountUpdate: (count) => {
305
- setUnreadCount(count);
306
- },
307
- });
308
-
309
- // Load initial data
310
- useEffect(() => {
311
- loadNotifications();
312
- }, []);
313
-
314
- return (
315
- <div className="relative">
316
- <button onClick={() => setIsOpen(!isOpen)} className="relative p-2">
317
- <Bell className="w-5 h-5" />
318
- {unreadCount > 0 && (
319
- <span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
320
- {unreadCount > 99 ? '99+' : unreadCount}
321
- </span>
322
- )}
323
- </button>
324
-
325
- {isOpen && (
326
- <NotificationDropdown
327
- notifications={notifications}
328
- onMarkAsRead={handleMarkAsRead}
329
- onMarkAllAsRead={handleMarkAllAsRead}
330
- onDelete={handleDelete}
331
- />
332
- )}
333
- </div>
334
- );
335
- }
336
- ```
337
-
338
- ---
339
-
340
- ## TEMPLATES
341
-
342
- ### Template Service (Backend)
343
-
344
- ```csharp
345
- // Services/{Module}/{Module}Service.cs
346
-
347
- public async Task<Result> Create{Entity}Async(Create{Entity}Command command, CancellationToken ct)
348
- {
349
- // ... creation de l'entite ...
350
-
351
- // Notification au createur (confirmation)
352
- await _notificationService.SendNotificationAsync(
353
- _currentUser.Id,
354
- NotificationType.{Entity}Created,
355
- "{Entity} creee",
356
- $"Votre {entity.Name} a ete creee avec succes",
357
- relatedEntityType: "{Entity}",
358
- relatedEntityId: entity.Id,
359
- actionUrl: $"/{module}/{entity.Id}",
360
- ct);
361
-
362
- // Notification aux responsables
363
- if (entity.AssignedToId.HasValue)
364
- {
365
- await _notificationService.SendNotificationAsync(
366
- entity.AssignedToId.Value,
367
- NotificationType.{Entity}Assigned,
368
- "{Entity} assignee",
369
- $"La {entity.Name} vous a ete assignee par {_currentUser.DisplayName}",
370
- relatedEntityType: "{Entity}",
371
- relatedEntityId: entity.Id,
372
- actionUrl: $"/{module}/{entity.Id}",
373
- ct);
374
- }
375
-
376
- return Result.Success();
377
- }
378
- ```
379
-
380
- ### Template Controller (Backend)
381
-
382
- ```csharp
383
- // Controllers/{Area}/{Module}Controller.cs
384
-
385
- [HttpPost]
386
- [RequirePermission(Permissions.{Module}.Create)]
387
- [ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
388
- public async Task<ActionResult<{Entity}Dto>> Create(
389
- [FromBody] Create{Entity}Request request,
390
- CancellationToken ct)
391
- {
392
- var entity = await _service.CreateAsync(request, ct);
393
-
394
- // Notification automatique dans le service
395
- // OU notification ici si logique specifique au controller
396
-
397
- _logger.LogInformation(
398
- "User {UserId} created {EntityType} {EntityId}",
399
- _currentUser.Id, nameof({Entity}), entity.Id);
400
-
401
- return CreatedAtAction(
402
- nameof(GetById),
403
- new { id = entity.Id },
404
- entity);
405
- }
406
- ```
407
-
408
- ### Template Hook (Frontend)
409
-
410
- ```typescript
411
- // hooks/use{Module}Notifications.ts
412
-
413
- import { useSignalR } from '@/hooks/useSignalR';
414
- import { useQueryClient } from '@tanstack/react-query';
415
- import { toast } from 'sonner';
416
-
417
- export function use{Module}Notifications() {
418
- const queryClient = useQueryClient();
419
-
420
- useSignalR({
421
- onNotification: (notification) => {
422
- // Filtrer les notifications de ce module
423
- if (notification.relatedEntityType === '{Entity}') {
424
- // Invalider les queries pour refresh
425
- queryClient.invalidateQueries(['{module}']);
426
-
427
- // Toast avec action
428
- toast.info(notification.title, {
429
- description: notification.message,
430
- action: notification.actionUrl ? {
431
- label: 'Voir',
432
- onClick: () => navigate(notification.actionUrl),
433
- } : undefined,
434
- });
435
- }
436
- },
437
- });
438
- }
439
- ```
440
-
441
- ---
442
-
443
- ## CHECKLIST INTEGRATION
444
-
445
- ```
446
- □ Type de notification identifie (existant ou nouveau)
447
- □ Si nouveau type: ajoute a NotificationType enum
448
- □ Service injecte: INotificationService
449
- □ Notification envoyee avec tous les champs:
450
- □ userId (destinataire)
451
- □ type (NotificationType)
452
- □ title (titre court)
453
- □ message (description)
454
- □ relatedEntityType (optionnel)
455
- □ relatedEntityId (optionnel)
456
- □ actionUrl (optionnel)
457
- □ Logging ajoute (LogInformation)
458
- □ Frontend: useSignalR hook configure
459
- □ Frontend: toast ou UI update sur reception
460
- □ Si email requis: workflow configure
461
- ```
462
-
463
- ---
464
-
465
- ## PATTERNS AVANCES
466
-
467
- ### Pattern: Notification avec Delai (SLA)
468
-
469
- ```csharp
470
- // Pour les SLA warnings, utiliser un job Hangfire
471
- public class SlaNotificationJob
472
- {
473
- public async Task CheckSlaBreaches()
474
- {
475
- var tickets = await _context.Tickets
476
- .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30))
477
- .Where(t => !t.SlaWarningNotificationSent)
478
- .ToListAsync();
479
-
480
- foreach (var ticket in tickets)
481
- {
482
- await _notificationService.SendNotificationAsync(
483
- ticket.AssignedToId ?? ticket.CreatedById,
484
- NotificationType.SlaWarning,
485
- "SLA proche expiration",
486
- $"Le ticket #{ticket.Number} expire dans 30 minutes",
487
- relatedEntityType: "Ticket",
488
- relatedEntityId: ticket.Id,
489
- actionUrl: $"/support/tickets/{ticket.Id}");
490
-
491
- ticket.SlaWarningNotificationSent = true;
492
- }
493
-
494
- await _context.SaveChangesAsync();
495
- }
496
- }
497
- ```
498
-
499
- ### Pattern: Notification Groupee
500
-
501
- ```csharp
502
- // Pour eviter le spam, grouper les notifications similaires
503
- public async Task SendBatchNotification(
504
- Guid userId,
505
- NotificationType type,
506
- List<EntitySummary> entities)
507
- {
508
- if (entities.Count == 1)
509
- {
510
- await _notificationService.SendNotificationAsync(
511
- userId, type,
512
- $"Nouveau {entities[0].Type}",
513
- entities[0].Description,
514
- relatedEntityType: entities[0].Type,
515
- relatedEntityId: entities[0].Id);
516
- }
517
- else
518
- {
519
- await _notificationService.SendNotificationAsync(
520
- userId, type,
521
- $"{entities.Count} nouveaux elements",
522
- $"Vous avez {entities.Count} nouveaux {entities[0].Type}s a traiter",
523
- actionUrl: "/dashboard/pending");
524
- }
525
- }
526
- ```
527
-
528
- ---
529
-
530
- ## REGLES ABSOLUES
531
-
532
- 1. **TOUJOURS** utiliser INotificationService (jamais acces DB direct)
533
- 2. **TOUJOURS** utiliser les types NotificationType enum
534
- 3. **TOUJOURS** inclure le context (relatedEntityType/Id) quand applicable
535
- 4. **TOUJOURS** ajouter actionUrl pour navigation
536
- 5. **TOUJOURS** logger les notifications envoyees
537
- 6. **TOUJOURS** respecter les preferences utilisateur
538
- 7. **JAMAIS** envoyer de notification sans destinataire valide
539
- 8. **JAMAIS** hardcoder les messages (utiliser i18n ou templates)
540
- 9. **JAMAIS** spam l'utilisateur (grouper les notifications similaires)
541
- 10. **JAMAIS** oublier le CancellationToken
542
-
543
- ---
544
-
545
- ## FICHIERS CLES
546
-
547
- | Fichier | Role |
548
- |---------|------|
549
- | `Domain/Support/Notification.cs` | Entite notification |
550
- | `Domain/Support/Enums/NotificationType.cs` | Types de notification |
551
- | `Application/Common/Interfaces/INotificationService.cs` | Interface service |
552
- | `Infrastructure/Services/Support/NotificationService.cs` | Implementation |
553
- | `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
554
- | `web/src/hooks/useSignalR.ts` | Hook frontend |
555
- | `web/src/components/notifications/NotificationBell.tsx` | UI Component |
1
+ ---
2
+ name: notification
3
+ description: |
4
+ Integre le systeme de notifications SmartStack dans le developpement.
5
+ Utiliser ce skill quand:
6
+ - L'utilisateur veut envoyer des notifications depuis une feature
7
+ - L'utilisateur mentionne "notifier", "alerte", "notification", "in-app"
8
+ - Creation d'un module qui necessite des notifications utilisateur
9
+ - Integration SignalR pour real-time
10
+ Types: In-App, Email (via Workflow), Push (futur)
11
+ ---
12
+
13
+ # Skill Notification SmartStack
14
+
15
+ > **Architecture:** Notifications = In-App (DB + SignalR) + Email (via Workflows)
16
+ > Les notifications push sont prevues mais pas encore implementees.
17
+
18
+ ## QUAND CE SKILL S'ACTIVE
19
+
20
+ Claude invoque automatiquement ce skill quand il detecte :
21
+
22
+ | Declencheur | Exemple |
23
+ |-------------|---------|
24
+ | Demande explicite | "Notifie l'utilisateur quand un ticket est cree" |
25
+ | Mention notification | "Il faut alerter l'admin si..." |
26
+ | Event-driven | "Quand le SLA expire, prevenir l'utilisateur" |
27
+ | Real-time | "Afficher en temps reel les nouvelles notifications" |
28
+ | Mots-cles | "notifier", "alerte", "notification", "SignalR", "real-time" |
29
+
30
+ ---
31
+
32
+ ## ARCHITECTURE NOTIFICATIONS
33
+
34
+ ```
35
+ ┌─────────────────────────────────────────────────────────────────────────────┐
36
+ │ NOTIFICATION FLOW │
37
+ ├─────────────────────────────────────────────────────────────────────────────┤
38
+ │ │
39
+ │ [EVENT] ─────┬──────────────────────────────────────────────────────────┐ │
40
+ │ │ │ │
41
+ │ ▼ │ │
42
+ │ ┌─────────────────────────┐ │ │
43
+ │ │ INotificationService │ │ │
44
+ │ │ SendNotificationAsync │ │ │
45
+ │ └────────────┬────────────┘ │ │
46
+ │ │ │ │
47
+ │ ┌───────┴───────┐ │ │
48
+ │ ▼ ▼ │ │
49
+ │ ┌─────────┐ ┌───────────────────┐ │ │
50
+ │ │ DB │ │ SignalR Hub │ │ │
51
+ │ │ (store) │ │ (real-time push) │ │ │
52
+ │ └─────────┘ └─────────┬─────────┘ │ │
53
+ │ │ │ │
54
+ │ ▼ │ │
55
+ │ ┌──────────────────┐ │ │
56
+ │ │ Frontend Hook │ │ │
57
+ │ │ useSignalR() │ │ │
58
+ │ └─────────┬────────┘ │ │
59
+ │ │ │ │
60
+ │ ▼ │ │
61
+ │ ┌──────────────────┐ │ │
62
+ │ │ NotificationBell │ │ │
63
+ │ │ (UI Component) │ │ │
64
+ │ └──────────────────┘ │ │
65
+ │ │ │
66
+ │ [EMAIL] ── IWorkflowService.TriggerAsync() ── EmailTemplate ── SMTP │ │
67
+ │ │ │
68
+ └─────────────────────────────────────────────────────────────────────────────┘
69
+ ```
70
+
71
+ ---
72
+
73
+ ## TYPES DE NOTIFICATION
74
+
75
+ ### Enum NotificationType
76
+
77
+ | Type | Declencheur | Usage |
78
+ |------|-------------|-------|
79
+ | `TicketCreated` | Nouveau ticket | Support |
80
+ | `TicketAssigned` | Ticket assigne | Support |
81
+ | `TicketStatusChanged` | Changement statut | Support |
82
+ | `TicketCommentAdded` | Nouveau commentaire | Support |
83
+ | `TicketResolved` | Ticket resolu | Support |
84
+ | `RoleAssigned` | Role attribue | Admin |
85
+ | `RoleRemoved` | Role retire | Admin |
86
+ | `RolePermissionsChanged` | Permissions modifiees | Admin |
87
+ | `SystemAnnouncement` | Annonce systeme | Global |
88
+ | `AccountUpdated` | Compte modifie | User |
89
+ | `SlaWarning` | SLA proche expiration | SLA |
90
+ | `SlaResponseBreached` | SLA reponse depasse | SLA |
91
+ | `SlaResolutionBreached` | SLA resolution depasse | SLA |
92
+
93
+ ### Ajouter un Nouveau Type
94
+
95
+ ```csharp
96
+ // 1. Domain/Support/Enums/NotificationType.cs
97
+ public enum NotificationType
98
+ {
99
+ // ... existants ...
100
+
101
+ // Ajouter le nouveau type
102
+ $NEW_TYPE = XX, // Increment depuis le dernier
103
+ }
104
+
105
+ // 2. Utilisation dans le service
106
+ await _notificationService.SendNotificationAsync(
107
+ userId,
108
+ NotificationType.$NEW_TYPE,
109
+ title,
110
+ message,
111
+ relatedEntityType: "EntityName",
112
+ relatedEntityId: entityId,
113
+ actionUrl: "/path/to/entity"
114
+ );
115
+ ```
116
+
117
+ ---
118
+
119
+ ## WORKFLOW INTEGRATION
120
+
121
+ ### ETAPE 1: Identifier le Besoin
122
+
123
+ | Question | Reponse → Action |
124
+ |----------|------------------|
125
+ | L'utilisateur doit etre notifie en temps reel ? | → In-App + SignalR |
126
+ | L'utilisateur doit recevoir un email ? | → Workflow + EmailTemplate |
127
+ | L'action est liee a une entite ? | → Ajouter relatedEntityType/Id |
128
+ | L'utilisateur peut naviguer vers l'entite ? | → Ajouter actionUrl |
129
+
130
+ ### ETAPE 2: Injection du Service
131
+
132
+ ```csharp
133
+ // Dans le Controller ou Service
134
+ public class MyController : ControllerBase
135
+ {
136
+ private readonly INotificationService _notificationService;
137
+ private readonly ILogger<MyController> _logger;
138
+
139
+ public MyController(
140
+ INotificationService notificationService,
141
+ ILogger<MyController> logger)
142
+ {
143
+ _notificationService = notificationService;
144
+ _logger = logger;
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### ETAPE 3: Envoi de Notification
150
+
151
+ ```csharp
152
+ // Notification simple
153
+ await _notificationService.SendNotificationAsync(
154
+ userId: targetUserId,
155
+ type: NotificationType.TicketCreated,
156
+ title: "Nouveau ticket",
157
+ message: $"Le ticket #{ticket.Number} a ete cree",
158
+ cancellationToken: ct
159
+ );
160
+
161
+ // Notification avec entite liee
162
+ await _notificationService.SendNotificationAsync(
163
+ userId: ticket.AssignedToId.Value,
164
+ type: NotificationType.TicketAssigned,
165
+ title: "Ticket assigne",
166
+ message: $"Le ticket #{ticket.Number} vous a ete assigne",
167
+ relatedEntityType: "Ticket",
168
+ relatedEntityId: ticket.Id,
169
+ actionUrl: $"/support/tickets/{ticket.Id}",
170
+ cancellationToken: ct
171
+ );
172
+
173
+ // Notification a plusieurs utilisateurs
174
+ await _notificationService.SendNotificationsAsync(
175
+ userIds: teamUserIds,
176
+ type: NotificationType.SystemAnnouncement,
177
+ title: "Annonce equipe",
178
+ message: "Reunion planifiee demain a 10h",
179
+ cancellationToken: ct
180
+ );
181
+
182
+ // Notification a un role
183
+ await _notificationService.SendNotificationToRoleAsync(
184
+ roleName: "Admin",
185
+ type: NotificationType.SystemAnnouncement,
186
+ title: "Alerte systeme",
187
+ message: "Maintenance planifiee ce soir",
188
+ cancellationToken: ct
189
+ );
190
+ ```
191
+
192
+ ### ETAPE 4: Preferences Utilisateur
193
+
194
+ ```csharp
195
+ // Verifier si l'utilisateur veut etre notifie
196
+ var (shouldEmail, shouldInApp, shouldPush) =
197
+ await _notificationService.ShouldNotifyAsync(userId, notificationType);
198
+
199
+ if (shouldInApp)
200
+ {
201
+ await _notificationService.SendNotificationAsync(...);
202
+ }
203
+
204
+ if (shouldEmail)
205
+ {
206
+ // Declencher workflow email
207
+ await _workflowService.TriggerAsync("ticket.created", new Dictionary<string, object>
208
+ {
209
+ ["ticketNumber"] = ticket.Number,
210
+ ["ticketTitle"] = ticket.Title,
211
+ ["userEmail"] = user.Email,
212
+ ["userName"] = user.DisplayName
213
+ });
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ## FRONTEND INTEGRATION
220
+
221
+ ### Hook useSignalR
222
+
223
+ ```typescript
224
+ // hooks/useSignalR.ts
225
+ import { useSignalR } from '@/hooks/useSignalR';
226
+
227
+ function MyComponent() {
228
+ useSignalR({
229
+ onNotification: (notification) => {
230
+ // Notification recue en real-time
231
+ console.log('New notification:', notification);
232
+ toast.info(notification.title);
233
+ },
234
+ onUnreadCountUpdate: (count) => {
235
+ // Mise a jour du badge
236
+ setUnreadCount(count);
237
+ },
238
+ onPermissionsChanged: () => {
239
+ // Permissions modifiees - recharger
240
+ queryClient.invalidateQueries(['permissions']);
241
+ },
242
+ });
243
+ }
244
+ ```
245
+
246
+ ### API Notifications
247
+
248
+ ```typescript
249
+ // services/api/notificationsApi.ts
250
+ import { apiClient } from './apiClient';
251
+
252
+ export const notificationsApi = {
253
+ // Recuperer les non-lues
254
+ getUnread: (limit = 10) =>
255
+ apiClient.get<NotificationDto[]>(`/notifications/unread?limit=${limit}`),
256
+
257
+ // Recuperer avec pagination
258
+ getAll: (page = 1, pageSize = 20, isRead?: boolean) =>
259
+ apiClient.get<PagedResult<NotificationDto>>('/notifications', {
260
+ params: { page, pageSize, isRead }
261
+ }),
262
+
263
+ // Compter les non-lues
264
+ getUnreadCount: () =>
265
+ apiClient.get<{ count: number }>('/notifications/unread/count'),
266
+
267
+ // Marquer comme lue
268
+ markAsRead: (id: string) =>
269
+ apiClient.post(`/notifications/${id}/read`),
270
+
271
+ // Marquer toutes comme lues
272
+ markAllAsRead: () =>
273
+ apiClient.post('/notifications/read-all'),
274
+
275
+ // Supprimer
276
+ delete: (id: string) =>
277
+ apiClient.delete(`/notifications/${id}`),
278
+
279
+ // Supprimer toutes
280
+ deleteAll: () =>
281
+ apiClient.delete('/notifications'),
282
+ };
283
+ ```
284
+
285
+ ### Composant NotificationBell
286
+
287
+ ```tsx
288
+ // components/notifications/NotificationBell.tsx
289
+ import { Bell } from 'lucide-react';
290
+ import { useSignalR } from '@/hooks/useSignalR';
291
+ import { notificationsApi } from '@/services/api/notificationsApi';
292
+
293
+ export function NotificationBell() {
294
+ const [notifications, setNotifications] = useState<NotificationDto[]>([]);
295
+ const [unreadCount, setUnreadCount] = useState(0);
296
+ const [isOpen, setIsOpen] = useState(false);
297
+
298
+ // Real-time updates
299
+ useSignalR({
300
+ onNotification: (notification) => {
301
+ setNotifications(prev => [notification, ...prev]);
302
+ setUnreadCount(prev => prev + 1);
303
+ },
304
+ onUnreadCountUpdate: (count) => {
305
+ setUnreadCount(count);
306
+ },
307
+ });
308
+
309
+ // Load initial data
310
+ useEffect(() => {
311
+ loadNotifications();
312
+ }, []);
313
+
314
+ return (
315
+ <div className="relative">
316
+ <button onClick={() => setIsOpen(!isOpen)} className="relative p-2">
317
+ <Bell className="w-5 h-5" />
318
+ {unreadCount > 0 && (
319
+ <span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
320
+ {unreadCount > 99 ? '99+' : unreadCount}
321
+ </span>
322
+ )}
323
+ </button>
324
+
325
+ {isOpen && (
326
+ <NotificationDropdown
327
+ notifications={notifications}
328
+ onMarkAsRead={handleMarkAsRead}
329
+ onMarkAllAsRead={handleMarkAllAsRead}
330
+ onDelete={handleDelete}
331
+ />
332
+ )}
333
+ </div>
334
+ );
335
+ }
336
+ ```
337
+
338
+ ---
339
+
340
+ ## TEMPLATES
341
+
342
+ ### Template Service (Backend)
343
+
344
+ ```csharp
345
+ // Services/{Module}/{Module}Service.cs
346
+
347
+ public async Task<Result> Create{Entity}Async(Create{Entity}Command command, CancellationToken ct)
348
+ {
349
+ // ... creation de l'entite ...
350
+
351
+ // Notification au createur (confirmation)
352
+ await _notificationService.SendNotificationAsync(
353
+ _currentUser.Id,
354
+ NotificationType.{Entity}Created,
355
+ "{Entity} creee",
356
+ $"Votre {entity.Name} a ete creee avec succes",
357
+ relatedEntityType: "{Entity}",
358
+ relatedEntityId: entity.Id,
359
+ actionUrl: $"/{module}/{entity.Id}",
360
+ ct);
361
+
362
+ // Notification aux responsables
363
+ if (entity.AssignedToId.HasValue)
364
+ {
365
+ await _notificationService.SendNotificationAsync(
366
+ entity.AssignedToId.Value,
367
+ NotificationType.{Entity}Assigned,
368
+ "{Entity} assignee",
369
+ $"La {entity.Name} vous a ete assignee par {_currentUser.DisplayName}",
370
+ relatedEntityType: "{Entity}",
371
+ relatedEntityId: entity.Id,
372
+ actionUrl: $"/{module}/{entity.Id}",
373
+ ct);
374
+ }
375
+
376
+ return Result.Success();
377
+ }
378
+ ```
379
+
380
+ ### Template Controller (Backend)
381
+
382
+ ```csharp
383
+ // Controllers/{Area}/{Module}Controller.cs
384
+
385
+ [HttpPost]
386
+ [RequirePermission(Permissions.{Module}.Create)]
387
+ [ProducesResponseType(typeof({Entity}Dto), StatusCodes.Status201Created)]
388
+ public async Task<ActionResult<{Entity}Dto>> Create(
389
+ [FromBody] Create{Entity}Request request,
390
+ CancellationToken ct)
391
+ {
392
+ var entity = await _service.CreateAsync(request, ct);
393
+
394
+ // Notification automatique dans le service
395
+ // OU notification ici si logique specifique au controller
396
+
397
+ _logger.LogInformation(
398
+ "User {UserId} created {EntityType} {EntityId}",
399
+ _currentUser.Id, nameof({Entity}), entity.Id);
400
+
401
+ return CreatedAtAction(
402
+ nameof(GetById),
403
+ new { id = entity.Id },
404
+ entity);
405
+ }
406
+ ```
407
+
408
+ ### Template Hook (Frontend)
409
+
410
+ ```typescript
411
+ // hooks/use{Module}Notifications.ts
412
+
413
+ import { useSignalR } from '@/hooks/useSignalR';
414
+ import { useQueryClient } from '@tanstack/react-query';
415
+ import { toast } from 'sonner';
416
+
417
+ export function use{Module}Notifications() {
418
+ const queryClient = useQueryClient();
419
+
420
+ useSignalR({
421
+ onNotification: (notification) => {
422
+ // Filtrer les notifications de ce module
423
+ if (notification.relatedEntityType === '{Entity}') {
424
+ // Invalider les queries pour refresh
425
+ queryClient.invalidateQueries(['{module}']);
426
+
427
+ // Toast avec action
428
+ toast.info(notification.title, {
429
+ description: notification.message,
430
+ action: notification.actionUrl ? {
431
+ label: 'Voir',
432
+ onClick: () => navigate(notification.actionUrl),
433
+ } : undefined,
434
+ });
435
+ }
436
+ },
437
+ });
438
+ }
439
+ ```
440
+
441
+ ---
442
+
443
+ ## CHECKLIST INTEGRATION
444
+
445
+ ```
446
+ □ Type de notification identifie (existant ou nouveau)
447
+ □ Si nouveau type: ajoute a NotificationType enum
448
+ □ Service injecte: INotificationService
449
+ □ Notification envoyee avec tous les champs:
450
+ □ userId (destinataire)
451
+ □ type (NotificationType)
452
+ □ title (titre court)
453
+ □ message (description)
454
+ □ relatedEntityType (optionnel)
455
+ □ relatedEntityId (optionnel)
456
+ □ actionUrl (optionnel)
457
+ □ Logging ajoute (LogInformation)
458
+ □ Frontend: useSignalR hook configure
459
+ □ Frontend: toast ou UI update sur reception
460
+ □ Si email requis: workflow configure
461
+ ```
462
+
463
+ ---
464
+
465
+ ## PATTERNS AVANCES
466
+
467
+ ### Pattern: Notification avec Delai (SLA)
468
+
469
+ ```csharp
470
+ // Pour les SLA warnings, utiliser un job Hangfire
471
+ public class SlaNotificationJob
472
+ {
473
+ public async Task CheckSlaBreaches()
474
+ {
475
+ var tickets = await _context.Tickets
476
+ .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30))
477
+ .Where(t => !t.SlaWarningNotificationSent)
478
+ .ToListAsync();
479
+
480
+ foreach (var ticket in tickets)
481
+ {
482
+ await _notificationService.SendNotificationAsync(
483
+ ticket.AssignedToId ?? ticket.CreatedById,
484
+ NotificationType.SlaWarning,
485
+ "SLA proche expiration",
486
+ $"Le ticket #{ticket.Number} expire dans 30 minutes",
487
+ relatedEntityType: "Ticket",
488
+ relatedEntityId: ticket.Id,
489
+ actionUrl: $"/support/tickets/{ticket.Id}");
490
+
491
+ ticket.SlaWarningNotificationSent = true;
492
+ }
493
+
494
+ await _context.SaveChangesAsync();
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### Pattern: Notification Groupee
500
+
501
+ ```csharp
502
+ // Pour eviter le spam, grouper les notifications similaires
503
+ public async Task SendBatchNotification(
504
+ Guid userId,
505
+ NotificationType type,
506
+ List<EntitySummary> entities)
507
+ {
508
+ if (entities.Count == 1)
509
+ {
510
+ await _notificationService.SendNotificationAsync(
511
+ userId, type,
512
+ $"Nouveau {entities[0].Type}",
513
+ entities[0].Description,
514
+ relatedEntityType: entities[0].Type,
515
+ relatedEntityId: entities[0].Id);
516
+ }
517
+ else
518
+ {
519
+ await _notificationService.SendNotificationAsync(
520
+ userId, type,
521
+ $"{entities.Count} nouveaux elements",
522
+ $"Vous avez {entities.Count} nouveaux {entities[0].Type}s a traiter",
523
+ actionUrl: "/dashboard/pending");
524
+ }
525
+ }
526
+ ```
527
+
528
+ ---
529
+
530
+ ## REGLES ABSOLUES
531
+
532
+ 1. **TOUJOURS** utiliser INotificationService (jamais acces DB direct)
533
+ 2. **TOUJOURS** utiliser les types NotificationType enum
534
+ 3. **TOUJOURS** inclure le context (relatedEntityType/Id) quand applicable
535
+ 4. **TOUJOURS** ajouter actionUrl pour navigation
536
+ 5. **TOUJOURS** logger les notifications envoyees
537
+ 6. **TOUJOURS** respecter les preferences utilisateur
538
+ 7. **JAMAIS** envoyer de notification sans destinataire valide
539
+ 8. **JAMAIS** hardcoder les messages (utiliser i18n ou templates)
540
+ 9. **JAMAIS** spam l'utilisateur (grouper les notifications similaires)
541
+ 10. **JAMAIS** oublier le CancellationToken
542
+
543
+ ---
544
+
545
+ ## FICHIERS CLES
546
+
547
+ | Fichier | Role |
548
+ |---------|------|
549
+ | `Domain/Support/Notification.cs` | Entite notification |
550
+ | `Domain/Support/Enums/NotificationType.cs` | Types de notification |
551
+ | `Application/Common/Interfaces/INotificationService.cs` | Interface service |
552
+ | `Infrastructure/Services/Support/NotificationService.cs` | Implementation |
553
+ | `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
554
+ | `web/src/hooks/useSignalR.ts` | Hook frontend |
555
+ | `web/src/components/notifications/NotificationBell.tsx` | UI Component |