critical priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

DiffComputer is a pure Dart class with no Flutter or service dependencies — it only operates on ActivityRecord value objects
DiffComputer.compute(ActivityRecord a, ActivityRecord b) returns an ActivityDiffResult containing a FieldDiffResult for each of: date, duration, activityType, contactId, notes
FieldDiffResult contains: fieldName (String), isDivergent (bool), valueA (dynamic), valueB (dynamic)
Date comparison uses day-level granularity (two activities on the same calendar day are NOT divergent on date, regardless of time-of-day)
Notes comparison treats null and empty string as equivalent (both map to 'No notes' — not divergent against each other)
Duration comparison uses integer minutes — 59.9 and 60.0 minutes are NOT divergent
DuplicateComparisonPanel consumes ActivityDiffResult and passes isDivergent to each FieldRow
When isDivergent is true, the FieldRow renders with a visual distinction (placeholder styling acceptable in this task — full styling in task-010)
DiffComputer.compute() is a pure function: same inputs always produce the same output, no side effects
ActivityDiffResult is an immutable value object (use freezed or manual const constructor)

Technical Requirements

frameworks
Flutter
Dart (pure)
freezed (for immutable value objects)
data models
ActivityRecord
FieldDiffResult
ActivityDiffResult
performance requirements
DiffComputer.compute() must complete synchronously in < 1ms — no async, no I/O
security requirements
DiffComputer must not log or expose field values externally — it is a pure transformation utility
ui components
FieldRow (updated to accept isDivergent bool and render placeholder divergent style)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place `DiffComputer` in `lib/features/deduplication/domain/diff_computer.dart` — domain layer, no Flutter imports. Define `FieldDiffResult` as a freezed data class: `@freezed class FieldDiffResult with _$FieldDiffResult { const factory FieldDiffResult({ required String fieldName, required bool isDivergent, required Object? valueA, required Object? valueB }) = _FieldDiffResult; }`.

`ActivityDiffResult` holds 5 `FieldDiffResult` entries. For date comparison: `a.date.year == b.date.year && a.date.month == b.date.month && a.date.day == b.date.day`. For notes: normalize with `(s?.trim().isEmpty ?? true) ?

null : s?.trim()` then compare. In `DuplicateComparisonPanel.build()`, call `DiffComputer.compute(activityA, activityB)` synchronously and pass `isDivergent` flags down to each `FieldRow`. This design keeps the panel stateless — diff computation is O(1) and adds negligible build time.

Testing Requirements

Pure unit tests (no Flutter test runner needed for DiffComputer): test all 5 fields for divergent and non-divergent cases. Test date day-granularity edge case (same day different time = not divergent). Test notes null vs empty equivalence. Test duration integer rounding.

Test that DiffComputer.compute() is referentially transparent (call twice with same inputs, same output). Widget tests: render DuplicateComparisonPanel with a pair where 2 of 5 fields are divergent and assert those FieldRow widgets carry isDivergent=true. Minimum 100% statement coverage on DiffComputer (it is a pure utility).

Component
Duplicate Comparison Panel
ui medium
Epic Risks (2)
medium impact medium prob technical

If the duplicate check RPC fails due to a network error or Supabase outage, the service must decide whether to block submission entirely (safe but disruptive) or allow submission to proceed silently (functional but risks data duplication). An incorrect choice leads to either user frustration or data quality issues.

Mitigation & Contingency

Mitigation: Define an explicit error policy in the service: RPC failures result in a DuplicateCheckResult with status: 'check_failed' and no candidates. The caller treats this as 'allow submission, flag for async review'. Document this as the intended graceful degradation behaviour in the service interface contract.

Contingency: If stakeholders require blocking on RPC failure, expose a configurable `failMode` parameter in the service that can be toggled per organisation via the feature flag system without a code deployment.

medium impact medium prob scope

The DuplicateComparisonPanel must handle varying activity schemas across organisations (NHF, HLF, Blindeforbundet each have different activity fields). A rigid layout may not accommodate all field variations, causing truncation or missing data in the comparison view.

Mitigation & Contingency

Mitigation: Design the panel to render a dynamic list of key-value pairs rather than a fixed-column layout. Define a `ComparisonField` model that each service populates with only the fields relevant to the activity type and organisation, allowing the panel to adapt without schema knowledge.

Contingency: If dynamic rendering proves too complex within the timeline, ship a simplified panel showing only the five most critical fields (peer mentor, activity type, date, chapter, submitter) and log a follow-up ticket for full field rendering in a later sprint.