diff --git a/apps/macos/Sources/Clawdis/GeneralSettings.swift b/apps/macos/Sources/Clawdis/GeneralSettings.swift index 6e47ea752..2d59ea29b 100644 --- a/apps/macos/Sources/Clawdis/GeneralSettings.swift +++ b/apps/macos/Sources/Clawdis/GeneralSettings.swift @@ -330,14 +330,34 @@ private enum RemoteStatus: Equatable { extension GeneralSettings { private var healthRow: some View { - HStack(spacing: 10) { - Circle() - .fill(self.healthStore.state.tint) - .frame(width: 10, height: 10) - Text(self.healthStore.summaryLine) - .font(.callout) - .lineLimit(1) - .frame(maxWidth: .infinity, alignment: .leading) + VStack(alignment: .leading, spacing: 6) { + HStack(spacing: 10) { + Circle() + .fill(self.healthStore.state.tint) + .frame(width: 10, height: 10) + Text(self.healthStore.summaryLine) + .font(.callout) + .frame(maxWidth: .infinity, alignment: .leading) + } + + if let detail = self.healthStore.detailLine { + Text(detail) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + + HStack(spacing: 10) { + Button("Retry now") { + Task { await HealthStore.shared.refresh(onDemand: true) } + } + .disabled(self.healthStore.isRefreshing) + + Button("Open logs") { self.revealLogs() } + .buttonStyle(.link) + .foregroundStyle(.secondary) + } + .font(.caption) } } diff --git a/apps/macos/Sources/Clawdis/HealthStore.swift b/apps/macos/Sources/Clawdis/HealthStore.swift index 7b576c3fd..d2492a930 100644 --- a/apps/macos/Sources/Clawdis/HealthStore.swift +++ b/apps/macos/Sources/Clawdis/HealthStore.swift @@ -149,6 +149,21 @@ final class HealthStore: ObservableObject { return "linked · auth \(auth) · socket ok" } + /// Short, human-friendly detail for the last failure, used in the UI. + var detailLine: String? { + if let error = self.lastError, !error.isEmpty { + let lower = error.lowercased() + if lower.contains("connection refused") { + return "The relay control port (127.0.0.1:18789) isn’t listening — restart Clawdis to bring it back." + } + if lower.contains("timeout") { + return "Timed out waiting for the control server; the relay may be crashed or still starting." + } + return error + } + return nil + } + private func describeFailure(from snap: HealthSnapshot, fallback: String?) -> String { if !snap.web.linked { return "Not linked — run clawdis login"