high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

RouteInputFieldsWidget now contains both origin (required) and destination (optional) TextFormFields in vertical order
Destination field label reads 'To (optional)' (localised) with a visible privacy notice subtitle 'Not stored permanently' directly below the label
Privacy notice uses design token caption style and secondary/muted color token — it is always visible, not only on hover
Destination field has no validator; empty value passes form validation silently
Widget exposes ValueChanged<String?> onDestinationChanged and optional String? initialDestination constructor parameters
Semantics node for destination field announces 'Destination, optional, privacy-protected, text field'
Destination field uses TextInputAction.done (last field in form section)
Minimum touch target height of destination field is ≥48dp
Privacy notice text is surfaced as a Semantics label or Tooltip so screen readers can access it
No destination data is stored in any local cache or prefill service (enforcement at widget level: no write calls)

Technical Requirements

frameworks
Flutter
flutter_test
data models
MileageReimbursementEntry
performance requirements
Widget renders both fields in a single synchronous build
No network calls triggered by this widget
security requirements
Destination field value must NOT be written to SharedPreferences or any persistence layer from this widget
Privacy notice must be present at all times (not hidden behind a toggle) per GDPR transparency requirements for Norwegian apps
Input length capped at 200 characters
ui components
AppTextField wrapper
Semantics widget with explicit label
Caption Text widget for privacy notice
Tooltip widget (optional) for extended privacy description

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The privacy notice is a legal transparency requirement (GDPR / Norwegian personal data act) since the destination reveals physical movement. Place it as a static Row with an info icon and caption text below the destination label — do not use a Tooltip alone as tooltips are not accessible to all screen readers without additional interaction. Implement the Semantics wrapper with excludeSemantics: false on child nodes so the full combined label is announced. For the 'optional' marking, use both visual text cue ('To (optional)') and Semantics label — do not rely on color alone to convey optionality.

TextInputAction.next on origin and TextInputAction.done on destination enables keyboard navigation flow.

Testing Requirements

Widget tests: (1) both 'From' and 'To (optional)' labels are visible; (2) privacy notice text 'Not stored permanently' is rendered below destination label; (3) empty destination + non-empty origin passes Form.validate() without error; (4) typing in destination fires onDestinationChanged; (5) Semantics tree for destination field includes 'optional' and 'privacy-protected' in the label; (6) verify destination field height ≥48dp; (7) confirm DistancePrefillService.saveLastUsedDistance is never called when destination is typed (mock the service and assert zero calls). Run the full RouteInputFieldsWidget in a test Form to confirm combined validation behavior.

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.