high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

DistancePrefillService exposes a Future<double?> getLastUsedDistance() method backed by SharedPreferences or Supabase user profile
On first render the field is pre-populated with the cached value if one exists; field is empty otherwise
Helper text below the field reads 'Last used: X km' (localised) when a cached value is present; helper text is absent when no cache exists
The pre-filled value triggers onChanged immediately after initState so parent form is initialised with the value without user interaction
Saving a new mileage submission updates the cache via DistancePrefillService.saveLastUsedDistance(double)
Cache read/write is asynchronous and does not block widget render; a loading state (empty field) is shown while the future resolves
If the cache read throws an error, the field renders empty and the error is silently logged (no crash)
Helper text uses design token text style (caption/label size, secondary color token) for visual hierarchy

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
SharedPreferences (local cache) or Supabase user_preferences table
data models
DistancePrefillCache
MileageReimbursementEntry
performance requirements
Cache read completes in <100ms on device (local SharedPreferences preferred over network for prefill)
Widget does not rebuild more than once during prefill resolution
security requirements
Distance values are non-sensitive; SharedPreferences storage is acceptable
Do not cache any route or destination data in this service
ui components
DistanceInputFieldWidget (task-001)
Helper text widget using design token caption style

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Introduce a minimal StatefulWidget wrapper (DistanceInputFieldConnector or convert task-001 widget to StatefulWidget) to handle the async prefill lifecycle. Use initState + mounted check before calling setState after the future resolves. If Riverpod is already in use for other features, expose DistancePrefillService as a Provider and read it with ref.watch/ref.read in the widget. Avoid FutureBuilder on the entire field to prevent layout jumps — instead initialise controller text after the future resolves inside setState.

The prefillSource label string should be constructed in the widget, not in the service, to keep service logic pure.

Testing Requirements

Write widget + unit tests: (1) mock DistancePrefillService returning 23.0 — field shows '23.0' and helper text shows 'Last used: 23 km'; (2) mock service returning null — field is empty, no helper text; (3) mock service throwing exception — field renders empty, no crash; (4) verify onChanged fires with 23.0 on first render when prefill value exists; (5) unit test DistancePrefillService save/load round-trip with fake SharedPreferences. Use Riverpod's ProviderContainer or mocktail for service injection in widget tests.

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.