critical priority high complexity backend pending testing specialist Tier 1

Acceptance Criteria

runAudit(BuildContext) collects the semantic tree via task-006's engine, runs all registered rules, and returns an AccessibilityAuditReport
Rule 1 — Unlabelled interactive elements (WCAG 1.3.1, 4.1.2): any node with SemanticsAction.tap or SemanticsFlag.isTextField and an empty label+hint+value produces a CRITICAL violation
Rule 2 — Missing semantic role (WCAG 4.1.2): interactive nodes missing an expected role flag (isButton, isTextField, isLink, isHeader, isImage) produce a HIGH violation
Rule 3 — Touch target too small (WCAG 2.5.5 Enhanced, 2.5.8 Target Size Minimum): any interactive node whose rect is smaller than 44×44dp produces a MEDIUM violation with the actual size in the hint
Rule 4 — Broken focus order (WCAG 1.3.2, 2.4.3): nodes whose geometric reading order (top-to-bottom, left-to-right) conflicts with their DOM/semantic-tree order by more than one position produce a HIGH violation
AccessibilityAuditReport contains: screenName (String), auditTimestamp, List<AuditViolation>, summary counts by severity (critical, high, medium, low), and a passed (bool) flag (true only if zero CRITICAL or HIGH violations)
Each AuditViolation contains: ruleId, ruleName, wcagCriteria, severity (critical/high/medium/low), nodeId, nodePath, nodeLabel, actualValue, expectedValue, remediationHint
runAudit() accepts an optional AuditConfig parameter to disable specific rules or override the minimum touch target size
The service can be called in flutter_test widget tests and in CI to gate releases on zero CRITICAL violations
A runAuditAndThrow(BuildContext) convenience variant throws an AccessibilityAuditException listing all violations if passed == false, suitable for use in assert() calls during testing

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
AccessibilityAuditService (task-006)
AuditSemanticTree query helpers
SemanticsFlag
SemanticsAction
MediaQuery (for dp to px conversion)
data models
AccessibilityAuditReport
AuditViolation
AuditConfig
AuditSeverity (enum)
AuditRule (abstract class)
AccessibilityAuditException
performance requirements
runAudit() for a 500-node tree with all 4 rules must complete within 100ms
Rules are applied as a pipeline over the flat node list — avoid re-traversing the tree per rule
security requirements
AuditReport must not be persisted to Supabase or any remote service in production — it is a local development/CI artifact only
remediationHint strings must not contain user data — only structural/technical descriptions

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Model each audit rule as a class implementing AuditRule { List check(AuditSemanticTree tree); } — this allows rules to be registered in a list and iterated, making it trivial to add new rules later. For Rule 4 (focus order), compare the geometric order (sort nodes by rect.top then rect.left) against the tree's depth-first traversal order; a node at geometric position i but semantic position i+2 or more is a violation. For touch target Rule 3, remember that SemanticsNode.rect is in logical pixels already on Flutter — compare directly against 44.0 without density conversion. The 44dp minimum aligns with Apple HIG and WCAG 2.5.8.

For the remediation hints, use a static lookup table keyed by ruleId to keep message strings out of the rule logic. For the CI gate pattern, document in the project's CLAUDE.md that every new screen widget test should include: expect(await auditService.runAudit(context), isAccessible) where isAccessible is a custom Matcher wrapping report.passed. This embeds accessibility regression prevention into the normal development workflow, which is critical for this app given Blindeforbundet's VoiceOver requirements.

Testing Requirements

Unit tests per rule: for each of the 4 rules, construct a minimal AuditSemanticTree that contains exactly one violation, run the rule, assert one AuditViolation with the correct ruleId and severity. Test for false positives: a fully labelled, correctly sized, properly ordered tree produces zero violations. Test AuditConfig: disabling Rule 3 means no MEDIUM violations appear even for small targets. Test runAuditAndThrow: verify it throws AccessibilityAuditException when CRITICAL violations exist and does not throw when report.passed == true.

Integration tests: run runAudit() against the Activity Registration wizard screen in a widget test, assert zero CRITICAL violations (this acts as a regression gate). CI gate test: add a test in the existing test suite that calls runAudit() on every major screen and fails the build if any CRITICAL violation is detected. Target 95% line coverage on rule implementations.

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.