critical priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Bottom sheet appears as a modal overlay intercepting the save action; the activity wizard remains mounted beneath it and is not popped from the navigation stack
Sheet displays the duplicate candidate's key fields (date, duration, activity type, peer mentor name) in a scannable summary row before showing the full DuplicateComparisonPanel
'Proceed Anyway' button triggers KEEP_BOTH resolution; 'Cancel' button triggers CANCEL resolution; 'View Details' expands the DuplicateComparisonPanel inline without navigating away
Sheet is NOT dismissible via back gesture, Android back button, or drag-to-dismiss — only explicit button taps close it, preventing accidental data loss
All interactive elements have Semantics labels readable by VoiceOver (iOS) and TalkBack (Android); focus order is logical (header → summary → comparison panel → buttons)
On sheet open, screen reader announces: 'Duplicate activity warning. A similar activity was found. Review the details and choose how to proceed.'
Warning message uses plain language (no jargon); font size respects the user's accessibility_preferences font_scale_factor; contrast ratio >= 4.5:1 (WCAG 2.2 AA)
Buttons use design token colors and meet minimum 44x44dp touch target size
Sheet renders correctly at font scale 1.0, 1.3, and 2.0 without text overflow or button clipping
Loading state shown in 'Proceed Anyway' button while the resolution handler is processing; button is disabled to prevent double-tap

Technical Requirements

frameworks
Flutter
BLoC
data models
activity
accessibility_preferences
performance requirements
Sheet must animate open within 300ms using standard Flutter bottom sheet animation
DuplicateComparisonPanel expansion must render within one frame (no async data fetch on expand — data already loaded)
security requirements
Peer mentor name and contact details shown in the comparison panel must be masked if the current user does not have coordinator role
No PII is logged to analytics when the sheet is displayed
ui components
DuplicateWarningBottomSheet (this task)
DuplicateComparisonPanel (dependency task-004)
AppButton (design system)
Semantics wrapper for all interactive elements
AnimatedSize for expandable comparison panel

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use showModalBottomSheet with isDismissible: false and enableDrag: false to prevent accidental closure. Wrap the sheet content in a DraggableScrollableSheet only if you need variable height — for this use case a fixed-height StatefulWidget is simpler and avoids accidental drag-dismiss. Read font_scale_factor from accessibility_preferences via a Riverpod provider and apply as a MediaQuery override scoped to the sheet, not globally. Use the BLoC's DuplicateWarningState to drive the loading state on the 'Proceed Anyway' button — never manage loading state locally in the widget.

For the expandable comparison panel, use AnimatedSize around the DuplicateComparisonPanel with a Curve.easeInOut animation. Ensure the Semantics tree announces the warning on sheet open by using a post-frame SemanticsService.announce() call. Follow NHF's cognitive accessibility requirement: keep warning text to maximum 2 sentences, use active voice, and avoid double negatives.

Testing Requirements

Widget tests: verify sheet cannot be dismissed via drag or back button. Verify all three action buttons are present and have correct Semantics labels. Verify loading state disables 'Proceed Anyway' button. Verify DuplicateComparisonPanel is hidden initially and expands on 'View Details' tap.

Verify plain-language warning text is present. Accessibility tests: use flutter_test's SemanticsController to verify focus order and screen reader announcements. Test at font scale 2.0 that no text overflows its container. Golden tests: screenshot at font scales 1.0 and 2.0 to catch layout regressions.

BLoC integration test: verify correct BLoC events are emitted on each button tap.

Component
Duplicate Detection BLoC
infrastructure medium
Epic Risks (2)
medium impact high prob technical

For bulk registration with many participants, running duplicate checks sequentially before surfacing the consolidated summary could introduce a multi-second delay as each peer mentor is checked individually against the RPC. This degrades the bulk submission UX significantly.

Mitigation & Contingency

Mitigation: Issue all duplicate check RPC calls concurrently using Dart's `Future.wait` or a bounded parallel executor (max 5 concurrent calls to avoid Supabase rate limits). The BLoC collects all results and emits a single BulkDuplicateSummary state with the consolidated list.

Contingency: If concurrent RPC calls hit Supabase connection limits or rate limits, implement a batched sequential approach with a progress indicator showing 'Checking participant N of M' so the coordinator understands the delay is expected and bounded.

high impact medium prob integration

In proxy registration, the peer mentor's ID must be used as the duplicate check parameter, not the coordinator's ID. If the proxy context is not correctly threaded through the BLoC and service layer, duplicate checks will silently run against the wrong person, missing actual duplicates.

Mitigation & Contingency

Mitigation: Define a `SubmissionContext` model that carries the effective `peer_mentor_id` (distinct from `submitter_id`) and pass it explicitly through the BLoC event payload. The DuplicateDetectionService always reads peer_mentor_id from SubmissionContext, never from the authenticated user session.

Contingency: If SubmissionContext threading proves difficult to retrofit into the existing proxy registration BLoC, add an assertion in DuplicateDetectionService that throws a descriptive error when peer_mentor_id is null or matches the coordinator's own ID in a proxy context, making the bug immediately visible in testing.