critical priority medium complexity integration pending frontend specialist Tier 1

Acceptance Criteria

When VoiceOver (iOS) or TalkBack (Android) focuses on a sensitive field widget, SensitiveFieldPrivacyGuard intercepts the accessibility focus event before the field value is announced
A privacy warning dialog is shown on first screen-reader focus of each sensitive field type (name, phone, assignment details) within a session
After the user dismisses the dialog, subsequent focuses on the same field type within the session do NOT re-trigger the dialog (session-suppression)
Session suppression state survives widget rebuilds and hot-reloads within the same app session, confirmed by toggling navigation to another screen and returning
Session suppression is cleared when the user logs out or the app session ends
Sensitive field values are NOT announced until the user has dismissed the privacy warning dialog
Integration works correctly on both iOS (VoiceOver) and Android (TalkBack)
If SensitiveFieldPrivacyGuard service is unavailable or throws, the field falls back to displaying a masked placeholder rather than exposing raw PII
Privacy dialog copy is localised and references the sensitivity classification (e.g. 'This field contains personal contact information')
All contact entity fields (first_name, last_name, email, phone) and assignment details are classified as sensitive and trigger the guard

Technical Requirements

frameworks
Flutter Semantics API
Flutter SemanticsService.announce
BLoC for guard state management
Riverpod for service injection
apis
Semantics.onTap / SemanticsProperties for focus interception
Flutter ExcludeSemantics / BlockSemantics widgets
Supabase Auth session lifecycle hooks
data models
contact
assignment
confidentiality_declaration
performance requirements
Privacy guard check must complete in under 16ms to avoid accessibility frame drops
Session-suppression state lookup must be O(1) using an in-memory Set keyed by field type
Dialog presentation must not block the main UI thread
security requirements
Sensitive field raw values must never be placed in the Semantics label when guard is active — use masked placeholder string
Session-suppression state must be stored in memory only, never persisted to disk or Supabase
Guard must integrate with flutter_secure_storage patterns; no PII in SharedPreferences
JWTs and session tokens must not be exposed to accessibility tree at any point
Per GDPR, personnummer and NIN fields require stricter guard — always re-prompt if session token refreshes
ui components
SensitiveFieldPrivacyGuard service (stateful, session-scoped)
PrivacyWarningDialog widget
EncryptedFieldDisplay wrapper widget with BlockSemantics integration
MaskedFieldLabel fallback widget

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use ExcludeSemantics or BlockSemantics wrapping the sensitive field content as the primary interception mechanism — when the guard has NOT granted access for this session, wrap the field with BlockSemantics(blocking: true) so no announcement is dispatched. Remove the blocking wrapper once the user dismisses the dialog. Store suppression state in a session-scoped service (registered via Riverpod as a keepAlive provider tied to the auth session). Listen to Supabase Auth onAuthStateChange to clear suppression when the user logs out.

Avoid using onFocusChange on individual FocusNodes for this because VoiceOver/TalkBack operate on the semantic tree, not the focus tree — use Semantics(onTap: ...) or a SemanticsAction to intercept. The dialog should be shown using Navigator.of(context).push with a transparent barrier so the screen reader can still interact with the dialog. Coordinate with the Blindeforbundet requirement: assignment detail fields (essentially encrypted mission packets) are the highest-sensitivity classification.

Testing Requirements

Unit tests: verify SensitiveFieldPrivacyGuard.shouldShowWarning() returns true on first access, false on subsequent accesses for the same field type, and resets on logout. Widget tests: mount EncryptedFieldDisplay with a mock guard; assert that BlockSemantics excludes child semantics while dialog is pending; assert dialog appears on first focus; assert suppression on second focus. Integration tests: simulate VoiceOver-style accessibility focus using flutter_test SemanticsController; verify end-to-end flow on both a name field and an assignment details field. Edge case: confirm behavior when the parent widget rebuilds mid-dialog (e.g., due to a BLoC state change).

Test coverage target: 90% for guard logic, 80% for widget integration. Run on physical iOS device and Android emulator with accessibility services enabled.

Component
Sensitive Field Privacy Guard
service high
Epic Risks (3)
high impact high prob integration

Flutter does not natively enforce a focus trap within a bottom sheet or modal dialog in the semantic tree — VoiceOver and TalkBack can navigate outside the sheet to background content. Implementing a reliable focus trap requires overriding the semantic tree, which may conflict with the existing modal helper infrastructure in the app and require changes to shared components beyond this feature's scope.

Mitigation & Contingency

Mitigation: Prototype the focus trap on the first modal sheet implementation before building the remaining sheets. Evaluate Flutter's ExcludeSemantics and BlockSemantics widgets as the trap mechanism, and coordinate with the team owning the shared modal helpers to agree on a non-breaking integration point before writing production code.

Contingency: If a complete semantic focus trap cannot be implemented without breaking existing modal patterns, implement a partial solution using FocusScope with autofocus on the modal's first element and a prominent 'Return to main content' semantic action, documenting the deviation from WCAG 2.4.3 with a scheduled remediation item.

high impact medium prob technical

The activity wizard uses BLoC state management and the UI rebuilds the entire step widget subtree on transition. If the semantic tree is traversed by VoiceOver before the build cycle settles, focus may land on a stale or partially rendered step, causing the wrong step label or progress value to be announced. This is particularly problematic for blind users who cannot visually verify the announcement against the screen.

Mitigation & Contingency

Mitigation: Coordinate ActivityWizardStepSemantics with FocusManagementService (from the core services epic) to delay focus placement until the post-build callback confirms the new step's semantic tree is complete. Write integration tests using the AccessibilityTestHarness that assert the full announcement sequence across all five wizard steps.

Contingency: If post-build focus delay is insufficient due to async BLoC emission timing, add an explicit semantic notification barrier in the wizard cubit that emits a 'step ready' event only after the new widget tree has been marked as built, decoupling the announcement trigger from the raw state transition.

medium impact medium prob scope

Automated WCAG contrast ratio checking on widget tree snapshots may produce false positives for gradient backgrounds, dark-mode overrides, or design token overrides that are resolved at runtime but appear as unresolvable colours at static analysis time. Excessive false positives would erode team trust in the CI gate, leading to suppression rules that also mask real violations.

Mitigation & Contingency

Mitigation: Scope the WCAGComplianceChecker to check only solid-colour backgrounds in the first iteration, explicitly excluding gradients from contrast checks with documented rationale. Design the check output to distinguish 'undetermined' (gradient/unknown) from 'fail' (solid colour below threshold) so the team can take targeted action on genuine failures only.

Contingency: If false positive rates exceed 20% of reported violations during initial CI runs, switch the CI gate from a hard build failure to a warning annotation on the pull request, combined with a mandatory manual review step, until the checker's rule set has been tuned to match actual design token values.