high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Empty input produces localised error string (e.g. 'Distance is required')
Non-numeric input (after FilteringTextInputFormatter) is rejected at formatter level; any residual non-numeric string in the validator produces 'Please enter a valid number'
Zero input (0 or 0.0) produces 'Distance must be greater than zero'
Negative input produces 'Distance must be a positive number'
Unreasonably large values (>9999 km) produce 'Distance seems too large — please check'
Validator runs on every onChanged event (autovalidateMode: AutovalidateMode.onUserInteraction) to provide real-time feedback
Validator also runs on form submission via the standard FormState.validate() call
Error text color uses the design token error color (errorColor token) and achieves ≥4.5:1 contrast ratio against the field background
Error text is announced by screen readers (Semantics liveRegion or Flutter's built-in errorText behavior)
Touch target height remains ≥48dp even when error text is visible below the field

Technical Requirements

frameworks
Flutter
flutter_test
data models
MileageReimbursementEntry
performance requirements
Validator function executes synchronously in <1ms
No re-renders triggered by validator beyond the standard form rebuild
security requirements
Validated value clamped to reasonable range (0 < distance ≤ 9999) before being passed upstream
No raw user string passed to any backend before successful validation
ui components
TextFormField validator parameter
Design token errorColor
AutovalidateMode.onUserInteraction

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define a standalone String? distanceValidator(String? value) function (pure function, easy to unit test in isolation). Apply FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')) as an inputFormatter to strip invalid characters at the OS level before the validator sees them — this prevents the validator from ever needing to handle truly garbage input.

Use double.tryParse for safe conversion. For the WCAG contrast requirement, rely exclusively on the design token errorColor — do not hardcode colors. The Flutter framework's built-in InputDecoration.errorText automatically marks the text with Semantics errorText, so screen reader support is provided for free.

Testing Requirements

Write widget tests using flutter_test: (1) empty string → error 'Distance is required'; (2) '0' → error about positive number; (3) '-5' → negative error; (4) 'abc' → numeric error; (5) '10000' → too-large error; (6) '15.5' → no error, onChanged fires with 15.5; (7) verify autovalidateMode triggers error after user types then clears field without submitting; (8) use tester.pump() after each interaction to confirm error widget renders; (9) check rendered height ≥48dp with error text present. All error strings should be verified against the localisation keys, not hardcoded.

Component
Distance Input Field Widget
ui low
Epic Risks (3)
medium impact low prob technical

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.

medium impact medium prob scope

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.

low impact medium prob integration

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.