import SessionCore import SwiftUI /// T-iOS-14 · Three-way sheet for a PLAN gate (B4): Approve+Auto / /// Approve+Review / Keep Planning — mapping mirrors public/tabs.ts:345-347 /// exactly via `GateState.Affordance.clientMessage` (acceptEdits / default / /// reject; never raw `auto`, and NO allowAutoMode gating — plan §7 T-iOS-14). /// /// Render-only: state lives in `GateViewModel`; every tap reports the /// affordance PLUS the epoch of the gate THIS sheet rendered (stale-tap /// first-line guard). Presentation (`.sheet`) wiring is T-iOS-15. struct PlanGateSheet: View { /// Mirror of public/tabs.ts:326 plan-gate headline. static let title = "Claude finished planning — how should it proceed?" let gate: GateState let onDecide: (GateState.Affordance, Int) -> Void private enum Metrics { static let spacing: CGFloat = 12 static let padding: CGFloat = 20 static let captionSpacing: CGFloat = 2 } var body: some View { VStack(alignment: .leading, spacing: Metrics.spacing) { Text(Self.title) .font(.headline) ForEach(GateChoiceSpec.specs(for: gate), id: \.affordance) { spec in choiceButton(spec) } } .padding(Metrics.padding) .presentationDetents([.medium]) .accessibilityElement(children: .contain) } private func choiceButton(_ spec: GateChoiceSpec) -> some View { Button { onDecide(spec.affordance, gate.epoch) // epoch of the RENDERED gate } label: { VStack(alignment: .leading, spacing: Metrics.captionSpacing) { Text(spec.label) .font(.body.weight(.medium)) Text(Self.caption(for: spec.affordance)) .font(.caption) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) } .buttonStyle(.bordered) .tint(tint(for: spec.affordance)) } /// Secondary line under each choice: what the wire mode actually does. static func caption(for affordance: GateState.Affordance) -> String { switch affordance { case .approveAuto: return "执行计划,编辑自动接受(acceptEdits)" case .approveReview: return "执行计划,每次编辑需确认(default)" case .keepPlanning: return "继续规划,不执行" case .approve: return "允许本次工具调用" case .reject: return "拒绝本次工具调用" } } private func tint(for affordance: GateState.Affordance) -> Color { switch affordance { case .approve, .approveAuto, .approveReview: return .green case .reject: return .red case .keepPlanning: return .indigo } } }