high priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

ExpiryNotificationBanner is a StatelessWidget that accepts an ExpiryNotificationRecord and an onRenewTap VoidCallback as required parameters
The widget renders only when record.status == ExpiryNotificationStatus.expiringSoon and record.isAcknowledged == false; otherwise it returns SizedBox.shrink()
Banner displays certificate name (text), days-until-expiry count formatted as 'X days remaining', and ExpiryStatusIndicator in compact mode
A renewal CTA button (using the project's AppButton widget) is rendered and calls onRenewTap when tapped
The banner uses the design token color for the expiring-soon state (warning/amber token — not a hardcoded hex value)
Banner background, text, and border colors all pass WCAG 2.2 AA contrast ratio (minimum 4.5:1 for normal text)
The widget integrates with BlocBuilder<CertificateExpiryNotificationBloc, CertificateExpiryNotificationState> to reactively rebuild when state changes
Banner does NOT disappear on its own — it is only hidden when the BLoC state reflects renewal or coordinator acknowledgement
The renewal CTA button dispatches the appropriate BLoC event or navigation action as defined by the epic architecture (confirm with task-003 output)
All text in the banner supports Semantics labels for screen reader accessibility (WCAG 2.2 AA)

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc — BlocBuilder)
data models
ExpiryNotificationRecord
CertificateExpiryNotificationLoaded
performance requirements
Widget build must complete in a single frame (no async operations in build method)
BlocBuilder buildWhen should be scoped to avoid unnecessary rebuilds when unrelated state fields change
security requirements
No coordinator-only actions (AcknowledgeLapse) must be exposed in this widget — it is peer mentor facing
ui components
ExpiryStatusIndicator (compact mode, from task-003)
AppButton (project reusable widget)
Design token color values for warning state

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Keep ExpiryNotificationBanner as a pure presentational widget — all BLoC wiring goes in the parent screen or a dedicated BlocBuilder wrapper widget. This makes the widget independently testable. Use the project's existing AppButton for the CTA so styling stays consistent. Source all colors from the design token system (e.g., AppColors.warningAmber or equivalent token name) — never hardcode hex.

The days-remaining string should be localized if the project uses Flutter's l10n; if not, define the string in a constants file. Confirm compact vs. full mode API of ExpiryStatusIndicator with task-003 implementer before coding the integration point.

Testing Requirements

Widget tests using flutter_test. Cover: (1) banner renders with correct certificate name and days remaining when status=expiringSoon and isAcknowledged=false, (2) banner is not visible (SizedBox.shrink) when isAcknowledged=true, (3) tapping the CTA button calls onRenewTap exactly once, (4) ExpiryStatusIndicator is present in compact mode, (5) Semantics widget wraps the banner with a meaningful label (use tester.getSemantics). Provide a fake BLoC or MockBloc to drive state in tests. Do NOT use real Supabase in widget tests.

Epic Risks (2)
medium impact medium prob technical

The persistent banner must remain visible across app sessions and only disappear when a specific backend condition is met (renewal or coordinator acknowledgement). If the BLoC state is not properly sourced from the notification record repository on every app launch, the banner may disappear prematurely or fail to reappear after a session restart.

Mitigation & Contingency

Mitigation: Drive the banner's visibility exclusively from a Supabase real-time subscription on the notification records table filtered by mentor_id and acknowledged_at IS NULL. Never persist banner visibility state locally. Write an integration test that restarts the BLoC and verifies the banner reappears from the database source.

Contingency: If real-time subscriptions introduce latency or connection reliability issues in offline-first scenarios, add a local cache flag that is only cleared when the repository confirms the acknowledgement write succeeded, with a cache TTL of 24 hours as a fallback.

high impact low prob security

The notification detail view must conditionally render coordinator-specific actions based on the authenticated user's role. Incorrect role resolution could expose the 'Acknowledge Lapse' action to peer mentors or hide it from coordinators, breaking the workflow and potentially allowing unauthorised state changes.

Mitigation & Contingency

Mitigation: Source the role check from the existing role_state_manager BLoC that is already authenticated against Supabase role claims. Do not rely on a local flag. The coordinator acknowledgement service backend also validates role server-side, providing defence in depth. Add widget tests that render the detail view with mentor and coordinator role fixtures and assert the presence or absence of coordinator actions.

Contingency: If a role resolution bug is found in production, immediately disable the acknowledge action via a feature flag and patch the role check in a hotfix release. The server-side validation in the coordinator acknowledgement service ensures no actual state change can occur even if the button is incorrectly rendered.