critical priority high complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

WrappedAnimationController is a ChangeNotifier (or StateNotifier for Riverpod) that holds a Map<String, AnimationController> keyed by animation name
initialise(TickerProvider vsync, List<AnimationConfig> configs) creates all AnimationControllers with the specified durations and curves and marks the controller as initialised
play(animationName) starts the named animation forward; throws an AssertionError (debug) or logs a warning (release) if the name is not registered
pause() suspends all currently running animations without resetting their values
reset() stops all animations and resets their values to 0.0
dispose() calls dispose() on every AnimationController in the map and clears the map; calling dispose() twice does not throw
The read-only controllers getter returns an UnmodifiableMapView so callers cannot mutate the internal map
Calling play() before initialise() throws a StateError with a descriptive message
No AnimationController is created without a vsync — the TickerProvider is always passed from the owning widget's State
Memory leak test: navigating away from the Wrapped screen and back does not accumulate AnimationController instances (verified via Flutter DevTools or widget test)

Technical Requirements

frameworks
Flutter
Riverpod
performance requirements
initialise() completes synchronously (no async work) within one frame
Animations must run at 60 fps on a mid-range device with no jank measured via Flutter DevTools timeline
dispose() completes within one frame — no deferred cleanup
security requirements
No user data or PII is involved in animation logic
ui components
AnimationController (Flutter SDK)
CurvedAnimation
TickerProviderStateMixin or SingleTickerProviderStateMixin on owning widget

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

WrappedAnimationController should be a plain Dart class (not a widget) that accepts a TickerProvider in initialise() — this decouples it from the widget tree and makes it unit-testable. Use a StateNotifierProvider in Riverpod if the controller needs to notify the UI of animation state changes (e.g. current page index). Define an AnimationConfig value object with fields: name, duration, curve, and autoReverse.

In the owning widget's State, override dispose() to call wrappedAnimationController.dispose() before super.dispose(). Consider using a TickerProviderStateMixin (not Single-) since multiple controllers are managed. Document the expected call order (initialise → play → pause/reset → dispose) in code comments for future maintainers.

Testing Requirements

Widget tests (flutter_test): (1) initialise() creates expected number of controllers, (2) play() transitions controller status to AnimationStatus.forward, (3) pause() sets controller status to AnimationStatus.dismissed or stops progress, (4) reset() returns all controller values to 0.0, (5) dispose() does not throw on double-call, (6) controllers getter returns UnmodifiableMapView, (7) calling play() before initialise() throws StateError. Use FakeAsync or pump() to advance animation frames in tests. Verify no controller leaks using debugPrintScheduleFrameStacks if needed.

Component
Wrapped Animation Controller
infrastructure high
Epic Risks (3)
medium impact medium prob dependency

Rive animation files may not be available at implementation time, blocking the wrapped-animation-controller from being fully tested. If asset delivery is delayed, the controller cannot be validated for memory-leak-free disposal.

Mitigation & Contingency

Mitigation: Implement the animation controller with stub/placeholder AnimationController instances first so the lifecycle and disposal logic can be unit-tested independently of Rive assets. Define a named animation registry interface early so UI components can reference animations by name without coupling to specific Rive files.

Contingency: If Rive assets are not delivered before Epic 3 begins, replace Rive animations with Flutter implicit animations (AnimatedOpacity, ScaleTransition) as a drop-in and schedule Rive integration as a follow-on task once assets arrive.

high impact medium prob technical

The annual_summaries Supabase RPC aggregating 12 months of activity records per mentor may exceed acceptable query latency (>2s) for mentors with high activity volumes such as the HLF mentor with 380 registrations cited in workshop notes.

Mitigation & Contingency

Mitigation: Design the RPC to materialise summary results into the annual_summaries table via a scheduled edge function rather than computing on demand. The repository reads pre-computed rows, keeping query latency constant regardless of activity volume.

Contingency: If on-demand queries are required for real-time period switching, add a PostgreSQL partial index on (mentor_id, activity_date) and implement a client-side loading skeleton so slow queries degrade gracefully rather than blocking the UI.

medium impact low prob technical

iOS requires photo library permission before saving a screenshot to the gallery. If the permission prompt is triggered at an unexpected point in the share flow, the UX breaks and users may deny permission permanently, making gallery save unavailable.

Mitigation & Contingency

Mitigation: Trigger the permission request only when the user explicitly chooses 'Save to gallery' in the share overlay, not on screen load. Implement a pre-prompt explanation screen following Apple HIG so users understand why the permission is needed before the system dialog appears.

Contingency: If permission is denied, gracefully fall back to clipboard copy and system share sheet options which do not require photo library access, and surface a non-blocking snackbar explaining the limitation.