high priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

DriverFeeFormBloc defines events: ContactSelected, FeeAmountChanged, FeeTypeSelected, SubmitFeeForm, DeclarationFlowCompleted, and ResetForm
On ContactSelected, the BLoC queries DeclarationStatusService; if the contact has no valid declaration, it emits declarationRequired state with the relevant contact ID
declarationRequired state causes the form to surface an inline action prompt (not a dialog) directing the user to complete the declaration flow
After returning from DeclarationSendScreen with a success result, SubmitFeeForm or DeclarationFlowCompleted re-checks declaration status before allowing submission
Inline validation runs on submit: contact field shows 'Contact is required' if empty; fee amount shows 'Must be a positive amount' if zero or negative, and 'Amount exceeds maximum allowed' if over the configured cap; fee type shows 'Please select a fee type' if unselected
Validation errors are displayed inline beneath each field using design token error color, not via a snackbar or dialog
On valid submission the BLoC emits submitting state (disabling the submit button) and calls DriverFeeService.submitFee()
On service success the BLoC emits success state; the parent navigates back or shows a success confirmation
On service error the BLoC emits error state with a user-facing message; the form remains populated so the user can retry
The submit button is disabled in submitting state and re-enabled in error state
All state transitions are pure (no side effects inside BLoC methods) — navigation is handled via BlocListener in the widget layer

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
Dart
apis
DriverFeeService.submitFee()
DeclarationStatusService.hasValidDeclaration(contactId)
FeeTypeRepository.getMaxFeeAmount()
data models
DriverFee
FeeType
Contact
DeclarationStatus
FeeValidationError
performance requirements
Declaration status check on ContactSelected must complete within 1.5 seconds; show an inline loading indicator on the contact field while pending
Form submission must not block the UI thread — all service calls are async and run outside the event loop synchronously
security requirements
Fee amount maximum limit must be enforced server-side as well; the client-side cap is a UX guard, not a security boundary
Declaration status must be re-verified at submission time (not cached from contact selection) to prevent TOCTOU race conditions
ui components
DriverFeeRegistrationForm (from task-010)
ValidationErrorText
DeclarationRequiredPrompt
InlineLoadingIndicator

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model the BLoC state as a sealed class hierarchy (Dart 3) rather than an enum + nullable fields — this makes exhaustive pattern matching in the UI safe and compiler-checked. Keep the declarationRequired state as a distinct sealed subclass carrying the contactId so the UI can pass it as a route argument to DeclarationSendScreen without additional lookups. For the TOCTOU fix on declaration status, add a private _verifyDeclarationBeforeSubmit() method that always calls the service fresh rather than reading from state. The max fee cap should be fetched once on BLoC initialization from FeeTypeRepository and stored as a private field — avoid fetching it on every validation call.

Use equatable or the built-in == override on state classes to prevent unnecessary BlocBuilder rebuilds.

Testing Requirements

Write unit tests for DriverFeeFormBloc covering all state transitions: (1) ContactSelected with valid declaration → idle with contact set; (2) ContactSelected with no declaration → declarationRequired; (3) SubmitFeeForm with empty contact → validating with contact error; (4) SubmitFeeForm with negative amount → validating with amount error; (5) SubmitFeeForm with amount over cap → validating with max error; (6) SubmitFeeForm with all valid → submitting → success; (7) service error → error state with message. Write widget tests verifying inline error text renders beneath the correct fields. Use mocktail to stub DeclarationStatusService and DriverFeeService. Target 90%+ branch coverage for the BLoC.

Component
Driver Fee Registration Form
ui medium
Epic Risks (3)
high impact medium prob technical

The declaration acknowledgement screen has the most complex accessibility requirements of any screen in this feature: scrollable long-form legal text, a conditional checkbox that is only enabled after reading, and a timestamp capture. Incorrect focus management or missing semantics annotations could fail VoiceOver navigation or cause the screen reader to announce the checkbox as available before the driver has scrolled, undermining the legal validity of the acknowledgement.

Mitigation & Contingency

Mitigation: Build the acknowledgement screen against the WCAG 2.2 AA checklist from the start, not as a post-hoc audit. Use semantics-wrapper-widget and live-region-announcer from the platform's accessibility toolkit. Include a VoiceOver test session in the acceptance criteria with a tester using the screen reader.

Contingency: If WCAG compliance cannot be fully achieved within the sprint, ship the screen with a documented list of accessibility gaps and a follow-up sprint commitment. Do not block the declaration workflow launch if the core interaction works but a non-critical semantics annotation is missing.

medium impact medium prob integration

Drivers receive a push notification with a deep link to the declaration acknowledgement screen for a specific assignment. If the deep link handler does not correctly route to the right screen and assignment context — particularly when the app is launched cold from the notification — the driver may see a blank screen or the wrong declaration.

Mitigation & Contingency

Mitigation: Implement and test all three notification scenarios: app foregrounded, app backgrounded, and cold start. Use the platform's existing deep-link-handler infrastructure. Add integration tests that simulate notification tap events and assert correct screen and data loading.

Contingency: If cold-start deep link routing proves unreliable, implement a notification-centre fallback where the driver can find the pending declaration from the notification centre screen, ensuring the workflow can always complete even if the direct deep link fails.

medium impact low prob technical

If the driver-feature-flag-guard has any rendering edge case — such as a brief flash of driver UI before the flag value is loaded, or a guard that fails open on a flag service error — driver-specific UI elements could be momentarily visible to coordinators in organizations that have not opted in, causing confusion and potentially a support escalation.

Mitigation & Contingency

Mitigation: Default the guard to rendering nothing (not a loading indicator) until the flag value is definitively resolved. Treat flag service errors as flag-disabled to fail closed. Write widget tests covering the loading, disabled, and enabled states including the error case.

Contingency: If fail-closed cannot be guaranteed within the sprint, add a server-side RLS check on the driver assignment endpoints so that even if the UI guard leaks, the data layer refuses to return driver data for organizations without the flag enabled.