high priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Tapping the submit button dispatches a RenewalSubmitted (or equivalent) event to CertificationBLoC with the selected cert type, issue date, and expiry date
While the BLoC is in a submitting state, the submit button shows a loading indicator and is non-interactive
On successful submission, a success banner or toast message is displayed with plain-language confirmation (e.g. 'Certification renewed successfully')
After the success feedback is shown, the screen is popped from the navigation stack
After the screen is popped, CertificationStatusScreen reloads its data via a BLoC reload event (not a full hot restart)
On failure, an inline error banner is shown on RecordRenewalScreen with a plain-language error message — the screen is NOT popped
Network/timeout errors produce a user-friendly error message, not a raw exception string
The submit flow is idempotent — tapping submit twice does not dispatch two events (button is disabled during submission)
All state transitions (idle → submitting → success/failure) are reflected in both UI and BLoC state for testability
Success and failure feedback messages are sourced from the error/message registry

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase REST API (certification update endpoint)
data models
Certification
CertificationRenewalRequest
performance requirements
Submission response (success or error) must be reflected in UI within 500ms of API response receipt
security requirements
Renewal request must include authenticated Supabase session token
Cert ID and mentor ID must be validated before dispatching renewal event — prevent tampering via route arguments
ui components
Loading indicator overlaid on or replacing submit button during submitting state
Success banner (SnackBar or custom top banner) with design token success color
Error banner/inline error widget with design token error color

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use BlocListener (not BlocBuilder) to react to one-time state transitions such as success and failure — BlocBuilder is for rebuilding widgets, BlocListener is for side effects like navigation and toasts. On success, call `Navigator.of(context).pop()` from within the BlocListener callback. To reload CertificationStatusScreen, pass a callback or use a shared BLoC instance that CertificationStatusScreen also listens to — avoid direct widget tree coupling. For the loading state, replace the submit button label with a `CircularProgressIndicator` of the same button dimensions to prevent layout shift.

Handle Supabase errors by mapping error codes to registry message keys in the BLoC, not in the UI layer.

Testing Requirements

Widget tests using bloc_test MockBloc: (1) emit submitting state → submit button shows loader; (2) emit success state → success banner appears, then Navigator.pop is called (use NavigatorObserver mock); (3) emit failure state → error banner shown, screen NOT popped. Integration test: submit form end-to-end against a Supabase staging environment and verify the certification record is updated. Test double-tap protection: rapid second tap does not fire a second event.

Component
Record Renewal Screen
ui low
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.