import SwiftUI /// T-iPad-2 · regular 宽度(iPad 全屏 / 大分屏 / Stage Manager 大窗)的分栏根 /// 视图。左 sidebar = 会话列表(复用 `SessionListScreen` **不改一行** + 与 /// stack 同款的「项目」leading 工具栏入口);右 detail = `TerminalContainerView` /// (终端 + gate/digest 叠层,复用不动),无打开会话时给「选择或新建会话」占位。 /// /// 设计要点: /// - **detail 复用 `TerminalContainerView`**:终端组装与它挂 push destination 还是 /// split detail 无关,只换外层容器、内容零改(PLAN_IOS_IPAD §1)。 /// - **选中态经 `AppCoordinator` 单一真值**:sidebar 触发面全部走 /// `selectSidebarItem` / `open`(同 stack 的既有路由),不新开会话生命周期。 /// - **detail 终端仍带 `.id(controller.id)`**:换会话时新 controller → 新 /// SwiftTerm 视图(回放 ring buffer),identity 稳定复用 T-iOS-29 语义。 /// - **隐私遮罩不在这里**:它在 `AdaptiveRootView` 的 ZStack 顶层,两分支共享, /// 故 detail 终端字节同样在 `scenePhase != .active` 时被遮(安全不变式)。 struct SplitRootView: View { @Bindable var coordinator: AppCoordinator var body: some View { NavigationSplitView { sidebar } detail: { detail } } // MARK: - Sidebar(会话列表 + 项目入口,复用 SessionListScreen) private var sidebar: some View { SessionListScreen( viewModel: coordinator.sessionList, onOpen: { request in // 分栏只是又一个触发面:映射到同一 selectSidebarItem 路由。 coordinator.selectSidebarItem(sidebarItem(for: request)) }, onAddHost: { coordinator.presentAddHost() } ) .safeAreaInset(edge: .bottom) { continueLastBanner } .toolbar { projectsToolbarItem } } /// 与 `StackRootView.continueLastBanner` 同款「继续上次会话」(冷启动第 5 步) /// —— 分栏 sidebar 也提供,iPad 用户不丢该入口(T-iPad-5 finding)。 @ViewBuilder private var continueLastBanner: some View { if coordinator.continueLastSessionId != nil { Button { coordinator.openContinueLast() } label: { Label(RootCopy.continueLast, systemImage: "arrow.uturn.forward.circle.fill") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .padding(.horizontal, RootMetrics.bannerHorizontalPadding) .padding(.vertical, RootMetrics.bannerVerticalPadding) .background(.thinMaterial) } } /// 列表的 `OpenRequest` → sidebar 选中项(sessionId 有无 = 采纳既有 / 新建)。 private func sidebarItem(for request: SessionListViewModel.OpenRequest) -> SidebarItem { request.sessionId.map(SidebarItem.session) ?? .newSession } /// 与 `StackRootView.projectsToolbarItem` 同款(leading 位、同 disabled 条件、 /// 复用 `presentProjects`)—— iPad 上 Projects 本期仍走既有 sheet(大屏化留 /// 给 T-iPad-4)。 @ToolbarContentBuilder private var projectsToolbarItem: some ToolbarContent { ToolbarItem(placement: .topBarLeading) { Button { coordinator.presentProjects() } label: { Label(RootCopy.projects, systemImage: "folder") } .disabled(coordinator.sessionList.activeHost == nil) .accessibilityIdentifier("sessions.projectsButton") } } // MARK: - Detail(终端或占位) @ViewBuilder private var detail: some View { if let controller = coordinator.terminalController { NavigationStack { TerminalContainerView( controller: controller, onNewSessionInCwd: { coordinator.openNewSessionInCurrentCwd() }, onKillSession: { coordinator.killCurrentSession() } // T-iPad-3 ) .id(controller.id) // T-iOS-29 · identity per controller } } else { placeholder } } private var placeholder: some View { ContentUnavailableView { Label(SplitCopy.placeholderTitle, systemImage: "sidebar.left") } description: { Text(SplitCopy.placeholderHint) } } } /// split 专属用户可见文案(「项目」标签复用 `RootCopy.projects`,DRY)。 private enum SplitCopy { static let placeholderTitle = "选择或新建会话" static let placeholderHint = "从左侧选择一个运行中的会话,或新建一个开始工作。" }