Commit bc2dcb7e82

bc2dcb7e825f5d814a6f456eca7b47e2f08b9110

parent: 948330fe88

Unsigned

cmc <hello@cleberg.net> · 2026-03-23 01:40 UTC

feat: surface an alert when a deep link fails to resolve

Layout: unified · split

Hutch/App/AppState.swift +10
@@ -54,6 +54,7 @@ final class AppState {
5454 /// Set by the deep link handler; consumed by RootView to drive navigation.
5555 var pendingDeepLink: DeepLink?
5656 var pendingTabNavigation: TabNavigationTarget?
57 var deepLinkError: String?
5758
5859 // MARK: - Init
5960
@@ -182,6 +183,14 @@ final class AppState {
182183 selectedTab = .more
183184 }
184185
186 func presentRepositoryDeepLinkError() {
187 deepLinkError = "The repository could not be found or is inaccessible."
188 }
189
190 func presentTicketDeepLinkError() {
191 deepLinkError = "The ticket could not be found or is inaccessible."
192 }
193
185194 // MARK: - Private
186195
187196 private static let meQuery = """
@@ -257,6 +266,7 @@ final class AppState {
257266 currentUser = nil
258267 pendingDeepLink = nil
259268 pendingTabNavigation = nil
269 deepLinkError = nil
260270 selectedTab = .home
261271 }
262272
Hutch/App/RootView.swift +21 −2
@@ -12,6 +12,8 @@ struct RootView: View {
1212 @State private var isResolvingDeepLink = false
1313
1414 var body: some View {
15 @Bindable var appState = appState
16
1517 Group {
1618 switch appState.authPhase {
1719 case .launching:
@@ -37,6 +39,23 @@ struct RootView: View {
3739 .onChange(of: appState.pendingTabNavigation) { _, newValue in
3840 consumePendingTabNavigationIfPossible(newValue)
3941 }
42 .alert(
43 "Couldn't Open Link",
44 isPresented: Binding(
45 get: { appState.deepLinkError != nil },
46 set: { isPresented in
47 if !isPresented {
48 appState.deepLinkError = nil
49 }
50 }
51 )
52 ) {
53 Button("OK") {
54 appState.deepLinkError = nil
55 }
56 } message: {
57 Text(appState.deepLinkError ?? "")
58 }
4059 }
4160
4261 // MARK: - Tab View
@@ -198,7 +217,7 @@ struct RootView: View {
198217 await settleNavigationTransition()
199218 repoPath.append(summary)
200219 } catch {
201 // Silently fail — the repo may not exist or be inaccessible
220 appState.presentRepositoryDeepLinkError()
202221 }
203222 }
204223 }
@@ -221,7 +240,7 @@ struct RootView: View {
221240 ticketId: ticketId
222241 ))
223242 } catch {
224 // Silently fail
243 appState.presentTicketDeepLinkError()
225244 }
226245 }
227246 }
HutchTests/AppStateTests.swift added +25
@@ -0,0 +1,25 @@
1import Testing
2@testable import Hutch
3
4struct AppStateTests {
5
6 @Test
7 @MainActor
8 func presentRepositoryDeepLinkErrorSetsUserFacingMessage() {
9 let appState = AppState()
10
11 appState.presentRepositoryDeepLinkError()
12
13 #expect(appState.deepLinkError == "The repository could not be found or is inaccessible.")
14 }
15
16 @Test
17 @MainActor
18 func presentTicketDeepLinkErrorSetsUserFacingMessage() {
19 let appState = AppState()
20
21 appState.presentTicketDeepLinkError()
22
23 #expect(appState.deepLinkError == "The ticket could not be found or is inaccessible.")
24 }
25}