high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

CertificationExpiryWarningBanner accepts expiryDate (DateTime), warningThresholdDays (int, default 30), and an optional onDismissed (VoidCallback?) parameter.
When expiryDate is in the past, the banner displays an 'expired' state with a design-token error color background and a message indicating the certification has expired.
When expiryDate is within warningThresholdDays from today, the banner displays a 'warning' state with a design-token warning color background and a message showing days remaining (e.g., 'Certification expires in 12 days').
When expiryDate is beyond the threshold, the widget renders SizedBox.shrink() (invisible, zero height) — callers do not need to conditionally include it.
If onDismissed is provided, a dismiss icon button is shown; tapping it calls onDismissed. If null, no dismiss button is shown.
Banner text is accessible: Semantics widget wraps the banner with a descriptive live region announcement ('Certification expired' or 'Certification expiring in N days').
All colors, text styles, border radius, and padding use design tokens — no hardcoded values.
Widget passes flutter_test golden tests for expired state, warning state (with dismiss), warning state (without dismiss), and hidden state.
Widget handles edge case where expiryDate is exactly today: treated as expired (not warning).

Technical Requirements

frameworks
Flutter
design token system (colors, typography, spacing, radii)
data models
certification
performance requirements
Widget must be const-constructible when no onDismissed callback is provided.
State computation (expired vs. warning vs. hidden) must be a pure function of the inputs — no async calls inside the widget.
security requirements
No certification data is transmitted externally from this widget — display only.
Expiry date must be treated as non-sensitive UI data; no special handling required.
ui components
Container with design token background color (error or warning variant)
Row with Icon, Text, and optional IconButton (dismiss)
Semantics live region wrapper

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Compute the banner variant (hidden, warning, expired) in a static method or simple conditional at the top of the build method — avoid StatefulWidget unless dismissal state needs to be managed locally (which it should not be: dismissal state belongs in the parent/BLoC). For the color tokens, use the existing design token system (e.g., AppColors.warningBackground, AppColors.errorBackground) — check the design token definitions before adding new color names. The days-remaining calculation: final daysRemaining = expiryDate.difference(DateTime.now()).inDays; — note that this returns negative for past dates, so the expired check is daysRemaining <= 0. For the Semantics live region (important for VoiceOver/TalkBack used by Blindeforbundet users): wrap the visible banner in Semantics(liveRegion: true, label: '...') so screen readers announce it when it appears.

Export from the shared widgets barrel file alongside ExpectedReturnDatePicker.

Testing Requirements

Write flutter_test widget tests covering: (1) banner is invisible (SizedBox.shrink) when expiry is 31+ days away with default threshold, (2) warning banner renders correctly when expiry is within threshold (e.g., 15 days), (3) expired banner renders when expiryDate is in the past, (4) today's date is treated as expired, (5) dismiss button appears when onDismissed is provided, (6) dismiss button is absent when onDismissed is null, (7) tapping dismiss calls onDismissed callback, (8) Semantics tree includes correct label for screen readers. Add golden tests for expired and warning states. Test with warningThresholdDays values of 0, 1, and 30 to verify boundary conditions.

Epic Risks (3)
high impact medium prob security

Supabase RLS policies for coordinator-scoped status queries may be difficult to express correctly, especially for peer mentors assigned to multiple coordinators or chapters, leading to data leakage or overly restrictive access blocking valid queries.

Mitigation & Contingency

Mitigation: Design RLS policies using security-definer RPCs rather than table-level policies for complex multi-coordinator scenarios. Write a comprehensive RLS test matrix covering all role and assignment permutations before marking complete.

Contingency: Fall back to application-level filtering in the repository layer with explicit coordinator_id parameter checks if RLS proves intractable, and document the trade-off for security review.

high impact medium prob dependency

The HLF Dynamics portal API contract may be undocumented or subject to change, causing the DynamicsPortalClient to break during development or production rollout.

Mitigation & Contingency

Mitigation: Obtain the full Dynamics portal API specification and credentials early in the sprint. Build the client behind a well-defined interface so the HLF-specific implementation can be swapped without affecting upstream services.

Contingency: If the Dynamics API is unavailable or unstable, stub the client with a feature-flag-guarded no-op implementation so all other epics can proceed to completion independently.

medium impact low prob technical

Supabase Edge Functions used as the nightly scheduler host may have cold-start latency or execution time limits that prevent reliable nightly certification checks on large mentor rosters.

Mitigation & Contingency

Mitigation: Benchmark Edge Function execution time against the expected roster size. Design the expiry check to process in paginated batches to stay within execution limits. Use pg_cron with a direct database function as an alternative trigger if Edge Functions prove unreliable.

Contingency: Migrate the scheduler trigger to pg_cron invoking a Postgres function directly, removing the Edge Function dependency entirely for the scheduling layer.