high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

MileageEntryForm is a StatefulWidget registered as a named route or GoRouter destination in the app's navigation configuration
A GlobalKey<FormState> is declared and passed to a Form widget that wraps all input fields
The screen uses a SingleChildScrollView (or CustomScrollView with SliverList) so content scrolls when the keyboard appears
A read-only date field displays today's date at screen mount using DateTime.now() formatted as 'dd.MM.yyyy' (Norwegian locale) via intl DateFormat
The date field is visually marked as read-only (e.g., disabled styling from design tokens) and cannot receive keyboard focus
Placeholder slots (SizedBox or Container with TODO comment) exist for DistanceInputField, RouteInputFields, RealtimeReimbursementDisplay, and the confirm AppButton
The confirm AppButton has a minimum height of 48dp and a minimum width spanning the full available width
All interactive elements (confirm button, future input fields) have minimum touch targets of 48×48dp per WCAG 2.2 AA
Screen has a page header with title 'Register mileage' using the app's PageHeader widget and design tokens
No hardcoded colours, font sizes, or spacing values — all from design token system
flutter_test smoke test: widget pumps without errors and shows today's date in the date field

Technical Requirements

frameworks
Flutter
flutter_test
intl
data models
MileageReimbursementEntry
performance requirements
Screen builds in under 16ms on mid-range devices — no expensive operations in build()
DateTime.now() called once in initState and stored, not called on every rebuild
security requirements
Date field is read-only at the UI level — no user manipulation of the submission date is permitted
ui components
Form widget with GlobalKey<FormState>
SingleChildScrollView with keyboard-aware padding (MediaQuery.viewInsetsOf)
AppTextField in read-only mode for date display
AppButton (design system primary button, full width, 48dp height)
PageHeader widget with screen title
Design token spacing for section gaps between form groups

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Follow the app's single-action screen layout pattern — confirm the pattern name/class used in existing screens (e.g., activity wizard screens) and replicate the same structure exactly. Use `final _formKey = GlobalKey();` as the form key. For keyboard-aware scrolling, wrap the SingleChildScrollView content in a Padding widget using `EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom)` to push content above the keyboard. The date is immutable for this form — today's date is the submission date, matching HLF's requirement for default date = today.

Store `_today = DateTime.now()` in initState. Use intl's DateFormat('dd.MM.yyyy').format(_today) for formatting. Placeholder slots should be clearly commented: `// TODO(task-010): embed DistanceInputFieldWidget here`. Keep this scaffold free of business logic — it is a structural shell to be composed in task-010.

Testing Requirements

Write flutter_test widget tests: (1) smoke test that MileageEntryForm pumps without exceptions. (2) Verify date field displays today's date in 'dd.MM.yyyy' format. (3) Verify confirm button is present and has a minimum height of 48dp using tester.getRect(). (4) Verify Form widget is present in the tree using find.byType(Form).

(5) Verify SingleChildScrollView is present. Use MockGoRouter or NavigatorObserver if route registration is tested. No integration tests required at scaffold stage.

Component
Mileage Entry Form
ui medium
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.