Build Duplicate Comparison Panel Widget
epic-duplicate-activity-detection-state-management-task-004 — Implement the DuplicateComparisonPanel Flutter widget that renders side-by-side or stacked comparison of the incoming activity and its candidate duplicate. Display peer mentor name, activity type, date, duration, and notes fields with visual diff highlights. Must be accessible (WCAG 2.2 AA) with proper semantics labels.
Acceptance Criteria
Technical Requirements
Implementation Notes
Build ComparisonFieldRow as a private internal widget within the same file to keep the component self-contained. Use Flutter's `LayoutBuilder` or `MediaQuery` to switch between side-by-side and stacked layouts at the 600dp breakpoint — avoid platform-specific breakpoint logic. For diff highlighting, compute the diff in a pure function outside the widget tree (e.g., `bool _fieldsDiffer(String? a, String?
b)`) to keep build() declarative. Use design tokens for the highlight color (e.g., `AppColors.warningSubtle`) and the indicator icon (`Icons.warning_amber_rounded` or equivalent from the design system). Pitfall: screen readers read each Semantics node in DOM order — ensure the Semantics tree interleaves fields in a logical reading order (incoming name → candidate name → incoming date → candidate date) rather than reading all incoming fields then all candidate fields, which breaks comprehension for VoiceOver users. This is NHF-critical: their user base includes people with cognitive disabilities and the comparison must be scannable, not confusing.
Testing Requirements
Widget tests using flutter_test: (1) renders both 'Incoming' and 'Candidate' column headers; (2) differing date field is highlighted, matching activity_type field is not highlighted; (3) responsive layout renders stacked at 400dp width, side-by-side at 700dp width — use `tester.binding.setSurfaceSize`; (4) Semantics tree contains labels for all displayed fields — use `tester.getSemantics(find.byType(DuplicateComparisonPanel))` and assert labels; (5) notes field truncates at 3 lines and 'Show more' expands it; (6) null notes field renders gracefully (empty state, no overflow); (7) golden tests for both layouts stored in `test/golden/`. Accessibility audit: run `tester.pump()` followed by `SemanticsHandle` checks. No unit tests needed (pure UI widget). Manual VoiceOver test on a physical iOS device should be performed as part of PR review.
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.
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.