critical priority high complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

On Confirm: the sensitive field widget transitions from masked/hidden state to revealed state within one frame
On Confirm: FocusManagementService.moveFocusTo(sensitiveFieldKey) is called immediately after dialog pop
On Cancel: the sensitive field remains in its masked/hidden state with no state change
On Cancel: FocusManagementService.moveFocusTo(triggerElementKey) is called immediately after dialog pop
Per-session confirmation decisions are stored in a Riverpod StateProvider<Set<SensitiveFieldType>> scoped to the app session (no Supabase/local disk persistence)
If a field type has already been confirmed in the current session, the dialog is skipped and the field is revealed directly
Session confirmation state is cleared on logout and on app restart
Focus restoration completes within 2 animation frames after dialog dismissal (verified by accessibility test harness)
If the trigger element is no longer mounted when Cancel focus restoration runs, FocusManagementService handles the unmounted case gracefully (no crash, focus falls back to nearest ancestor)
The confirmation flow is accessible: the 'Read aloud' button receives focus first when the dialog opens on screen readers
Integration works correctly when triggered from within a scrollable list (trigger element may be off-screen)

Technical Requirements

frameworks
Flutter
Riverpod
apis
FocusManagementService.moveFocusTo()
Riverpod StateProvider
Navigator.pop()
data models
SensitiveFieldType (enum)
SessionConfirmedFields (Set<SensitiveFieldType>)
performance requirements
Field reveal and focus restoration must complete within 2 frames (≤32ms) after dialog dismissal
Session state lookup must be O(1) (Set membership check)
security requirements
Session confirmation state must never be persisted to disk, Supabase, or shared preferences — in-memory only
On logout, the confirmed fields provider must be invalidated immediately before navigation to the login screen
ui components
SensitiveFieldWarningDialog (from task-010)
masked field widget
revealed field widget

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model the confirmation flow as a small state machine: FieldState {masked, awaitingConfirmation, revealed}. Use a Riverpod StateNotifierProvider per field instance (keyed by SensitiveFieldType + optional field ID for lists with multiple same-type fields). The session cache provider should be a simple StateProvider> where the key is '${fieldType.name}:${fieldId}'. When showing the dialog, store the trigger element's FocusNode reference before pushing the dialog route — Flutter's Navigator.push is async, so capture it synchronously before the await.

After awaiting showDialog(), call FocusManagementService in a post-frame callback (WidgetsBinding.instance.addPostFrameCallback) to ensure the dialog route has fully popped before requesting focus. For the scroll-inside-list case, call Scrollable.ensureVisible(sensitiveFieldContext) before moveFocusTo to handle off-screen triggers.

Testing Requirements

Write widget integration tests covering the full confirmation flow end-to-end. Test cases: (1) Confirm → field revealed + focus on field, (2) Cancel → field hidden + focus on trigger, (3) second trigger for same SensitiveFieldType skips dialog (session cache hit), (4) logout clears session cache (next trigger shows dialog again), (5) unmounted trigger on Cancel does not crash, (6) dialog opens with focus on Confirm button for screen reader users, (7) flow works inside a ListView (trigger may require scroll-into-view). Use AccessibilityTestHarness for focus verification. Target ≥90% branch coverage on the confirmation state machine.

Component
Sensitive Field Warning Dialog
ui high
Epic Risks (3)
high impact high prob technical

Flutter's build pipeline and SemanticsService.announce() operate asynchronously. Announcements triggered too early (before the semantic tree settles) may be swallowed silently on both platforms, causing acceptance criteria around the 500ms window to fail intermittently in CI and on device, which would block pilot launch.

Mitigation & Contingency

Mitigation: Implement the LiveRegionAnnouncer with a post-frame callback delay and an internal timing guard. Write integration tests using WidgetTester.pump() sequences that verify announcement delivery across multiple frame boundaries. Validate on physical devices at each sprint boundary, not only in CI.

Contingency: If consistent announcement timing cannot be achieved within Flutter's semantic pipeline, switch to a platform channel approach that calls native UIAccessibility.post (iOS) and AccessibilityManager.announce (Android) directly, bypassing Flutter's intermediary.

high impact medium prob technical

Flutter does not natively emit a focus-gain event to Dart code when VoiceOver or TalkBack moves focus to a specific widget. If the intercept mechanism for the SensitiveFieldWarningDialog relies on an unsupported or undocumented hook in the semantics tree, it may miss focus events for some field types or in some navigation contexts, leaving sensitive data unprotected.

Mitigation & Contingency

Mitigation: Prototype the focus-intercept mechanism during the first sprint of this epic, before building the dialog UI. Evaluate Flutter's SemanticsBinding.instance callbacks and custom SemanticsActions as intercept points. Document the chosen mechanism with platform compatibility notes.

Contingency: If no reliable focus-intercept is available, implement an alternative where sensitive fields show a static 'Screen reader active — tap to reveal' overlay instead of an OS dialog, which is less seamless but achieves equivalent privacy protection without relying on an unreliable event hook.

medium impact low prob dependency

The AccessibilityTestHarness depends on internal flutter_test semantic tree APIs that can change between Flutter minor versions. If the project upgrades Flutter during this epic, the harness may break silently, causing CI accessibility tests to pass while actually skipping assertions.

Mitigation & Contingency

Mitigation: Pin the Flutter SDK version in pubspec.yaml for the duration of this epic. Document which flutter_test APIs are used and their stability tier. Add a canary test that explicitly fails if the semantic tree API surface changes.

Contingency: If a forced Flutter upgrade breaks the harness, prioritise patching the harness as a blocking task before any other epic work continues, using the canary test failure as the trigger.