libra/docs/stories/story-4.2-timeline-updates-...

7.1 KiB

Story 4.2: Timeline Updates Management

Epic Reference

Epic 4: Case Timeline System

User Story

As an admin, I want to add and edit updates within a timeline, So that I can keep clients informed about their case progress.

Story Context

Existing System Integration

  • Integrates with: timeline_updates table, timelines table
  • Technology: Livewire Volt, rich text editor (Trix recommended)
  • Follows pattern: Nested CRUD pattern
  • Touch points: Client notifications, timeline view

Previous Story Context (Story 4.1)

Story 4.1 established:

  • Timeline model with fields: user_id, case_name, case_reference, status
  • updates() hasMany relationship on Timeline model
  • timelines table with foreign key to users
  • Admin can create timelines for any client
  • Initial notes saved as first timeline update

Prerequisites

  • Timeline model with updates() relationship (from Story 4.1)
  • TimelineUpdate model (create in this story)
  • AdminLog model for audit logging (from Epic 1)
  • HTML sanitization: use mews/purifier package with clean() helper
  • Rich text editor: Trix (ships with Laravel) or similar

Implementation Files

  • Volt Component: resources/views/livewire/pages/admin/timelines/updates.blade.php
  • Model: app/Models/TimelineUpdate.php
  • Notification: app/Notifications/TimelineUpdateNotification.php
  • Route: admin.timelines.show (timeline detail page with updates section)

Acceptance Criteria

Add Update

  • Add new update to timeline
  • Update text content (required)
  • Rich text formatting supported:
    • Bold, italic, underline
    • Bullet/numbered lists
    • Links
  • Timestamp automatically recorded
  • Admin name automatically recorded
  • Client notified via email on new update

Edit Update

  • Edit existing update text
  • Edit history preserved (updated_at changes)
  • Cannot change timestamp or admin

Display

  • Updates displayed in chronological order
  • Each update shows:
    • Date/timestamp
    • Admin name
    • Update content
  • Visual timeline representation

Quality Requirements

  • HTML sanitization using mews/purifier package
  • Audit log for add/edit operations via AdminLog
  • Feature tests for all operations

Test Scenarios

  • Can add update with valid text (min 10 chars)
  • Cannot add update with empty text - validation error
  • Cannot add update with text < 10 chars - validation error
  • HTML is sanitized (script tags, XSS vectors removed)
  • Client receives notification email on new update
  • Audit log created when update is added
  • Audit log created when update is edited (includes old/new values)
  • Updates display in chronological order (oldest first)
  • Admin name and timestamp automatically recorded
  • Edit preserves original created_at, updates updated_at
  • Cannot change timestamp or admin on edit

Technical Notes

Database Schema

Schema::create('timeline_updates', function (Blueprint $table) {
    $table->id();
    $table->foreignId('timeline_id')->constrained()->cascadeOnDelete();
    $table->foreignId('admin_id')->constrained('users');
    $table->text('update_text');
    $table->timestamps();
});

TimelineUpdate Model

// app/Models/TimelineUpdate.php
class TimelineUpdate extends Model
{
    protected $fillable = ['timeline_id', 'admin_id', 'update_text'];

    public function timeline(): BelongsTo
    {
        return $this->belongsTo(Timeline::class);
    }

    public function admin(): BelongsTo
    {
        return $this->belongsTo(User::class, 'admin_id');
    }
}

Timeline Model Relationship (add to existing model)

// In app/Models/Timeline.php - add this relationship
public function updates(): HasMany
{
    return $this->hasMany(TimelineUpdate::class)->orderBy('created_at', 'asc');
}

Setup Requirements

# Install HTML Purifier for sanitization
composer require mews/purifier

# Publish config (optional, defaults are secure)
php artisan vendor:publish --provider="Mews\Purifier\PurifierServiceProvider"

Volt Component

<?php

use App\Models\{Timeline, TimelineUpdate, AdminLog};
use App\Notifications\TimelineUpdateNotification;
use Livewire\Volt\Component;

new class extends Component {
    public Timeline $timeline;
    public string $updateText = '';
    public ?TimelineUpdate $editingUpdate = null;

    public function addUpdate(): void
    {
        $this->validate([
            'updateText' => ['required', 'string', 'min:10'],
        ]);

        // clean() is from mews/purifier - sanitizes HTML, removes XSS vectors
        $update = $this->timeline->updates()->create([
            'admin_id' => auth()->id(),
            'update_text' => clean($this->updateText),
        ]);

        // Notify client (queued - works when Epic 8 email is ready)
        $this->timeline->user->notify(new TimelineUpdateNotification($update));

        AdminLog::create([
            'admin_id' => auth()->id(),
            'action_type' => 'create',
            'target_type' => 'timeline_update',
            'target_id' => $update->id,
            'ip_address' => request()->ip(),
        ]);

        $this->updateText = '';
        session()->flash('success', __('messages.update_added'));
    }

    public function editUpdate(TimelineUpdate $update): void
    {
        $this->editingUpdate = $update;
        $this->updateText = $update->update_text;
    }

    public function saveEdit(): void
    {
        $this->validate([
            'updateText' => ['required', 'string', 'min:10'],
        ]);

        $oldText = $this->editingUpdate->update_text;

        $this->editingUpdate->update([
            'update_text' => clean($this->updateText),
        ]);

        AdminLog::create([
            'admin_id' => auth()->id(),
            'action_type' => 'update',
            'target_type' => 'timeline_update',
            'target_id' => $this->editingUpdate->id,
            'old_values' => ['update_text' => $oldText],
            'new_values' => ['update_text' => $this->updateText],
            'ip_address' => request()->ip(),
        ]);

        $this->editingUpdate = null;
        $this->updateText = '';
        session()->flash('success', __('messages.update_edited'));
    }

    public function cancelEdit(): void
    {
        $this->editingUpdate = null;
        $this->updateText = '';
    }
};

Definition of Done

  • Can add new updates with rich text
  • Can edit existing updates
  • Updates display chronologically
  • Admin name and timestamp shown
  • Client notification sent
  • HTML properly sanitized
  • Audit log created
  • Tests pass
  • Code formatted with Pint

Dependencies

  • Story 4.1: Timeline creation (REQUIRED - must be complete)
  • Epic 8: Email notifications (SOFT DEPENDENCY)
    • If Epic 8 is not complete, implement notification dispatch but skip email tests
    • Create TimelineUpdateNotification class with queued email
    • Notification will work once Epic 8 email infrastructure is ready

Estimation

Complexity: Medium Estimated Effort: 3-4 hours