high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

If new expiry date is in the past (before today), an inline error message is displayed below the expiry date field
If new expiry date is earlier than or equal to the new issue date, an inline error message is displayed below the expiry date field
Error messages are written in plain language (e.g. 'Expiry date must be after the issue date') — no technical jargon
Error messages are sourced from the error message registry (centralised string map), not hardcoded inline
The submit button is disabled (not just visually greyed) while any validation error is present
The submit button re-enables automatically when all validation errors are resolved
Validation runs on date field change (not only on submit attempt) to give immediate feedback
Error messages are announced by screen readers (Semantics live region or equivalent)
No error messages are shown on initial screen load before the user has interacted with a date field
Validation logic is pure/unit-testable — extracted into a standalone validator function or BLoC event handler

Technical Requirements

frameworks
Flutter
BLoC
data models
CertificationRenewalFormState
performance requirements
Validation evaluation must be synchronous — no async operations that delay error display
security requirements
Validation must be enforced server-side as well; client-side validation is UX only
ui components
Inline error text widget below date fields (design token error color)
AppButton in disabled state (design token disabled style)
Semantics live region or ExcludeSemantics wrapper for error text visibility

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Extract validation into a pure function `CertificationDateValidator.validate(issueDate, expiryDate) → String?` in a validators file for testability. Call this function from within the BLoC when handling date change events, storing the error string in BLoC state rather than local widget state. In the UI, read the error string from BLoC state and pass it to the field's errorText parameter (or a custom error row widget). Use `DateTime.now()` comparison with date-only precision (strip time component using `DateUtils.dateOnly()`).

For screen reader support, add a Semantics widget with `liveRegion: true` around the error text row so it is announced when it appears.

Testing Requirements

Unit test the validation function: (1) expiry in past → returns error; (2) expiry equals issue date → returns error; (3) expiry before issue date → returns error; (4) expiry after issue date and in future → returns null. Widget test: (1) set expiry to past date → error text visible, submit button disabled; (2) correct date → error text absent, submit button enabled; (3) initial state → no error text visible. Use bloc_test to verify BLoC emits correct validation state on date change events.

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.