import SwiftUI import WireProtocol /// # Status visuals — the SINGLE source (FROZEN public surface) /// /// Every place that shows a session's Claude-Code status (list rows, badges, /// thumbnails, project-detail rows, banners) resolves it through here, so the /// color + shape + label stay identical everywhere. Status is expressed as /// **color AND a distinct SF Symbol** — never color alone (accessibility; /// color-blind users read the shape). Labels are Chinese for VoiceOver + UI. /// The seven visual states a status indicator can show. A superset of the wire /// `ClaudeStatus` (working/waiting/idle/unknown/stuck) plus two App-layer /// emphasis states: /// - `pendingApproval` — a tool/plan gate is held server-side ("needs me"). /// Outranks status; mirrors `SessionListViewModel.Indicator.pendingApproval` /// (this type does NOT re-implement that priority — callers decide when to /// use it; here it's only its visual identity). /// - `exited` — the session is over (read-only). enum DisplayStatus: CaseIterable, Sendable, Equatable { case working case waiting case idle case stuck case unknown case pendingApproval case exited /// Bridge from the wire enum. Pure — no pending/exited emphasis (callers /// supply those explicitly, matching the VM's own indicator priority). init(_ claude: ClaudeStatus) { switch claude { case .working: self = .working case .waiting: self = .waiting case .idle: self = .idle case .stuck: self = .stuck case .unknown: self = .unknown } } } /// Resolved visuals for one status: a semantic color, a DISTINCT SF Symbol /// shape, and a Chinese label (used as the VoiceOver string too). The mapping /// itself is pure — no UIKit, deterministic, unit-testable. struct StatusStyle: Equatable, Sendable { /// Semantic color from `DS.Palette` (never the sole signal — see `symbolName`). let color: Color /// SF Symbol name. Distinct across all seven statuses so shape alone /// disambiguates (color-blind safe). let symbolName: String /// Chinese status word — shown as an optional label and always as the /// accessibility label. let label: String /// The frozen mapping. Each status → (color, distinct symbol, 中文 label). static func style(for status: DisplayStatus) -> StatusStyle { switch status { case .working: // solid filled circle — actively running return StatusStyle(color: DS.Palette.statusWorking, symbolName: "circle.fill", label: "运行中") case .waiting: // clock — waiting on something return StatusStyle(color: DS.Palette.statusWaiting, symbolName: "clock.fill", label: "等待中") case .idle: // hollow circle — quiet/empty (gray, distinct from working's fill) return StatusStyle(color: DS.Palette.statusIdle, symbolName: "circle", label: "空闲") case .stuck: // triangle — alarm return StatusStyle(color: DS.Palette.statusStuck, symbolName: "exclamationmark.triangle.fill", label: "卡住") case .unknown: // question mark — no signal yet return StatusStyle(color: DS.Palette.statusUnknown, symbolName: "questionmark.circle", label: "未知") case .pendingApproval: // filled ! circle — "needs me", the single most important prompt return StatusStyle(color: DS.Palette.statusWaiting, symbolName: "exclamationmark.circle.fill", label: "等待审批") case .exited: // checkered flag — session over (mirrors ReconnectBanner's exited icon) return StatusStyle(color: DS.Palette.statusExited, symbolName: "flag.checkered", label: "已退出") } } /// Convenience bridge from the wire enum. static func style(for claude: ClaudeStatus) -> StatusStyle { style(for: DisplayStatus(claude)) } }