high priority medium complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

After `RenewalSuccess` or `EnrolmentSuccess` is emitted, the BLoC automatically re-dispatches `LoadCertification` with the same certificationId without requiring the UI to do so manually
The auto-reload after mutation does not emit a second `CertificationLoading` state visible to the UI — the success state is held until the reload completes, then transitions directly to `CertificationLoaded`; or alternatively, the design decision (hold vs show loading) is documented in a code comment
A public `refresh(String certificationId)` method (or equivalent event `RefreshCertification`) exists on the BLoC that UI screens can call for pull-to-refresh; it emits `CertificationLoading` before fetching
If the BLoC already holds a `CertificationLoaded` state for the same certificationId and the reload result is equal (Equatable), no new state emission occurs — preventing unnecessary widget rebuilds
Concurrent refresh calls (e.g. user rapidly pulls-to-refresh) are handled by the droppable() transformer — only one in-flight reload at a time
The reload logic does not store a mutable `_currentCertificationId` field at class level unless it is properly reset on BLoC close to prevent stale state leaks
All reload paths are covered by existing unit tests from task-009 — no regressions introduced

Technical Requirements

frameworks
Flutter
flutter_bloc
BLoC
bloc_concurrency
apis
CertificationManagementService.getCertification()
data models
Certification
performance requirements
Reload after mutation must complete within the same event handler cycle — no external timers or delayed futures
Skip network call and state emission if the current state is already `CertificationLoaded` with an identical Certification object (Equatable comparison)
Pull-to-refresh must respond within 16ms of the user gesture triggering the event — BLoC event dispatch is synchronous, network latency is separate
security requirements
The cached certificationId used for auto-reload must be scoped to the active event's data — never read from a class-level cache that could reference a stale or different user's certification
ui components
PullToRefresh indicator (Flutter RefreshIndicator) — BLoC must emit CertificationLoading to drive the indicator's visibility

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

The cleanest pattern: inside the `RecordRenewal` handler, after emitting `RenewalSuccess`, immediately call `add(LoadCertification(certificationId: event.certificationId))`. This keeps the reload within the BLoC's own event queue rather than using a raw `await _handleLoad(...)` call, which would bypass the transformer and concurrency guard. For the equality-check optimisation, add a guard at the start of the `LoadCertification` handler: `if (state is CertificationLoaded && (state as CertificationLoaded).certification == fetchedCert) return;` before emitting. Expose `refresh` as a thin wrapper: `void refresh(String id) => add(LoadCertification(certificationId: id));` — no need for a separate event class unless refresh needs distinct UI behaviour (e.g.

showing a different shimmer). Document the chosen loading-state strategy (hold success vs show loading during reload) with a one-line comment so future developers understand the intentional UX decision.

Testing Requirements

Extend the unit test suite from task-007. Test 1 (auto-reload after RenewalSuccess): after emitting RenewalSuccess, assert that the BLoC subsequently emits CertificationLoaded with the refreshed data — verify service.getCertification was called exactly twice (once for initial load, once for reload). Test 2 (no duplicate emission when data unchanged): mock service to return the same Certification before and after reload; assert only one CertificationLoaded emission occurs. Test 3 (refresh method): call `bloc.refresh(id)` directly; assert states `[CertificationLoading, CertificationLoaded]`.

Test 4 (concurrent refresh drop): call refresh twice rapidly with droppable transformer; assert getCertification called only once. Test 5 (auto-reload after EnrolmentSuccess): mirrors test 1 for the enrolment mutation path.

Component
Certification BLoC
service medium
Epic Risks (2)
medium impact low prob technical

Supabase Edge Functions can have cold-start latency that causes the nightly cron to time out when processing large cohorts of expiring certifications, resulting in partial reminder dispatches.

Mitigation & Contingency

Mitigation: Batch the cron processing in chunks of 50 mentors per iteration. Use pagination with a cursor to resume processing if the function is re-invoked. Keep total invocation time well under the Edge Function timeout limit.

Contingency: If timeouts occur in production, split the cron into two separate functions: one for reminders and one for auto-pauses, each with its own schedule offset to reduce peak load.

low impact medium prob technical

Certification BLoC covers three distinct workflows (view, renew, enrol) which may lead to an overly complex state machine that is hard to test and maintain, particularly when error states from multiple concurrent operations need to be differentiated in the UI.

Mitigation & Contingency

Mitigation: Use separate sealed state classes per workflow (CertificationViewState, RenewalState, EnrolmentState) composed into a single BLoC state wrapper. Follow the existing BLoC patterns established in the codebase for consistency.

Contingency: If the BLoC grows too complex, split into two BLoCs: CertificationBLoC (view/load) and CertificationActionBLoC (mutations), connected via a shared stream.