critical priority high complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Dialog is rendered as CupertinoAlertDialog on iOS and AlertDialog (Material 3) on Android, detected via Platform.isIOS
Dialog title displays the field type label (e.g., 'Phone Number', 'Full Name', 'Assignment Details') passed as a constructor parameter
Dialog body contains a plain-language privacy warning (≤2 sentences) explaining that the screen reader will read this information aloud
Confirm button label is 'Read aloud' and Cancel button label is 'Keep private'; both are localised
Confirm button is positioned on the right (Material) / trailing position (Cupertino) per OS conventions
Cancel button is the default/safe action per OS conventions (left on Material, leading on Cupertino)
Dialog is assigned a GlobalKey<State> passed in from the caller for external focus management
The widget is exported from the component barrel file and has no hard-coded strings (all via l10n)
Dialog is dismissible via back button on Android (dismiss = Cancel action)
Dialog is not dismissible by tapping the scrim — user must choose Confirm or Cancel explicitly
SensitiveFieldType enum covers at minimum: name, phone, assignmentDetails, address, medicalInfo
Visual contrast ratios meet WCAG 2.2 AA (≥4.5:1 for body text, ≥3:1 for large text) on both platforms

Technical Requirements

frameworks
Flutter
apis
Platform.isIOS
CupertinoAlertDialog
AlertDialog (Material 3)
showDialog<bool>
data models
SensitiveFieldType (enum)
performance requirements
Dialog must appear within one frame (~16ms) of the trigger call
No network or database calls during dialog construction
security requirements
Dialog must not display the actual sensitive field value anywhere in its body
Field type label must be a human-readable category name, not the raw data value
ui components
CupertinoAlertDialog
AlertDialog
TextButton
CupertinoDialogAction

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use showDialog(barrierDismissible: false, ...) to prevent scrim dismissal. Return a bool? from the dialog (true = confirm, false/null = cancel). Detect platform via dart:io Platform.isIOS inside a factory constructor or builder function — avoid using Theme.of(context).platform as it can be overridden in tests.

For the GlobalKey, accept a GlobalKey? parameter; if provided, use it as the key on the root dialog widget so FocusManagementService can target it. Wrap the entire dialog in a Semantics node with namesRoute: true and label matching the dialog title for screen reader route announcement. Define SensitiveFieldType as a sealed class or enum in a shared accessibility types file to allow exhaustive switching.

The workshop documentation explicitly requires sensitive field warnings (NHF requirement section 1.2), making this a MUST HAVE for compliance.

Testing Requirements

Write flutter_test widget tests with both Material and Cupertino theme wrappers. Test: (1) correct dialog type rendered per platform, (2) title displays correct field type label for each SensitiveFieldType, (3) Confirm returns true, Cancel returns false from showDialog, (4) back button dismiss returns false on Android, (5) scrim tap does not dismiss, (6) all localised strings resolve without null. Run golden tests for both iOS and Android dialog variants at standard font size and at 200% font scale. Verify contrast ratios programmatically using flutter_test accessibility APIs.

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.