critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

A sealed class hierarchy for ContactEditState is defined with four subtypes: ContactEditIdle, ContactEditSaving, ContactEditSuccess, and ContactEditError
ContactEditSuccess carries the updated Contact object so the UI can reflect the saved state without a reload
ContactEditError carries a typed ContactEditErrorType enum covering validationFailed, networkFailure, and permissionDenied
ContactEditError optionally carries a Map<String, String> fieldErrors for per-field validation messages
A BLoC (ContactEditBloc) or Riverpod StateNotifier (ContactEditNotifier) skeleton is created with a clearly defined public method saveContact(Map<String, dynamic> formData) or equivalent event
ContactDetailRepository and ContactFormValidator are injected via constructor — not created inside the service
The provider/BLoC is registered and accessible from the correct scope (feature-level, not global)
All state classes are immutable with const constructors where applicable
No save logic is implemented in this task — skeleton and public API surface only

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
Riverpod (flutter_riverpod)
data models
Contact
ContactFormValidator
performance requirements
State classes must be immutable for efficient BLoC/Riverpod rebuild diffing
security requirements
ContactEditSuccess must not carry raw form data — only the server-confirmed Contact object
Field errors in ContactEditError must not expose internal validation rule logic to the UI, only user-facing messages

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Mirror the structural approach taken in ContactDetailService (task-001) for consistency. Use the same file naming and barrel export conventions. ContactEditSuccess carrying the updated Contact object is important — it allows the contact detail screen to update its displayed data immediately after a save without issuing a separate reload request. The ContactFormValidator should be a pure Dart class (no Flutter dependencies) so it can be tested without a widget environment.

Define the public API surface as a single saveContact method signature in the skeleton even if the implementation is a no-op — this allows the UI to be wired up in parallel.

Testing Requirements

Unit tests using flutter_test: (1) Verify initial state is ContactEditIdle. (2) Verify each state subtype is instantiable with the correct typed fields. (3) Verify ContactEditErrorType enum covers validationFailed, networkFailure, and permissionDenied. (4) Verify the provider/BLoC can be constructed with mocked repository and validator without throwing.

(5) Verify ContactEditError can carry both a top-level error type and a nullable per-field error map simultaneously.

Component
Contact Edit Service
service medium
Epic Risks (2)
medium impact medium prob technical

Parallel fetching of profile, activity history, and assignment status from contact-detail-service may produce race conditions where partial state is emitted to the UI before all fetches complete, resulting in flickering or incorrect loading indicators.

Mitigation & Contingency

Mitigation: Use Future.wait or a single composed BLoC event that only emits a loaded state once all three futures resolve. Define a strict state machine: initial → loading → loaded/error with no intermediate partial-loaded states emitted to the UI.

Contingency: If parallelism proves unreliable in testing, fall back to sequential fetching with a combined loading indicator. The 500ms target may need to be renegotiated with stakeholders if sequential fetching exceeds it on slow connections.

high impact low prob integration

The partial-field update pattern in contact-edit-service assumes the contact record has not changed between when the edit screen was loaded and when the save is submitted. Concurrent edits by another coordinator could cause the earlier editor's save to silently overwrite the later one.

Mitigation & Contingency

Mitigation: Include an updated_at timestamp in the PATCH request and configure Supabase to reject updates where the server-side timestamp differs from the client's version. Return a 409-equivalent error that the service maps to a user-readable conflict message.

Contingency: If optimistic locking is too complex for initial delivery, implement a simple 'reload and retry' flow: on save error, reload the contact detail and prompt the coordinator to re-apply their changes manually.