high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

NotificationDetailView is a screen widget (full-page route) that accepts a notificationId String parameter and reads ExpiryNotificationRecord from the BLoC loaded state
The peer mentor layout displays: certificate name (heading), expiry date formatted as a localized date string (e.g., 'DD.MM.YYYY'), and ExpiryStatusIndicator in full (non-compact) mode
A 'View certification status' AppButton is present and navigates to the certification status screen using the project's router (GoRouter or Navigator) when tapped
Role gating is implemented: if the authenticated user has the coordinator role, the peer mentor layout is NOT rendered — instead the coordinator layout (task-010) is rendered or a fallback route is used
Role detection uses the project's existing role-resolution service, not hardcoded checks
Loading state from BLoC shows a CircularProgressIndicator (or project loading widget) instead of content
Error state from BLoC shows an error message widget with a retry action
Empty state from BLoC shows an appropriate empty-state message
All text elements meet WCAG 2.2 AA contrast requirements against the screen background
Semantics annotations are present on the ExpiryStatusIndicator (full mode) and the navigation button so screen readers can announce them correctly
Screen uses the project's standard page header widget and back navigation (back button, not swipe-only)

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc — BlocBuilder/BlocConsumer)
GoRouter (or project router)
apis
Role-resolution service (existing)
Navigation route to certification status screen
data models
ExpiryNotificationRecord
CertificateExpiryNotificationLoaded
CertificateExpiryNotificationLoading
CertificateExpiryNotificationError
performance requirements
Screen must be interactive within 300 ms of navigation (no heavy computation on first build)
BlocBuilder buildWhen should filter to only rebuild when the relevant notificationId record changes
security requirements
Role gating must be enforced at this layer even though Supabase RLS also enforces it server-side — defense in depth
Coordinator-only UI elements must be completely absent from the widget tree for peer mentors, not just hidden
ui components
ExpiryStatusIndicator (full mode)
AppButton
Project page header widget
Loading widget (project standard)
Error widget (project standard)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Structure the screen as a composition of: NotificationDetailView (route shell with BlocBuilder) → _PeerMentorDetailLayout (stateless child) and _CoordinatorDetailLayout (stateless child from task-010). The parent screen selects the correct layout based on role before building. This keeps each layout independently testable. For the expiry date formatting, use the intl package (already likely in pubspec for Flutter) with DateFormat('dd.MM.yyyy') or equivalent.

The 'View certification status' route name/path must match the router configuration — do not hardcode path strings; use a route constants file. Confirm with the team whether the certification status screen already exists or is a future task, and use a placeholder route if needed.

Testing Requirements

Widget tests using flutter_test. Cover: (1) peer mentor role renders certificate name, expiry date, and full-mode ExpiryStatusIndicator, (2) coordinator role does NOT render the peer mentor layout (assert widget absent from tree), (3) tapping 'View certification status' triggers the correct navigation action (use NavigatorObserver or GoRouter override), (4) Loading BLoC state shows loading indicator, (5) Error BLoC state shows error message and retry button, (6) Semantics check on navigation button and status indicator. Also write one integration-level test (using flutter_test with a faked router) to verify the full navigation flow from NotificationDetailView to the certification status screen.

Epic Risks (2)
medium impact medium prob technical

The persistent banner must remain visible across app sessions and only disappear when a specific backend condition is met (renewal or coordinator acknowledgement). If the BLoC state is not properly sourced from the notification record repository on every app launch, the banner may disappear prematurely or fail to reappear after a session restart.

Mitigation & Contingency

Mitigation: Drive the banner's visibility exclusively from a Supabase real-time subscription on the notification records table filtered by mentor_id and acknowledged_at IS NULL. Never persist banner visibility state locally. Write an integration test that restarts the BLoC and verifies the banner reappears from the database source.

Contingency: If real-time subscriptions introduce latency or connection reliability issues in offline-first scenarios, add a local cache flag that is only cleared when the repository confirms the acknowledgement write succeeded, with a cache TTL of 24 hours as a fallback.

high impact low prob security

The notification detail view must conditionally render coordinator-specific actions based on the authenticated user's role. Incorrect role resolution could expose the 'Acknowledge Lapse' action to peer mentors or hide it from coordinators, breaking the workflow and potentially allowing unauthorised state changes.

Mitigation & Contingency

Mitigation: Source the role check from the existing role_state_manager BLoC that is already authenticated against Supabase role claims. Do not rely on a local flag. The coordinator acknowledgement service backend also validates role server-side, providing defence in depth. Add widget tests that render the detail view with mentor and coordinator role fixtures and assert the presence or absence of coordinator actions.

Contingency: If a role resolution bug is found in production, immediately disable the acknowledge action via a feature flag and patch the role check in a hotfix release. The server-side validation in the coordinator acknowledgement service ensures no actual state change can occur even if the button is incorrectly rendered.