high priority medium complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

CertificationsExpiringWidget renders a scrollable ListView of mentor rows filtered to certifications expiring within the current calendar month
Each row displays: mentor display name, certification type label, and a relative expiry label (e.g. 'Expires in 5 days', 'Expires today')
Relative expiry label is calculated at render time from the expiry date relative to today's date — not a static string
Certifications expiring in future months or already expired are excluded from the list
When no mentors have expiring certifications this month, a styled empty state widget is shown (not a blank space or hidden widget)
Empty state uses design token typography and secondary text color
List rows use design token spacing and typography — no hardcoded style values
Widget is embeddable as a dashboard card (accepts a bounded height or uses shrinkWrap appropriately)
Widget fetches or receives data via CertificationBLoC — no direct Supabase calls in the widget
Loading state is handled (show shimmer or placeholder rows while data is loading)

Technical Requirements

frameworks
Flutter
BLoC
data models
Certification
PeerMentor
CertificationStatus
performance requirements
List must render up to 50 rows without frame drops (use ListView.builder, not Column)
Date filtering must be performed in the BLoC or a selector, not on every widget rebuild
security requirements
Widget must only display certifications belonging to mentors within the coordinator's own organisation — enforce via BLoC data scoping
ui components
ListView.builder for scrollable mentor rows
Mentor row widget (display name, cert type chip, expiry label)
Empty state widget (design token icon + message)
Shimmer/skeleton placeholder for loading state

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Inject `DateTime now` as a constructor parameter (or via a clock abstraction) rather than calling `DateTime.now()` directly inside the filtering logic — this makes the widget fully testable with fixed dates. Compute the relative label as a simple `expiryDate.difference(now).inDays` comparison and map to a string via a helper (e.g. 0 days → 'Expires today', negative → skip). Filter logic belongs in a BLoC selector or a repository method, not in the build method.

For the empty state, use an `AnimatedSwitcher` to smoothly transition between the loading, empty, and populated states. Ensure `shrinkWrap: true` is only used when the widget is inside a non-scrollable parent — otherwise use a constrained SizedBox to bound the widget height inside dashboard cards.

Testing Requirements

Widget tests: (1) given a list of certifications with mixed expiry months, only current-month entries are rendered; (2) given an empty filtered list, empty state widget is visible; (3) relative expiry label shows correct day count for a known date delta; (4) loading BLoC state shows placeholder rows. Use `clock` package or dependency-inject `DateTime.now()` to make date filtering deterministic in tests. Test with 0, 1, and 50 items.

Epic Risks (3)
high impact medium prob technical

Flutter date pickers have historically poor screen reader support (VoiceOver/TalkBack), which is especially critical for this feature given that HLF peer mentors may have hearing impairment and the broader user base includes people with visual impairments. An inaccessible date picker on RecordRenewalScreen could block coordinator workflows entirely.

Mitigation & Contingency

Mitigation: Evaluate and adopt a third-party accessible date picker widget with verified WCAG 2.2 AA support, or build a custom picker using Flutter Semantics wrappers following the pattern established by the accessibility epic. Test all date pickers against VoiceOver on iOS and TalkBack on Android before UI sign-off.

Contingency: If no accessible date picker is available in time, provide a manual text field fallback for date entry (ISO format with clear labelling) alongside the picker, ensuring keyboard and screen reader users are never blocked.

medium impact high prob scope

Course enrolment initiation may redirect the user to the external HLF course portal (deep link or browser), which breaks the in-app flow and may confuse users expecting a seamless experience. The course data structure from Dynamics may also not be available in a machine-readable format in time for the initial release.

Mitigation & Contingency

Mitigation: Agree with HLF on whether enrolment is in-app or via deep link before UI design begins. If course data is not available from Dynamics at launch, design the enrolment prompt as a placeholder CTA that links to the HLF course portal homepage with a clear label indicating the user is leaving the app.

Contingency: Ship the course enrolment prompt as a configurable deep link per org. If Dynamics integration is delayed, the feature flag for the enrolment section can be disabled without affecting the rest of the certification status screen.

medium impact medium prob scope

Coordinators may attempt to record a renewal with an expiry date earlier than the previous certification's expiry (e.g., data entry error), or attempt to back-date a renewal. Without strict validation, the renewal history timeline could become chronologically inconsistent and mislead peer mentors about their coverage.

Mitigation & Contingency

Mitigation: CertificationManagementService validates that new expiry_date > current date and new issue_date >= previous renewal's issue_date before persisting. Surface validation errors as plain-language messages on RecordRenewalScreen using the error_message_registry pattern.

Contingency: If invalid renewal entries are discovered in production (from pre-validation data), provide a coordinator-only correction flow (edit renewal entry) behind an admin feature flag to fix historical records without requiring a full reset.