medium priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

The coordinator's bottom navigation tab for the deduplication queue displays a numeric badge when unresolved pair count > 0
Badge shows exact count for 1–99 unresolved pairs; displays '99+' for counts >= 100
Badge is absent (not rendered as zero) when unresolved count is 0
Badge count updates reactively within 2 seconds of a pair being resolved or a new duplicate being detected, without requiring a full app restart
Badge subscribes to a real-time stream from DeduplicationQueueService (e.g., Supabase Realtime or a polling interval of ≤ 30 s)
Badge is only visible to users with the coordinator role; peer mentor and org admin roles do not see this tab or badge
The badge has sufficient color contrast (≥ 4.5:1) against both the active and inactive tab icon states
Screen reader announces the badge value as part of the tab's accessible label (e.g., 'Deduplication queue, 5 unresolved')
Unit test: badge count stream emits correct values on resolve and on new detection events
Widget test: badge widget renders correctly for counts 0, 1, 99, and 100

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
DeduplicationQueueService.watchUnresolvedCount()
Supabase Realtime (postgres_changes on duplicate_pairs table)
data models
DuplicatePair
UnresolvedCountState
performance requirements
Badge count stream must not poll more frequently than every 30 seconds to avoid excessive Supabase reads
Stream subscription must be disposed when the coordinator logs out or the app is backgrounded for > 5 minutes
security requirements
Supabase Realtime subscription must be scoped to the coordinator's organization; row-level security must prevent cross-org data leakage
Badge must not render for non-coordinator roles; role guard must be applied at the navigation layer
ui components
BadgeOverlay widget (reuse existing badge component from design token system if available)
NavigationTabWithBadge (wraps standard BottomNavigationBarItem)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Expose a Stream from DeduplicationQueueService.watchUnresolvedCount() backed by a Supabase Realtime subscription on the duplicate_pairs table filtered by status='unresolved' and the coordinator's org_id. Wrap in a Riverpod StreamProvider or a BLoC that transforms the stream into a UI-safe integer. In the bottom navigation builder, read this provider and conditionally overlay the badge widget. Use Flutter's existing Badge widget (available from Flutter 3.x material library) rather than a custom implementation, so accessibility semantics are handled by the framework.

If Supabase Realtime is unavailable or the subscription drops, fall back to a 30-second polling interval using Stream.periodic; log the fallback event. Ensure the stream subscription is cancelled in the provider's onDispose or BLoC's close() method.

Testing Requirements

Unit tests (flutter_test) must cover the UnresolvedCountCubit: stream emits 0 when no pairs exist, emits correct count when pairs are added, and emits decremented count when a pair is resolved. Widget tests must verify: badge is not rendered when count is 0; badge shows '5' when count is 5; badge shows '99+' when count is 100; semantics label includes the count string. Use a mock DeduplicationQueueService that returns a StreamController-backed stream. Test stream disposal by verifying no stream listener leaks after widget disposal.

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.