critical priority medium complexity frontend pending frontend specialist Tier 4

Acceptance Criteria

SendCertificationReminder event accepts either a single mentorId or a list of mentorIds for bulk chapter reminders
BLoC state contains a Map<String, ReminderStatus> keyed by mentorId with values: idle, pending, sent, failed
While a reminder is pending for a mentor, emitting another SendCertificationReminder for the same mentorId is a no-op (debounced)
On successful dispatch, mentor's ReminderStatus transitions to sent within 3 seconds of the event being handled
On service failure, mentor's ReminderStatus transitions to failed and includes a localised error message accessible to the UI
Sent/failed states persist in BLoC state for the lifetime of the portal session so the UI does not reset confirmation badges on unrelated state updates
Debounce window for repeated reminder sends is at least 5 seconds per mentorId
Bulk chapter reminder correctly sets all mentors in the chapter to pending simultaneously before dispatching
AdminNotificationDispatcher is called with both mentorId and the dispatching admin's userId for audit trail
All transitions are testable via bloc_test without real network calls

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
Dart async
apis
AdminNotificationDispatcher (internal Supabase Edge Function or REST endpoint)
data models
ReminderStatus (enum: idle, pending, sent, failed)
ReminderRecord (mentorId, status, errorMessage?, timestamp)
AdminPortalState
performance requirements
Reminder state map updates must not trigger full panel rebuild; use BlocSelector in the UI
Bulk reminder for up to 50 mentors must complete state transition to pending within one frame
security requirements
Notification dispatch must include authenticated admin's userId for server-side audit logging
BLoC must not retry failed reminders automatically — retries must be explicit user actions
AdminNotificationDispatcher endpoint must validate admin scope on the server; BLoC should not perform client-side scope checks as sole guard

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Store reminder state as an immutable Map inside AdminPortalState, updated via copyWith to avoid mutating shared state. Use a per-mentorId debounce Map inside the BLoC to record last dispatch time and skip re-dispatch within the window. For bulk sends, collect all mentorIds, filter out any already pending/sent, set all to pending in a single state emit, then fire concurrent Future calls to AdminNotificationDispatcher and collect results. Avoid await-ing each call sequentially — use Future.wait for the batch.

Wrap each individual dispatch in try/catch so one failure does not abort others. Keep this as a pure BLoC concern — no service calls from the widget layer.

Testing Requirements

Unit test with bloc_test and a mock AdminNotificationDispatcher: (1) SendCertificationReminder sets status to pending then sent on success; (2) sets status to failed with error on service exception; (3) duplicate event within debounce window is ignored (state unchanged); (4) bulk event sets all mentorIds to pending simultaneously; (5) failed reminder for one mentor does not affect other mentors' statuses in a bulk send; (6) sent/failed status is preserved after subsequent unrelated BLoC events. Target 100% branch coverage on the reminder state machine.

Component
Certification Status Panel
ui medium
Epic Risks (3)
high impact medium prob technical

If org node selection in AdminStateBLoC does not correctly propagate to all dependent data streams (statistics, activity log, user list, certification panel), some panels may show data from the previously selected org scope, creating a confusing and potentially dangerous mixed-scope view.

Mitigation & Contingency

Mitigation: Model org node selection as a single source of truth in AdminStateBLoC. All downstream providers derive their query parameters from this single stream via Riverpod's watch pattern. Write integration tests that verify every data stream emits a reload event when the selected node changes.

Contingency: If scope propagation bugs are detected in QA, add an explicit full-state reset on org node change (clear all cached data and refetch from scratch) as a safe but less efficient fallback until the targeted propagation is fixed.

medium impact medium prob technical

The Admin Dashboard Screen must adapt its layout for Flutter Web (wider viewports, mouse interaction, larger grid) and mobile embedding. Flutter Web responsive layout support has historically required non-trivial workarounds, and the adaptive grid may introduce significant additional development time.

Mitigation & Contingency

Mitigation: Define breakpoints and grid behaviour in the design system before implementation. Use LayoutBuilder with explicit breakpoint constants rather than MediaQuery scattered across widgets. Prototype the web layout with a skeleton screen before implementing live data binding.

Contingency: If web layout proves intractable within sprint, deliver a mobile-first layout for all platforms initially and track a dedicated web-optimisation task for the next sprint.

high impact low prob security

A bug in the Role Assignment Panel's permission scope validation could allow an org_admin to assign roles beyond their authority (e.g., assigning super_admin to a user), representing a serious privilege escalation vulnerability.

Mitigation & Contingency

Mitigation: Enforce role assignment scope on both the client (disable unavailable roles in the panel UI) and the server (UserManagementService validates the target role is within the admin's permitted scope before persisting). Write security-focused tests that attempt out-of-scope role assignments and assert rejection.

Contingency: If an escalation vulnerability is discovered, immediately disable the role assignment panel via feature flag, revoke any incorrectly assigned roles, and deploy a server-side fix before re-enabling.