high priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

DriverHonorariumForm widget is rendered only when the parent activity type has is_travel_expense_eligible=true and the driver feature flag is active for the organisation
Widget is completely absent from the widget tree (not just hidden) when driver feature flag is inactive — no empty space or placeholder rendered
Honorarium amount input field is pre-populated with the org-specific rate guidance value fetched from DriverHonorariumService
Rate hint text is displayed inline below the amount field showing the recommended rate (e.g. 'Anbefalt sats: 450 kr/time') sourced from org configuration
Amount field shows validation error state when value is zero, negative, or exceeds a configurable maximum (sourced from org config)
Amount field shows validation error state when non-numeric input is entered
ExpenseFormBLoC state is updated in real-time as the user edits the honorarium amount
DriverHonorariumForm correctly emits a DriverHonorariumFormChanged event to the parent BLoC on every valid input change
Widget passes WCAG 2.2 AA: all interactive elements have semantic labels, minimum touch target 44x44dp, sufficient colour contrast for hint text
Rate guidance fetch failure is handled gracefully — field remains editable with an empty pre-fill and a visible error banner
Loading state is shown while rate guidance is being fetched from DriverHonorariumService

Technical Requirements

frameworks
Flutter
BLoC
apis
DriverHonorariumService (internal)
Supabase (via service layer)
data models
activity
activity_type
performance requirements
Rate guidance fetch must complete within 2 seconds on a 4G connection
Widget build time must not exceed 16ms to maintain 60fps scroll performance in parent form
security requirements
Organisation feature flag must be evaluated server-side via Supabase RLS — never rely solely on client-side flag
Honorarium amount must be validated both client-side and server-side before persistence
ui components
DriverHonorariumForm (StatelessWidget wrapping BlocBuilder)
AppTextField with numeric keyboard and currency formatting
RateHintText widget (inline subtitle style)
ValidationErrorText widget
LoadingIndicator (inline, small)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use BlocBuilder to conditionally show/hide the form — return SizedBox.shrink() (not Visibility) when inactive to keep the widget tree clean. Fetch rate guidance in the BLoC's event handler triggered on activity type selection, not in the widget's initState, to keep the widget stateless. Use TextEditingController bound to the BLoC state; debounce input with a 300ms timer before emitting BLoC events. Store org-specific rates in a dedicated Supabase table (e.g.

driver_honorarium_rates) with RLS scoped to organisation_id. Format the amount field with Norwegian locale (comma decimal separator). Avoid hardcoding any rate values — all must come from the service layer.

Testing Requirements

Widget tests: (1) form renders when feature flag active + activity is transport type, (2) form absent from widget tree when flag inactive, (3) amount field pre-populates with mocked rate guidance, (4) validation error shown for zero/negative/over-max amounts, (5) BLoC state updated on valid input, (6) loading state shown during fetch, (7) error banner shown on fetch failure. BLoC unit tests: DriverHonorariumFormChanged event correctly updates ExpenseFormState. Accessibility test: Semantics widget tree has correct labels for all interactive elements. Use flutter_test and mocktail for mocking DriverHonorariumService.

Component
Driver Honorarium Form
ui medium
Epic Risks (3)
high impact high prob integration

The Dynamics portal (HLF) and Xledger (Blindeforbundet) APIs have organisation-managed API contracts that may change without notice. Field mapping requirements, authentication flows, and export formats are not fully documented and may only be clarified during integration testing.

Mitigation & Contingency

Mitigation: Engage HLF and Blindeforbundet technical contacts early to obtain API documentation, sandbox credentials, and example payloads before implementation starts. Design the accounting integration client as a thin adapter layer with organisation-specific mappers so that field mapping changes require only mapper updates, not core client changes.

Contingency: If API documentation is unavailable or the API is unstable during Phase 3, implement a CSV/JSON file export as an interim deliverable. Coordinators can manually upload the file to their respective accounting systems until the live API integration is completed.

high impact medium prob scope

The confidentiality declaration for Blindeforbundet drivers may have specific legal requirements around content, format, wording, and record-keeping that are not yet specified. Implementing the wrong declaration flow could expose Blindeforbundet to compliance risk.

Mitigation & Contingency

Mitigation: Treat the declaration content and acknowledgement flow as a Blindeforbundet-controlled configuration, not hardcoded text. Implement the declaration as a templated document fetched from Supabase and reviewed by Blindeforbundet before any production deployment. Obtain written sign-off on the declaration text and acknowledgement mechanism before the epic is considered complete.

Contingency: If legal requirements cannot be confirmed in time for the sprint, deliver the driver honorarium form without the confidentiality declaration and gate the entire driver feature behind its feature flag. The declaration can be added in a follow-up sprint once requirements are confirmed, without blocking other feature delivery.

high impact medium prob integration

If the accounting export can be triggered multiple times for the same approved claims batch, duplicate records may be created in Dynamics or Xledger, causing accounting reconciliation problems that are difficult to reverse.

Mitigation & Contingency

Mitigation: Implement idempotent export runs: each export batch is assigned a unique run ID stored in the database. The accounting integration client checks for an existing successful export run for the same claim IDs before submitting. Approved claims that have been exported are marked with exported_at timestamp to prevent re-export.

Contingency: If duplicate exports occur despite idempotency checks (e.g. network failure after API success but before local confirmation), provide coordinators with an export history panel showing run IDs and timestamps. Implement a reconciliation endpoint that can query the accounting system for existing records before re-submitting flagged claims.