critical priority low complexity frontend pending frontend specialist Tier 2

Acceptance Criteria

`ThemeData.light` and `ThemeData.dark` both include all token `ThemeExtension` instances in the `extensions` list
A `BuildContext` extension (e.g., `extension AppTokensContext on BuildContext { AppDesignTokens get tokens => Theme.of(this).extension<AppDesignTokens>()!; }`) is defined in `lib/core/theme/theme_extensions.dart`
`context.tokens` is usable in any widget without a null-check — the getter uses `!` and throws a clear `FlutterError` if the extension is absent (fail-fast for misconfiguration)
A widget test pumps `MaterialApp` with `theme: AppTheme.light` and `darkTheme: AppTheme.dark`, then retrieves `context.tokens` in both modes and asserts non-null values for at least color, typography, and spacing tokens
No widget in the peer mentor detail feature calls `Theme.of(context).extension<...>()` directly — all access goes through `context.tokens`
The `AppTheme` factory class (or equivalent) is the single place where `ThemeData` is constructed — not scattered across multiple files

Technical Requirements

frameworks
Flutter
performance requirements
`Theme.of(context).extension<T>()` is O(1) map lookup — no performance concern, but must be called only when needed (not inside `build` loops)
ui components
MaterialApp
ThemeData
BuildContext extension

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Create `lib/core/theme/app_theme.dart` with two static getters: `AppTheme.light` and `AppTheme.dark`, each returning a `ThemeData` with `extensions: [AppColorTokens.light, AppTypographyTokens.standard, AppSpacingTokens.standard]` (or merged equivalents). The `BuildContext` extension should use the non-null assertion `!` because a missing extension is a programming error caught at development time, not a runtime condition to handle gracefully. If the app supports dynamic theme switching (user preference), the `context.tokens` accessor automatically returns the correct variant because `Theme.of` is already theme-mode aware. Register the theme in `MaterialApp` via `theme:` and `darkTheme:` — do not pass `ThemeData` inline in widget tests; always import from `AppTheme` to keep tests representative.

Testing Requirements

Widget tests with `flutter_test`. Test 1: pump `MaterialApp(theme: AppTheme.light, home: Builder(builder: (ctx) { final t = ctx.tokens; expect(t, isNotNull); return Container(); }))` — passes without exception. Test 2: same with `darkTheme: AppTheme.dark` and `themeMode: ThemeMode.dark`. Test 3: pump without the extension registered and confirm the getter throws a `FlutterError` (negative test for fail-fast behavior).

All three tests must pass in CI.

Component
Design Token Theme
infrastructure low
Epic Risks (2)
high impact medium prob integration

The design token theme extension may conflict with existing ThemeData extensions already registered in the app, causing runtime assertion errors or token resolution failures across all screens that consume the tokens.

Mitigation & Contingency

Mitigation: Audit all existing ThemeData extensions before implementation. Use a unique extension key namespace and add integration tests that instantiate the combined theme in a test app harness.

Contingency: If conflicts arise, isolate design tokens behind a dedicated provider singleton (Riverpod) rather than a ThemeData extension, updating all consuming widgets to read from the provider instead.

medium impact medium prob scope

The 30-day warning threshold for expiring_soon status may differ between HLF's stated requirement in workshops (60 days mentioned in user stories) and the 30-day value in component documentation, causing disagreement during acceptance testing.

Mitigation & Contingency

Mitigation: Explicitly confirm the threshold value with HLF stakeholder before implementation. Make the threshold a named constant (kCertificationWarnDays) so it can be updated without logic changes.

Contingency: If stakeholder confirms 60 days post-implementation, update the constant and re-run the unit test suite — no architectural change required.