medium priority low complexity frontend pending frontend specialist Tier 6

Acceptance Criteria

A push notification for an unresolved duplicate pair carries a deep-link URL in the format: app://deduplication/detail/{pairId}
Tapping the notification while the app is in the background or terminated launches the app and navigates directly to DeduplicationDetailScreen for the given pairId
Tapping the notification while the app is in the foreground navigates to DeduplicationDetailScreen without restarting the app
If pairId is not a valid UUID format, the deep link redirects to the DeduplicationQueueScreen with a toast 'Invalid link'
If pairId references a pair that has already been resolved, DeduplicationDetailScreen is shown in read-only mode with an 'Already resolved' notice instead of resolution action buttons
If pairId references a pair that does not exist (deleted or never existed), a 'Pair not found' error state is shown with a back button to DeduplicationQueueScreen
Deep link navigation is gated by authentication: unauthenticated users are redirected to the login screen; after login, the original deep-link destination is restored
Deep link navigation is gated by role: only coordinators can access the detail screen; other roles are redirected to their home screen with a 'No access' message
Deep link works on both iOS (Universal Links or custom scheme) and Android (App Links or custom scheme)
Unit test: route parser correctly extracts pairId from valid URLs and rejects invalid ones
Widget test: invalid UUID deep link renders error state on DeduplicationDetailScreen

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
GoRouter (or existing project router) deep-link handler
DeduplicationQueueService.fetchPairById(pairId)
Push notification payload parser (FCM/APNs)
data models
DuplicatePair
DeepLinkRoute
AuthState
performance requirements
Deep-link resolution and navigation to the detail screen must complete within 1 second of app foreground on a mid-range device
Auth redirect must not introduce perceptible delay when the user is already authenticated
security requirements
pairId extracted from the URL must be validated as a UUID v4 before any Supabase query is issued
Deep-link handler must not bypass authentication or role-access checks
Push notification payload must not contain sensitive activity data — only the pairId reference
ui components
DeduplicationDetailScreen (reused from task-010, accepts pairId route param)
PairNotFoundErrorState widget
AuthRedirectGuard (reuse or extend existing auth guard)

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Register the deep-link scheme in the project's GoRouter configuration as a named route /deduplication/detail/:pairId. Ensure GoRouter's redirect logic is applied before this route to handle authentication and role checks — use the existing project auth guard pattern rather than reimplementing it. In the notification tap handler (FCM onMessageOpenedApp / getInitialMessage), extract the deep-link URL from the notification data payload and pass it to GoRouter.go(). Validate the pairId format using a UUID regex before routing; on failure, route to /deduplication with a query parameter error=invalid-link.

In DeduplicationDetailScreen's BLoC init, handle the three pair-fetch outcomes (found+unresolved, found+resolved, not-found) as distinct states — these were already required by task-010's AlreadyResolvedNotice, so no new states are needed. On iOS, ensure the custom URL scheme is registered in Info.plist; on Android, register an intent-filter in AndroidManifest.xml. If the project already uses Firebase Dynamic Links or a similar service, align the scheme with that system.

Testing Requirements

Unit tests must cover the route parsing logic: valid UUID extracts pairId correctly; non-UUID string triggers invalid-link path; missing pairId segment triggers invalid-link path. Widget tests must cover: deep link with valid pairId for resolved pair renders read-only AlreadyResolvedNotice; deep link with valid pairId for non-existent pair renders PairNotFoundErrorState; unauthenticated deep link redirects to login and, after login mock, restores the detail screen. Use GoRouter's testRoutePath helpers or equivalent. Integration test must simulate FCM notification tap using flutter_test pump and verify the correct route is pushed.

Component
Deduplication Queue Screen
ui medium
Epic Risks (3)
high impact medium prob technical

The merge resolution path requires identifying which fields from the draft differ from the existing record, applying those differences to the existing record, and cancelling the draft — all as an atomic operation. Partial failures (e.g., update succeeds but draft cancellation fails) could leave the system in an inconsistent state.

Mitigation & Contingency

Mitigation: Implement the merge path as a Supabase RPC transaction that updates the existing record and soft-deletes the draft in a single atomic call. The DuplicateResolutionHandler should never attempt field-level merge at the application layer.

Contingency: If the atomic RPC approach proves too complex for the initial release, simplify the merge path to: mark existing record as the canonical record and cancel the new submission without field merging, displaying a message to the user to manually verify the existing record's fields. Log a follow-up ticket for full field-merge in a later sprint.

medium impact medium prob integration

The DuplicateWarningBottomSheet must intercept the activity wizard's save action without disrupting the wizard's existing navigation stack. If the bottom sheet is implemented as a separate route rather than an overlay, back navigation could break the wizard's step state.

Mitigation & Contingency

Mitigation: Use a `showModalBottomSheet` overlay pattern so the bottom sheet sits above the wizard route without pushing a new route onto the Navigator stack. The wizard's CuBit/BLoC retains all draft state while the sheet is visible. Test this integration with the existing activity-registration-cubit before merging.

Contingency: If the overlay approach causes Z-order or focus issues with the wizard's keyboard-aware layout, route the duplicate check result back to the wizard as a state event (DuplicateDetected), and let the wizard render a local inline warning banner instead of a bottom sheet.

medium impact medium prob technical

The bottom sheet and comparison panel involve complex layouts with multiple interactive elements. Screen reader users (particularly relevant for Blindeforbundet) may struggle with the side-by-side comparison layout if semantics are not carefully ordered.

Mitigation & Contingency

Mitigation: Design the comparison panel with a single-column semantic order (read record A fully, then record B fully) regardless of visual layout. Use Flutter's `Semantics` widget with `sortKey` to enforce correct traversal order. Test with TalkBack and VoiceOver against the WCAG 2.2 AA reading order criteria.

Contingency: If side-by-side layout cannot achieve acceptable screen reader ordering, switch to a stacked tab layout (Tab A / Tab B) for the comparison panel that is semantically simple even if less visually immediate for sighted users.