critical priority medium complexity frontend pending fullstack developer Tier 3

Acceptance Criteria

Screen is reachable via GoRouter route `contact/:id` and loads contact data via `contact-detail-service` using the `id` path parameter
Screen meets the 500ms time-to-interactive target: profile header (name, role badge, org metadata) is visible within 500ms of navigation on a mid-range device
Profile header shows: contact display name, role badge (colour-coded by role using design tokens), organisation name, and membership number (if present)
Edit button in the header is visible only to users with `coordinator` or `admin` roles (verified via `permission-checker`) and navigates to `contact/:id/edit`
For Blindeforbundet contacts, `assignment-status-indicator` is rendered beneath the header showing open/closed assignment state
For NHF contacts with multiple chapter affiliations, `multi-chapter-affiliation-chip` is rendered showing up to 5 chapters
Sensitive fields (name, address, epikrise reference) for Blindeforbundet contacts are rendered as `encrypted-field-display` widgets, never as plain text
For contacts with `peer_mentor` role, a 'Peer Mentor Profile' button/tab is rendered that navigates to `peer-mentor-detail-screen-widget`; this element is absent for non-peer-mentor contacts
`activity-history-list` is rendered below the profile section and supports infinite scroll as per its own acceptance criteria
Full VoiceOver/TalkBack traversal: every interactive element has a meaningful `Semantics` label; traversal order matches visual top-to-bottom, left-to-right layout; no focus traps
Screen handles loading state (skeleton header + skeleton sections), error state (full-screen error with retry), and empty/not-found state (404 message with back button)
Screen uses only design token colours, typography, and spacing — no hardcoded values
Deep-linking to `contact/:id` from a push notification resolves correctly (GoRouter `initialLocation` handling)

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
GoRouter
apis
contact-detail-service (watchContact stream)
permission-checker
assignment-status-indicator
multi-chapter-affiliation-chip
encrypted-field-display
activity-history-list
peer-mentor-detail-screen-widget
data models
Contact
ContactRole (enum)
OrganisationType (enum: nhf | blindeforbundet | hlf)
ChapterAffiliation
AssignmentStatus
performance requirements
Profile header visible within 500ms of navigation (use optimistic data from contacts list cache if available)
Screen must maintain 60fps scroll through all sections on mid-range Android (Pixel 4a equivalent)
Lazy-load `activity-history-list` section — defer first page fetch until the section is scrolled into view (or 200ms after mount)
Screen must not perform redundant Supabase fetches if contact data is already in the Riverpod cache
security requirements
Route must validate that the authenticated user has permission to view the requested contact ID (via `contact-detail-service` RLS — no client-side-only check)
Sensitive fields must never be passed as constructor arguments in plain String form — always pass encrypted blobs to `encrypted-field-display`
Edit button visibility check via `permission-checker` must not be the sole security control — server-side RLS is the authoritative guard
Deep-link handling must not bypass the auth check: unauthenticated deep-link navigates to login first, then returns to contact/:id
ui components
ContactProfileHeader (name, role badge, org metadata, edit button)
AssignmentStatusIndicator (Blindeforbundet only)
MultiChapterAffiliationChip (NHF only)
EncryptedFieldDisplay (Blindeforbundet sensitive fields)
PeerMentorProfileButton (peer mentor role only)
ActivityHistoryList
ContactDetailSkeleton (loading state)
ContactDetailError (error + retry)
ContactDetailNotFound (404 state)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use a `StreamProvider` (Riverpod) that wraps `contact-detail-service.watchContact(id)` to reactively drive the screen. The screen widget is a `ConsumerWidget` that maps the `AsyncValue` to loading/error/data states using `.when()`. Pre-warm the contact data by passing the contact snapshot from the contacts list navigation into the Riverpod provider family as an initial value — this achieves the 500ms target without waiting for the Supabase round-trip. Use a `CustomScrollView` with `SliverToBoxAdapter` sections to allow smooth scroll performance across heterogeneous content.

Organisation-specific sections (`AssignmentStatusIndicator`, `MultiChapterAffiliationChip`, encrypted fields) should be conditionally rendered based on `contact.organisationType` and `contact.roles` — centralise this logic in a `ContactDetailSectionResolver` helper to keep the screen widget clean. For accessibility traversal, wrap the profile header, each section, and the activity list in `MergeSemantics` or explicit `Semantics` nodes with `sortKey` to enforce VoiceOver reading order independent of widget tree order.

Testing Requirements

Widget tests (flutter_test): (1) render with NHF coordinator contact — verify header, chapter chips, activity list, no encrypted fields, no assignment indicator; (2) render with Blindeforbundet contact — verify encrypted field widgets present, assignment indicator visible, no chapter chips; (3) render with peer mentor contact — verify peer mentor button/tab is present; (4) render with non-peer-mentor — verify peer mentor button absent; (5) render as read-only role — verify edit button absent; (6) render as coordinator — verify edit button present and tappable; (7) loading state — verify skeleton renders; (8) error state — verify retry button; (9) contact not found — verify 404 state. Integration test: navigate via GoRouter to `contact/:id` with a mock route parameter and verify correct contact is loaded. Accessibility: full `SemanticsHandle` traversal test — verify all interactive elements have labels and traversal order. Performance: measure frame build time for initial render using `WidgetTester.pump()` timing.

Target ≥85% widget test coverage.

Component
Contact Detail Screen
ui medium
Dependencies (6)
Implement the assignment status indicator UI widget that visually communicates whether a contact has an active assignment, is unassigned, or is on a waiting list. Use design token colors and accessible semantics labels. The widget must be stateless, accept an AssignmentStatus enum, and render correctly in both light and dark themes for Blindeforbundet contexts. epic-contact-detail-and-edit-main-screen-task-005 Implement the activity history list section widget that renders paginated past activity entries for a contact, sourced from contact-detail-service. Each row shows activity type icon, date, duration, and coordinator name. Support infinite scroll with skeleton loading states. Fully accessible with screen reader row announcements and focus management per WCAG 2.2 AA. epic-contact-detail-and-edit-main-screen-task-010 Build the contact detail service that orchestrates data retrieval from contact-detail-repository and assignment-repository, assembles a unified ContactDetailViewModel, resolves org-specific field visibility rules, and exposes a Riverpod AsyncNotifier. Handles loading, error, and empty states. Provides a 500ms load time target by prefetching on route push. epic-contact-detail-and-edit-main-screen-task-007 Implement the multi-chapter affiliation chip widget for NHF contacts who belong to up to 5 chapters simultaneously. Render chips in a horizontal scrollable row with truncation and overflow indicator when more than 5 chips are present. Each chip must include an accessible label with the chapter name and a tooltip for the full name on long press. Use design token typography and spacing. epic-contact-detail-and-edit-main-screen-task-006 Implement the edit contact screen that composes multi-chapter-affiliation-chip for NHF chapter management, drives validation through contact-form-validator, and submits via contact-edit-service. The screen must be visible only to coordinators and admins (hidden for read-only roles via permission-checker). Handle GoRouter route contact/:id/edit, inline error display, and save confirmation feedback. epic-contact-detail-and-edit-main-screen-task-011 Implement the encrypted field display widget for Blindeforbundet sensitive fields (name, address, epikrise reference). The widget shows a masked placeholder by default, requires an explicit tap-to-reveal interaction, uses field-encryption-utils for decryption on reveal, triggers read-receipt-service on first reveal, and displays a screen reader warning dialog before exposing sensitive content. Must meet WCAG 2.2 AA contrast and focus requirements. epic-contact-detail-and-edit-main-screen-task-012
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.