high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

NotificationFilterBar widget renders as a horizontally scrollable single-row chip list
Filter chips are rendered for all 4 notification types: assignment_reminder, certification_expiry, pause_status, scenario_prompt
Filter chips are rendered for all 3 read-state options: all, unread, read
Tapping a type chip emits a FilterTypeChanged event to the NotificationBloc
Tapping a read-state chip emits a FilterReadStateChanged event to the NotificationBloc
The active chip is visually distinguished from inactive chips using the project's design token accent color (not a hardcoded color)
Inactive chips use the design token secondary/muted surface color
Chip labels use translated/localized strings, not raw enum values
Each chip is wrapped in a Semantics widget with a meaningful label (e.g., 'Filter by unread notifications, currently active')
The widget supports keyboard navigation and switch-access traversal (all chips reachable via focus)
Color contrast between chip label and background meets WCAG 2.2 AA (minimum 4.5:1 for normal text)
The widget does not manage its own filter state — it is a stateless display widget driven by external state
The widget renders correctly on screen widths from 320px to 428px (small to large phone)
Horizontal scroll does not interfere with the parent page's vertical scroll

Technical Requirements

frameworks
Flutter
flutter_bloc (BlocBuilder for state wiring in task-002)
Design token system
data models
NotificationFilter (NotificationTypeFilter, ReadStateFilter enums)
NotificationEvent (FilterTypeChanged, FilterReadStateChanged)
performance requirements
Widget must be stateless (StatelessWidget) — no internal setState calls
Chip row must use ListView.builder or SingleChildScrollView with Row to avoid building off-screen chips eagerly if count grows
security requirements
Chip labels must not render raw user data — only enum-derived localized strings
ui components
FilterChip or ChoiceChip (Flutter Material)
SingleChildScrollView with scrollDirection: Axis.horizontal
Semantics wrapper per chip
Design token accent color applied via Theme or AppColors constants

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Build NotificationFilterBar as a StatelessWidget accepting a currentFilter parameter and an onFilterChanged callback (or wired directly to BLoC in task-002 — keep it pure in this task). Use FilterChip or a custom chip widget styled with design tokens. Access accent color via AppColors.accentPrimary or equivalent design token constant — never use Color(0xFF...) literals in widget code. For accessibility, wrap each chip in Semantics(label: '...', button: true, selected: isActive).

For WCAG contrast, verify using the Flutter accessibility checker or manually compute contrast ratio. Separate the chip data (label, type, value) into a const list of chip descriptors to avoid repetitive widget code. Use clipBehavior: Clip.none on the scroll view to prevent chip shadow clipping. Test on both light and dark theme if the app supports it.

Testing Requirements

Widget tests using flutter_test. Cover: (1) all 7 chips render with correct labels, (2) active chip has the correct accent color from design tokens, (3) inactive chips have the correct muted color, (4) tapping a type chip triggers the correct callback/event, (5) tapping a read-state chip triggers the correct callback/event, (6) Semantics tree contains meaningful labels for all chips (use find.bySemanticsLabel), (7) widget renders without overflow on 320px wide screen. Use mockito or mocktail to verify event dispatch. No integration tests required at this stage.

Component
Notification Filter Bar
ui low
Epic Risks (3)
medium impact medium prob integration

If a referenced entity (contact, certification, activity) has been deleted or its RLS policy now excludes the current user, the deep link handler may navigate to a screen that renders in an error state or throws an unhandled exception.

Mitigation & Contingency

Mitigation: The deep link handler must perform a lightweight existence check (HEAD request or minimal SELECT) before pushing the route. Define a contract with each destination screen for how to handle a not-found entity ID passed as a route parameter.

Contingency: If the existence check itself fails (network error), navigate to the destination screen anyway and let it handle the error gracefully with its own error state; do not block navigation for network timeouts.

medium impact low prob technical

If the tab badge widget triggers a full rebuild of the bottom navigation bar on every unread count change, it will cause visible jank on devices with many active Realtime events (e.g., org admins receiving org-wide alerts).

Mitigation & Contingency

Mitigation: Scope the badge widget to a dedicated BlocSelector that rebuilds only when the unread count value changes, not on any BLoC state emission. Use RepaintBoundary to isolate the badge from the rest of the nav bar.

Contingency: If performance issues persist, debounce badge updates to a maximum of one rebuild per 500ms and display the last known count during the debounce window.

high impact medium prob scope

Complex swipe-to-mark-read gestures and dynamic list updates may conflict with VoiceOver/TalkBack navigation patterns, particularly for Blindeforbundet users who rely exclusively on screen readers.

Mitigation & Contingency

Mitigation: Provide a dedicated accessibility action (Semantics.onTap / CustomSemanticsAction) for mark-as-read on each list item so screen reader users do not need the swipe gesture. Test with VoiceOver on iOS and TalkBack on Android before each release.

Contingency: If the swipe gesture proves incompatible with assistive technologies, disable it when a screen reader is detected (via ScreenReaderDetectionService) and rely solely on the tap-to-read and accessible action pathways.