high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Three semantic token groups defined: CertificateExpiryTokens.active, CertificateExpiryTokens.expiringSoon, CertificateExpiryTokens.expired — each containing background, foreground, and border Color values
All foreground-on-background combinations meet WCAG 2.2 AA minimum contrast ratio of 4.5:1 for normal text (verified with a contrast calculation utility)
Dark mode variants defined for all six color roles (background + foreground + border × 3 states)
Token naming follows the existing project design token naming convention (confirm by inspecting existing token file)
Tokens are defined as Dart const values — no runtime computation
Token file is importable from the widget layer without circular dependencies
Color choices visually distinguish the three states without relying solely on hue (shape or icon must also differentiate, but colors must be independently distinguishable)
PR description includes a color swatch table showing hex values and computed contrast ratios for each combination

Technical Requirements

frameworks
Flutter
Dart
performance requirements
All values are compile-time constants (const) — zero runtime overhead
ui components
CertificateExpiryTokens class (new)
existing AppColors / design token file (extension)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Inspect the existing design token file (likely lib/design/tokens/ or similar) before creating the new file — match the class structure, file naming, and barrel export pattern exactly. For active state, prefer a neutral or green-adjacent tone that does not conflict with success states already in the system. For expiring-soon, amber/yellow tones are conventional but verify they do not collide with warning tokens already defined. For expired, red-adjacent or muted gray tones — avoid using the exact same red as error states to prevent semantic confusion.

Use Flutter's Color(0xFFRRGGBB) format for const compatibility. Implement the WCAG contrast formula as a private static method in the test file (not in production code) — it is test infrastructure only. Consider adding a static CertificateExpiryTokens.forState(CertificateExpiryStatus status) factory that returns the correct token group, as this will simplify widget implementation in subsequent tasks.

Testing Requirements

Write a Dart unit test that imports CertificateExpiryTokens and asserts the contrast ratio between each foreground/background pair meets 4.5:1 using a helper that implements the WCAG relative luminance formula. Test all 6 light-mode pairs and all 6 dark-mode pairs (18 assertions total). No widget rendering needed — pure color math. Tests live in test/design_tokens/certificate_expiry_tokens_test.dart.

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.