high priority medium complexity frontend pending frontend specialist Tier 5

Acceptance Criteria

Tapping any list item in DeduplicationQueueScreen navigates to the DeduplicationDetailScreen for that pair
DeduplicationDetailScreen renders DuplicateComparisonPanel showing both activities side by side (or stacked on narrow screens) with all relevant fields: activity type, date, time, duration, participant name, and any notes
Fields that differ between the two activities are visually highlighted (e.g., color accent or bold) to draw coordinator attention
Three resolution action buttons are displayed: 'Merge', 'Dismiss as Non-Duplicate', 'Mark Reviewed'; each button has a descriptive accessible label
Tapping 'Merge' invokes DuplicateResolutionHandler.merge(pairId) and, on success, navigates back to the queue with a success snackbar and the item removed from the list
Tapping 'Dismiss as Non-Duplicate' shows a confirmation dialog before invoking DuplicateResolutionHandler.dismiss(pairId)
Tapping 'Mark Reviewed' invokes DuplicateResolutionHandler.markReviewed(pairId) and updates the pair's status without removing it from the queue (it moves to resolved section if filter shows resolved items)
While any resolution action is pending, all three buttons are disabled and a loading indicator is shown to prevent double-submission
If a resolution action fails (network error), an error snackbar is shown and buttons are re-enabled; the pair is not modified
If the pair was already resolved before the coordinator opens the detail view (race condition), a notice is shown and resolution buttons are hidden
The detail view is reachable via deep link (required by task-011); it must accept a pairId route parameter
Widget tests cover all three resolution flows including error states

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
DuplicateResolutionHandler.merge(pairId)
DuplicateResolutionHandler.dismiss(pairId)
DuplicateResolutionHandler.markReviewed(pairId)
DeduplicationQueueService.fetchPairById(pairId)
data models
DuplicatePair
Activity
ResolutionAction
ResolutionStatus
performance requirements
Detail view must load and render within 500 ms of navigation on a mid-range device
Resolution actions must show a loading state within 100 ms of tap to provide immediate feedback
security requirements
Resolution actions must verify coordinator role server-side via Supabase RLS; client-side role check is a UX guard only
pairId must be validated as a valid UUID before any API call; invalid IDs must show an error state, not crash
ui components
DeduplicationDetailScreen (new route)
DuplicateComparisonPanel (reused from task-005)
FieldDiffHighlight widget
ResolutionActionBar (three-button row with loading state)
ConfirmationDialog (for Dismiss action)
AlreadyResolvedNotice widget

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Route this screen as /deduplication/detail/:pairId using GoRouter or the project's existing routing solution, accepting pairId as a path parameter — this doubles as the deep-link entry point required by task-011. Fetch the full pair details inside the screen's BLoC/Cubit initiation rather than passing the full object via route arguments, so deep links (which only have a pairId) also work. Model resolution state as a sealed class: Idle, Loading(action), Success(action), Failure(action, error), AlreadyResolved. The DuplicateComparisonPanel should be a stateless widget that receives two Activity objects and an optional Set of differing field keys for highlight rendering.

Implement field diffing as a pure utility function comparing field-by-field. Use a confirmation dialog (showDialog) only for the destructive 'Dismiss' action to reduce friction on 'Merge' and 'Mark Reviewed'. Ensure all three buttons share a single isLoading flag to prevent concurrent action submissions.

Testing Requirements

Unit tests must cover DeduplicationDetailBloc/Cubit: initial loading state, loaded state with pair data, each resolution action transitioning through loading → success/failure, and the already-resolved state. Widget tests must verify: DuplicateComparisonPanel renders both activities' fields; diff highlights appear on fields with different values; resolution buttons are disabled during loading; success snackbar appears and back-navigation is triggered on merge success; error snackbar appears on API failure with buttons re-enabled; AlreadyResolvedNotice is shown when pair status is already resolved. Integration test must mock DuplicateResolutionHandler and assert correct method is called with the correct pairId for each action.

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.