ai-dispatch 10.40.0

Multi-AI CLI team orchestrator
// Agent and drive glyphs — SVG paths ported to SwiftUI Path.
// Exports: UnitGlyph, DriveGlyph, GlyphQuantizer.

import SwiftUI

struct UnitGlyph: View {
    @Environment(\.theme) private var theme
    let agent: String
    var size: CGFloat = 24

    var body: some View {
        agentPath
            .stroke(theme.ink, style: StrokeStyle(lineWidth: 1.7, lineCap: .round, lineJoin: .round))
            .frame(width: size, height: size)
            .modifier(GlyphQuantizer(enabled: theme.motion.stepped))
    }

    private var agentPath: Path {
        switch agent.lowercased() {
        case "codex":
            return path("M9 4 L3 12 L9 20 M15 4 L21 12 L15 20 M12 7 L12 17")
        case "cursor":
            return path("M5 3 L19 11 L12 12.5 L9.5 20 Z")
        case "grok":
            return path("M3 12 C7 7 17 7 21 12 C17 17 7 17 3 12 Z M12 9.6 A2.4 2.4 0 1 0 12 14.4 A2.4 2.4 0 1 0 12 9.6")
        case "gemini":
            return path("M12 2 L14 10 L22 12 L14 14 L12 22 L10 14 L2 12 L10 10 Z")
        case "opencode":
            return path("M12 3 L20 7.5 L20 16.5 L12 21 L4 16.5 L4 7.5 Z M12 8.5 L15.5 10.5 L15.5 14 L12 16 L8.5 14 L8.5 10.5 Z")
        case "kilo":
            return path("M4 19 L12 5 L20 19 M8.5 19 L12 12 L15.5 19")
        default:
            return path("M12 3 L20 7.5 L20 16.5 L12 21 L4 16.5 L4 7.5 Z")
        }
    }

    private func path(_ d: String) -> Path {
        SVGPathParser.parse(d, in: CGRect(x: 0, y: 0, width: 24, height: 24))
    }
}

struct DriveGlyph: View {
    @Environment(\.theme) private var theme
    let model: String?
    var size: CGFloat = 14

    var body: some View {
        Group {
            if let model {
                drivePath(for: model)
            } else {
                Text("—")
                    .font(theme.font(.caption))
                    .foregroundStyle(theme.ink3)
            }
        }
        .frame(width: size, height: size)
        .modifier(GlyphQuantizer(enabled: theme.motion.stepped))
    }

    @ViewBuilder
    private func drivePath(for model: String) -> some View {
        let lower = model.lowercased()
        if lower.contains("gpt") {
            hexagon.stroke(theme.ink2, lineWidth: 1.2)
        } else if lower.contains("grok") {
            arrow.stroke(theme.ink2, lineWidth: 1.2)
        } else if lower.contains("gemini") {
            sparkle.stroke(theme.ink2, lineWidth: 1.2)
        } else if lower.contains("glm") {
            bars.stroke(theme.ink2, lineWidth: 1.2)
        } else if lower.contains("composer") {
            ringedDot
        } else if lower == "auto" {
            Circle().stroke(theme.ink2, lineWidth: 1.2)
        } else {
            Rectangle().stroke(theme.ink2, lineWidth: 1.2)
        }
    }

    private var hexagon: Path {
        path("M12 2 L20 7 L20 17 L12 22 L4 17 L4 7 Z")
    }

    private var arrow: Path {
        path("M4 12 L20 4 L16 20 Z")
    }

    private var sparkle: Path {
        path("M12 2 L13 9 L20 12 L13 15 L12 22 L11 15 L4 12 L11 9 Z")
    }

    private var bars: Path {
        path("M4 6 L4 18 M9 4 L9 20 M14 8 L14 16")
    }

    private var ringedDot: some View {
        ZStack {
            Circle().stroke(theme.ink2, lineWidth: 1)
            Circle().fill(theme.ink2).scaleEffect(0.35)
        }
    }

    private func path(_ d: String) -> Path {
        SVGPathParser.parse(d, in: CGRect(x: 0, y: 0, width: 24, height: 24))
    }
}

struct GlyphQuantizer: ViewModifier {
    let enabled: Bool

    func body(content: Content) -> some View {
        if enabled {
            content.drawingGroup()
        } else {
            content
        }
    }
}