medium priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

ClaimStatusAuditTimeline accepts a List<ClaimEvent> and renders one row per event in ascending chronological order (oldest at top)
Each row displays: event type as a human-readable label (e.g., 'Submitted', 'Auto-approved', 'Coordinator approved'), formatted timestamp (date + time), actor name or 'System' for automated events
Rows with non-empty notes field show an expand/collapse toggle; tapping reveals the notes text
Collapsed state shows only the primary row; expanded state appends a notes card below the row without layout shift
Empty state (no events) renders a 'No history yet' message rather than a blank space
Each timeline item has a Semantics node: 'Event: [type], [timestamp], by [actor]' read as a single announcement
Expanded notes are also included in the Semantics tree when visible
Timeline uses a vertical line connector between items using design token colors — no hardcoded hex values
Widget is stateful only for expand/collapse state — data is passed in, not fetched internally
Renders correctly with 1 event, 10 events, and 50 events without performance degradation
All timestamps are displayed in the device's local timezone

Technical Requirements

frameworks
Flutter
flutter_test
intl (for date formatting)
data models
ClaimEvent (id, claim_id, event_type, actor_id, actor_name, notes, created_at)
performance requirements
ListView.builder used for lists > 5 items to avoid rendering off-screen widgets
Expand/collapse animation <= 200ms
security requirements
Notes field content must be HTML-escaped before display to prevent injection if content originates from user input
ui components
ClaimStatusAuditTimeline (container widget)
ClaimEventRow (individual row with expand/collapse)
TimelineConnector (vertical line design token component)
NotesCard (expandable notes display)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Separate the widget into ClaimStatusAuditTimeline (stateful, manages a Set of expanded event IDs) and a stateless ClaimEventRow child. Pass the expansion state and toggle callback down to ClaimEventRow as parameters — this keeps ClaimEventRow independently testable. For the timeline connector line, use a CustomPaint or a simple Container with a border on the left edge — a full CustomPainter is overkill for a straight line. Use AnimatedCrossFade or AnimatedSize for the expand/collapse animation to avoid abrupt layout jumps.

For timestamp formatting, use the intl package's DateFormat.yMMMd().add_jm() pattern with the device locale. Map event_type enum values to human-readable strings via a const Map — do not do string manipulation on enum names. For accessibility, wrap each ClaimEventRow in a MergeSemantics so VoiceOver reads the entire row as one item.

Testing Requirements

Widget tests: render with an empty list and assert 'No history yet' message. Render with 3 events and assert all rows appear in correct order. Tap the expand toggle on a row with notes and assert notes card becomes visible; tap again and assert it collapses. Assert Semantics labels are present on each row.

Golden tests: snapshot collapsed and expanded states. Unit tests: verify timestamp formatting logic for different locales. Test that events with null actor_id display 'System'. Target 90% line coverage for widget files.

Epic Risks (3)
medium impact medium prob technical

Maintaining multi-select state across paginated list pages is architecturally complex in Flutter with Riverpod/BLoC. If the selection state is stored in the widget tree rather than the state layer, page transitions and list redraws can silently clear selections, causing coordinators to lose their multi-select and re-enter it.

Mitigation & Contingency

Mitigation: Store the selected claim ID set in a dedicated Riverpod StateNotifier outside the paginated list widget tree. The paginated list reads selection state from this provider and does not own it. Selection persists independently of list scroll position or page loads.

Contingency: If cross-page selection proves prohibitively complex, limit bulk selection to the currently visible page (add a clear warning in the UI) and prioritise single-page bulk approval for the initial release.

medium impact medium prob integration

If a coordinator has the queue open while another coordinator approves claims from the same queue (possible in large organisations with shared chapter coverage), the Realtime update may arrive out of order or be missed during a reconnect, leaving the first coordinator's view stale and allowing them to attempt to approve an already-actioned claim.

Mitigation & Contingency

Mitigation: The ApprovalWorkflowService's optimistic locking (from the foundation epic) will catch the concurrent edit at the database level. The CoordinatorReviewQueueScreen should handle the resulting ConcurrencyException by removing the claim from the local list and showing a brief snackbar: 'This claim was already actioned by another coordinator.'

Contingency: Add a queue staleness indicator (a subtle 'last updated X seconds ago' label) and a manual refresh button as a fallback for coordinators who notice inconsistencies.

low impact high prob dependency

The end-to-end test requirement that a peer mentor receives a push notification within 30 seconds of coordinator approval depends on FCM delivery latency, which is outside the application's control and can vary significantly in CI/CD environments.

Mitigation & Contingency

Mitigation: Structure end-to-end tests to verify notification intent (correct FCM payload dispatched, correct Realtime event emitted) rather than actual device delivery timing. Use test doubles for FCM delivery in automated tests and reserve real-device delivery tests for manual pre-release validation.

Contingency: If notification timing requirements must be validated in automation, instrument the ApprovalNotificationService with a test hook that records dispatch timestamps and assert against those rather than actual FCM callbacks.