high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

Service exposes an `updateContact(ContactUpdatePayload payload)` method returning a `Stream<ContactEditResult>` that emits optimistic, success, or failure states
Optimistic update is applied immediately to local state before Supabase round-trip completes; UI reflects changes within 50ms of submission
On Supabase success, optimistic state is confirmed and a `ContactEditSuccess` event is emitted with the persisted contact snapshot
On Supabase error (network, RLS violation, constraint), optimistic state is rolled back and a `ContactEditFailure` event is emitted with a typed error (e.g., `FieldValidationError`, `ConflictError`, `NetworkError`)
Concurrent edit conflict (ETag / updated_at mismatch) is detected and surfaces a `ConflictError` with both local and remote snapshots for UI resolution
Each `FieldValidationError` carries the field identifier matching the form field key so the edit screen can highlight the correct input
Service reads current contact state exclusively from `contact-detail-repository`; it never fetches independently from Supabase
All mutations pass through `contact-form-validator` before being dispatched to Supabase; invalid payloads are rejected with typed errors before any network call
Service is injectable via Riverpod and has no singleton state — each screen instance gets its own service scope
Unit tests cover: successful edit, validation rejection, optimistic rollback on network failure, and conflict detection

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
apis
Supabase REST API (contacts table PATCH)
Supabase Realtime (optional — for conflict detection via updated_at)
data models
Contact
ContactUpdatePayload
ContactEditResult (sealed class: optimistic / success / failure)
ContactFieldError
performance requirements
Optimistic state must be emitted within 50ms of `updateContact()` call
Supabase PATCH must complete within 2s under normal network conditions
Service must not block the UI thread; all async work on Dart isolate-safe async paths
security requirements
Supabase RLS must prevent coordinators from editing contacts outside their organisation scope
Service must not log or surface PII (name, address, epikrise reference) in error messages or stack traces
Payload must be sanitised (trimmed, null-coalesced) before transmission
updated_at timestamp from Supabase must be used for optimistic concurrency control (no blind overwrites)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Model `ContactEditResult` as a sealed class (Dart 3) with subtypes `ContactEditOptimistic`, `ContactEditSuccess`, and `ContactEditFailure`. Store the pre-edit snapshot in memory before dispatching the PATCH so rollback is instantaneous. For concurrency control, include `updated_at` in the PATCH request as a Supabase `match` filter — if no row is updated (0 rows affected), treat this as a conflict. Use Riverpod `StateNotifierProvider` or `AsyncNotifierProvider` to scope service lifetime to the edit screen's route.

Avoid global singletons. Granular error types should be defined in a shared `contact_errors.dart` file so both the service and form widget can import the same constants without circular dependencies.

Testing Requirements

Unit tests (flutter_test): (1) mock contact-detail-repository and contact-form-validator; verify that `updateContact()` emits `Optimistic → Success` on happy path; (2) verify rollback to previous state on Supabase error; (3) verify `FieldValidationError` payload contains correct field key when validator rejects; (4) verify `ConflictError` is emitted when `updated_at` from Supabase response differs from local snapshot. Integration tests: use a local Supabase instance (Docker) to verify RLS rejection returns `ContactEditFailure` with `PermissionError` type. Target ≥90% line coverage on the service class.

Component
Contact Detail Screen
ui medium
Epic Risks (2)
low impact medium prob dependency

The Peer Mentor Profile tab on the contact detail screen depends on the peer-mentor-detail-screen-widget being delivered by the separate Peer Mentor Detail feature. If that feature is delayed, the navigation affordance will be present but lead to a stub screen, which may confuse coordinators in the TestFlight pilot.

Mitigation & Contingency

Mitigation: Implement the peer mentor tab with a feature flag guard. When the Peer Mentor Detail feature is incomplete, the flag disables the tab. Coordinate delivery timelines with the team responsible for Peer Mentor Detail to align TestFlight releases.

Contingency: If the Peer Mentor Detail feature is significantly delayed, ship the contact detail screen without the peer mentor tab in the first TestFlight build and add it as an incremental update once the dependent screen is ready.

medium impact medium prob technical

The contact detail screen must adapt its layout significantly based on organisation context: NHF shows affiliation chips, Blindeforbundet shows encrypted fields and assignment status, standard contacts show neither. Managing this conditional rendering without introducing bugs in each variant is complex and increases the risk of organisation-specific regressions.

Mitigation & Contingency

Mitigation: Define a ContactDetailViewModel that resolves all org-specific flags (showEncryptedFields, showAssignmentStatus, showMultiChapterChips) from the organisation config before the widget tree renders. Widget tests must cover all three organisation variants as separate test cases to catch regressions.

Contingency: If conditional rendering logic grows unwieldy, refactor into separate composable section widgets (ProfileHeaderSection, AffiliationSection, EncryptedFieldsSection) that are conditionally included by the parent screen, isolating org-specific logic to individual components.