high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

Tapping the link opens the admin portal URL in the device's default browser
The admin portal URL is sourced from a constant or configuration, never hardcoded inline in the widget
The touch target is at least 44×44 dp (use GestureDetector with a minimum size or InkWell with padding)
The link has a visible underline or clear button/link affordance distinguishable from plain body text
The link text is sourced from the ARB localisation file
A Semantics widget provides a descriptive label (e.g. 'Open admin portal in browser') for screen readers
If url_launcher cannot launch the URL (canLaunchUrl returns false), an error state or snackbar is shown instead of a silent failure
The link is styled with a colour that meets WCAG 2.2 AA contrast ratio (4.5:1) against the background
The widget does not open multiple browser tabs if tapped rapidly (debounce or disable on first tap)

Technical Requirements

frameworks
Flutter
apis
url_launcher (launchUrl, canLaunchUrl)
performance requirements
URL launch must be async and not block the UI thread
canLaunchUrl check must be awaited before calling launchUrl
security requirements
The admin portal URL must be validated as an HTTPS URL before launching
Never pass user-supplied strings to launchUrl
Use LaunchMode.externalApplication to open in browser, not a webview that could expose session state
ui components
InkWell or GestureDetector with minimum 44×44 dp padding
Text widget with TextDecoration.underline or equivalent link style from design tokens
Semantics widget with label and hint for screen readers

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use the project's existing url-launcher-util wrapper if one exists (check utils/ or shared/ for a UrlLauncherUtil class) rather than calling url_launcher directly, to keep the dependency injectable for testing. The admin portal URL should be stored in AppConstants or read from a Riverpod provider so it can vary per organisation or environment. Implement a simple boolean _isLaunching state variable to prevent double-tap issues. Use LaunchMode.externalApplication.

Apply a link-specific colour token (e.g. AppColors.linkDefault) which should already exist in the design token system. Keep the ARB key something like noAccessScreenAdminPortalLinkLabel.

Testing Requirements

Write widget and unit tests using flutter_test. Test 1: mock the url-launcher-util and assert launchUrl is called with the correct URL when the link is tapped. Test 2: mock canLaunchUrl to return false and assert a user-facing error message is displayed. Test 3: verify the Semantics label is present in the semantics tree.

Test 4: verify the touch target size is at least 44×44 dp by inspecting the RenderBox size in the widget test. Test 5: verify rapid double-tap does not call launchUrl twice (debounce). Use mockito or mocktail to mock the url_launcher dependency.

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.