critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

recordReadReceipt(contactId, fieldName) writes one row to read_receipts containing: user_id (from Supabase auth), contact_id, field_name, viewed_at (UTC timestamp), and device_platform (ios/android)
The write is performed exclusively via a named Supabase RPC (rpc_record_read_receipt), never via a direct table insert from the client
The RPC enforces that user_id matches auth.uid() server-side; the client cannot spoof another user's receipt
The service method is fire-and-forget: it returns a Future<ReadReceiptResult> that the caller does NOT need to await
ReadReceiptResult is a sealed class with subtypes: ReadReceiptSuccess and ReadReceiptFailure(String reason)
Network errors, Supabase timeouts, and RPC errors all map to ReadReceiptFailure; none throw uncaught exceptions
The service is injectable (accepts a SupabaseClient parameter) to support testing with a mock client
Integration test verifies that calling recordReadReceipt with a valid authenticated session creates a row in the read_receipts table
No sensitive field value (plaintext) is ever passed to or stored by this service

Technical Requirements

frameworks
Flutter
Dart
Supabase
apis
Supabase RPC: rpc_record_read_receipt(p_contact_id uuid, p_field_name text)
data models
read_receipts (user_id, contact_id, field_name, viewed_at, device_platform)
ReadReceiptResult (sealed class)
performance requirements
RPC call must not block the UI thread; execute inside an unawaited Future or via unawaited()
RPC round-trip should complete within 500 ms on a 4G connection — log a warning (non-blocking) if it exceeds this
security requirements
RPC must validate auth.uid() server-side via Supabase Row Level Security — client-supplied user_id is ignored
field_name is a free-text label only (e.g. 'phone_number'); no plaintext field value is transmitted
The read_receipts table must have RLS policy: INSERT allowed for authenticated users where user_id = auth.uid(); SELECT restricted to coordinator/admin roles only
Supabase anon key is never used; the service requires an authenticated session

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Create lib/services/read_receipt_service.dart. The class ReadReceiptService takes a SupabaseClient in its constructor for dependency injection. The public method signature: `Future recordReadReceipt({required String contactId, required String fieldName})`. Inside, call `_supabase.rpc('rpc_record_read_receipt', params: {'p_contact_id': contactId, 'p_field_name': fieldName})` and wrap in try/catch for PostgrestException and generic Exception.

Use dart:io Platform.isIOS to set device_platform — pass it as a third RPC param. The Supabase RPC definition (SQL): `CREATE OR REPLACE FUNCTION rpc_record_read_receipt(p_contact_id uuid, p_field_name text, p_platform text) RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$ BEGIN INSERT INTO read_receipts(user_id, contact_id, field_name, device_platform) VALUES (auth.uid(), p_contact_id, p_field_name, p_platform); END; $$;`. Register the service in your DI container (Riverpod provider or BLoC service locator) so EncryptedFieldDisplay can call it without tight coupling.

Testing Requirements

Unit tests (flutter_test + mockito): mock SupabaseClient.rpc() to return success and verify ReadReceiptSuccess is returned; mock to throw PostgrestException and verify ReadReceiptFailure is returned; verify that no second call is made on failure (no retry logic). Integration test (optional, requires Supabase test instance): call recordReadReceipt with a seeded authenticated user, then query read_receipts and assert exactly one row exists with correct contact_id and field_name. RLS test: attempt insert as unauthenticated user and assert permission denied. Target 90% line coverage of the service class.

Component
Encrypted Field Display Widget
ui high
Epic Risks (3)
medium impact medium prob scope

The encrypted-field-display confirmation dialog adds interaction steps that may frustrate coordinators who access sensitive fields frequently, leading to requests to bypass the flow or skip read-receipt logging, which would violate Blindeforbundet's compliance requirements.

Mitigation & Contingency

Mitigation: Design the confirmation dialog to be as minimal as possible (one clear sentence, single confirm action) and ensure it does not reappear for the same field within a single screen session. Validate the UX with Blindeforbundet coordinators during the TestFlight pilot before finalising.

Contingency: If coordinators raise strong objections, escalate to Blindeforbundet's data protection officer to determine whether a lighter confirmation pattern (e.g., biometric confirmation instead of dialog) satisfies their compliance obligation.

medium impact medium prob technical

The activity-history-list infinite scroll requires paginated Supabase queries. Contacts with hundreds of activities (e.g., an HLF peer mentor with 380 annual registrations) could cause slow page loads or memory pressure on older devices if pagination boundaries are set too large.

Mitigation & Contingency

Mitigation: Use a page size of 20 records with cursor-based pagination. Implement list item recycling using Flutter's ListView.builder. Benchmark memory usage with 400+ item simulation on a low-end test device before TestFlight release.

Contingency: If performance degrades on older devices, reduce page size to 10 and add a time-window filter (last 30 days, last 6 months, all) so the default view loads a manageable record count for most coordinators.

low impact high prob scope

The cognitive load rule engine (from the Cognitive Accessibility feature) mandates no more than 7 fields per screen section. If a contact model has more than 7 editable fields, the edit-contact-screen layout must be split into sections, adding complexity not accounted for in the initial scope.

Mitigation & Contingency

Mitigation: Audit the full contact field list from all four organisations before implementation. Group fields into logical sections (personal info, contact details, affiliation) so no single section exceeds 7 fields. Use the cognitive-load-rule-engine component if it is already delivered by the Cognitive Accessibility feature.

Contingency: If the rule-engine component is not yet available, implement a simple manual section layout with accordion-style expansion for less-frequently edited fields to stay within the 7-field guideline without blocking delivery.