Files
web-terminal/ios/App/WebTerm/Wiring/SplitRootView.swift
Yaojia Wang 660a40491a feat(ios): comprehensive iPhone+iPad UX polish — refined-native design system
Freeze a shared design system (DesignSystem/{Tokens,Typography,StatusStyle,
Primitives}.swift): indigo #7C8CFF accent (root .tint), semantic status colors,
2-4-8-12-16-20-24 spacing + sm8/md12/lg16 radii scale, SF Mono tabular numbers,
reduce-motion-gated animations, haptics, reusable StatusBadge/TelemetryChip/Card/
SectionHeader/DSButtonStyle/ContinueLastBanner. Every changed view consumes tokens
— no hardcoded colors/spacing.

Applied across all surfaces (visual only — zero behavior/logic change, all suites
green): session list rows + status system (shape+color, not color-alone) +
telemetry chips + thumbnail placeholders; terminal gate card (≥44pt approve/reject)
+ keybar + reconnect + quick-reply + digest + SwiftTerm accent theme; pairing hero
+ warning tiers + Projects cards + Timeline/Diff; nav chrome + iPad split placeholder
+ privacy shade + motion. Chinese gate copy. UX finding fixed: timeline class colors
now via DS.Palette (+timelineTool/timelineUser tokens).

Design review 8.5/10. Verified: iPhone 16 290 + iPad Pro 11 290 tests green;
packages + integration green; consistency audit ~clean; zero changes under
ios/Packages, src/, public/.
2026-07-05 22:00:31 +02:00

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