critical priority high complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

User list renders as a scrollable ListView with each user occupying exactly one row regardless of chapter count
Each row displays: full name, role badge (colour-coded per design tokens), account status chip (active/inactive/pending), chapter affiliation chips (up to 5, overflow shown as '+N more'), certification state icon
Role badge colours match the design token system — no hardcoded hex values in widget code
Infinite scroll: when the user scrolls within 3 rows of the bottom, LoadNextPage event is dispatched to the BLoC
A loading spinner row is appended at the bottom of the list while isLoadingNextPage is true
When hasMore is false, a 'End of list' indicator replaces the spinner
Pull-to-refresh (RefreshIndicator) dispatches PullToRefresh event and resets to page 1
Empty state (zero users matching filters) shows an illustrated empty state with actionable copy ('No users match the current filters')
Error state shows an error card with a retry button that re-dispatches the last fetch event
Loading state (initial load) shows a shimmer skeleton of 8 user rows — not a centered spinner
Multi-chapter display: chapters shown as compact chips inline; if > 3 chips exceed row width, collapse to first 2 + '+N more' chip that is tappable to expand
Tapping a user row navigates to the user detail screen (no inline expansion — detail is a separate route)
All text meets WCAG 2.2 AA contrast ratio (minimum 4.5:1 for normal text, 3:1 for large text)
Widget is accessible: all interactive elements have semantic labels, role badges are not colour-only (include text), status chips include screen reader descriptions
Widget tree is efficient: use const constructors wherever possible; user row is extracted as a separate StatelessWidget to limit rebuilds

Technical Requirements

frameworks
Flutter
flutter_bloc
Design token system (colors, typography, spacing, radii)
apis
UserManagementBloc (task-004)
AppRouter for navigation to user detail screen
data models
UserSummary
UserManagementState
FilterParams
performance requirements
List must render 100 rows at 60fps on a mid-range Android device
Row rebuild must be scoped: only the changed row rebuilds on optimistic update — use BlocSelector or Equatable
Shimmer skeleton must be rendered without a layout pass for each skeleton item — use a fixed-height SliverList
security requirements
PII (names, emails) displayed in the list must not be logged or persisted to local storage by the widget layer
Inactive user rows must be visually distinct but must not reveal the reason for deactivation in the list view
ui components
UserListView
UserRowWidget
ChapterChipRow
RoleBadge
StatusChip
CertificationStateIcon
UserListSkeleton
EmptyUserListState
UserListErrorState

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Extract UserRowWidget as a StatelessWidget accepting a UserSummary and callback functions — this keeps it testable in isolation and prevents the entire list from rebuilding when one row changes. For infinite scroll, add a ScrollController to the ListView and listen for scroll position; dispatch LoadNextPage when `position.pixels >= position.maxScrollExtent - (3 * rowHeight)`. Use shimmer package or a custom shimmer implementation for the loading skeleton — avoid a single circular progress indicator as it is poor UX for list loading. For chapter chips, measure available width using LayoutBuilder and calculate how many chips fit before collapsing to '+N more'.

Use design token constants for all colours and typography — never reference Color(0xff...) directly in widget files. For WCAG compliance, test against the dark and light themes if both are supported. The certification state icon must include both an icon and a tooltip/semantics label so it is accessible to screen readers (critical for Blindeforbundet users).

Testing Requirements

Widget tests (flutter_test): render list with 10 mock users and assert all fields visible; render empty state when users list is empty; render skeleton when state is loading; render error card with retry button on error state; assert infinite scroll triggers LoadNextPage when scrolled to bottom; assert pull-to-refresh triggers PullToRefresh. Golden tests for UserRowWidget covering all role/status/certification combinations. Accessibility audit: run flutter_test semantics checks to confirm all interactive elements have semantic labels. Performance test: render 200 rows in a test and assert no jank (frame time < 16ms).

WCAG contrast check: assert text colours meet 4.5:1 minimum using colour contrast utility in tests.

Component
User Account Management Screen
ui high
Epic Risks (3)
medium impact medium prob technical

Displaying NHF users with membership in up to 5 local chapters in a flat list view without duplicating entries requires a non-trivial aggregation query. Incorrect query design could result in duplicated user rows or missing chapter affiliations, confusing admins and causing incorrect role assignments.

Mitigation & Contingency

Mitigation: Design the user list query to GROUP BY user_id and aggregate chapter affiliations as an array field. Use AdminRepository's typed models to surface this aggregated structure to the UI. Validate with a test dataset containing users in 5 chapters.

Contingency: If aggregation query complexity proves too high for real-time filtering, implement a separate multi-chapter affiliation fetch triggered only when a specific user row is expanded, reducing query complexity for the base list.

medium impact medium prob technical

Composable multi-dimensional filters (role + chapter + status + certification state) applied server-side against an org with 2,000+ users may produce slow queries, particularly when filtering by certification state requires joining an additional table.

Mitigation & Contingency

Mitigation: Ensure the relevant filter columns (role, status, chapter_id, certification_expiry) are indexed in Supabase. Use cursor-based pagination rather than OFFSET to maintain consistent performance at high page numbers. Profile filter query combinations against a large dataset during development.

Contingency: If multi-filter performance degrades in production, introduce a denormalised search index table updated on user status changes, allowing the list query to filter from a single table.

medium impact medium prob integration

Deactivating a user account that has ongoing activity assignments, open expense claims, or active chapter affiliations may leave orphaned records or break downstream workflows if the deactivation does not trigger correct cascade handling.

Mitigation & Contingency

Mitigation: Define and document the expected state of each dependent record type on user deactivation before implementing the toggle. Implement deactivation as a UserManagementService operation that checks for and warns about open dependencies before persisting. Write integration tests covering each dependency type.

Contingency: If orphaned record issues are discovered post-launch, provide an admin-accessible reconciliation view that surfaces users with inconsistent dependency states and allows manual resolution without requiring a code deploy.