15 KiB
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:
TimelineUpdatemodel with fields:timeline_id,admin_id,update_textTimelineUpdatebelongs toTimelineandUser(admin)Timelinehas manyTimelineUpdaterecords- 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_languagefield (string, defaults to 'ar') - from Epic 2 user registrationisActive()method returning boolean (checksstatus !== 'deactivated') - from Epic 2
- Route requirement:
client.timelines.showroute must exist (from Story 4.5)
- Models available:
Timelinemodel withuser()relationshipTimelineUpdatemodel withtimeline()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
namespace App\Notifications;
use App\Models\TimelineUpdate;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class TimelineUpdateNotification extends Notification
{
use Queueable;
public function __construct(
public TimelineUpdate $update
) {}
public function via(object $notifiable): array
{
return ['mail'];
}
public function toMail(object $notifiable): MailMessage
{
$locale = $notifiable->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
<x-mail::message>
# تحديث جديد على قضيتك
عزيزي {{ $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 !!}
---
<x-mail::button :url="route('client.timelines.show', $timeline)">
عرض التفاصيل الكاملة
</x-mail::button>
مع أطيب التحيات،
مكتب ليبرا للمحاماة
</x-mail::message>
English
<x-mail::message>
# 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 !!}
---
<x-mail::button :url="route('client.timelines.show', $timeline)">
View Full Details
</x-mail::button>
Best regards,
Libra Law Firm
</x-mail::message>
Trigger in Update Creation
// 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_languagefor template selection (defaults to 'ar') - Email is queued (uses
ShouldQueueinterface) - Email contains valid link to timeline view
- Subject line includes case name in correct language
Testing Examples
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
- Email sent on new update
- Arabic template works
- English template works
- Uses client's preferred language
- Link to timeline works
- Queued for performance
- No email to deactivated users
- Tests pass
- 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.showroute used in email link) - Epic 2: User model with
preferred_languagefield andisActive()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
TimelineUpdateNotificationclass to match story specifications:- Changed property name from
$timelineUpdateto$update - Updated
toMail()to usemarkdown()method instead ofview() - Added case name to email subject line for both languages
- Template path changed from
emails.timeline-updatetoemails.timeline.update.{locale}
- Changed property name from
- Created separate Arabic and English markdown email templates at
resources/views/emails/timeline/update/ - Added
isActive()check inaddUpdate()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
- The notification class already existed and implemented
ShouldQueue, so emails are automatically queued - The notification trigger already existed in the Volt component but lacked the
isActive()check - this was added - Used
<x-mail::message>component syntax in templates as specified in story rather than@componentsyntax used elsewhere in codebase - both work with Laravel's mail markdown system - All 37 tests in
TimelineUpdatesManagementTest.phppass, including 8 new notification-specific tests - Full test suite passes with 364 tests and 944 assertions
DOD Checklist
-
Requirements Met:
- All functional requirements specified in the story are implemented
- All acceptance criteria defined in the story are met
-
Coding Standards & Project Structure:
- Code adheres to Operational Guidelines (class-based Volt, Flux UI patterns)
- Code aligns with Project Structure (notification in
app/Notifications/, templates inresources/views/emails/) - Tech Stack followed (Laravel Notifications, Livewire, Pest)
- Security best practices applied (HTML sanitization via
clean()already in place) - No new linter errors (Pint passed)
- [N/A] Code comments - logic is straightforward and self-documenting
-
Testing:
- Unit tests implemented (8 new notification tests)
- Integration tests via Volt component tests
- All 364 tests pass
- Test coverage adequate for notification functionality
-
Functionality & Verification:
- Tests verify notification sending behavior
- Edge cases handled (deactivated users, null language defaults to 'ar')
-
Story Administration:
- All tasks marked complete
- Changes documented in this section
- Story wrap up complete
-
Dependencies, Build & Configuration:
- Project builds successfully
- Linting passes (Pint)
- [N/A] No new dependencies added
- [N/A] No new environment variables
-
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:
- Notification class implements ShouldQueue for queued delivery
- isActive() check prevents notifications to deactivated users
- Arabic and English templates properly render with case details
- Subject line includes case name in appropriate language
- HTML sanitization via clean() protects against XSS in update text
- 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
ShouldQueuefor 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.