critical priority high complexity backend pending backend specialist Tier 3

Acceptance Criteria

On LoadPeerMentorDetail, BLoC emits PeerMentorDetailLoading immediately before calling the service
On successful aggregation with no per-source errors, BLoC emits PeerMentorDetailLoaded with full model
On aggregation with non-empty perSourceErrors map, BLoC emits PeerMentorDetailPartialError with partial model and error map
On fatal aggregation failure (profile fetch fails), BLoC emits PeerMentorDetailError with the error
RefreshPeerMentorDetail emits PeerMentorDetailLoading (without clearing existing data if available) then follows the same success/partial/error mapping as load
During a refresh, the previously loaded data remains accessible in state so the UI can show stale data behind a refresh indicator
Concurrent LoadPeerMentorDetail events for different mentorIds cancel the previous in-flight request using emit.isDone or by using a transformer
BLoC is registered as a Riverpod provider and receives PeerMentorAggregationService via constructor injection
BLoC emits no states after close() is called (standard flutter_bloc contract)
All async event handlers use on<Event>(..., transformer: sequential()) or droppable() as appropriate to prevent race conditions

Technical Requirements

frameworks
Flutter
flutter_bloc
bloc_concurrency
Riverpod
data models
PeerMentorDetailModel
PeerMentorDetailState
PeerMentorDetailEvent
DataSource
performance requirements
BLoC must not hold a reference to the raw Supabase client — all data access goes through the aggregation service
State emissions must be synchronous after the async service call resolves — no additional async gaps
security requirements
mentorId received in events must be validated as a non-empty string before passing to the service
BLoC must not log or print sensitive mentor data in debug output

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use `bloc_concurrency` package: apply `restartable()` transformer to LoadPeerMentorDetail to cancel in-flight requests on new load, and `droppable()` to RefreshPeerMentorDetail to ignore taps during an active refresh. For refresh-while-loaded, emit a `PeerMentorDetailLoading` state that carries the stale model: `emit(PeerMentorDetailLoading(mentorId: event.mentorId, staleData: currentLoadedState?.data))` — the UI layer uses staleData to keep showing content behind the refresh indicator. Use `on(_onLoad, transformer: restartable())` syntax. Avoid try/catch on the entire handler body; instead use the service's typed result to branch, and only wrap in try/catch for truly unexpected exceptions that weren't handled by the service layer.

Testing Requirements

Unit tests (flutter_test + bloc_test): (1) Happy path — blocTest emits [Loading, Loaded] on LoadPeerMentorDetail with successful mock service. (2) Partial error — mock service returns partial model with error map; assert [Loading, PartialError] with correct error map. (3) Fatal error — mock service throws; assert [Loading, Error]. (4) Refresh with existing data — emit Loaded state first, then send RefreshPeerMentorDetail; assert Loading carries previous data, then Loaded.

(5) Concurrent load — send two LoadPeerMentorDetail events with different mentorIds; assert only the second one's Loaded state is emitted (droppable/restart transformer). (6) Close mid-fetch — close BLoC while fetch is in progress; assert no state emitted after close. Minimum 95% line coverage on BLoC file.

Component
Peer Mentor Detail BLoC
service medium
Epic Risks (3)
medium impact medium prob technical

The parallel Future.wait aggregation pattern may produce race conditions or incorrect merged state when some repositories resolve significantly faster than others, particularly if the BLoC receives a RefreshDetail event while a prior fetch is still in flight.

Mitigation & Contingency

Mitigation: Implement cancellation token pattern in the aggregation service to abort in-flight fetches on new events. Add BLoC test scenarios for rapid successive refresh events to validate state consistency.

Contingency: If race conditions persist, switch to a sequential-with-timeout fetch strategy for the first release and profile the performance impact before deciding whether parallel fetch optimization is worth the complexity.

medium impact medium prob integration

Integrating PeerMentorDetailScreenWidget into the existing StatefulShellRoute navigation structure may conflict with the Contacts tab's existing route hierarchy, requiring changes to navigation-route-config that could affect other teams' features.

Mitigation & Contingency

Mitigation: Coordinate with the Contact List and Contact Detail feature teams before adding the new route. Review the existing StatefulShellRoute configuration and confirm the peer mentor detail route can be nested under the Contacts branch without path conflicts.

Contingency: If route conflicts arise, temporarily implement the peer mentor detail as a modal overlay (push route) rather than a shell route child, preserving functionality while the navigation architecture conflict is resolved.

low impact high prob dependency

The course enrollment screen that the certification alert banner links to may not yet exist or may be implemented in a separate feature epic, leaving a broken navigation tap for HLF users in the initial release.

Mitigation & Contingency

Mitigation: Check the certification management feature implementation status before finalizing Epic 4 scope. If the enrollment screen is not available, design the tap action to open the HLF course enrollment URL in an external browser as an interim solution.

Contingency: Implement the CTA as a configurable action: if the enrollment route exists in the router, push it; otherwise, launch the configured org-specific enrollment URL via url_launcher, ensuring HLF users can always take action on expired certifications.