critical priority high complexity backend pending backend specialist Tier 0

Acceptance Criteria

AccessibilityAuditService.collectSemanticTree(BuildContext) returns an AuditSemanticTree value object representing the full hierarchy of SemanticsNodes rooted at the given context
Each AuditSemanticNode in the tree contains: nodeId, label, hint, value, flags (as Set<SemanticsFlag>), actions (as Set<SemanticsAction>), rect (Rect in global coordinates), depth (int), parentId (nullable), and children (List<AuditSemanticNode>)
The traversal captures merged semantics correctly — merged groups are represented as a single AuditSemanticNode with a combinedLabel field containing the concatenation
The traversal handles scrollable containers: nodes inside a scrollable that are not currently visible are still collected (requires scrolling semantics to be enabled)
collectSemanticTree() completes synchronously for trees up to 500 nodes and returns within 50ms
The engine correctly handles the case where SemanticsBinding has not been enabled — it returns an empty tree and logs a clear diagnostic message
AuditSemanticTree exposes helper query methods: findByLabel(String), findByFlag(SemanticsFlag), findInteractiveNodes(), findHeadings()
The service is implemented as a Riverpod Provider so it can be injected in both production audit runs and tests
Full dartdoc documentation with a usage example showing how to collect and query a tree

Technical Requirements

frameworks
Flutter
Riverpod
flutter/semantics
apis
SemanticsBinding.instance.ensureSemantics
SemanticsOwner
SemanticsNode
SemanticsNode.visitChildren
SemanticsNode.rect
SemanticsNode.transform
SemanticsData
SemanticsFlag
SemanticsAction
RenderObject.describeSemanticsConfiguration
data models
AuditSemanticTree
AuditSemanticNode
AuditSemanticFlags
AuditSemanticActions
performance requirements
Tree collection for 500-node trees must complete within 50ms on a mid-range device
The internal node model must be immutable (all fields final) to allow safe sharing across isolates if needed
No retained references to live SemanticsNode objects after collection — copy all data into value objects
security requirements
The collected tree may contain user-facing text from sensitive screens (e.g., financial data, health info). Audit runs must never be logged to persistent storage or remote analytics without explicit opt-in
In production builds, AccessibilityAuditService should be gated behind a compile-time flag (kDebugMode or a feature flag) to avoid accidental data exposure

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

To access the semantic tree, call SemanticsBinding.instance.ensureSemantics() in the service's initialise() method and retain the SemanticsHandle to keep semantics active. Walk the tree using a recursive depth-first helper _visitNode(SemanticsNode, int depth, int? parentId) that appends to a flat List while also building the parent-child links. To convert SemanticsNode.rect to global screen coordinates, apply SemanticsNode.transform (a Matrix4) to the local rect using MatrixUtils.transformRect.

For merged semantics, check SemanticsNode.mergeAllDescendantsIntoThisNode; if true, collect the subtree labels into combinedLabel by joining with a space. The service itself contains no audit logic — keep it a pure data collector. Audit rules are added in task-007. This clean separation means task-007 can be developed and tested against a mock AuditSemanticTree without needing the full Flutter render pipeline.

Expose a dispose() method that releases the SemanticsHandle to prevent semantics from staying active in production builds.

Testing Requirements

Unit tests with flutter_test: build a hand-crafted widget tree with known semantic structure (3-level hierarchy, mixed flags), call collectSemanticTree(), assert the returned AuditSemanticTree matches the expected node count, depths, labels, and flags. Test merged semantics: a MergeSemantics widget produces a single node with combinedLabel. Test query helpers: findByFlag(SemanticsFlag.isHeader) returns exactly the expected nodes. Test empty tree: no Semantics widgets → empty tree returned.

Test disabled semantics: SemanticsBinding not enabled → empty tree + diagnostic log. Performance test: generate a widget tree of 500 Semantics nodes, assert collection completes within 50ms using Stopwatch. Target 95% line coverage on traversal engine.

Component
Accessibility Audit Service
service 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.