high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

All 6 entity type resolution paths are wrapped in try/catch blocks that catch both repository not-found results and network exceptions
When a repository returns null or throws a NotFoundException, the snackbar message 'This item is no longer available' is displayed to the user
The snackbar is accessible: Semantics label matches the snackbar text so screen readers announce it
MarkNotificationRead is dispatched to NotificationBLoC in the deleted-entity path — notification is marked read even when navigation fails
The user's current screen remains visible after the error — no blank screen, no empty route push
Network errors (timeout, no connectivity) during the existence check also trigger the snackbar — they do not crash the app
No unhandled exception escapes the handler under any error condition — all code paths terminate gracefully
Integration test for each of the 6 entity types: mock repository to return not-found, assert snackbar appears and notification is marked read
After the snackbar, a second tap on the same notification (now marked as read) still shows the snackbar if entity still does not exist — no silent failures
Error logging: deleted-entity events are logged to the app's logging service with entity_type and entity_id for debugging

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase (repository not-found response handling)
data models
NotificationPayload
RepositoryException / NotFoundException
performance requirements
Fallback path (snackbar + mark read) must complete within 300ms of receiving the not-found response
security requirements
Error messages shown to users must not include internal entity IDs, database error codes, or stack traces
Exception logs must redact personally identifiable entity content — only log entity_type and entity_id (UUID)
ui components
ScaffoldMessenger + SnackBar (Flutter built-in)
Semantics (Flutter built-in) wrapping snackbar content

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Extend the entity resolver methods from task-005 with a uniform try/catch wrapper. Create a private method `_handleNavigationFailure(BuildContext context, String notificationId)` that (1) dispatches MarkNotificationRead, (2) shows the snackbar. Call this method from both the not-found branch and the catch block. This avoids duplicating the fallback logic across all 6 entity resolvers.

For the snackbar Semantics, wrap the SnackBar content in a Semantics widget with `liveRegion: true` so VoiceOver/TalkBack announces it automatically — critical for Blindeforbundet users. Define a single error string constant (e.g., `AppStrings.notificationEntityDeleted`) rather than inline strings. Log errors using the app's existing logger (e.g., `logger.warning('Deep link entity not found', {type: entity_type, id: entity_id})`).

Testing Requirements

Write integration tests (using flutter_test with a test Supabase environment or fully mocked repositories) covering the deleted-entity path for all 6 entity types. Each test: (1) set up repository mock to return null/throw NotFoundException, (2) invoke handler, (3) assert SnackBar with correct message appears in widget tree, (4) assert MarkNotificationRead event was dispatched. Additionally write tests for network failure (repository throws TimeoutException) and confirm same snackbar behavior.

Test that the app does not crash (no unhandled exception) for any entity type in the deleted path. Use WidgetTester.pump() to flush async operations before assertions.

Component
Notification Deep Link Handler
service 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.