medium priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

CertificationExpiryBadge is rendered inside the peer mentor card widget when the mentor's certification is expiring soon or expired
Badge is NOT rendered when the mentor's certification is valid and not near expiry — the card layout must be identical to its current state when badge is absent
The condition for showing the badge (e.g. expiry within N days, or already expired) matches the definition used in CertificationsExpiringWidget and CertificationStatusScreen
Badge integrates without breaking the card's layout on screen widths from 320dp to 428dp
Badge does not truncate the mentor's display name or other card content on small screens — use Flexible/Expanded layout
Badge receives certification data either from CertificationBLoC (if card has BLoC access) or as a passed-in nullable parameter — no direct Supabase query inside the card widget
If certification data has not yet loaded, badge is hidden (not shown as an error state)
Badge meets WCAG 2.2 AA contrast ratio for its text and background colors
Existing peer mentor card widget tests continue to pass after this integration (no regression)

Technical Requirements

frameworks
Flutter
BLoC
data models
PeerMentor
Certification
CertificationStatus
performance requirements
Badge conditional check must be O(1) — do not iterate over all certifications inside the card build method
security requirements
Certification status data passed to card must be scoped to the authenticated user's organisation — do not display cross-org certification data
ui components
CertificationExpiryBadge (from foundation epic)
Row/Stack layout adjustment in PeerMentorCardWidget to accommodate badge
Flexible or Expanded wrapper around mentor name text to prevent overflow

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Accept an optional `CertificationStatus? certificationStatus` parameter on PeerMentorCardWidget rather than having the card fetch its own data — this keeps the card a pure presentational widget and avoids coupling it to BLoC. The parent (list screen or BLoC Consumer) is responsible for looking up the certification status per mentor ID and passing it in. Inside the card, use `if (certificationStatus != null && certificationStatus.isExpiringSoon) CertificationExpiryBadge(status: certificationStatus)` — keep the condition in one place.

For layout, place the badge in a Row alongside the mentor name, wrapping the name in `Flexible(child: Text(..., overflow: TextOverflow.ellipsis))` and the badge in a non-flexible slot so name truncates before badge overflows. Ensure the badge's color tokens are correct for dark backgrounds if the card uses a coloured background.

Testing Requirements

Widget tests: (1) pass certification data with expiring-soon status → badge visible; (2) pass valid certification data → badge hidden; (3) pass null certification data → badge hidden, no error thrown; (4) render card at 320dp width → no RenderOverflow error and badge visible. Run existing PeerMentorCardWidget tests to confirm no regression. Snapshot/golden test the card in both badge-present and badge-absent states if golden tests exist in the project.

Component
Certification Status Screen
ui medium
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.