critical priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

When the no-access screen is first rendered, a live region announcement is fired automatically without user interaction
The announcement text includes the full denial reason message (sourced from the ARB localisation system)
The announcement fires exactly once per screen entry — it does not re-fire on widget rebuilds or hot reload
The announcement is implemented via SemanticsService.announce or an equivalent post-frame callback (WidgetsBinding.instance.addPostFrameCallback)
The TextDirection parameter passed to SemanticsService.announce is correct for the active locale
The announcement does not interrupt or conflict with the heading announcement from task-006
The feature degrades gracefully when accessibility services are not active (no exceptions or errors)
The widget uses a StatefulWidget or ConsumerStatefulWidget so initState/didChangeDependencies can schedule the post-frame callback
Manual verification: enabling VoiceOver (iOS) or TalkBack (Android) and navigating to the screen causes the denial message to be spoken automatically

Technical Requirements

frameworks
Flutter
Riverpod
apis
SemanticsService.announce
WidgetsBinding.instance.addPostFrameCallback
performance requirements
The post-frame callback must be registered only in initState, not in build, to prevent repeated announcements
security requirements
The announced string must be sourced from localisation only — never include user-specific data (e.g. email address) in the announcement

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Convert the widget to a StatefulWidget (or ConsumerStatefulWidget if using Riverpod). In initState, register a single addPostFrameCallback that calls SemanticsService.announce(context.l10n.noAccessScreenDenialMessage, TextDirection.ltr). Use a boolean flag _hasAnnounced to guard against re-announcement on subsequent builds — set it to true immediately after scheduling the callback. Do not call SemanticsService.announce inside the build method.

Note that SemanticsService.announce is a best-effort API — it will be silently ignored if no accessibility service is active, which is the desired degradation behaviour. If the project already has a utility function for live region announcements (check utils/ or accessibility_utils.dart), use that instead of calling SemanticsService directly.

Testing Requirements

Write widget tests using flutter_test with semanticsEnabled: true. Test 1: pump the widget and use tester.pump() to flush post-frame callbacks, then verify SemanticsService.announce was called with the expected denial message string (mock SemanticsService or capture via test binding). Test 2: rebuild the widget (setState or provider update) and assert the announcement is NOT fired a second time. Test 3: verify the announcement text matches the localised denial message for the active locale.

Test 4: assert no exception is thrown when semantics is disabled in the test environment. Perform a manual device test with VoiceOver on iOS and document that the announcement fires on screen entry.

Component
No-Access Screen
ui low
Epic Risks (2)
medium impact medium prob technical

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.

low impact low prob scope

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.