Implement Contact Edit Service
epic-contact-detail-and-edit-main-screen-task-008 — Build the contact edit service that reads from contact-detail-repository, applies changes validated through contact-form-validator, and persists updates back via Supabase with optimistic UI support. Handle concurrent edit conflicts, expose granular error types for form field binding, and emit success/failure events consumed by the edit screen's feedback layer.
Acceptance Criteria
Technical Requirements
Execution Context
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.
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.
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.