# Story 4.6: Timeline Update Notifications ## Epic Reference **Epic 4:** Case Timeline System ## User Story As a **client**, I want **to receive email notifications when my timeline is updated**, So that **I stay informed about my case progress without checking the portal**. ## Story Context ### Existing System Integration - **Integrates with:** timeline_updates creation, email system - **Technology:** Laravel Notifications, queued emails - **Follows pattern:** Event-driven notification pattern - **Touch points:** Timeline update creation ### Previous Story Context (Story 4.2) Story 4.2 established: - `TimelineUpdate` model with fields: `timeline_id`, `admin_id`, `update_text` - `TimelineUpdate` belongs to `Timeline` and `User` (admin) - `Timeline` has many `TimelineUpdate` records - Updates created via `addUpdate()` method in timeline management Volt component - Notification trigger point: after `$this->timeline->updates()->create([...])` ### Prerequisites The following must exist before implementing this story: - **User model requirements:** - `preferred_language` field (string, defaults to 'ar') - from Epic 2 user registration - `isActive()` method returning boolean (checks `status !== 'deactivated'`) - from Epic 2 - **Route requirement:** - `client.timelines.show` route must exist (from Story 4.5) - **Models available:** - `Timeline` model with `user()` relationship - `TimelineUpdate` model with `timeline()` relationship ### Implementation Files - **Notification:** `app/Notifications/TimelineUpdateNotification.php` - **Arabic Template:** `resources/views/emails/timeline/update/ar.blade.php` - **English Template:** `resources/views/emails/timeline/update/en.blade.php` - **Modify:** Story 4.2 Volt component to trigger notification ## Acceptance Criteria ### Notification Trigger - [ ] Email sent when new update added to timeline - [ ] Triggered automatically on TimelineUpdate creation - [ ] Queued for performance ### Email Content - [ ] Case name and reference - [ ] Update summary or full text - [ ] Date of update - [ ] Link to view timeline - [ ] Libra branding ### Language Support - [ ] Email in client's preferred language - [ ] Arabic template - [ ] English template ### Exclusions - [ ] No email for archived timeline reactivation - [ ] No email if client deactivated ### Quality Requirements - [ ] Professional email template - [ ] Tests for notification sending - [ ] Error handling for failed sends ## Technical Notes ### Notification Class ```php preferred_language ?? 'ar'; $timeline = $this->update->timeline; return (new MailMessage) ->subject($this->getSubject($locale, $timeline->case_name)) ->markdown('emails.timeline.update.' . $locale, [ 'update' => $this->update, 'timeline' => $timeline, 'user' => $notifiable, ]); } private function getSubject(string $locale, string $caseName): string { return $locale === 'ar' ? "تحديث جديد على قضيتك: {$caseName}" : "New update on your case: {$caseName}"; } } ``` ### Email Templates #### Arabic ```blade # تحديث جديد على قضيتك عزيزي {{ $user->name }}، تم إضافة تحديث جديد على قضيتك: **القضية:** {{ $timeline->case_name }} @if($timeline->case_reference) **الرقم المرجعي:** {{ $timeline->case_reference }} @endif **تاريخ التحديث:** {{ $update->created_at->translatedFormat('d M Y - g:i A') }} --- {!! $update->update_text !!} --- عرض التفاصيل الكاملة مع أطيب التحيات، مكتب ليبرا للمحاماة ``` #### English ```blade # New Update on Your Case Dear {{ $user->name }}, A new update has been added to your case: **Case:** {{ $timeline->case_name }} @if($timeline->case_reference) **Reference:** {{ $timeline->case_reference }} @endif **Update Date:** {{ $update->created_at->format('M d, Y - g:i A') }} --- {!! $update->update_text !!} --- View Full Details Best regards, Libra Law Firm ``` ### Trigger in Update Creation ```php // In Story 4.2 addUpdate method $update = $this->timeline->updates()->create([...]); // Check if user is active before notifying if ($this->timeline->user->isActive()) { $this->timeline->user->notify(new TimelineUpdateNotification($update)); } ``` ### Test Scenarios - [ ] Notification sent when timeline update created - [ ] No notification to deactivated user (`status === 'deactivated'`) - [ ] Arabic template renders with correct content (case name, update text, date) - [ ] English template renders with correct content - [ ] Uses client's `preferred_language` for template selection (defaults to 'ar') - [ ] Email is queued (uses `ShouldQueue` interface) - [ ] Email contains valid link to timeline view - [ ] Subject line includes case name in correct language ### Testing Examples ```php use App\Models\{User, Timeline, TimelineUpdate}; use App\Notifications\TimelineUpdateNotification; use Illuminate\Support\Facades\Notification; it('sends notification when timeline update created', function () { Notification::fake(); $user = User::factory()->create(['status' => 'active']); $timeline = Timeline::factory()->for($user)->create(); $update = TimelineUpdate::factory()->create(['timeline_id' => $timeline->id]); // Simulate the trigger from Story 4.2 if ($timeline->user->isActive()) { $timeline->user->notify(new TimelineUpdateNotification($update)); } Notification::assertSentTo($timeline->user, TimelineUpdateNotification::class); }); it('does not send notification to deactivated user', function () { Notification::fake(); $user = User::factory()->create(['status' => 'deactivated']); $timeline = Timeline::factory()->for($user)->create(); $update = TimelineUpdate::factory()->create(['timeline_id' => $timeline->id]); // Simulate the trigger from Story 4.2 - should NOT notify if ($timeline->user->isActive()) { $timeline->user->notify(new TimelineUpdateNotification($update)); } Notification::assertNotSentTo($user, TimelineUpdateNotification::class); }); it('uses arabic template for arabic-preferred user', function () { Notification::fake(); $user = User::factory()->create(['preferred_language' => 'ar']); $timeline = Timeline::factory()->for($user)->create(['case_name' => 'Test Case']); $update = TimelineUpdate::factory()->create(['timeline_id' => $timeline->id]); $user->notify(new TimelineUpdateNotification($update)); Notification::assertSentTo($user, TimelineUpdateNotification::class, function ($notification, $channels, $notifiable) { $mail = $notification->toMail($notifiable); return str_contains($mail->subject, 'تحديث جديد على قضيتك'); }); }); it('uses english template for english-preferred user', function () { Notification::fake(); $user = User::factory()->create(['preferred_language' => 'en']); $timeline = Timeline::factory()->for($user)->create(['case_name' => 'Test Case']); $update = TimelineUpdate::factory()->create(['timeline_id' => $timeline->id]); $user->notify(new TimelineUpdateNotification($update)); Notification::assertSentTo($user, TimelineUpdateNotification::class, function ($notification, $channels, $notifiable) { $mail = $notification->toMail($notifiable); return str_contains($mail->subject, 'New update on your case'); }); }); ``` ## Definition of Done - [x] Email sent on new update - [x] Arabic template works - [x] English template works - [x] Uses client's preferred language - [x] Link to timeline works - [x] Queued for performance - [x] No email to deactivated users - [x] Tests pass - [x] Code formatted with Pint ## Dependencies - **Story 4.2:** Timeline updates management (REQUIRED - notification triggered from addUpdate method) - **Story 4.5:** Client timeline view (REQUIRED - provides `client.timelines.show` route used in email link) - **Epic 2:** User model with `preferred_language` field and `isActive()` method - **Epic 8:** Email infrastructure (SOFT DEPENDENCY - notification will queue but email delivery requires Epic 8) ## Estimation **Complexity:** Low-Medium **Estimated Effort:** 2-3 hours --- ## Dev Agent Record ### Status **Ready for Review** ### Agent Model Used Claude Opus 4.5 ### File List | File | Action | Description | |------|--------|-------------| | `app/Notifications/TimelineUpdateNotification.php` | Modified | Updated notification to use `markdown()` method, renamed property to `update`, added case name to subject line | | `resources/views/emails/timeline/update/ar.blade.php` | Created | Arabic email template with case details, update text, and timeline link | | `resources/views/emails/timeline/update/en.blade.php` | Created | English email template with case details, update text, and timeline link | | `resources/views/livewire/admin/timelines/show.blade.php` | Modified | Added `isActive()` check before sending notification to prevent emails to deactivated users | | `tests/Feature/Admin/TimelineUpdatesManagementTest.php` | Modified | Added 8 new tests for notification behavior (deactivated users, language templates, subject lines, queueing) | ### Change Log - Updated `TimelineUpdateNotification` class to match story specifications: - Changed property name from `$timelineUpdate` to `$update` - Updated `toMail()` to use `markdown()` method instead of `view()` - Added case name to email subject line for both languages - Template path changed from `emails.timeline-update` to `emails.timeline.update.{locale}` - Created separate Arabic and English markdown email templates at `resources/views/emails/timeline/update/` - Added `isActive()` check in `addUpdate()` method to prevent notifications to deactivated users - Added comprehensive tests covering: - Deactivated user exclusion - Arabic subject with case name - English subject with case name - Default language behavior - Queue implementation verification - Correct markdown template selection ### Completion Notes 1. The notification class already existed and implemented `ShouldQueue`, so emails are automatically queued 2. The notification trigger already existed in the Volt component but lacked the `isActive()` check - this was added 3. Used `` component syntax in templates as specified in story rather than `@component` syntax used elsewhere in codebase - both work with Laravel's mail markdown system 4. All 37 tests in `TimelineUpdatesManagementTest.php` pass, including 8 new notification-specific tests 5. Full test suite passes with 364 tests and 944 assertions ### DOD Checklist 1. **Requirements Met:** - [x] All functional requirements specified in the story are implemented - [x] All acceptance criteria defined in the story are met 2. **Coding Standards & Project Structure:** - [x] Code adheres to Operational Guidelines (class-based Volt, Flux UI patterns) - [x] Code aligns with Project Structure (notification in `app/Notifications/`, templates in `resources/views/emails/`) - [x] Tech Stack followed (Laravel Notifications, Livewire, Pest) - [x] Security best practices applied (HTML sanitization via `clean()` already in place) - [x] No new linter errors (Pint passed) - [N/A] Code comments - logic is straightforward and self-documenting 3. **Testing:** - [x] Unit tests implemented (8 new notification tests) - [x] Integration tests via Volt component tests - [x] All 364 tests pass - [x] Test coverage adequate for notification functionality 4. **Functionality & Verification:** - [x] Tests verify notification sending behavior - [x] Edge cases handled (deactivated users, null language defaults to 'ar') 5. **Story Administration:** - [x] All tasks marked complete - [x] Changes documented in this section - [x] Story wrap up complete 6. **Dependencies, Build & Configuration:** - [x] Project builds successfully - [x] Linting passes (Pint) - [N/A] No new dependencies added - [N/A] No new environment variables 7. **Documentation:** - [N/A] No new public APIs requiring JSDoc - [N/A] No user-facing documentation changes needed - [N/A] No architectural changes ## QA Results ### Review Date: 2025-12-27 ### Reviewed By: Quinn (Test Architect) ### Code Quality Assessment The implementation is clean, well-structured, and follows Laravel best practices. The notification class properly implements `ShouldQueue` for performance, uses constructor property promotion, and has clear separation of concerns. Email templates are properly organized by locale and use Laravel's markdown mail component syntax correctly. ### Refactoring Performed None required - code quality is high and follows project conventions. ### Compliance Check - Coding Standards: ✓ Code follows Laravel conventions, Pint passes - Project Structure: ✓ Files in correct locations (Notifications, email views) - Testing Strategy: ✓ Comprehensive Pest tests with proper assertions - All ACs Met: ✓ All 11 acceptance criteria verified ### Improvements Checklist All items already addressed by development: - [x] Notification class implements ShouldQueue for queued delivery - [x] isActive() check prevents notifications to deactivated users - [x] Arabic and English templates properly render with case details - [x] Subject line includes case name in appropriate language - [x] HTML sanitization via clean() protects against XSS in update text - [x] Email templates handle both individual (full_name) and company (company_name) clients ### Security Review **Status: PASS** - HTML sanitization using `clean()` function prevents XSS in update_text - No sensitive data exposed in email beyond necessary case information - Proper authorization through admin middleware on update creation endpoint ### Performance Considerations **Status: PASS** - Notification implements `ShouldQueue` for async delivery - No N+1 queries - timeline relationship is eagerly loaded - Email rendering happens in queue worker, not blocking HTTP request ### Files Modified During Review None - no modifications were necessary. ### Gate Status Gate: PASS → docs/qa/gates/4.6-timeline-update-notifications.yml ### Recommended Status ✓ Ready for Done - All acceptance criteria met, tests pass, code quality is excellent.