high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

DistanceInputFieldWidget is a StatelessWidget in its own file under lib/features/mileage/widgets/distance_input_field_widget.dart
Constructor accepts required ValueChanged<double?> onChanged and optional double? prefillValue parameters
TextFormField uses TextInputType.numberWithOptions(decimal: true) as keyboardType
When prefillValue is provided, the TextEditingController is initialized with the formatted value (e.g. '23.5')
onChanged fires with a parsed double? whenever the text changes; null is emitted for unparseable or empty input
Widget uses AppTextField or equivalent design-token-aware wrapper for consistent typography, spacing, and color tokens
Field displays a 'km' suffix or label using design token text styles
Minimum touch target height is 48dp as per WCAG 2.2 AA (use SizedBox or minHeight constraint)
Widget renders correctly in both light and dark theme variants without hardcoded colors
Widget is exported from the features/mileage barrel file

Technical Requirements

frameworks
Flutter
flutter_test
data models
MileageReimbursementEntry
performance requirements
Widget build method is pure and free of side effects
TextEditingController is disposed if widget is converted to StatefulWidget in future tasks
security requirements
No PII stored or logged from this widget
Input restricted to numeric characters to prevent injection via keyboard
ui components
AppTextField (existing reusable widget)
Design token spacing/typography constants
SizedBox for WCAG touch target enforcement

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Keep this widget strictly presentational (StatelessWidget). Do not introduce BLoC or Riverpod at this stage — those are added in later tasks. Use a local TextEditingController only if needed for prefill; if so, wrap in a thin StatefulWidget shell. Reference the existing AppTextField widget to ensure design token wiring is consistent with the rest of the app.

Use FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')) to restrict raw input at the formatter level before the validator runs. Keep the decimal separator as '.' internally; locale-aware formatting can be layered later.

Testing Requirements

Write widget tests using flutter_test: (1) render with no prefillValue — field is empty; (2) render with prefillValue: 23.0 — field displays '23.0'; (3) simulate typing '15' — onChanged fires with 15.0; (4) simulate typing non-numeric string — onChanged fires with null; (5) verify keyboardType is numberWithOptions; (6) verify minimum rendered height is ≥48dp using tester.getSize(). Aim for 100% branch coverage of the constructor and onChanged path.

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.