Implement ExpiryStatusIndicator widget — compact mode
epic-certificate-expiry-notifications-user-interface-task-002 — Build the reusable ExpiryStatusIndicator Flutter widget in its compact display mode, suitable for embedding in list rows. The widget receives an expiry state enum (active, expiringSoon, expired) and renders a color-coded badge using the design tokens defined in task-001. Include Semantics node with descriptive label for screen reader support.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Define CertificateExpiryStatus as a Dart enum with values: active, expiringSoon, expired. Place it in lib/domain/enums/certificate_expiry_status.dart so it is accessible from both domain and UI layers without coupling. The widget itself belongs in lib/features/certificate_expiry/widgets/expiry_status_indicator.dart. Use a switch expression on the enum to select the token group — avoid if/else chains.
For the badge shape, use a Container with BoxDecoration(borderRadius: BorderRadius.circular(token), color: tokens.background, border: Border.all(color: tokens.border)). The Semantics label should be driven by a localized string map (or an extension method on the enum) rather than hardcoded English strings, to support future l10n. Compact mode should constrain height to match a standard list tile's leading widget zone (typically 20–24px height) so it embeds cleanly in ListTile without affecting row height.
Testing Requirements
Widget tests in test/widgets/expiry_status_indicator_test.dart. Use flutter_test pumpWidget with MaterialApp wrapper. Test cases: (1) each of the 3 states renders the correct label text, (2) each state applies the correct background color from tokens (use find.byType(Container) + decoration inspection or find.byWidgetPredicate), (3) Semantics node is present with the correct label for each state, (4) widget renders without overflow at textScaleFactor 2.0, (5) widget renders in dark mode ThemeData without assertion errors. Do not test visual pixel output — test widget tree structure and semantics.
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.
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.