M7 #3: Chat 'Connecting…' overlay during SSH exec handshake

ChatController already transitioned through a `.connecting` state
between tap-Chat and first-message-ready (ACP initialize + session/new
take ~0.5–1.5 s on a warm network), but there was no visible UI
— the screen stayed on the idle layout with a disabled composer.
Users interpreted the silence as a frozen app (pass-1 M7 #3).

Adds a `.regularMaterial` overlay with a large ProgressView +
"Connecting to <nickname>…" text, rendered whenever
`controller.state == .connecting`. Disappears automatically when
state flips to `.ready` (normal path) or `.failed` (handoff to the
existing errorOverlay).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-04-24 13:08:35 +02:00
parent fee5e72d30
commit 742605d359
+20
View File
@@ -63,6 +63,8 @@ struct ChatView: View {
.overlay {
if case .failed(let msg) = controller.state {
errorOverlay(msg)
} else if controller.state == .connecting {
connectingOverlay
}
}
.sheet(item: Binding(
@@ -163,6 +165,24 @@ struct ChatView: View {
.background(.regularMaterial)
}
/// Shown while we're opening the SSH exec channel + spawning
/// `hermes acp` + creating the ACP session. Typically ~0.51.5 s
/// on a warm network silent before this overlay existed, which
/// made the app feel frozen (pass-1 M7 #3).
@ViewBuilder
private var connectingOverlay: some View {
VStack(spacing: 12) {
ProgressView()
.controlSize(.large)
Text("Connecting to \(config.displayName)")
.font(.callout)
.foregroundStyle(.secondary)
}
.padding(24)
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 14))
}
@ViewBuilder
private func errorOverlay(_ message: String) -> some View {
VStack(spacing: 12) {