high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

ReadReceiptConfirmed state carries fieldKey: String so consumers can identify which field was confirmed
Each distinct field key has its own independent confirmation state — confirming field A does not affect field B
The EncryptedFieldDisplay widget can read the confirmation state for its specific fieldKey without receiving the full service state
Confirmation state does not auto-reset to Idle after display — it persists until the widget is disposed or a new reveal is triggered for the same field
If two fields are revealed in rapid succession, both eventually reach ReadReceiptConfirmed independently (no state collision)
The state stream is broadcast-capable — multiple widgets watching the same fieldKey receive the same update
A selector or computed provider is exposed so widgets can subscribe to a boolean 'isConfirmed(fieldKey)' without rebuilding on unrelated state changes
Unit tests verify that confirming field X does not alter the state of field Y

Technical Requirements

frameworks
Flutter
Riverpod
data models
ReadReceiptConfirmed
ReadReceiptState
performance requirements
State updates must cause widget rebuilds only for the specific fieldKey that changed — use Riverpod select() to prevent unnecessary rebuilds
No polling interval — state is push-based from the service stream
security requirements
Confirmation state must not expose the revealed field value — only the fieldKey identifier
ui components
EncryptedFieldDisplay

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

To support per-field-key state independence, change the provider family key from String contactId to a record type (String contactId, String fieldKey): final readReceiptServiceProvider = StateNotifierProvider.family. This gives each (contactId, fieldKey) pair its own isolated StateNotifier instance. In EncryptedFieldDisplay, watch readReceiptServiceProvider((contactId, fieldKey)).select((s) => s is ReadReceiptConfirmed) to get a bool that only triggers rebuilds on Idle↔Confirmed transitions. Alternatively, if a single notifier per contact is preferred, use a Map as the state type and update only the key for the affected fieldKey — but the family approach is simpler and more testable.

The confirmation UI element (tick icon or 'Sett' label with timestamp) should use the design token color system for the confirmed visual state.

Testing Requirements

Write unit tests: (1) confirm that after recordFieldReveal('contact-1', 'ssn') the state for fieldKey 'ssn' is ReadReceiptConfirmed and the state for fieldKey 'phone' remains ReadReceiptIdle, (2) confirm that rapid successive reveals of different field keys each produce distinct Confirmed states, (3) confirm that a widget using select() to watch isConfirmed('ssn') is not notified when 'phone' is confirmed, (4) confirm that calling recordFieldReveal for an already-confirmed field re-enters Writing then Confirmed (creates a new audit record). Write a widget test confirming EncryptedFieldDisplay shows the tick/timestamp only after the Confirmed state is emitted for its specific fieldKey.

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.