high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

NotificationFilterBar is wrapped in a BlocBuilder<NotificationBloc, NotificationState> in the parent screen
BlocBuilder uses a buildWhen condition that returns true only when the activeFilter field of NotificationState changes
buildWhen returns false when only the notifications list, unread count, or loading status changes (preventing unnecessary chip row redraws)
The current activeFilter from BLoC state is passed to NotificationFilterBar, causing the correct chips to display as active
Tapping a type chip dispatches FilterTypeChanged (or equivalent) event to the NotificationBloc via context.read<NotificationBloc>().add(...)
Tapping a read-state chip dispatches FilterReadStateChanged (or equivalent) event to the NotificationBloc
Active chip visual state updates immediately upon state emission from the BLoC (no perceptible lag)
No setState or local widget state is used for tracking active filter — BLoC state is the single source of truth
Widget tests verify the BlocBuilder rebuilds only on filter changes
Widget tests verify the correct chip appears active for each possible filter state

Technical Requirements

frameworks
Flutter
flutter_bloc
BLoC (bloc ^8.x)
data models
NotificationBloc
NotificationState (activeFilter field)
NotificationEvent (FilterTypeChanged, FilterReadStateChanged)
NotificationFilter
performance requirements
buildWhen must prevent chip row rebuilds on any state change other than activeFilter — verified by counting build calls in tests
BlocBuilder must not be placed higher in the widget tree than necessary to minimize rebuild scope
ui components
BlocBuilder<NotificationBloc, NotificationState> with buildWhen
NotificationFilterBar (from task-001)
context.read<NotificationBloc>() for event dispatch

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The buildWhen implementation should compare previous.activeFilter == current.activeFilter and return the negation. Avoid comparing entire state objects unless NotificationState implements Equatable with only activeFilter in props (it should have all fields). Place BlocBuilder as close to NotificationFilterBar in the tree as possible — not at the screen root. For event dispatch, use context.read().add(FilterTypeChanged(filter)) inside the onFilterChanged callback passed to NotificationFilterBar.

This keeps NotificationFilterBar itself BLoC-agnostic (pure widget), which improves testability. Verify the wiring works end-to-end by running the full notification screen manually or via a golden test if the team uses golden_toolkit. Document the buildWhen logic with a brief inline comment explaining why it guards against non-filter state changes.

Testing Requirements

Widget tests using flutter_test and bloc_test/mocktail. Cover: (1) BlocBuilder only rebuilds the chip row when activeFilter changes — simulate a list-only state change and assert build count is unchanged, (2) correct chip is marked active for each of the 7 filter values, (3) tapping a type chip dispatches FilterTypeChanged with the correct filter value to a mock bloc, (4) tapping a read-state chip dispatches FilterReadStateChanged with the correct value, (5) no chip is double-highlighted when filter is 'all' (default state). Use a MockNotificationBloc with mocktail and provide it via BlocProvider in pump(). Use a build call counter (via a flag in a test-only wrapper) to verify buildWhen prevents extra builds.

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.