high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

App logo is wrapped in Semantics(excludeSemantics: true) so screen readers skip decorative images entirely
Prompt message text is wrapped in Semantics(liveRegion: true) so VoiceOver/TalkBack announces it immediately when the overlay appears
Biometric trigger button has a Semantics label that reads 'Authenticate with biometrics' (or localised equivalent) and button role
Credential login fallback link has a Semantics label describing its destination, e.g. 'Log in with username and password'
Focus order traverses elements top-to-bottom: prompt message → biometric button → fallback link
When the overlay is first presented, the screen reader focus lands on the prompt message automatically (use FocusNode.requestFocus or autofocus on the first Semantics node)
All interactive elements have a minimum touch target of 44×44 logical pixels per WCAG 2.2 AA
Passing flutter_test semantics tests: SemanticsHandle is opened, all expected Semantics nodes are found with correct labels and flags
No regression on existing BiometricPromptOverlay widget tests

Technical Requirements

frameworks
Flutter
flutter_test
performance requirements
Semantics tree overhead must not cause measurable frame-time increase (< 1ms per frame on mid-range Android device)
security requirements
Semantics labels must not expose underlying biometric credential status strings — labels must be human-readable only
ui components
Semantics widget (Flutter core)
MergeSemantics widget for grouping related nodes
ExcludeSemantics widget for decorative logo
FocusNode with autofocus for initial announcement

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use Semantics widgets as close to the leaf widgets as possible — avoid wrapping large subtrees which degrades tree clarity. For the live region, wrap the Text widget directly: `Semantics(liveRegion: true, child: Text(promptMessage))`. For the logo, use `ExcludeSemantics(child: Image(...))` rather than `Semantics(excludeSemantics: true)` — the latter still creates a node. To control focus order use FocusTraversalGroup with ReadingOrderTraversalPolicy.

For auto-focus on overlay open, call FocusScope.of(context).requestFocus(_promptFocusNode) inside a post-frame callback in initState. Consult the organisation's dynamic terminology system for all label strings — do not hardcode English strings.

Testing Requirements

Write widget tests using flutter_test with tester.pumpWidget and SemanticsHandle (tester.ensureSemantics()). Assert: (1) logo node has isHidden or excludeSemantics set, (2) prompt message node has liveRegion flag, (3) biometric button node has label matching expected string and isButton flag, (4) fallback link node has label and isLink or isButton flag. Run accessibility audit via flutter_test AccessibilityGuideline (androidTapTargetGuideline, iOSTapTargetGuideline, labeledTapTargetGuideline). Test on both iOS simulator (VoiceOver) and Android emulator (TalkBack) manually before merge.

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.