high priority low complexity infrastructure pending frontend specialist Tier 1

Acceptance Criteria

BadgeIconAssetManager exposes: resolveIcon(String iconKey) → BadgeIconSource, preloadIcons(List<String> iconKeys, BuildContext context) → Future<void>, and a placeholder icon source for unresolved keys
BadgeIconSource is a sealed class (or equivalent) with two subtypes: AssetBadgeIcon (assetPath: String) and NetworkBadgeIcon (url: String)
resolveIcon checks a bundled icon manifest (Map<String, BadgeIconSource>) first; if not found, returns the placeholder without throwing
preloadIcons uses Flutter's precacheImage to warm the image cache for all specified icon keys before the badge shelf widget renders
Missing icon fallback renders a neutral placeholder (e.g., a grey circle with a question mark) — never a broken image or exception
Icon manifest is populated from BadgeDefinitionRepository icon keys at app startup or on first badge screen open
BadgeShelfWidget (component 544) uses BadgeIconAssetManager exclusively — no direct asset path strings in widget code
Unit tests cover: resolveIcon for known key returns correct subtype, resolveIcon for unknown key returns placeholder, preloadIcons completes without error for empty list
WCAG 2.2 AA: all badge icons include a semantic label (alt text equivalent) accessible to screen readers — the manager exposes a resolveLabel(String iconKey) → String method

Technical Requirements

frameworks
Flutter
Riverpod
data models
BadgeDefinition
BadgeIconSource
performance requirements
preloadIcons must complete before the badge shelf widget is displayed — loading state must be shown during preload
resolveIcon must be synchronous (O(1) map lookup) — no async work on the hot rendering path
Preloading must not block the main thread — use compute or async image loading
security requirements
Network badge icon URLs must be validated against an allowlist of trusted domains before loading — no arbitrary URLs from the database
Asset paths must be relative paths within the Flutter asset bundle — no absolute file system paths
ui components
BadgeShelfWidget (consumer)
Placeholder icon widget (grey circle with question mark)
Loading indicator shown during preloadIcons

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define BadgeIconSource as a sealed class with AssetBadgeIcon and NetworkBadgeIcon subtypes to enable exhaustive pattern matching in widgets. The icon manifest is a static Map built at initialization time from the list of badge definitions fetched by BadgeDefinitionRepository. Bundle all known badge icons as Flutter assets under assets/badges/icons/ — register them in pubspec.yaml. The icon key convention (e.g., 'badge_honorar_3') should directly map to an asset filename (assets/badges/icons/badge_honorar_3.png) for maintainability.

For network icons (future-proofing), validate URLs against an allowlist constant before creating a NetworkBadgeIcon. Register BadgeIconAssetManager as a Riverpod Provider so the badge shelf BLoC can access it. The resolveLabel method should return the badge displayName from BadgeDefinitionRepository as the accessibility label — avoid hardcoding strings in the asset manager. This ensures screen reader users of Blindeforbundet (who rely heavily on VoiceOver) get meaningful descriptions of each badge.

Testing Requirements

Unit tests using flutter_test. Test cases: (1) resolveIcon returns AssetBadgeIcon for an icon key present in the manifest, (2) resolveIcon returns a placeholder BadgeIconSource for an unknown key, (3) preloadIcons with an empty list completes successfully, (4) resolveLabel returns a non-empty string for known icon key and a generic fallback string for unknown key, (5) URL allowlist rejects a network URL from an untrusted domain. Widget test: BadgeShelfWidget renders placeholder for an unresolved icon key without throwing. Verify WCAG semantic labels are present on rendered icon images using the Flutter accessibility tester.

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.