high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

RouteInputFieldsWidget is a StatelessWidget in lib/features/mileage/widgets/route_input_fields_widget.dart
Widget contains a single origin TextFormField with a visible label 'From' (localised)
Origin field is marked as required; empty submission produces a localised error 'Origin is required'
Label is rendered as a separate Text widget above the field (not InputDecoration.labelText floating) for predictable screen reader association
Semantics widget wraps label + field pair with a label attribute so VoiceOver/TalkBack reads 'From, text field, required'
Minimum rendered height of the field (not counting label) is ≥48dp
All colors, font sizes, and spacing come from design token constants — zero hardcoded values
Widget exposes ValueChanged<String?> onOriginChanged and optional String? initialOrigin constructor parameters
Widget is exported from the mileage feature barrel

Technical Requirements

frameworks
Flutter
flutter_test
data models
MileageReimbursementEntry
performance requirements
Widget is purely presentational with no async work
Build method completes in a single frame
security requirements
Origin field content is treated as non-sensitive location text
Input length capped at 200 characters via maxLength to prevent unreasonable payloads
ui components
AppTextField or equivalent design-token wrapper
Semantics widget for accessibility
Design token spacing (vertical padding between label and field)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use a Column with the label Text above the TextFormField, not the floating InputDecoration.labelText, because floating labels can confuse some screen readers and are harder to style consistently with design tokens. Wrap the Column in a Semantics widget with label set to the combined announcement string. TextInputAction.next is recommended so users can tab to the destination field (task-005) with the keyboard. Keep origin validation minimal at this stage: just non-empty check.

Route-specific validation (e.g. origin ≠ destination) is a separate concern and should not be added here.

Testing Requirements

Widget tests: (1) widget renders with a visible 'From' label and empty field; (2) provide initialOrigin: 'Oslo' — field shows 'Oslo'; (3) simulate typing 'Bergen' — onOriginChanged fires with 'Bergen'; (4) submit empty field in a Form — error 'Origin is required' appears; (5) verify Semantics tree contains a node with label including 'From' and 'required'; (6) verify rendered field height ≥48dp. Use tester.getSemantics() or the SemanticsController to assert accessibility node properties.

Component
Route Input Fields 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.