fix(macos): make status lines non-selectable

This commit is contained in:
Peter Steinberger
2025-12-13 13:59:53 +00:00
parent 3ca77c46c7
commit d52ef185b1

View File

@@ -21,11 +21,17 @@ struct MenuContent: View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Toggle(isOn: self.activeBinding) { Toggle(isOn: self.activeBinding) {
let label = self.state.connectionMode == .remote ? "Remote Clawdis Active" : "Clawdis Active" let label = self.state.connectionMode == .remote ? "Remote Clawdis Active" : "Clawdis Active"
Text(label) VStack(alignment: .leading, spacing: 2) {
Text(label)
self.statusLine(label: self.healthStatus.label, color: self.healthStatus.color)
}
}
Toggle(isOn: self.heartbeatsBinding) {
VStack(alignment: .leading, spacing: 2) {
Text("Send Heartbeats")
self.statusLine(label: self.heartbeatStatus.label, color: self.heartbeatStatus.color)
}
} }
self.statusRow
Toggle(isOn: self.heartbeatsBinding) { Text("Send Heartbeats") }
self.heartbeatStatusRow
Toggle(isOn: self.voiceWakeBinding) { Text("Voice Wake") } Toggle(isOn: self.voiceWakeBinding) { Text("Voice Wake") }
.disabled(!voiceWakeSupported) .disabled(!voiceWakeSupported)
.opacity(voiceWakeSupported ? 1 : 0.5) .opacity(voiceWakeSupported ? 1 : 0.5)
@@ -198,93 +204,70 @@ struct MenuContent: View {
NotificationCenter.default.post(name: .clawdisSelectSettingsTab, object: tab) NotificationCenter.default.post(name: .clawdisSelectSettingsTab, object: tab)
} }
private var statusRow: some View { private var healthStatus: (label: String, color: Color) {
let (label, color): (String, Color) = { if let activity = self.activityStore.current {
if let activity = self.activityStore.current { let color: Color = activity.role == .main ? .accentColor : .gray
let color: Color = activity.role == .main ? .accentColor : .gray let roleLabel = activity.role == .main ? "Main" : "Other"
let roleLabel = activity.role == .main ? "Main" : "Other" let text = "\(roleLabel) · \(activity.label)"
let text = "\(roleLabel) · \(activity.label)" return (text, color)
return (text, color) }
}
let health = self.healthStore.state let health = self.healthStore.state
let isRefreshing = self.healthStore.isRefreshing let isRefreshing = self.healthStore.isRefreshing
let lastAge = self.healthStore.lastSuccess.map { age(from: $0) } let lastAge = self.healthStore.lastSuccess.map { age(from: $0) }
if isRefreshing { if isRefreshing {
return ("Health check running…", health.tint) return ("Health check running…", health.tint)
} }
switch health { switch health {
case .ok: case .ok:
let ageText = lastAge.map { " · checked \($0)" } ?? "" let ageText = lastAge.map { " · checked \($0)" } ?? ""
return ("Health ok\(ageText)", .green) return ("Health ok\(ageText)", .green)
case .linkingNeeded: case .linkingNeeded:
return ("Health: login required", .red) return ("Health: login required", .red)
case let .degraded(reason): case let .degraded(reason):
let detail = HealthStore.shared.degradedSummary ?? reason let detail = HealthStore.shared.degradedSummary ?? reason
let ageText = lastAge.map { " · checked \($0)" } ?? "" let ageText = lastAge.map { " · checked \($0)" } ?? ""
return ("\(detail)\(ageText)", .orange) return ("\(detail)\(ageText)", .orange)
case .unknown: case .unknown:
return ("Health pending", .secondary) return ("Health pending", .secondary)
} }
}()
return Button(
action: {},
label: {
HStack(spacing: 8) {
Circle()
.fill(color)
.frame(width: 8, height: 8)
Text(label)
.font(.caption.weight(.semibold))
.foregroundStyle(.primary)
}
.padding(.vertical, 4)
})
.buttonStyle(.plain)
.disabled(true)
} }
private var heartbeatStatusRow: some View { private var heartbeatStatus: (label: String, color: Color) {
let (label, color): (String, Color) = { if case .degraded = self.controlChannel.state {
if case .degraded = self.controlChannel.state { return ("Control channel disconnected", .red)
return ("Control channel disconnected", .red) } else if let evt = self.heartbeatStore.lastEvent {
} else if let evt = self.heartbeatStore.lastEvent { let ageText = age(from: Date(timeIntervalSince1970: evt.ts / 1000))
let ageText = age(from: Date(timeIntervalSince1970: evt.ts / 1000)) switch evt.status {
switch evt.status { case "sent":
case "sent": return ("Last heartbeat sent · \(ageText)", .blue)
return ("Last heartbeat sent · \(ageText)", .blue) case "ok-empty", "ok-token":
case "ok-empty", "ok-token": return ("Heartbeat ok · \(ageText)", .green)
return ("Heartbeat ok · \(ageText)", .green) case "skipped":
case "skipped": return ("Heartbeat skipped · \(ageText)", .secondary)
return ("Heartbeat skipped · \(ageText)", .secondary) case "failed":
case "failed": return ("Heartbeat failed · \(ageText)", .red)
return ("Heartbeat failed · \(ageText)", .red) default:
default: return ("Heartbeat · \(ageText)", .secondary)
return ("Heartbeat · \(ageText)", .secondary)
}
} else {
return ("No heartbeat yet", .secondary)
} }
}() } else {
return ("No heartbeat yet", .secondary)
}
}
return Button( @ViewBuilder
action: {}, private func statusLine(label: String, color: Color) -> some View {
label: { HStack(spacing: 6) {
HStack(spacing: 8) { Circle()
Circle() .fill(color)
.fill(color) .frame(width: 6, height: 6)
.frame(width: 8, height: 8) Text(label)
Text(label) .font(.caption)
.font(.caption.weight(.semibold)) .foregroundStyle(.secondary)
.foregroundStyle(.primary) }
} .padding(.top, 2)
.padding(.vertical, 2)
})
.buttonStyle(.plain)
.disabled(true)
} }
private var activeBinding: Binding<Bool> { private var activeBinding: Binding<Bool> {