ai-dispatch 10.40.0

Multi-AI CLI team orchestrator
// HUD bar — ship mark, rank/XP, condition lamp, tabs, clock.
// Exports: HUDBarView.

import SwiftUI

struct HUDBarView: View {
    @Environment(\.theme) private var theme
    @Environment(\.consoleLayout) private var layout
    let snapshot: FleetSnapshot
    let xpState: XPState
    let commanderName: String
    @Binding var selectedTab: CenterTab
    var onToggleRail: () -> Void

    @State private var clock = Date()

    var body: some View {
        HStack(spacing: theme.spacing.lg) {
            if layout == .compact {
                Button(action: onToggleRail) {
                    MonoLabel(text: "scan")
                }
                .buttonStyle(.plain)
            }
            shipMark
            titleBlock
            rankBlock
            conditionLamp
            Spacer()
            tabGroup
            clockView
            linkLamp
        }
        .padding(.horizontal, theme.spacing.lg)
        .frame(height: 62)
        .background(theme.bg)
        .onAppear { startClock() }
    }

    private var shipMark: some View {
        ZStack {
            Rectangle()
                .fill(theme.accent.opacity(0.2))
                .frame(width: 22, height: 22)
                .rotationEffect(.degrees(45))
            StatusLamp(color: theme.accent)
        }
    }

    private var titleBlock: some View {
        VStack(alignment: .leading, spacing: 2) {
            Text("AID · FLEET COMMAND")
                .font(theme.font(.title))
                .foregroundStyle(theme.ink)
            Text("BUILD \(snapshot.serverVersion) · SECTORS \(snapshot.summary.sectorCount) · \(commanderName)")
                .font(theme.font(.caption))
                .foregroundStyle(theme.ink2)
        }
    }

    private var rankBlock: some View {
        VStack(alignment: .leading, spacing: 4) {
            MonoLabel(text: "rank \(xpState.rankLabel)")
            GeometryReader { geo in
                ZStack(alignment: .leading) {
                    Rectangle().fill(theme.panelEdge)
                    Rectangle().fill(theme.accent).frame(width: geo.size.width * xpState.barProgress)
                }
            }
            .frame(width: 80, height: 6)
        }
    }

    private var conditionLamp: some View {
        let failed = snapshot.summary.failed
        let color: Color = failed >= 4 ? theme.fail : (failed >= 1 ? theme.stop : theme.done)
        let label = failed >= 4 ? "RED" : (failed >= 1 ? "AMBER" : "GREEN")
        return HStack(spacing: 6) {
            StatusLamp(color: color)
            MonoLabel(text: "cond \(label)", color: color)
        }
    }

    private var tabGroup: some View {
        HStack(spacing: theme.spacing.xs) {
            ForEach(CenterTab.allCases, id: \.self) { tab in
                ThemedButton(title: tab.rawValue, selected: selectedTab == tab) {
                    selectedTab = tab
                }
            }
        }
    }

    private var clockView: some View {
        TimelineView(.periodic(from: .now, by: 1)) { context in
            Text(context.date.formatted(date: .omitted, time: .standard))
                .font(theme.font(.label))
                .foregroundStyle(theme.ink2)
                .monospacedDigit()
        }
    }

    private var linkLamp: some View {
        let color: Color
        let label: String
        switch snapshot.connection {
        case .live:
            color = theme.done; label = "LINK"
        case .connecting:
            color = theme.stop; label = "LINK…"
        case .degraded(let age):
            color = theme.stop; label = "STALE \(Int(age))s"
        case .error(let message):
            color = theme.fail; label = String(message.prefix(24))
        case .disconnected:
            color = theme.fail; label = "OFF"
        }
        return HStack(spacing: 4) {
            StatusLamp(color: color, active: snapshot.connection == .live)
            MonoLabel(text: label, color: color)
        }
    }

    private func startClock() {
        clock = Date()
    }
}