high priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

Widget renders earned state with full-colour badge image, badge name, and ISO-formatted earned timestamp
Widget renders locked state with greyscale image, locked icon overlay, and a LinearProgressIndicator showing progress toward earning
Contrast ratio between badge label text and card background meets WCAG 2.2 AA (≥4.5:1 for normal text, ≥3:1 for large text)
Semantics widget wraps the card with a descriptive label combining badge name, earned/locked state, and progress percentage for VoiceOver/TalkBack
Tap on an earned badge invokes the provided onTap callback with the badge ID; locked badges show a tooltip/snackbar explaining unlock criteria
Widget correctly displays design token colours (no hardcoded hex values) for both states
Widget is stateless and accepts a BadgeCardModel (or equivalent) as its sole data input
Widget renders correctly at system font scales 1.0×, 1.5×, and 2.0× without overflow
Golden tests pass for both earned and locked states at 375px and 768px screen widths
No accessibility warnings emitted by flutter_test's `SemanticsHandle` during test execution

Technical Requirements

frameworks
Flutter
flutter_test
data models
BadgeDefinition
EarnedBadge
performance requirements
Widget build time must not exceed 16ms on a mid-range device (60fps target)
Greyscale filter applied via ColorFilter.matrix — no additional image assets required
Image assets loaded via CachedNetworkImage or pre-bundled asset; no blocking I/O in build()
security requirements
No PII stored or rendered inside the widget
Badge image URLs must be loaded over HTTPS only
ui components
BadgeCardWidget (stateless)
BadgeImageView (image + greyscale overlay)
BadgeProgressIndicator (LinearProgressIndicator wrapper)
Semantics label wrapper

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use ColorFilter.matrix with a luminance-preserving greyscale matrix for the locked state — do not maintain a separate greyscale asset. Design token colours must be consumed from the project's token system (e.g., AppColors.badgeEarned, AppColors.badgeLocked) to support future theming. The widget should be purely presentational (no BLoC dependency); state is passed in via a BadgeCardModel. For progress, accept a double (0.0–1.0) rather than raw counts to keep the widget reusable.

Wrap the entire card in a Semantics node with `button: true` when onTap is non-null so screen readers announce it as interactive. Use ExcludeSemantics on the progress bar to avoid double-announcing progress — include it in the parent Semantics label string instead.

Testing Requirements

Unit tests: verify BadgeCardModel mapping for earned vs locked states; verify Semantics label string construction. Widget tests: render both states and assert key widget presence (Image, Text, LinearProgressIndicator, Semantics). Golden tests: capture screenshots for earned and locked at two breakpoints. Accessibility test: use flutter_test SemanticsHandle to assert no critical a11y violations.

Minimum 90% branch coverage on widget logic.

Component
Badge Shelf Widget
ui medium
Epic Risks (2)
medium impact medium prob integration

The badge-earned-celebration overlay must appear within 2 seconds of the triggering activity being saved, but badge evaluation runs server-side in an edge function triggered by a database webhook. Network latency, edge function cold start, and Supabase Realtime delivery delays could cause the overlay to appear late or not at all, breaking the motivational loop.

Mitigation & Contingency

Mitigation: Implement an optimistic UI path: after activity save, badge-bloc immediately checks whether any badge thresholds are crossed client-side using cached stats and badge definitions, showing the overlay speculatively before server confirmation. The server result then reconciles. Subscribe to Supabase Realtime on the earned_badges table for authoritative confirmation.

Contingency: If Realtime delivery is unreliable in production, add a polling fallback: badge-bloc polls for new earned badges 3 seconds after an activity save and shows the overlay if a new record is detected, accepting up to 5-second latency as a fallback SLA.

high impact low prob technical

The celebration overlay uses animation for positive reinforcement, but motion sensitivity (prefers-reduced-motion) and screen reader users require a non-animated or text-only alternative. Failing to handle this risks excluding Blindeforbundet users or triggering vestibular discomfort for motion-sensitive volunteers.

Mitigation & Contingency

Mitigation: Check MediaQuery.disableAnimations in badge-earned-celebration-overlay and skip animation entirely when true, showing a static card instead. Add an ExcludeSemantics wrapper around the decorative animation widget and a separate Semantics node with a live region announcement of the badge name and congratulatory message.

Contingency: If accessibility issues are identified in TestFlight testing with Blindeforbundet's test group, fast-track a patch that defaults to the static card path and gates the animation behind a user preference setting in notification preferences.