Files
web-terminal/ios/App/WebTerm/Wiring/SplitRootView.swift
Yaojia Wang 07bcbf0c08 feat(ios): device enrollment flow + silent cert rotation (B3)
Wire the SecureEnclave enroll library into a real flow (login->bearer->CSR->
/device/enroll->keychain identity), presented on the existing mTLS path; add a
rotation scheduler. Atomic keychain replace (add-before-delete); renew body is
{csr}-only; renewal-failing surfaced in the UI. ClientTLS 48 tests pass.
2026-07-18 13:32:05 +02:00

107 lines
4.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
/// `coordinator.sidebarSelection` highlight sidebar List
/// `selection:``SessionListScreen` List Owns
/// - **detail `.id(controller.id)`** controller
/// SwiftTerm ring bufferidentity T-iOS-29
/// - **** `AdaptiveRootView` ZStack
/// detail `scenePhase != .active`
/// - ****// `DS.*` token Primitive
/// stack
struct SplitRootView: View {
@Bindable var coordinator: AppCoordinator
@Environment(\.accessibilityReduceMotion) private var reduceMotion
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() },
onDeviceCert: { coordinator.presentDeviceCert() },
onEnroll: { coordinator.presentEnrollment() }
)
.safeAreaInset(edge: .bottom) { continueLastBanner }
.animation(
DS.Motion.gated(DS.Motion.base, reduceMotion: reduceMotion),
value: coordinator.continueLastSessionId
)
.toolbar { ProjectsToolbarItem(coordinator: coordinator) }
}
/// stack 5
/// `ContinueLastBanner`iPad T-iPad-5 finding
@ViewBuilder private var continueLastBanner: some View {
if coordinator.continueLastSessionId != nil {
ContinueLastBanner { coordinator.openContinueLast() }
.transition(.move(edge: .bottom).combined(with: .opacity))
}
}
/// `OpenRequest` sidebar sessionId = /
private func sidebarItem(for request: SessionListViewModel.OpenRequest) -> SidebarItem {
request.sessionId.map(SidebarItem.session) ?? .newSession
}
// 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
.transition(.opacity)
}
}
/// detail accent + DS ContentUnavailableView
/// + + accent App
private var placeholder: some View {
ContentUnavailableView {
Label {
Text(SplitCopy.placeholderTitle)
} icon: {
Image(systemName: "terminal")
.foregroundStyle(DS.Palette.accent)
}
} description: {
Text(SplitCopy.placeholderHint)
}
}
}
/// split / `RootCopy`DRY
private enum SplitCopy {
static let placeholderTitle = "选择或新建会话"
static let placeholderHint = "从左侧选择一个运行中的会话,或新建一个开始工作。"
}