Files
web-terminal/ios/App/WebTerm/Wiring/SplitRootView.swift
Yaojia Wang e38e6d1689 feat(ios): device client-cert mTLS — ClientTLS package, transport wiring, install UX (C-iOS)
- ios/Packages/ClientTLS: SecIdentity wrapper, PKCS12 importer (typed errors), keychain store
  (AfterFirstUnlockThisDeviceOnly), pure MutualTLSChallengeResponder truth table, cross-platform
  X.509 DER summary. 14/14 tests.
- Both transports (SessionCore URLSessionTermTransport, App URLSessionHTTPTransport) + SessionThumbnail
  take a lazy @Sendable ()->ClientIdentity? provider: WS resolves per-connect, HTTP per client-cert
  challenge, so a freshly-imported cert applies without an app relaunch. AppEnvironment injects
  { store.loadedIdentityOrNil() }.
- ClientCertScreen (.fileImporter([.pkcs12]) + passphrase -> import -> keychain), reachable via a
  设备证书 entry in SessionListScreen.hostMenu. PairingViewModel gates tunnel-host probes on cert
  presence and re-maps mTLS-reject to a clientCertRejected message.
Verified: ClientTLS 14/14, SessionCore 93/93, xcodegen + xcodebuild BUILD SUCCEEDED.
2026-07-07 09:42:12 +02:00

106 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() }
)
.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 = "从左侧选择一个运行中的会话,或新建一个开始工作。"
}