critical priority medium complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

DuplicateWarningBottomSheet renders as a modal bottom sheet that cannot be dismissed via back gesture, Android back button, or drag — only the Cancel button closes it
Sheet has a fixed header with title 'Duplicate Activity Found' using the heading design token (typography.heading3 or equivalent) and a warning icon using the design system's warning color token
A clearly delineated placeholder area is present for the DuplicateComparisonPanel with a minimum height of 120dp and a visible border using the design token border color
Three action buttons are rendered at the bottom: 'Proceed Anyway' (primary style), 'Merge' (secondary style), 'Cancel' (text/ghost style) — all using design token colors and meeting 44dp minimum touch target
Button labels are in plain English with no abbreviations; font size respects design token typography.body
Sheet background uses the design token surface color (not hardcoded hex); sheet corner radius uses design token radii.modal
Sheet is scrollable when content exceeds viewport height, ensuring the action buttons remain visible at the bottom (sticky footer pattern)
Widget accepts a required `onProceed`, `onMerge`, and `onCancel` callback parameters — no internal navigation logic
Widget renders without overflow errors at screen widths from 320dp to 428dp
All three buttons and the header are wrapped in Semantics widgets with descriptive labels

Technical Requirements

frameworks
Flutter
performance requirements
Sheet must animate open in under 300ms using standard Flutter bottom sheet curve
Widget must not cause any frame drops during open animation (maintain 60fps)
security requirements
No PII is rendered in this scaffold task — placeholder only; ensure the placeholder area cannot overflow to expose underlying wizard content
ui components
DuplicateWarningBottomSheet (this task)
AppButton (design system primary, secondary, ghost variants)
Design token: typography.heading3, typography.body, colors.warning, colors.surface, radii.modal, spacing tokens

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use showModalBottomSheet with isDismissible: false, enableDrag: false, and useRootNavigator: true to ensure the sheet renders above all other navigation layers including the activity wizard. Wrap the sheet body in a Column with a scrollable Expanded middle section and a non-scrollable sticky bottom bar for the action buttons — use a Column(children: [Expanded(child: SingleChildScrollView(...)), actionButtonRow]) pattern. For the WillPopScope/PopScope (Flutter 3.16+: PopScope with canPop: false), intercept the Android back button and do nothing (do not call Navigator.pop). The placeholder area for DuplicateComparisonPanel should be a Container with a BoxDecoration using the border token, ConstrainedBox(minHeight: 120), and a centered Text('Duplicate details will appear here') placeholder.

All design tokens must be accessed via the project's established token access pattern (e.g., context.tokens.spacing.md) — never hardcode values. Pass onProceed, onMerge, onCancel as required VoidCallback parameters; the calling widget (or BLoC) owns the resolution logic.

Testing Requirements

Widget tests: verify sheet cannot be dismissed via WillPopScope/PopScope or drag gesture. Verify all three buttons render with correct design token styles. Verify placeholder area is present with minimum height constraint. Verify callbacks are invoked on correct button taps.

Verify no RenderOverflow errors at widths 320dp, 375dp, and 428dp. Verify action buttons remain visible (sticky footer) when placeholder content height is set to 500dp (simulating a tall DuplicateComparisonPanel). Accessibility test: verify Semantics labels for all three buttons and header icon. Golden test: screenshot at 375dp width for visual regression baseline.

Component
Duplicate Warning Bottom Sheet
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.