high priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

BlocBuilder renders the shimmer skeleton view when BLoC emits NotificationsLoading state
Shimmer animation uses design token colors for the base and highlight shades — no hardcoded color values
Skeleton layout visually mimics the shape of a notification list item (avatar placeholder, title line, subtitle line)
At least 5 skeleton list items are shown during loading to fill the visible viewport on a standard phone
All skeleton widgets have `excludeSemantics: true` to prevent screen readers from announcing meaningless placeholder content
BlocBuilder renders the NotificationEmptyState widget (520-notification-empty-state) when BLoC emits NotificationsLoaded state with an empty notification list
NotificationEmptyState displays message 'No notifications yet' when no notifications exist at all
NotificationEmptyState displays message 'No notifications match this filter' when a filter is active but returns no results
BlocBuilder renders the full notification list when BLoC emits NotificationsLoaded state with items
Transitions between skeleton → list and skeleton → empty state are smooth (no visual flash or layout shift)
Widget test covers all three state branches: loading → skeleton shown, loaded-empty → empty state shown, loaded-with-items → list shown
Empty state widget is accessible: its message text is announced by screen readers, no `excludeSemantics` applied

Technical Requirements

frameworks
Flutter
BLoC
data models
NotificationState (loading, loaded, error variants)
NotificationItem
performance requirements
Shimmer animation must run at 60fps — use AnimationController with vsync, not a Timer-based animation
Skeleton list must not allocate more widgets than are visible in the viewport (use ListView.builder with itemCount capped at visible count)
ui components
520-notification-empty-state (existing component)
Custom shimmer skeleton widget (new, stateless with AnimationController in parent)
BlocBuilder<NotificationBLoC, NotificationState>
Semantics with excludeSemantics: true (for skeletons)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Implement the shimmer skeleton as a private widget within the notification centre screen file. Use a `SingleTickerProviderStateMixin` on the NotificationCentreScreen State to drive the shimmer AnimationController — do not create a separate StatefulWidget just for animation to avoid extra widget tree depth. The skeleton item shape should be: a Row with a circular avatar placeholder (40×40) and a Column of two rounded rectangles (title: 60% width, subtitle: 40% width). Use `LinearGradient` animated by the controller for the shimmer effect, referencing design token colors `AppColors.skeletonBase` and `AppColors.skeletonHighlight`.

For the empty state discrimination logic, add a boolean field `isFilterActive` to the NotificationsLoaded state so the BlocBuilder can determine which message to pass to NotificationEmptyState. Do not use a string comparison on the filter — use the typed state field.

Testing Requirements

Write widget tests using flutter_test with a mocked NotificationBLoC. Test each of the three state branches independently: (1) emit NotificationsLoading → find skeleton widgets in tree, confirm no Semantics nodes from skeletons; (2) emit NotificationsLoaded([]) with no active filter → find empty state widget with 'No notifications yet' text; (3) emit NotificationsLoaded([]) with active filter → find empty state widget with 'No notifications match this filter' text; (4) emit NotificationsLoaded([item1, item2]) → find list items, confirm skeleton and empty state are absent. Verify shimmer animation does not cause test-time exceptions (mock ticker provider using `TestVSync`). Check that empty state message text is in the Semantics tree (not excluded).

Component
Notification Centre Screen
ui medium
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.