From 3ed2bb4da64d7cc9b67ef86a3892ab41b14fcc51 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Sun, 22 Mar 2026 20:40:53 -0500 Subject: feat: surface an alert when a deep link fails to resolve --- Hutch/App/AppState.swift | 10 ++++++++++ Hutch/App/RootView.swift | 23 +++++++++++++++++++++-- HutchTests/AppStateTests.swift | 25 +++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 HutchTests/AppStateTests.swift diff --git a/Hutch/App/AppState.swift b/Hutch/App/AppState.swift index d5a86fd..e74a58b 100644 --- a/Hutch/App/AppState.swift +++ b/Hutch/App/AppState.swift @@ -54,6 +54,7 @@ final class AppState { /// Set by the deep link handler; consumed by RootView to drive navigation. var pendingDeepLink: DeepLink? var pendingTabNavigation: TabNavigationTarget? + var deepLinkError: String? // MARK: - Init @@ -182,6 +183,14 @@ final class AppState { selectedTab = .more } + func presentRepositoryDeepLinkError() { + deepLinkError = "The repository could not be found or is inaccessible." + } + + func presentTicketDeepLinkError() { + deepLinkError = "The ticket could not be found or is inaccessible." + } + // MARK: - Private private static let meQuery = """ @@ -257,6 +266,7 @@ final class AppState { currentUser = nil pendingDeepLink = nil pendingTabNavigation = nil + deepLinkError = nil selectedTab = .home } diff --git a/Hutch/App/RootView.swift b/Hutch/App/RootView.swift index f615541..9c4e71a 100644 --- a/Hutch/App/RootView.swift +++ b/Hutch/App/RootView.swift @@ -12,6 +12,8 @@ struct RootView: View { @State private var isResolvingDeepLink = false var body: some View { + @Bindable var appState = appState + Group { switch appState.authPhase { case .launching: @@ -37,6 +39,23 @@ struct RootView: View { .onChange(of: appState.pendingTabNavigation) { _, newValue in consumePendingTabNavigationIfPossible(newValue) } + .alert( + "Couldn't Open Link", + isPresented: Binding( + get: { appState.deepLinkError != nil }, + set: { isPresented in + if !isPresented { + appState.deepLinkError = nil + } + } + ) + ) { + Button("OK") { + appState.deepLinkError = nil + } + } message: { + Text(appState.deepLinkError ?? "") + } } // MARK: - Tab View @@ -198,7 +217,7 @@ struct RootView: View { await settleNavigationTransition() repoPath.append(summary) } catch { - // Silently fail — the repo may not exist or be inaccessible + appState.presentRepositoryDeepLinkError() } } } @@ -221,7 +240,7 @@ struct RootView: View { ticketId: ticketId )) } catch { - // Silently fail + appState.presentTicketDeepLinkError() } } } diff --git a/HutchTests/AppStateTests.swift b/HutchTests/AppStateTests.swift new file mode 100644 index 0000000..ca34df2 --- /dev/null +++ b/HutchTests/AppStateTests.swift @@ -0,0 +1,25 @@ +import Testing +@testable import Hutch + +struct AppStateTests { + + @Test + @MainActor + func presentRepositoryDeepLinkErrorSetsUserFacingMessage() { + let appState = AppState() + + appState.presentRepositoryDeepLinkError() + + #expect(appState.deepLinkError == "The repository could not be found or is inaccessible.") + } + + @Test + @MainActor + func presentTicketDeepLinkErrorSetsUserFacingMessage() { + let appState = AppState() + + appState.presentTicketDeepLinkError() + + #expect(appState.deepLinkError == "The ticket could not be found or is inaccessible.") + } +} -- cgit v1.2.3