Apply WCAG 2.2 AA semantics tree and heading
epic-no-access-screen-ui-task-006 — Wrap the denial headline text in a Semantics widget with header:true so screen readers announce it as a heading. Audit the full semantics tree of the widget to ensure all interactive elements have correct semantic roles, labels, and values. Verify logical focus order flows from logo → heading → explanation → portal link → logout button.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Use tester.ensureSemantics() at the top of each semantics test to enable the semantics tree. To verify traversal order, retrieve the root SemanticsNode and walk its children in depth-first order, comparing labels against the expected sequence. Use Semantics(sortKey: OrdinalSortKey(n)) if the default DOM order does not match the desired focus order — but prefer correct widget ordering in the Column first before resorting to sort keys. Check that MergeSemantics is used around the portal link if its visual presentation combines an icon and label text, to prevent the screen reader from announcing them separately.
Document the final semantics tree shape in a code comment at the top of the widget file for future maintainers.
Testing Requirements
Write widget tests using flutter_test with semanticsEnabled: true (via WidgetTester.ensureSemantics()). Test 1: obtain the SemanticsNode tree and assert the heading node exists with isHeader: true. Test 2: traverse all SemanticsNodes and assert no interactive node has an empty label. Test 3: assert the traversal order of SemanticsNodes matches the expected sequence (logo, heading, explanation, link, button).
Test 4: assert decorative elements are absent from the semantics tree using findsNothing on their expected SemanticsNodes. Perform a manual test pass with VoiceOver on a physical iOS device and document results in a test report comment.
Flutter's live region (SemanticsProperties.liveRegion) announcement may be delayed or swallowed by the OS accessibility engine if the Semantics tree is not fully built when the screen mounts, causing screen-reader users to miss the denial announcement.
Mitigation & Contingency
Mitigation: Trigger the live region announcement from a post-frame callback (WidgetsBinding.addPostFrameCallback) to ensure the Semantics tree is committed before the announcement fires. Test on both VoiceOver (iOS) and TalkBack (Android) physical devices.
Contingency: If live region timing is unreliable, fall back to using SemanticsService.announce() directly in the initState post-frame callback, which provides more deterministic announcement timing.
The organisation logo may fail to load (network error, missing asset) leaving a broken image in an otherwise functional screen, degrading the professional appearance and potentially confusing users.
Mitigation & Contingency
Mitigation: Wrap the logo widget in an error builder that renders a styled fallback (organisation name text or a generic icon) when the logo asset or network image fails to load.
Contingency: If logo loading is persistently unreliable across organisations, remove the logo from the no-access screen entirely in favour of a text-only header using the organisation's display name from the design token system.