critical priority high complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

A predefined catalogue of at least the following expense types is rendered as selectable chips: mileage (km reimbursement), public transit, toll/parking, and fixed-amount expenses.
Selecting 'mileage' disables 'public transit' and vice versa; incompatible combinations are visually distinct (greyed out) and non-interactive.
A conflict warning message appears inline immediately when the user attempts to activate an incompatible type, with no page reload or navigation required.
The conflict warning is wrapped in a Semantics widget with liveRegion: true so screen readers announce it automatically.
Each expense type chip exposes a Semantics label of the form '{type name}, {selected|not selected}{, unavailable due to conflict with {conflicting type}}' as appropriate.
All enabled chips have a minimum touch target of 44×44 logical pixels.
Contrast ratio between chip label text and chip background meets WCAG 2.2 AA (≥ 4.5:1 for normal text).
The widget emits a typed callback / stream event whenever the selected set changes, including the new selection and any newly disabled types.
Deselecting the conflicting type re-enables the previously disabled chips and dismisses the warning; the live region announces the dismissal.
The mutual exclusion rules are driven by a data-driven configuration object (not hardcoded conditionals) so new types can be added without logic changes.
Widget renders correctly on both iOS (VoiceOver) and Android (TalkBack) in Flutter integration tests.

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
data models
ExpenseType
ExpenseTypeConflictRule
ExpenseTypeSelectionState
performance requirements
Widget rebuild triggered only when selection state changes; use const constructors for static children.
Conflict resolution computation completes in O(n) where n is the number of expense types (<20); no perceptible lag on low-end devices.
No unnecessary setState calls outside the BLoC-driven rebuild cycle.
security requirements
Expense type catalogue must be loaded from a trusted internal configuration source; do not accept type definitions from untrusted user input.
No PII is stored or logged by this widget.
ui components
ExpenseTypeSelectorWidget (stateless, driven by BLoC state)
ExpenseTypeChip (individual selectable/disabled chip)
ConflictWarningBanner (live-region-enabled inline warning)
Semantics wrappers on every interactive element

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Model conflict rules as a Map> loaded from a const config. The widget itself should be stateless — receive ExpenseTypeSelectionState from ExpenseFormBloc and dispatch TypeSelected/TypeDeselected events. Use Semantics(liveRegion: true, label: conflictMessage) for the warning banner; wrap it in an AnimatedSwitcher so it fades in/out without layout shift. For the chips, wrap each FilterChip with Semantics(label: buildChipLabel(type, state), excludeSemantics: true) to override Flutter's default label.

Validate against design tokens for color; do not use hardcoded hex values. Keep conflict rules and type catalogue as const data at the top of the config file to make future additions trivial.

Testing Requirements

Unit tests (flutter_test): test conflict resolution logic for all documented incompatible pairs; test that enabling a type disables exactly the correct peers; test re-enabling on deselection. Widget tests: pump the widget in isolation, simulate taps, assert chip states and warning visibility. Accessibility tests: use flutter_test's SemanticsController to verify Semantics labels and liveRegion flags. Integration test: full tap-through on both iOS Simulator (VoiceOver) and Android Emulator (TalkBack).

Target ≥ 90% line coverage on conflict-rule logic.

Component
Expense Type Selector Widget
ui high
Epic Risks (3)
medium impact medium prob dependency

The image_picker Flutter plugin requires platform-specific permissions (NSPhotoLibraryUsageDescription, camera permission) and behaves differently across iOS and Android versions. Permission denial or plugin misconfiguration can silently prevent receipt attachment.

Mitigation & Contingency

Mitigation: Configure all required permission strings in Info.plist and AndroidManifest.xml during initial plugin setup. Use the permission_handler package to check and request permissions before launching the picker, with clear user-facing explanations. Test on both platforms across at least two OS versions.

Contingency: If image_picker proves unreliable on a specific platform version, fall back to file_picker as an alternative that uses the OS document picker interface, which requires fewer permissions on some Android versions.

high impact medium prob technical

The expense form BLoC manages interconnected state across expense type selection, field visibility, receipt requirement, threshold evaluation, and submission flow. Incorrect state transitions can cause UI inconsistencies such as required receipt indicator not updating after amount change, or form appearing valid when mutual exclusion is violated.

Mitigation & Contingency

Mitigation: Model BLoC states as sealed classes with exhaustive pattern matching. Write state transition unit tests covering every combination of: type selection change, amount field change above/below threshold, receipt attachment/removal, and offline mode toggle. Use bloc_test for comprehensive state sequence assertions.

Contingency: If BLoC complexity becomes unmanageable, split into two BLoCs — one for type selection/exclusion state and one for field values/submission — coordinating via a parent provider, accepting the small overhead of inter-BLoC communication.

high impact medium prob technical

The expense type selector must enforce mutual exclusion visually by disabling options and showing conflict tooltips, while remaining fully accessible to screen reader users who cannot perceive visual disable states. Incorrect semantics labelling will fail WCAG 2.2 AA requirements critical for Blindeforbundet and HLF users.

Mitigation & Contingency

Mitigation: Use Flutter Semantics widgets to explicitly set disabled state and provide conflict explanations as semanticLabel strings on disabled options. Run accessibility audits with TalkBack and VoiceOver during widget development, not post-completion. Reference the project's accessibility test harness for required test coverage.

Contingency: If custom widget accessibility is difficult to certify, implement the selector as a standard Flutter Radio/Checkbox group with built-in accessibility semantics and an explanatory Text widget below each conflicting option, sacrificing visual elegance for guaranteed WCAG compliance.