critical priority high complexity backend pending backend specialist Tier 4

Acceptance Criteria

When the user selects Merge, the existing duplicate activity record is updated with fields from the draft where the draft value differs from the existing record
Fields that are identical between draft and existing record are not overwritten — only changed fields are applied
The draft is discarded after a successful merge — no new activity record is inserted
merge_strategy defines which fields from the draft take precedence: date, duration, and activity_type_id from draft always win; contact_id and organization_id from existing record always win
Bottom sheet closes after successful merge and a confirmation message is shown: 'Existing activity updated'
If the update fails, the bottom sheet remains open with an error message — no partial update is persisted (atomic update)
The updated record retains its original id and created_at — only mutable activity fields are updated
Updated record has duplicate_reviewed = true set by DuplicateReviewedFlagMiddleware
BLoC emits ActivityMergeSuccess state on success, ActivityMergeError on failure
Merge operation is authorized — the authenticated user must be the peer_mentor_id of the existing record or a coordinator in the same organization

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase PostgreSQL 15
Supabase Auth
data models
activity
performance requirements
Update operation must complete within 3 seconds on 4G
Merge field-diff computation is done client-side before the API call — no round-trips for diffing
security requirements
RLS must enforce that the authenticated user can only update activity records where peer_mentor_id matches their user_id or they are a coordinator in the same organization_id
organization_id on the existing record must not be changed during merge — immutable field
Merge payload must be built server-side-safely: only fields in the allowed-update whitelist are included in the PATCH body
ui components
Merge button loading state
Confirmation snackbar: 'Existing activity updated'
Error inline message in bottom sheet

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement merge() as a separate method on DuplicateResolutionHandler. Build a MergePayloadBuilder utility that diffs ActivityDraft against the existing Activity and returns only the mutable changed fields. Define a MERGEABLE_FIELDS constant (e.g., ['date', 'duration_minutes', 'activity_type_id', 'summary']) to act as the whitelist — never merge id, organization_id, peer_mentor_id, contact_id, or created_at. Use Supabase's .update(payload).eq('id', existingId) — never use upsert for merge as it can silently create a record if the id is not found.

Wrap in a transaction if Supabase Edge Function is available; otherwise accept last-write-wins semantics with the understanding that coordinators can correct records.

Testing Requirements

Write unit tests for DuplicateResolutionHandler.merge() covering: (1) only differing fields are included in the update payload; (2) organization_id and contact_id are not overwritten; (3) duplicate_reviewed = true is set on the payload; (4) on success, ActivityMergeSuccess emitted; (5) on failure, ActivityMergeError emitted. Write integration tests against Supabase test project: (6) existing record is updated with draft fields; (7) no new record is inserted; (8) RLS blocks merging into another user's record. Test field-diff logic exhaustively with equal fields, all-different fields, and partially different fields.

Component
Duplicate Resolution Handler
service 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.