high priority low complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

ExpiryNotificationBanner renders a visually distinct expired state when record.status == ExpiryNotificationStatus.expired and record.isAcknowledged == false
Expired state uses the design token color for error/danger (e.g., red/rose token) — distinct from the expiring-soon warning/amber token — with no hardcoded hex values
Label text changes from 'X days remaining' to 'Certificate expired' (or localized equivalent) in the expired state
CTA button copy changes from the renewal copy used in expiring-soon to a distinct expired-state copy (e.g., 'View options' or as specified in design)
Banner in expired state remains visible until record.isAcknowledged becomes true in the BLoC loaded state
The expiring-soon state rendering from task-006 is preserved without regression; both states are handled within the same ExpiryNotificationBanner widget via conditional rendering
All text and background color combinations in the expired state pass WCAG 2.2 AA contrast ratio (4.5:1 minimum)
Semantics labels are updated to reflect the expired state so screen readers announce the correct urgency
The widget does not contain any coordinator-specific controls (acknowledge button belongs in NotificationDetailView)

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc — BlocBuilder)
data models
ExpiryNotificationRecord
CertificateExpiryNotificationLoaded
performance requirements
State differentiation (expiring-soon vs expired) must be resolved in O(1) — a simple enum check on record.status
security requirements
Expired state must not expose AcknowledgeLapse functionality — that is coordinator-only and belongs in NotificationDetailView
ui components
ExpiryStatusIndicator (compact mode)
AppButton
Design token error/danger color values

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Refactor ExpiryNotificationBanner (from task-006) to switch on record.status using Dart's exhaustive pattern matching (switch expression). Each case returns the appropriate child subtree. Extract a private _buildBannerContent(ExpiryNotificationRecord, BannerConfig) method where BannerConfig is a small data class holding backgroundColor, labelText, ctaCopy, and statusIndicatorMode — this avoids duplicating layout code between states. Design tokens for the error state likely already exist (used elsewhere in the app for form validation errors); confirm token names before implementing.

The expiry-soon amber and expired red tokens must be clearly different hues so users with color vision deficiency can also distinguish them — check with design for deuteranopia-safe palette.

Testing Requirements

Widget tests using flutter_test. Cover: (1) expired state renders with 'Certificate expired' label and error color scheme, (2) expired banner remains visible when isAcknowledged=false, (3) expired banner hides when isAcknowledged=true (BLoC state updated), (4) expiring-soon state still renders correctly after this change (regression), (5) CTA button in expired state uses the correct copy and triggers the correct callback, (6) Semantics label reflects expired state. Run all existing task-006 widget tests to confirm no regressions.

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.