// FloatingHUDView.swift — Content for the always-on-top focus HUD panel import SwiftUI struct FloatingHUDView: View { @Environment(SessionManager.self) private var session var body: some View { VStack(alignment: .leading, spacing: 0) { header Divider() content } .background(.ultraThinMaterial) .clipShape(.rect(cornerRadius: 12)) .shadow(color: .black.opacity(0.25), radius: 12, x: 0, y: 4) .frame(width: 320) .animation(.spring(duration: 0.3), value: session.proactiveCard?.id) .animation(.spring(duration: 0.3), value: session.isExecuting) .animation(.spring(duration: 0.3), value: session.executorOutput?.title) } // MARK: - Header private var header: some View { HStack(spacing: 8) { Image(systemName: "eye.fill") .foregroundStyle(.blue) .font(.caption) Text(session.activeTask?.title ?? "Focus Session") .font(.caption.bold()) .lineLimit(1) Spacer() // Pulse dot — green when capturing, orange when executing if session.isExecuting { Image(systemName: "bolt.fill") .font(.caption2) .foregroundStyle(.orange) } else { Circle() .fill(session.isCapturing ? Color.green : Color.secondary.opacity(0.4)) .frame(width: 7, height: 7) .overlay( Circle() .fill(session.isCapturing ? Color.green.opacity(0.3) : .clear) .frame(width: 14, height: 14) ) } } .padding(.horizontal, 14) .padding(.vertical, 10) } // MARK: - Content @ViewBuilder private var content: some View { // Executor output sticky card (highest priority — persists until dismissed) if let output = session.executorOutput { ExecutorOutputCard(title: output.title, content: output.content) { session.executorOutput = nil } .transition(.move(edge: .top).combined(with: .opacity)) } // Executing spinner else if session.isExecuting { HStack(spacing: 10) { ProgressView() .controlSize(.small) Text("Executing action…") .font(.caption) .foregroundStyle(.orange) } .padding(14) .transition(.opacity) } // Proactive friction card else if let card = session.proactiveCard { HUDCardView(card: card) .transition(.move(edge: .top).combined(with: .opacity)) } // Latest VLM summary (idle state) else { Text(session.latestVlmSummary ?? "Monitoring your screen…") .font(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) .padding(14) .transition(.opacity) } } } // MARK: - HUD Card (friction + proposed actions) private struct HUDCardView: View { let card: ProactiveCard @Environment(SessionManager.self) private var session var body: some View { VStack(alignment: .leading, spacing: 10) { // Title row HStack(alignment: .top, spacing: 8) { Image(systemName: card.icon) .foregroundStyle(.purple) VStack(alignment: .leading, spacing: 3) { Text(card.title) .font(.caption.bold()) .foregroundStyle(.purple) Text(bodyText) .font(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) .lineLimit(3) } Spacer() Button { session.dismissProactiveCard() } label: { Image(systemName: "xmark") .font(.caption2.bold()) .foregroundStyle(.secondary) } .buttonStyle(.plain) } // Action buttons — only for VLM friction with proposed actions if case .vlmFriction(_, _, let actions) = card.source, !actions.isEmpty { VStack(alignment: .leading, spacing: 6) { ForEach(Array(actions.prefix(2).enumerated()), id: \.offset) { index, action in Button { session.approveProactiveCard(actionIndex: index) } label: { VStack(alignment: .leading, spacing: 2) { Text(action.label) .font(.caption.bold()) .lineLimit(2) .multilineTextAlignment(.leading) if let details = action.details, !details.isEmpty { Text(details) .font(.caption2) .foregroundStyle(.purple.opacity(0.7)) .lineLimit(2) .multilineTextAlignment(.leading) } } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 10) .padding(.vertical, 6) .background(Color.purple.opacity(0.12)) .clipShape(.rect(cornerRadius: 8)) } .buttonStyle(.plain) .foregroundStyle(.purple) } Button("Not now — I'm good") { session.dismissProactiveCard() } .font(.caption) .foregroundStyle(.secondary) .buttonStyle(.plain) .padding(.top, 2) } } } .padding(14) .background(Color.purple.opacity(0.07)) } private var bodyText: String { switch card.source { case .vlmFriction(_, let description, _): return description ?? "I noticed something that might be slowing you down." case .appSwitchLoop(let apps, let count): return "You've switched between \(apps.joined(separator: " ↔ ")) \(count)× — are you stuck?" } } } // MARK: - Executor Output Sticky Card private struct ExecutorOutputCard: View { let title: String let content: String let onDismiss: () -> Void var body: some View { VStack(alignment: .leading, spacing: 8) { HStack(spacing: 6) { Image(systemName: "checkmark.circle.fill") .foregroundStyle(.green) .font(.caption) Text(title) .font(.caption.bold()) .foregroundStyle(.green) .lineLimit(1) Spacer() Button { onDismiss() } label: { Image(systemName: "xmark") .font(.caption2.bold()) .foregroundStyle(.secondary) } .buttonStyle(.plain) } ScrollView { Text(content) .font(.caption) .foregroundStyle(.primary) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) } .frame(maxHeight: 120) Button("Dismiss") { onDismiss() } .font(.caption) .foregroundStyle(.secondary) .buttonStyle(.plain) .frame(maxWidth: .infinity, alignment: .trailing) } .padding(14) .background(Color.green.opacity(0.07)) } }