medium priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

ConversionFunnelWidget renders three stages in vertical order: 'Shared' (links issued), 'Clicked', 'Registered' with a visually narrowing funnel shape
Each stage displays: stage label, absolute count, and drop-off percentage from previous stage (e.g. '42% drop-off')
Drop-off percentage for first stage (Shared) shows '—' or is omitted since there is no prior stage
Widget accepts a ReferralStats model as a required parameter and is stateless — caller is responsible for providing updated data
Widget re-renders correctly when parent passes a new ReferralStats instance (no stale state)
All text elements (labels, counts, percentages) meet WCAG 2.2 AA contrast ratio (minimum 4.5:1 against background)
Funnel segment colors are sourced from design tokens — not hardcoded hex values
Widget displays a loading shimmer or empty state when ReferralStats is null or all counts are zero
Widget is fully accessible: each funnel stage has a Semantics label combining stage name, count, and drop-off (e.g. 'Clicked stage: 87 clicks, 13% drop-off from shared')
Widget renders correctly on screen widths from 320px to 480px without overflow or clipping
Widget is covered by widget tests verifying correct label, count, and drop-off computation for a known ReferralStats fixture

Technical Requirements

frameworks
Flutter
fl_chart (for bar or funnel rendering)
CustomPainter (fallback if fl_chart lacks funnel support)
data models
activity
assignment
performance requirements
Widget must render within one frame after receiving new ReferralStats — no async operations inside the widget
Avoid canvas repaints on every parent rebuild — use RepaintBoundary wrapper
security requirements
No PII rendered in the funnel widget — aggregate counts only
Widget must not make direct Supabase calls — receives pre-aggregated data from BLoC
ui components
ConversionFunnelWidget (new StatelessWidget)
FunnelStageRow (sub-widget per stage: label + count + drop-off)
FunnelConnector (arrow or narrowing visual between stages)
AccessibleFunnelStage (Semantics wrapper)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

fl_chart does not have a built-in funnel chart type as of current versions. Use BarChart with descending bar widths to approximate a funnel, or implement a CustomPainter that draws three trapezoids with Path. The CustomPainter approach gives more visual control and avoids fl_chart dependency for this specific widget. Drop-off percentage formula: ((prev_count - current_count) / prev_count * 100).toStringAsFixed(1).

Guard against prev_count == 0 before division. Use Semantics(label: ..., child: ...) wrappers — do not rely on default widget semantics for custom painted content. Colors: use AppColors.recruitmentFunnelStage1/2/3 tokens (create if not existing) — avoid magic colors. Keep the widget file self-contained and under 200 lines; extract FunnelStageRow separately.

Testing Requirements

Widget tests (flutter_test) required: (1) verify correct drop-off percentage calculation for known input (e.g. shared=100, clicked=87, registered=52 → clicked drop-off=13%, registered drop-off=40%), (2) verify zero-count edge case renders empty state without division-by-zero crash, (3) verify all three stage Semantics labels are present and correctly composed, (4) verify widget renders without overflow on 320px wide screen using a SizedBox constraint in test. Golden tests recommended for the funnel shape to catch visual regressions. WCAG contrast must be validated manually during design review — automated contrast check not feasible in flutter_test.

Component
Coordinator Recruitment Dashboard
ui medium
Epic Risks (3)
medium impact high prob dependency

BadgeCriteriaIntegration must reference specific badge definition IDs from the badge-definition-repository for recruitment badges. If those badge definitions have not been created in the database when this epic is implemented, the integration will silently fail to award badges.

Mitigation & Contingency

Mitigation: As the first task of this epic, create the four recruitment badge definitions (seed data migration) with known, stable IDs. BadgeCriteriaIntegration hardcodes these IDs as constants. Include an assertion in the integration tests that verifies the badge definition records exist in the test database.

Contingency: If the badge definitions system does not support seeding at migration time, store the badge definition IDs in a feature-flag-style config table and look them up at runtime, falling back to a no-op with a warning log if they are absent.

medium impact medium prob technical

The coordinator dashboard aggregates referral stats across all peer mentors in an organisation. For large organisations (HLF has many peer mentors nationally), the aggregation query may be slow, causing the dashboard to feel unresponsive.

Mitigation & Contingency

Mitigation: Implement the aggregation as a Supabase database view or RPC that runs server-side with appropriate indexes on (mentor_id, org_id, created_at, event_type). Add a composite index on referral_events during the foundation epic's migration. Cache the result in the Riverpod provider with a 5-minute TTL.

Contingency: If query performance remains unacceptable at scale, materialise the aggregation in a nightly pg_cron job into a stats_cache table, and serve the dashboard from the cache with a 'last updated' timestamp shown to the coordinator.

medium impact low prob integration

The existing badge award service is implemented by the achievement-badges feature. If that feature's public API (BadgeAwardService interface) changes while this epic is in progress, the BadgeCriteriaIntegration will break at compile time or behave incorrectly at runtime.

Mitigation & Contingency

Mitigation: Confirm the BadgeAwardService interface is stable and document the exact method signatures this integration depends on. Write a narrow integration test that constructs the real BadgeAwardService against a test database to detect breaking changes immediately.

Contingency: If the badge service interface changes, adapt the BadgeCriteriaIntegration adapter class to match the new contract. The adapter pattern used here isolates the change to a single class.