critical priority low complexity infrastructure pending backend specialist Tier 0

Acceptance Criteria

File `lib/core/utils/url_launcher_util.dart` exists and exports `UrlLauncherUtil` and the `UrlLaunchResult` sealed class/enum
`UrlLaunchResult` has at least three variants: `success`, `unsupported`, `error(String message)`
`launchUrl(String url)` returns `UrlLaunchResult.success` when `canLaunchUrl` returns true and `launchUrl` (package) returns true
`launchUrl(String url)` returns `UrlLaunchResult.unsupported` when `canLaunchUrl` returns false
`launchUrl(String url)` returns `UrlLaunchResult.error(message)` when `Uri.tryParse` returns null or the url_launcher package throws a `PlatformException`
The method uses `LaunchMode.externalApplication` when calling the underlying package
No `throw` statements escape the public method boundary—all errors are captured in the result type
The class is constructible with an optional `UrlLauncherPlatform` dependency for testability
`npm run build` equivalent (`flutter analyze`) passes with zero issues

Technical Requirements

frameworks
Flutter
url_launcher (^6.x)
data models
UrlLaunchResult (sealed class or enum with data)
performance requirements
Method is non-blocking; any synchronous URI validation completes in <1ms
No unnecessary async gaps before the canLaunchUrl check
security requirements
Validate URI scheme before launching; only allow https:// and mailto: by default (configurable allowlist)
Do not log the full URL at info level to avoid leaking sensitive query params in production logs

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define a `UrlLaunchResult` sealed class using Dart 3 `sealed class` syntax with three subclasses: `UrlLaunchSuccess`, `UrlLaunchUnsupported`, and `UrlLaunchError(String message)`. This enables exhaustive `switch` pattern matching in callers. The wrapper should accept an abstract `UrlLauncherInterface` (or use `url_launcher_platform_interface`) in its constructor with a default value of the real implementation, enabling mock injection in tests. Avoid a static `instance` pattern as it complicates testing.

Keep the class stateless—all state lives in the caller. The URI validation (`Uri.tryParse` + scheme allowlist check) should happen before any async platform call to fail fast synchronously. Follow the no-access screen's design requirement that admin portal links open reliably even on constrained devices.

Testing Requirements

Covered by `epic-no-access-screen-foundation-task-007`. This task focuses on implementation only. However, add a basic smoke assertion in a `main()` debug entry point or document the manual test steps (open a valid HTTPS URL, observe browser opening) to confirm platform wiring before the test task runs.

Component
URL Launcher Utility
infrastructure low
Epic Risks (2)
high impact medium prob technical

Supabase remote config may be unavailable at app startup (network error, cold start), causing the repository to return no blocked-role list. If the fallback is empty, blocked users could access the app.

Mitigation & Contingency

Mitigation: Define a local constants fallback list of blocked roles compiled into the app binary. Remote config enriches or overrides this list when available.

Contingency: If remote config repeatedly fails in production, pin the blocked-role list to local constants only and disable remote override until the Supabase config endpoint is stabilised.

medium impact low prob dependency

url_launcher package behaviour differs between iOS and Android (e.g. canLaunchUrl returning false on some Android configurations), leading to silent failures when the admin portal link is tapped.

Mitigation & Contingency

Mitigation: Run canLaunchUrl check before every launch attempt and surface a descriptive inline error message (e.g. 'Could not open link — visit admin.example.org manually') when the check fails.

Contingency: If canLaunchUrl is consistently unreliable on a target platform, replace the tap-to-open pattern with a copyable text field showing the URL as a fallback.