critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

Four sealed/abstract state classes exist: ReadReceiptIdle, ReadReceiptWriting, ReadReceiptConfirmed(fieldKey: String), ReadReceiptError(userMessage: String)
ReadReceiptConfirmed carries the fieldKey so the UI can identify which field was confirmed
A Riverpod StateNotifier<ReadReceiptState> or BLoC is scaffolded with the correct provider definition and exported from read_receipt_service.dart
The public method recordFieldReveal(String contactId, String fieldKey) is declared with correct parameter types — implementation may throw UnimplementedError at this stage
FieldEncryptionUtils is injected via constructor — the service does not instantiate it directly
The provider is keyed per-contact (using a family provider) so each contact detail screen has its own state
All state classes are immutable (use final fields, const constructors where possible)
The file compiles with zero errors and zero analyzer warnings under strict analysis_options.yaml

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
data models
ReadReceiptState
ReadReceiptIdle
ReadReceiptWriting
ReadReceiptConfirmed
ReadReceiptError
performance requirements
State classes must be lightweight value objects — no heavy initialization in constructors
security requirements
FieldEncryptionUtils must be injected, never instantiated internally, to allow test doubles
No plaintext field values stored in any state class — only field keys (identifiers)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use a sealed class hierarchy for ReadReceiptState in Dart 3+ (sealed class ReadReceiptState {}). This enables exhaustive pattern matching in the UI without a default branch. Define the family provider as: final readReceiptServiceProvider = StateNotifierProvider.family((ref, contactId) => ReadReceiptService(contactId: contactId, encryptionUtils: ref.watch(fieldEncryptionUtilsProvider))). Keep ReadReceiptWriting stateless (no payload needed — it signals 'in progress').

The fieldKey in ReadReceiptConfirmed should be a non-nullable String matching the key used in the encrypted fields schema. Follow the same state naming convention as ContactEditService and ContactDetailService for consistency across the codebase.

Testing Requirements

Write unit tests verifying: (1) ReadReceiptService initializes in ReadReceiptIdle state, (2) ReadReceiptConfirmed.fieldKey returns the correct value passed at construction, (3) ReadReceiptError.userMessage is non-null, (4) the provider family returns distinct instances for different contactId keys, (5) FieldEncryptionUtils can be swapped for a mock via constructor injection. No Supabase interaction is tested at this stage.

Component
Read Receipt 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.