high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

RealtimeReimbursementDisplayWidget is a StatefulWidget accepting distanceKm (double?) and orgRateNok (double) as constructor parameters
When distanceKm is null or 0.0, the widget displays the placeholder string 'Enter distance to see reimbursement' using design token tertiary text style
When distanceKm is a positive double, the widget renders 'NOK {rate}/km × {distance} km = NOK {total}' with values formatted to 2 decimal places
All monetary values are formatted using Norwegian locale conventions (e.g. 'NOK 112.70' not '112.7 NOK')
Text styles and colours are sourced exclusively from the app's design token system — no hardcoded colours or font sizes
The display panel has a visually distinct background or border using design token surface/card tokens to separate it from surrounding form fields
Widget renders correctly at both minimum (1 km) and large (999 km) distance inputs without overflow
Widget passes flutter_test widget tests for all three states: null distance, zero distance, and valid positive distance

Technical Requirements

frameworks
Flutter
flutter_test
data models
MileageReimbursementEntry
performance requirements
Widget build completes within a single frame — no async operations
No unnecessary rebuilds; widget only rebuilds when distanceKm or orgRateNok props change
security requirements
No sensitive data stored in widget state — distanceKm and orgRateNok are display-only inputs
ui components
Design token text styles (headline/body/caption variants)
Design token colour tokens (surface, onSurface, primary, tertiary)
Design token spacing tokens for internal padding
Container/Card widget using design token border radius and elevation

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Keep the widget stateless in practice — all display logic derives purely from the two input props; the StatefulWidget shell is scaffolded now to accommodate the didUpdateWidget hook in task-008 without refactoring. Use NumberFormat from the intl package (already likely present) with locale 'nb_NO' or 'en_US' as agreed with the team for NOK formatting. The formatted string should be built in a pure helper method `_formatDisplay(double? distanceKm, double orgRateNok) -> String` so it can be unit-tested independently.

Apply design tokens via Theme.of(context).textTheme and the app's AppColors or token extension — never hardcode Color(0xFF...) values. The placeholder and result strings should be defined as string constants at the top of the file to support future localisation.

Testing Requirements

Write flutter_test widget tests covering: (1) placeholder rendered when distanceKm is null, (2) placeholder rendered when distanceKm is 0.0, (3) correct formatted string rendered for distanceKm=23.0 and orgRateNok=4.90 producing 'NOK 4.90/km × 23 km = NOK 112.70', (4) large value edge case distanceKm=999.0 renders without overflow. All tests use pumpWidget with a MaterialApp wrapper supplying the design token theme. No golden tests required at this stage.

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.