high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Each chip's Semantics label includes: human-readable type name, current state (selected / not selected), and — when disabled due to conflict — the phrase 'unavailable, conflicts with {type name}'.
When a conflict warning banner appears, a screen reader announces the warning text within 500 ms without the user moving focus.
When a conflict warning is dismissed, a screen reader announces 'Conflict resolved' or equivalent without the user moving focus.
All interactive chips have a minimum touch target of 44×44 logical pixels verified by widget test using tester.getSize().
All chip label text / background combinations pass WCAG 2.2 AA contrast (≥ 4.5:1) using design token color values only.
Selected chip uses a design-token-defined selected color that also passes 4.5:1 contrast.
Disabled chip communicates its disabled state without relying on colour alone (e.g. reduced opacity + icon or pattern).
No duplicate or conflicting Semantics nodes are present in the widget tree (validated via flutter_test SemanticsController).
Widget passes flutter_test accessibility guidelines check (tester.ensureSemantics()).
Manual VoiceOver (iOS) and TalkBack (Android) smoke test passes: tester can navigate all chips by swipe and activate/deactivate without visual inspection.

Technical Requirements

frameworks
Flutter
data models
ExpenseTypeSelectionState
performance requirements
Semantics tree update must not cause additional frame drops; rebuild only affected Semantics nodes.
Live-region announcement triggered within one frame of state change.
security requirements
No user data is read or transmitted by the accessibility layer.
ui components
Semantics (liveRegion: true) on ConflictWarningBanner
Semantics (label, button, enabled flags) on each ExpenseTypeChip
MergeSemantics or ExcludeSemantics where appropriate to avoid duplicate announcements

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Build chip Semantics labels dynamically in a helper function buildChipSemanticLabel(ExpenseType type, ExpenseTypeSelectionState state) → String. Use Semantics(onTap: ..., label: label, button: true, enabled: isEnabled, excludeSemantics: true) wrapping the FilterChip to prevent double-announcement of the chip's own internal semantics. For the live region, use a ValueKey on the Semantics widget and swap the label string when the warning changes — Flutter will re-announce when the key or label changes. Contrast validation: write a dart test that reads design token hex values and computes the W3C contrast ratio formula; fail the test if any pair is below 4.5:1.

This keeps contrast checks in CI rather than requiring manual audits.

Testing Requirements

Widget tests (flutter_test): assert SemanticsData.label for each chip in selected, unselected, and disabled states. Assert SemanticsData.hasFlag(SemanticsFlag.isEnabled) is false for disabled chips. Use tester.getSize() to verify touch targets ≥ 44×44. Integration accessibility test: call tester.ensureSemantics() and run SemanticsController.find() for each chip.

Manual test checklist: VoiceOver on iOS 17 Simulator and TalkBack on Android 14 Emulator — navigate all chips, trigger conflict, confirm announcement, resolve conflict, confirm dismissal announcement.

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.