high priority low complexity frontend pending frontend specialist Tier 6

Acceptance Criteria

AdminStatWidget is a StatelessWidget accepting required parameters: label (String), value (String), and accentColor (Color)
Optional parameters: trend (AdminStatTrend enum: up, down, neutral) and isLoading (bool, default false)
When isLoading is true, widget renders a shimmer/skeleton placeholder instead of value and label text
When an error state is needed, widget accepts an optional errorMessage (String) and renders an error indicator with the message
Trend indicator renders an upward arrow (green accessible), downward arrow (red accessible), or neutral dash with sufficient contrast
accentColor is applied to a visual accent element (e.g., left border, top stripe) — not as background to ensure WCAG 2.2 AA contrast on text
All text elements meet WCAG 2.2 AA contrast ratio (4.5:1 for normal text, 3:1 for large text) against the card background
Widget uses only design token values from the project's token system for spacing, typography sizes, border radius, and shadow
Semantics widget wraps the card with a combined accessible label: '{label}: {value}, trend {trend description}'
Widget renders correctly at font scale 1.0, 1.5, and 2.0 (text does not overflow or clip)
Widget is covered by golden tests for loading, error, and normal states

Technical Requirements

frameworks
Flutter
data models
AdminStatDto (data source, but widget takes primitive params — decoupled from data layer)
performance requirements
Widget must be a StatelessWidget — no internal state management
Skeleton animation must use AnimatedContainer or shimmer package already in pubspec — no new dependencies
security requirements
No PII must be passed directly into the widget — values are aggregated counts only
errorMessage must be sanitised before display — do not show raw Supabase error strings
ui components
AdminStatWidget
AdminStatTrend enum (up, down, neutral)
Shimmer/skeleton placeholder
Trend arrow icon (using Flutter Icons or project icon set)

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Keep the widget purely presentational — it must not reference any BLoC, Riverpod provider, or repository directly. The parent screen is responsible for passing the resolved value string and loading/error states. Define AdminStatTrend as a Dart enum with extension methods for icon data and semantic description string (e.g., AdminStatTrend.up.semanticLabel returns 'trending up'). Use the project's existing design token constants for all styling — do not hardcode color hex values or pixel sizes.

For the WCAG contrast requirement: place text on the card's background color (not on the accentColor); use accentColor only for decorative elements like a 4px left border. The shimmer effect should use a grey gradient animation consistent with other loading states in the app. Reference the existing AppButton or similar reusable widgets in the codebase for code style conventions.

Testing Requirements

Widget tests (flutter_test): (1) Renders label and value correctly in normal state. (2) Renders shimmer skeleton when isLoading=true and hides value text. (3) Renders errorMessage when provided and hides value text. (4) Trend.up renders upward icon; Trend.down renders downward icon; Trend.neutral renders dash.

(5) Semantics test: find semantics node with combined label string. (6) Golden tests for all three states (normal, loading, error) at 1.0 font scale. (7) Overflow test: render with a 12-character label and 8-digit value at font scale 2.0 — no overflow errors. Accessibility audit: use flutter_accessibility_test or manual VoiceOver check to confirm the Semantics label is read correctly.

Component
Admin KPI Stat Widget
ui low
Epic Risks (3)
high impact medium prob security

Missing RLS policies on one or more tables (e.g., a newly added join table or a Supabase view) could expose cross-org data to org_admin queries, creating a GDPR-reportable data breach.

Mitigation & Contingency

Mitigation: Enumerate all tables and views accessed by admin queries before writing any policy. Create an automated test that attempts a cross-org query for each table from an org_admin JWT and asserts an empty result set.

Contingency: If a gap is discovered post-deployment, immediately disable the affected query surface and deploy a hotfix policy before re-enabling. Log the incident and notify DPO if any cross-org data was returned.

high impact medium prob technical

The recursive CTE for NHF's deeply nested org tree (up to 5 levels, 1,400 local chapters) may exceed the 2-second dashboard load target when resolving large subtrees on every request.

Mitigation & Contingency

Mitigation: Benchmark the recursive CTE against a synthetic NHF-scale dataset during development. Introduce a short-TTL server-side cache for subtree resolution results. Index the parent_id column on the organisations table.

Contingency: If CTE performance remains insufficient, materialise the org subtree as a precomputed closure table updated on org structure changes, and switch the RLS guard to query the closure table instead.

high impact low prob security

Incorrect JWT claim injection in AdminRlsGuard (e.g., wrong claim key name or missing refresh on org switch) could silently apply the wrong org scope, causing org_admin to see a different organisation's data without an explicit error.

Mitigation & Contingency

Mitigation: Write unit tests for the guard that verify the injected claim value against the authenticated user's org_id for every admin route. Add a server-side assertion that the claim matches the user's database record before executing any query.

Contingency: Roll back the guard to a deny-all fallback, invalidate active admin sessions, and re-issue corrected JWTs. Audit query logs to identify any sessions that received incorrect scope.