import APIClient import SwiftUI import WireProtocol /// T-iOS-32 · 新建 Worktree(`POST /projects/worktree`,G)。 /// /// 表单只有两格:新分支名(必填、即时校验)+ 起点 ref(留空 = 当前 HEAD)。 /// 成功后展示服务器给的 canonical 路径/分支,并提供"在新 Worktree 开会话"—— /// 这就是 "spin up a worktree, land the winner, delete the rest" 循环的入口。 /// /// 安全:分支名先过 `WorktreeBranchRule`(校验先于网络);服务器返回的路径/分支/ /// 错误文案都是不可信字节 → `Text(verbatim:)`。 struct WorktreeSheet: View { @State private var viewModel: WorktreeViewModel /// 创建成功(详情屏据此刷新 worktree 列表)。 let onCreated: () -> Void /// "在新 worktree 开会话":把服务器给的 canonical 路径交回详情屏的开会话钩子。 let onOpenSession: (String) -> Void @Environment(\.dismiss) private var dismiss init( viewModel: WorktreeViewModel, onCreated: @escaping () -> Void, onOpenSession: @escaping (String) -> Void ) { _viewModel = State(initialValue: viewModel) self.onCreated = onCreated self.onOpenSession = onOpenSession } var body: some View { @Bindable var bindable = viewModel return NavigationStack { Form { if case .created(let path, let branch) = viewModel.createPhase { createdSection(path: path, branch: branch) } else { formSection( branch: $bindable.branchText, base: $bindable.baseText ) } } .navigationTitle(WorktreeCopy.sheetTitle) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button(WorktreeCopy.cancel) { dismiss() } } } } } @ViewBuilder private func formSection( branch: Binding, base: Binding ) -> some View { Section { TextField(WorktreeCopy.branchField, text: branch) .textInputAutocapitalization(.never) .autocorrectionDisabled() .submitLabel(.done) .onSubmit { submit() } TextField(WorktreeCopy.baseField, text: base) .textInputAutocapitalization(.never) .autocorrectionDisabled() } footer: { if let message = viewModel.branchValidationMessage { Text(message) .font(DS.Typography.caption) .foregroundStyle(DS.Palette.statusStuck) } } Section { Button { submit() } label: { if viewModel.createPhase == .creating { ProgressView() .frame(maxWidth: .infinity, minHeight: DS.Layout.minHitTarget) } else { Label(WorktreeCopy.submit, systemImage: "plus.rectangle.on.folder") } } .buttonStyle(DSButtonStyle(kind: .primary)) .disabled(!viewModel.canSubmitCreate) .listRowBackground(Color.clear) if case .failed(let message) = viewModel.createPhase { InlineMessage(text: message, tone: .error) } } } @ViewBuilder private func createdSection(path: String, branch: String) -> some View { Section(WorktreeCopy.createdTitle) { VStack(alignment: .leading, spacing: DS.Space.xs4) { Text(verbatim: branch) .font(DS.Typography.callout) .foregroundStyle(DS.Palette.textPrimary) Text(verbatim: path) .font(DS.Typography.mono(.caption)) .foregroundStyle(DS.Palette.textSecondary) .lineLimit(2) .truncationMode(.middle) } } Section { Button { DS.Haptics.selection() onOpenSession(path) dismiss() } label: { Label(WorktreeCopy.openInNewWorktree, systemImage: "terminal.fill") } .buttonStyle(DSButtonStyle(kind: .primary)) .disabled(path.isEmpty) .listRowBackground(Color.clear) Button(WorktreeCopy.cancel) { dismiss() } .buttonStyle(DSButtonStyle(kind: .secondary)) .listRowBackground(Color.clear) } } private func submit() { Task { await viewModel.create() if case .created = viewModel.createPhase { DS.Haptics.success() onCreated() // 详情屏刷新(新 worktree 立刻出现在列表里) } } } }