high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

getCurrentTier(mentorId) returns the RecognitionTier enum value for the mentor's current tier based on persisted data
isNewTier(mentorId) returns true exactly once per tier promotion — subsequent calls return false until another promotion occurs
markTierSeen(mentorId) persists the 'seen' flag via recognition-tier-repository; subsequent isNewTier calls return false
shouldShowRevealAnimation(mentorId) returns true only when isNewTier is true AND the animation has not yet been triggered in the current session
markAnimationTriggered(mentorId) resets shouldShowRevealAnimation to false for the current session without affecting the persisted 'new tier' flag
Service correctly handles the case where a mentor has no tier yet (returns RecognitionTier.none or equivalent)
Cache is invalidated when the mentor's badge count changes (receives a BadgeEarnedEvent)
All methods are safe to call from the UI thread and complete within 100ms for cache hits
Unit tests cover: first-time tier assignment, promotion detection, seen/unseen transitions, animation trigger lifecycle

Technical Requirements

frameworks
Flutter
Supabase
flutter_test
apis
RecognitionTierRepository.getTierRecord(mentorId)
RecognitionTierRepository.updateTierRecord(TierRecord)
DomainEventBus.on<BadgeEarnedEvent>()
data models
RecognitionTier
TierRecord
BadgeEarnedEvent
performance requirements
Cache hit for getCurrentTier must be synchronous
Database persistence for markTierSeen must complete within 500ms
security requirements
mentorId must match the authenticated session; reject cross-mentor queries
Tier data is read-only for peer mentors — only the system (badge-award-service) may promote tiers

Execution Context

Execution Tier
Tier 1

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.

Component
Recognition Tier Banner
ui low
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.