iOS onboarding: prevent pairing flicker during auto-resume (#20310)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 691808b747
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
Mariano
2026-02-18 19:39:41 +00:00
committed by GitHub
parent c2d12b7e31
commit 6e7f1a6a1b
2 changed files with 23 additions and 5 deletions

View File

@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- iOS/Onboarding: prevent pairing-status flicker during auto-resume by keeping resumed state transitions stable. (#20310) Thanks @mbelinky.
- OpenClawKit/Protocol: preserve JSON boolean literals (`true`/`false`) when bridging through `AnyCodable` so Apple client RPC params no longer re-encode booleans as `1`/`0`. Thanks @mbelinky.
- iOS/Onboarding: stabilize pairing and reconnect behavior by resetting stale pairing request state on manual retry, disconnecting both operator and node gateways on operator failure, and avoiding duplicate pairing loops from operator transport identity attachment. (#20056) Thanks @mbelinky.
- Browser/Relay: reuse an already-running extension relay when the relay port is occupied by another OpenClaw process, while still failing on non-relay port collisions to avoid masking unrelated listeners. (#20035) Thanks @mbelinky.

View File

@@ -1,4 +1,5 @@
import CoreImage
import Combine
import OpenClawKit
import PhotosUI
import SwiftUI
@@ -68,6 +69,7 @@ struct OnboardingWizardView: View {
@State private var scannerError: String?
@State private var selectedPhoto: PhotosPickerItem?
@State private var lastPairingAutoResumeAttemptAt: Date?
private static let pairingAutoResumeTicker = Timer.publish(every: 2.0, on: .main, in: .common).autoconnect()
let allowSkip: Bool
let onClose: () -> Void
@@ -271,6 +273,7 @@ struct OnboardingWizardView: View {
}
.onChange(of: self.appModel.gatewayServerName) { _, newValue in
guard newValue != nil else { return }
self.showQRScanner = false
self.statusLine = "Connected."
if !self.didMarkCompleted, let selectedMode {
OnboardingStateStore.markCompleted(mode: selectedMode)
@@ -282,6 +285,9 @@ struct OnboardingWizardView: View {
guard newValue == .active else { return }
self.attemptAutomaticPairingResumeIfNeeded()
}
.onReceive(Self.pairingAutoResumeTicker) { _ in
self.attemptAutomaticPairingResumeIfNeeded()
}
}
@ViewBuilder
@@ -685,7 +691,16 @@ struct OnboardingWizardView: View {
Task { await self.retryLastAttempt() }
}
private func resumeAfterPairingApprovalInBackground() {
// Keep the pairing issue sticky to avoid visual flicker while we probe for approval.
self.appModel.gatewayAutoReconnectEnabled = true
self.appModel.gatewayPairingPaused = false
self.appModel.gatewayPairingRequestId = nil
Task { await self.retryLastAttempt(silent: true) }
}
private func attemptAutomaticPairingResumeIfNeeded() {
guard self.scenePhase == .active else { return }
guard self.step == .auth else { return }
guard self.issue.needsPairing else { return }
guard self.connectingGatewayID == nil else { return }
@@ -695,7 +710,7 @@ struct OnboardingWizardView: View {
return
}
self.lastPairingAutoResumeAttemptAt = now
self.resumeAfterPairingApproval()
self.resumeAfterPairingApprovalInBackground()
}
private func detectQRCode(from data: Data) -> String? {
@@ -837,11 +852,13 @@ struct OnboardingWizardView: View {
await self.gatewayController.connectManual(host: host, port: self.manualPort, useTLS: self.manualTLS)
}
private func retryLastAttempt() async {
self.connectingGatewayID = "retry"
private func retryLastAttempt(silent: Bool = false) async {
self.connectingGatewayID = silent ? "retry-auto" : "retry"
// Keep current auth/pairing issue sticky while retrying to avoid Step 3 UI flip-flop.
self.connectMessage = "Retrying…"
self.statusLine = "Retrying last connection"
if !silent {
self.connectMessage = "Retrying…"
self.statusLine = "Retrying last connection…"
}
defer { self.connectingGatewayID = nil }
await self.gatewayController.connectLastKnown()
}