high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

getActiveBadgesByCategory(category) returns a non-null List<BadgeDefinition> filtered to active records only; returns empty list (not null) when no badges exist for the category
getBadgeById(id) returns the BadgeDefinition or throws BadgeDefinitionNotFoundException for unknown IDs
resolveDisplayMetadata(badgeDefinitionId) returns a BadgeDisplayMetadata object with name, imageUrl, description, and category
All methods use the in-memory cache on subsequent calls within the TTL window; repository is not called again
Cache expires after the configured TTL (default 5 minutes); subsequent call after expiry triggers a repository re-fetch
Only badges with status == 'active' are included in all listing methods
Service validates that BadgeDefinition objects from repository contain required fields; logs a warning and skips malformed records rather than throwing
TTL is configurable via constructor injection (for testability)
Service is safe to call from multiple isolates concurrently (read-only cache access)

Technical Requirements

frameworks
Flutter
Supabase
flutter_test
apis
BadgeDefinitionRepository.getAll()
BadgeDefinitionRepository.getById(id)
data models
BadgeDefinition
BadgeDisplayMetadata
BadgeCategory
performance requirements
Cache hit must resolve synchronously (Future.value, no async gap)
Full repository fetch on cache miss must complete within 1 second
Memory footprint: cache must not exceed 500 BadgeDefinition objects (enforced by LRU or size cap)
security requirements
No user-specific data in this service — definitions are global configuration
imageUrl values must be validated as HTTPS before being included in BadgeDisplayMetadata

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement the TTL cache as a private _CacheEntry class holding the data and a DateTime expiry. Check DateTime.now().isAfter(expiry) at the start of each public method. Inject a DateTime Function() clock parameter defaulting to DateTime.now for easy test overriding. Use a single Map indexed by ID as the primary cache structure, with a separate Map> for category index (storing IDs, not full objects, to avoid duplication).

Populate both structures in a single _refreshCache() private method. The service should be a singleton in the DI container since badge definitions change infrequently. Consider pre-warming the cache on app start as part of the app initialization sequence.

Testing Requirements

Unit tests: (1) first call fetches from repository and populates cache; (2) second call within TTL uses cache — verify repository called exactly once via mock; (3) call after TTL expiry re-fetches from repository; (4) getBadgeById with unknown ID throws BadgeDefinitionNotFoundException; (5) malformed record in repository response is skipped with warning log. Use fake clock (injectable DateTime provider) for TTL tests to avoid real time delays. Minimum 90% branch coverage.

Component
Badge BLoC
infrastructure 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.