Implement Recognition Tier Service
epic-achievement-badges-ui-task-009 — Create the recognition-tier-service that determines and caches the current recognition tier for a peer mentor, detects when a new tier has been newly awarded (vs. already seen), exposes the tier-reveal animation trigger flag, and delegates persistence to recognition-tier-repository.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Separate two concerns: (1) persisted 'is new' flag in RecognitionTierRepository (survives app restart) — represents whether the user has acknowledged the new tier; (2) in-memory 'animation triggered' flag — represents whether the animation has played in the current session (reset on each app launch). This prevents re-playing the animation on every app open while still showing it once per session on first post-promotion launch. The tier determination logic (which tier threshold maps to which tier enum) should be defined as a const map in a TierThresholds configuration class, not hardcoded in the service. Subscribe to BadgeEarnedEvent to invalidate the cache so that a badge award immediately recalculates tier eligibility without waiting for the next explicit fetch.
Testing Requirements
Unit tests with mocktail: (1) getCurrentTier returns correct enum for each tier level; (2) isNewTier returns true before markTierSeen, false after; (3) shouldShowRevealAnimation lifecycle (true → markAnimationTriggered → false, without affecting isNewTier); (4) BadgeEarnedEvent triggers cache invalidation and re-fetch; (5) no-tier scenario returns RecognitionTier.none. Test session isolation: verify markAnimationTriggered only affects in-memory session state, not persisted 'new tier' flag. Minimum 90% branch coverage.
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.
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.