WCAG screen-reader audit for Route Input Fields
epic-mileage-reimbursement-entry-user-interface-task-006 — Run flutter_test accessibility audit on RouteInputFieldsWidget to verify that all labels pass WCAG 2.2 AA contrast ratio (≥4.5:1 for normal text, ≥3:1 for large text), focus order is logical, and both origin and destination labels are correctly associated via Semantics.label. Fix any violations found by the accessibility audit runner and document the final compliance state.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
The flutter_test accessibility guidelines (AccessibilityGuideline) are the primary automated tool. Supplement with a manual checklist for VoiceOver (iOS) and TalkBack (Android) covering: (a) field announcement on focus, (b) error announcement on validation failure, (c) privacy notice reachability without activating the field. If textContrastGuideline fails for the privacy notice caption (likely due to muted secondary color), adjust the design token secondary color to meet the 4.5:1 threshold against the background — do not suppress the guideline check. For the compliance comment in the widget file, use the format: // WCAG 2.2 AA: Audited [DATE] — all four flutter_test guidelines pass in empty, error, and privacy-notice states.
Testing Requirements
Use flutter_test's expect(tester, meetsGuideline(...)) API with all four built-in guidelines: androidTapTargetGuideline, iOSTapTargetGuideline, labeledTapTargetGuideline, and textContrastGuideline. Test in three states: (1) default empty render; (2) origin field in error state (empty submit); (3) destination field with privacy notice visible. Additionally write a Semantics traversal test using tester.getSemantics(find.byType(RouteInputFieldsWidget)) to assert the label strings on each node.
All four guidelines must pass in all three states. Document any guideline that is intentionally skipped and justify why.
Triggering MileageCalculationService on every keystroke in the distance field could cause frame drops on lower-end Android devices if the widget rebuild chain is too broad. Jank during real-time calculation would degrade the peer-mentor experience, particularly for users with motor impairments who may type slowly and need immediate feedback.
Mitigation & Contingency
Mitigation: Keep the reimbursement calculation inside the RealtimeReimbursementDisplay widget subtree only, using a StatefulWidget with a local state variable rather than lifting state to the BLoC. This limits rebuilds to a single widget. Profile on a low-end device (Qualcomm Snapdragon 450 class) before code review.
Contingency: If profiling shows frame drops, debounce the onChanged callback by 50ms using a Timer before updating the local state, which remains imperceptible to the user while eliminating excessive rebuilds.
The optional destination field requires balancing two competing requirements: WCAG 2.2 AA demands a clear label association for screen readers, while the privacy requirement means the label must communicate optionality and privacy sensitivity without exposing the user to undue pressure to fill it in. A poorly worded label could cause screen-reader users to misunderstand the field's optional nature or feel compelled to enter private location data.
Mitigation & Contingency
Mitigation: Draft label text and hint text with input from at least one screen-reader user during the accessibility review. Use Flutter's Semantics widget to provide a separate semantic label that is more descriptive than the visual placeholder. Test with TalkBack and VoiceOver before sign-off.
Contingency: If user testing reveals confusion, replace the inline hint with a tappable information icon that opens a brief explanation of why the field is optional and how the data is used, reducing cognitive load on the field label itself.
If OrgRateConfigRepository has not yet resolved the org rate when the form loads (e.g. slow network, first launch with empty cache), the reimbursement display cannot show a meaningful value. Showing '0' or an error state could confuse mentors and undermine trust in the feature.
Mitigation & Contingency
Mitigation: Display a loading skeleton in the reimbursement display widget until the rate is available. Once loaded, animate the value in. Show a subtle 'rate unavailable' message if the fetch times out, but allow form completion and submission (rate will be fetched server-side at submission time).
Contingency: If rate unavailability is frequent due to connectivity issues, store the last successfully fetched rate in SharedPreferences as an additional fallback so the widget can render a stale-but-indicative value with a caveat label.