Skip to content

Commit 4cfafa3

Browse files
committed
fix(ios): preserve gateway problem recovery state
1 parent ce6376f commit 4cfafa3

5 files changed

Lines changed: 63 additions & 37 deletions

File tree

apps/.i18n/native-source.json

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8059,79 +8059,79 @@
80598059
},
80608060
{
80618061
"kind": "ui-modifier",
8062-
"line": 208,
8062+
"line": 213,
80638063
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
80648064
"source": "Scan QR Code",
80658065
"surface": "apple",
80668066
"id": "native.apple.adae4106b8b9c38b"
80678067
},
80688068
{
80698069
"kind": "ui-modifier",
8070-
"line": 222,
8070+
"line": 227,
80718071
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
80728072
"source": "Reset Onboarding?",
80738073
"surface": "apple",
80748074
"id": "native.apple.22b4d5d925ada9ee"
80758075
},
80768076
{
80778077
"kind": "ui-call",
8078-
"line": 223,
8078+
"line": 228,
80798079
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
80808080
"source": "Reset",
80818081
"surface": "apple",
80828082
"id": "native.apple.4ed955a25c8ed47c"
80838083
},
80848084
{
80858085
"kind": "ui-call",
8086-
"line": 226,
8086+
"line": 231,
80878087
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
80888088
"source": "Cancel",
80898089
"surface": "apple",
80908090
"id": "native.apple.0cb64be43c0b3d97"
80918091
},
80928092
{
80938093
"kind": "ui-call",
8094-
"line": 228,
8094+
"line": 233,
80958095
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
80968096
"source": "This disconnects, clears saved gateway credentials, and reopens onboarding.",
80978097
"surface": "apple",
80988098
"id": "native.apple.800904a3c6cdc147"
80998099
},
81008100
{
81018101
"kind": "ui-modifier",
8102-
"line": 230,
8102+
"line": 235,
81038103
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
81048104
"source": "QR Scanner Unavailable",
81058105
"surface": "apple",
81068106
"id": "native.apple.fed30be42b5be08e"
81078107
},
81088108
{
81098109
"kind": "ui-call",
8110-
"line": 236,
8110+
"line": 241,
81118111
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
81128112
"source": "OK",
81138113
"surface": "apple",
81148114
"id": "native.apple.541078d5af9de662"
81158115
},
81168116
{
81178117
"kind": "ui-call",
8118-
"line": 281,
8118+
"line": 286,
81198119
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
81208120
"source": "Enable OpenClaw Hosted Push Relay?",
81218121
"surface": "apple",
81228122
"id": "native.apple.9bb1dae351d0e3a4"
81238123
},
81248124
{
81258125
"kind": "ui-call",
8126-
"line": 294,
8126+
"line": 299,
81278127
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
81288128
"source": "Continue",
81298129
"surface": "apple",
81308130
"id": "native.apple.f8c042a923222472"
81318131
},
81328132
{
81338133
"kind": "ui-call",
8134-
"line": 297,
8134+
"line": 302,
81358135
"path": "apps/ios/Sources/Design/SettingsProTab.swift",
81368136
"source": "Not Now",
81378137
"surface": "apple",
@@ -8211,167 +8211,167 @@
82118211
},
82128212
{
82138213
"kind": "conditional-branch",
8214-
"line": 602,
8214+
"line": 610,
82158215
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82168216
"source": "Configured",
82178217
"surface": "apple",
82188218
"id": "native.apple.fbf8fb158f556a76"
82198219
},
82208220
{
82218221
"kind": "conditional-branch",
8222-
"line": 602,
8222+
"line": 610,
82238223
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82248224
"source": "Not configured",
82258225
"surface": "apple",
82268226
"id": "native.apple.ad28c6e82fda63b0"
82278227
},
82288228
{
82298229
"kind": "conditional-branch",
8230-
"line": 667,
8230+
"line": 675,
82318231
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82328232
"source": "Connect to the gateway.",
82338233
"surface": "apple",
82348234
"id": "native.apple.0e6f25ab4a59543a"
82358235
},
82368236
{
82378237
"kind": "conditional-branch",
8238-
"line": 667,
8238+
"line": 675,
82398239
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82408240
"source": "Gateway requests will appear here.",
82418241
"surface": "apple",
82428242
"id": "native.apple.0b7a54926d06b7d8"
82438243
},
82448244
{
82458245
"kind": "conditional-branch",
8246-
"line": 722,
8246+
"line": 730,
82478247
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82488248
"source": "High",
82498249
"surface": "apple",
82508250
"id": "native.apple.0194fd3744125fd1"
82518251
},
82528252
{
82538253
"kind": "conditional-branch",
8254-
"line": 722,
8254+
"line": 730,
82558255
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82568256
"source": "Resolving",
82578257
"surface": "apple",
82588258
"id": "native.apple.cf64badb547e25c3"
82598259
},
82608260
{
82618261
"kind": "conditional-branch",
8262-
"line": 727,
8262+
"line": 735,
82638263
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82648264
"source": "One-time approval",
82658265
"surface": "apple",
82668266
"id": "native.apple.c84829a753b01fe6"
82678267
},
82688268
{
82698269
"kind": "conditional-branch",
8270-
"line": 727,
8270+
"line": 735,
82718271
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82728272
"source": "Permission can be saved",
82738273
"surface": "apple",
82748274
"id": "native.apple.b02830c5966591df"
82758275
},
82768276
{
82778277
"kind": "conditional-branch",
8278-
"line": 729,
8278+
"line": 737,
82798279
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82808280
"source": "Medium",
82818281
"surface": "apple",
82828282
"id": "native.apple.3ce9dc6fee455ee5"
82838283
},
82848284
{
82858285
"kind": "conditional-branch",
8286-
"line": 729,
8286+
"line": 737,
82878287
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82888288
"source": "Review",
82898289
"surface": "apple",
82908290
"id": "native.apple.7d5aa3128fb859da"
82918291
},
82928292
{
82938293
"kind": "conditional-branch",
8294-
"line": 754,
8294+
"line": 762,
82958295
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
82968296
"source": "\\(diagnosticsIssueCount)",
82978297
"surface": "apple",
82988298
"id": "native.apple.8ae57748b0b13d62"
82998299
},
83008300
{
83018301
"kind": "conditional-branch",
8302-
"line": 764,
8302+
"line": 772,
83038303
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83048304
"source": "Location \\(self.locationLabel)",
83058305
"surface": "apple",
83068306
"id": "native.apple.f93745d2550799de"
83078307
},
83088308
{
83098309
"kind": "conditional-branch",
8310-
"line": 764,
8310+
"line": 772,
83118311
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83128312
"source": "Location off",
83138313
"surface": "apple",
83148314
"id": "native.apple.acbedcae9f022c29"
83158315
},
83168316
{
83178317
"kind": "conditional-branch",
8318-
"line": 769,
8318+
"line": 777,
83198319
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83208320
"source": "Off",
83218321
"surface": "apple",
83228322
"id": "native.apple.539d9f2b9cde9cb3"
83238323
},
83248324
{
83258325
"kind": "conditional-branch",
8326-
"line": 770,
8326+
"line": 778,
83278327
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83288328
"source": "While Using",
83298329
"surface": "apple",
83308330
"id": "native.apple.86a934ca21b05d9c"
83318331
},
83328332
{
83338333
"kind": "conditional-branch",
8334-
"line": 771,
8334+
"line": 779,
83358335
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83368336
"source": "Always",
83378337
"surface": "apple",
83388338
"id": "native.apple.7774b67d295ea77d"
83398339
},
83408340
{
83418341
"kind": "conditional-branch",
8342-
"line": 785,
8342+
"line": 793,
83438343
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83448344
"source": "Checking iOS notification permission.",
83458345
"surface": "apple",
83468346
"id": "native.apple.2829150c12160b55"
83478347
},
83488348
{
83498349
"kind": "conditional-branch",
8350-
"line": 787,
8350+
"line": 795,
83518351
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83528352
"source": "OpenClaw can show approval prompts and event alerts when the app is not active.",
83538353
"surface": "apple",
83548354
"id": "native.apple.ea2016fd75ff79c9"
83558355
},
83568356
{
83578357
"kind": "conditional-branch",
8358-
"line": 789,
8358+
"line": 797,
83598359
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83608360
"source": "Notifications have been denied. Enable them in iOS Settings.",
83618361
"surface": "apple",
83628362
"id": "native.apple.408e00d224b5a5ac"
83638363
},
83648364
{
83658365
"kind": "conditional-branch",
8366-
"line": 791,
8366+
"line": 799,
83678367
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83688368
"source": "Enable notifications to receive approval prompts and event alerts outside the app.",
83698369
"surface": "apple",
83708370
"id": "native.apple.33add8a7917bad7e"
83718371
},
83728372
{
83738373
"kind": "conditional-branch",
8374-
"line": 793,
8374+
"line": 801,
83758375
"path": "apps/ios/Sources/Design/SettingsProTabActions.swift",
83768376
"source": "OpenClaw cannot determine the current notification permission state.",
83778377
"surface": "apple",
@@ -10907,39 +10907,39 @@
1090710907
},
1090810908
{
1090910909
"kind": "ui-modifier",
10910-
"line": 720,
10910+
"line": 721,
1091110911
"path": "apps/ios/Sources/RootTabs.swift",
1091210912
"source": "Close canvas",
1091310913
"surface": "apple",
1091410914
"id": "native.apple.8af43f59602ec0d8"
1091510915
},
1091610916
{
1091710917
"kind": "conditional-branch",
10918-
"line": 974,
10918+
"line": 975,
1091910919
"path": "apps/ios/Sources/RootTabs.swift",
1092010920
"source": "Gateway needs attention",
1092110921
"surface": "apple",
1092210922
"id": "native.apple.8a4751c86aa6fe77"
1092310923
},
1092410924
{
1092510925
"kind": "conditional-branch",
10926-
"line": 974,
10926+
"line": 975,
1092710927
"path": "apps/ios/Sources/RootTabs.swift",
1092810928
"source": "OpenClaw iOS",
1092910929
"surface": "apple",
1093010930
"id": "native.apple.a55a43940452b04b"
1093110931
},
1093210932
{
1093310933
"kind": "conditional-branch",
10934-
"line": 1010,
10934+
"line": 1011,
1093510935
"path": "apps/ios/Sources/RootTabs.swift",
1093610936
"source": "Available",
1093710937
"surface": "apple",
1093810938
"id": "native.apple.35b6f99d627028f6"
1093910939
},
1094010940
{
1094110941
"kind": "conditional-branch",
10942-
"line": 1010,
10942+
"line": 1011,
1094310943
"path": "apps/ios/Sources/RootTabs.swift",
1094410944
"source": "Gateway default",
1094510945
"surface": "apple",

apps/ios/Sources/Design/SettingsProTab.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ struct SettingsProTab: View {
175175
.onChange(of: self.appModel.gatewaySetupRequestID) { _, _ in
176176
self.applyPendingGatewaySetupLinkIfNeeded()
177177
}
178+
.onChange(of: self.onboardingRequestID) { _, _ in
179+
// Root-owned resets leave Settings mounted behind onboarding.
180+
// Reload cleared credentials before the view can persist stale state.
181+
self.syncAfterOnboardingReset()
182+
}
178183
.onChange(of: self.navigationPath) { _, _ in
179184
self.notifyRouteChange()
180185
}

apps/ios/Sources/Design/SettingsProTabActions.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ extension SettingsProTab {
173173
self.gatewayPassword = GatewaySettingsStore.loadGatewayPassword(instanceId: trimmedInstanceId) ?? ""
174174
}
175175

176+
func syncAfterOnboardingReset() {
177+
self.connectingGatewayID = nil
178+
self.setupStatusText = nil
179+
self.stagedGatewaySetupLink = nil
180+
self.pendingManualAuthOverride = nil
181+
self.syncSettingsState()
182+
}
183+
176184
func connect(_ gateway: GatewayDiscoveryModel.DiscoveredGateway) async {
177185
self.connectingGatewayID = gateway.id
178186
defer { self.connectingGatewayID = nil }

apps/ios/Sources/RootTabs.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,8 +639,9 @@ struct RootTabs: View {
639639
}
640640

641641
private var activeGatewayProblemToast: GatewayConnectionProblem? {
642+
// Operator-scope auth/pairing failures can coexist with a connected node.
643+
// The problem itself, not aggregate gateway status, owns toast visibility.
642644
guard let problem = self.appModel.lastGatewayProblem,
643-
self.gatewayStatus != .connected,
644645
!self.isGatewayToastSwipeDismissed
645646
else { return nil }
646647
return problem

apps/ios/Tests/RootTabsSourceGuardTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,10 @@ struct RootTabsSourceGuardTests {
747747
let trustSource = try String(contentsOf: Self.gatewayTrustPromptAlertSourceURL(), encoding: .utf8)
748748
let controllerSource = try String(contentsOf: Self.gatewayConnectionControllerSourceURL(), encoding: .utf8)
749749
let rootSource = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
750+
let activeProblemToast = try Self.extract(
751+
rootSource,
752+
from: "private var activeGatewayProblemToast: GatewayConnectionProblem?",
753+
to: "private var gatewayToastAnimation: Animation?")
750754

751755
#expect(sectionsSource.contains("var gatewayDestination: some View"))
752756
#expect(sectionsSource.contains("self.gatewayActions"))
@@ -769,6 +773,10 @@ struct RootTabsSourceGuardTests {
769773
#expect(!sectionsSource.contains("GatewayProblemBanner("))
770774
#expect(rootSource.contains("GatewayProblemBanner("))
771775
#expect(rootSource.contains(".gesture(self.gatewayToastSwipeGesture)"))
776+
// Operator auth/pairing problems can coexist with a connected node, so the
777+
// root's only remediation surface must not depend on aggregate status.
778+
#expect(activeProblemToast.contains("self.appModel.lastGatewayProblem"))
779+
#expect(!activeProblemToast.contains("gatewayStatus"))
772780
// Every problem report re-surfaces a swiped-away toast or shakes the
773781
// visible one; value equality alone must not keep the toast hidden.
774782
#expect(rootSource.contains("self.appModel.gatewayProblemReportCount"))
@@ -785,6 +793,10 @@ struct RootTabsSourceGuardTests {
785793
#expect(actionsSource.contains("Tailscale is off on this device. Turn it on, then try again."))
786794
#expect(actionsSource.contains("Run /pair approve in your OpenClaw chat"))
787795
#expect(settingsSource.contains("self.resetOnboarding()"))
796+
#expect(settingsSource.contains(".onChange(of: self.onboardingRequestID)"))
797+
#expect(settingsSource.contains("self.syncAfterOnboardingReset()"))
798+
#expect(actionsSource.contains("func syncAfterOnboardingReset()"))
799+
#expect(actionsSource.contains("self.pendingManualAuthOverride = nil"))
788800
// The root toast is the only gateway problem surface outside covers, so it
789801
// must keep the reset-onboarding primary action the settings banner had.
790802
#expect(rootSource.contains("resetTitle: \"Reset onboarding\""))

0 commit comments

Comments
 (0)