high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

Service is implemented as a Riverpod AsyncNotifier<ContactDetailViewModel> exposed via a family provider keyed by contactId
ContactDetailViewModel combines data from contact-detail-repository and assignment-repository into a single immutable view model
Field visibility rules are resolved per org context: NHF shows chapter_affiliations and role_badges; Blindeforbundet shows sensitive_field_identifiers list and encrypted field markers; base fields shown for all orgs
Loading state is emitted immediately while both repository calls are in-flight (parallel fetch using Future.wait)
On success, AsyncData<ContactDetailViewModel> is emitted within 500ms for cached or prefetched data
On repository error, AsyncError with a typed ContactDetailServiceFailure is emitted — UI can display an actionable error message
On empty result (contact not found), a ContactNotFoundFailure is emitted distinguishable from network errors
Prefetch is triggered on route push by calling ref.read(contactDetailProvider(id).notifier).prefetch(id) from the router
Service does not contain any UI or Flutter widget code — pure Dart with Riverpod
Re-fetching the same contact within a 30-second window returns cached data without a new Supabase call

Technical Requirements

frameworks
Flutter
Riverpod (AsyncNotifier, family)
freezed
apis
ContactDetailRepository interface
AssignmentRepository interface
data models
ContactDetailViewModel
ContactDetailModel
AssignmentModel
OrgFieldVisibilityRules
ContactDetailServiceFailure
performance requirements
Parallel fetch of contact detail and assignment data using Future.wait — not sequential
500ms load time target from route push to first data render
30-second in-memory cache keyed by contactId to avoid redundant fetches
security requirements
Field visibility rules must be enforced server-side (RLS) as the primary control — service-side rules are an additional UI layer only
Service must not expose encrypted field values directly in ContactDetailViewModel — only the encrypted result and field identifier
Org context must be verified from the authenticated session, not passed as a client parameter

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement as a Riverpod AsyncNotifierProvider.family where the family parameter is the contactId. In the build method, use Future.wait([contactRepo.getContactDetail(id), assignmentRepo.getAssignment(id)]) for parallel fetching. Resolve field visibility rules via an injected OrgFieldVisibilityRules service (or a simple value class derived from the current user's org context read from a Riverpod auth provider). For the 30-second cache, store a _lastFetched timestamp in the notifier and check it at the start of build — if within the window, return the current state without re-fetching.

For prefetch on route push, use GoRouter's redirect or a route observer to call the notifier's prefetch method; alternatively, use Riverpod's ref.listen in the router configuration. Keep the ViewModel assembly in a private _buildViewModel() method for readability and independent testability.

Testing Requirements

Unit tests using flutter_test with mocked repository interfaces. Test cases: both repositories return successfully — verify ContactDetailViewModel assembled correctly, repository fetch runs in parallel (verify Future.wait used, not sequential await), contact-detail-repository fails — verify AsyncError emitted with correct failure type, assignment-repository fails — verify graceful degradation (contact data shown without assignment info, not total failure), contact not found — verify ContactNotFoundFailure distinguishable from NetworkFailure, NHF org context — verify chapter_affiliations included in ViewModel, Blindeforbundet context — verify sensitive_field_identifiers included. Cache test: two calls within 30s use same data (mock repository called only once). Target 90%+ coverage.

Component
Contact Detail Screen
ui medium
Epic Risks (2)
low impact medium prob dependency

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.

medium impact medium prob technical

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.