critical priority medium complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

BiometricPromptOverlay is a full-screen widget (covers entire display including safe areas) with no gaps or transparent edges
The widget contains: (1) app logo centered in upper third, (2) localised prompt message text, (3) a 'Use password instead' / 'Log in differently' fallback TextButton
Zero inline style properties — all colors, spacing, typography, and radii are referenced from the project's design token system
All text elements pass WCAG 2.2 AA contrast ratio (minimum 4.5:1 for normal text, 3:1 for large text) verified with a contrast checker against the overlay background color token
The fallback link is reachable and activatable via keyboard/switch access and screen reader (VoiceOver / TalkBack) with a meaningful semantic label
All user-visible strings are extracted to the project's localization ARB files — no hardcoded English strings in the widget
The widget is a StatefulWidget (to support loading and error states added in task-005) even though this task only implements the static structure
Pressing the Android system back button while the overlay is shown does not dismiss it — the user must use the fallback link or authenticate

Technical Requirements

frameworks
Flutter
Dart
performance requirements
Widget must render first frame within 16ms (one frame) — no heavy computation in build()
App logo must use a cached asset image (AssetImage), not a network image, to avoid loading delay on resume
security requirements
The overlay must be non-dismissible via back gesture or system back button to prevent bypass
No session data or user PII should be rendered on the overlay surface — only the logo and generic prompt
Semantic labels on the fallback button must not describe the underlying session state
ui components
Full-screen overlay container (design token background)
App logo (AssetImage, centered)
Localised prompt text (design token typography, body/headline scale)
Fallback TextButton with semantic label
WillPopScope or PopScope wrapper to intercept Android back

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use `PopScope(canPop: false)` (Flutter 3.16+) rather than the deprecated WillPopScope for Android back interception. Structure the layout with a `Stack` containing a full-screen `Container` with the background color token, and a centered `Column` for logo + text + button. Use `SafeArea` inside the Stack child to position content within safe insets. Expose three callbacks as constructor parameters: `onFallbackTapped`, `onAuthRequested` (to be wired in task-005), and `onAuthSuccess` (to be wired in task-005) — define them as nullable with empty default implementations so the static widget compiles and previews without task-005.

The design token system should provide a `AppColors.surfaceOverlay` or equivalent — if no overlay-specific token exists, use the primary surface token and document the choice for design review. For localization, add keys: `biometricPrompt.title`, `biometricPrompt.subtitle`, `biometricPrompt.fallbackButton` to all ARB files before submitting the task.

Testing Requirements

Widget tests using flutter_test. Test cases: (1) widget renders without overflow on 320px wide screen (minimum supported width); (2) widget renders without overflow on 430px wide screen (large iPhone); (3) fallback button is present and has a non-empty semantic label; (4) tapping fallback button calls the provided onFallbackTapped callback; (5) Android back button press does not pop the route (test with `tester.pageBack()` and verify widget still present); (6) all text uses design token TextStyles (check no raw TextStyle constructors in widget tree via debugDumpApp snapshot). Accessibility test: run flutter accessibility checker on the widget and assert zero violations.

Component
Biometric Prompt Overlay
ui low
Epic Risks (3)
medium impact high prob technical

Flutter's AppLifecycleState.resumed event fires in scenarios beyond simple user-initiated app-switching: it also fires when the native biometric dialog itself dismisses and returns control to Flutter, when system alerts (low battery, notifications) temporarily cover the app, and when the app is foregrounded by a deep link. Without careful debouncing and state tracking, SessionResumeManager can trigger multiple overlapping biometric prompts or prompt immediately after a just-completed authentication, creating a confusing loop.

Mitigation & Contingency

Mitigation: Implement a state machine inside SessionResumeManager with explicit states (idle, prompting, authenticated, awaiting-fallback) and guard all prompt triggers with a state check. Add a minimum inter-prompt interval of 3 seconds. Write widget tests that simulate rapid lifecycle event sequences and verify only one prompt is shown.

Contingency: If the state machine approach proves difficult to test or maintain, replace it with a simple boolean isPromptActive flag with a debounce timer, accepting slightly less precise semantics in exchange for simpler reasoning about concurrent lifecycle events.

high impact medium prob technical

The BiometricPromptOverlay appears on top of the existing app content when the app resumes. If focus is not correctly transferred to the overlay and returned to the underlying screen after authentication, screen reader users (VoiceOver/TalkBack) will either be unable to interact with the prompt or will lose their navigation position in the app after authentication completes, violating WCAG 2.2 focus management requirements and creating a broken experience for Blindeforbundet users.

Mitigation & Contingency

Mitigation: Use Flutter's FocusScope and FocusTrap utilities to capture focus within the overlay on presentation and restore the previous FocusNode after dismissal. Add a live region announcement (using the accessibility live region announcer component) when the overlay appears. Include dedicated VoiceOver and TalkBack test cases in the acceptance criteria.

Contingency: If FocusTrap behaviour proves unreliable across Flutter versions, implement the overlay as a full Navigator push to a modal route rather than an overlay widget, which gives Flutter's built-in modal semantics and focus management automatic WCAG-compliant behaviour.

low impact high prob technical

The BiometricUnavailableBanner needs to deep-link to biometric enrollment settings on both iOS and Android. iOS uses a single URL scheme (app-settings:) that opens the app's settings page. Android has no universal URL for biometric settings — the correct Intent action (Settings.ACTION_BIOMETRIC_ENROLL) was introduced in API 30, with different fallback actions required for API 23-29. Using the wrong action on older Android devices either crashes or navigates to an unrelated settings screen.

Mitigation & Contingency

Mitigation: Build an Android API version check into LocalAuthIntegration that selects the correct Intent action based on the runtime SDK version. Test against Android API 23, 28, and 30+ in the CI matrix. For iOS, validate that the app-settings: URL scheme is correctly declared.

Contingency: If the Android settings Intent fragmentation cannot be resolved reliably for all target API levels, fall back to navigating to the top-level Settings screen (Settings.ACTION_SETTINGS) with an overlay instruction telling the user to navigate to 'Security > Biometrics' manually, ensuring the user always has a path to resolve the issue even if the deep link is imprecise.