feat(ios): W0 scaffold + day-1 spike + WireProtocol frozen contract + TestSupport doubles
T-iOS-1: ios/ XcodeGen project (iOS 17, Swift 6 strict concurrency, ATS per PLAN §5.2), 5 SPM package shells, CI skeleton T-iOS-2: Origin spike vs real server — URLSessionWebSocketTask custom Origin CONFIRMED (no Starscream); 16MiB replay + EMSGSIZE(40) errno correction written back to plan T-iOS-3: WireProtocol frozen contract, 59 tests, 100% line coverage, cross-impl vectors vs src/protocol.ts via tsx T-iOS-4: FakeTransport/FakeClock/FakeHTTPTransport doubles Verify: independent agent re-ran all acceptance — 6/6 PASS
This commit is contained in:
89
ios/App/WebTerm/Screens/SpikeTerminalScreen.swift
Normal file
89
ios/App/WebTerm/Screens/SpikeTerminalScreen.swift
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// SpikeTerminalScreen.swift — T-iOS-2 Day-1 spike ②【临时文件,W4/T-iOS-15 删除】
|
||||
//
|
||||
// 目的:SwiftTerm TerminalView 的真机键盘/IME smoke 载体(plan §7 T-iOS-2):
|
||||
// - 键盘弹出 / first-responder 行为
|
||||
// - 中文 IME 组合输入(不自加 keydown 监听,组合事件全交系统栈)
|
||||
// - inputAccessoryView 上 Esc/Ctrl-C 可发 —— SwiftTerm iOS 端自带
|
||||
// TerminalAccessory(含 esc/ctrl/tab 键,iOSAccessoryView.swift),
|
||||
// spike 直接用它验证;正式 key-bar(逐字节复刻 public/keybar.ts)归 T-iOS-11
|
||||
// - 文本选择不崩(SwiftTerm 自绘选择是历史坑点)
|
||||
//
|
||||
// 接线为【纯本地回显】(无服务器依赖):typed bytes → send() → feed() 原样喂回,
|
||||
// CR 扩成 CRLF。真实 SessionEngine 接线是 W2/W3 的事。
|
||||
//
|
||||
// 2026-07-04:无可用真机 —— 真机清单各项记 DEFERRED(真机不可用)(见 PROGRESS_LOG
|
||||
// 条目)。本文件当下的验收是:作为 App target 源码在模拟器 xcodebuild 构建通过。
|
||||
|
||||
import SwiftTerm
|
||||
import SwiftUI
|
||||
|
||||
/// Day-1 spike 的最小 SwiftUI 壳。不接入 WebTermApp 导航(WebTermApp.swift 归
|
||||
/// T-iOS-1/T-iOS-15 所有);存在本身即证明 App target 在 Swift 6 strict
|
||||
/// concurrency 下能链接并承载 SwiftTerm 的 TerminalView。
|
||||
struct SpikeTerminalScreen: View {
|
||||
var body: some View {
|
||||
LocalEchoTerminal()
|
||||
.ignoresSafeArea(.container, edges: .bottom)
|
||||
.navigationTitle("SwiftTerm spike")
|
||||
}
|
||||
}
|
||||
|
||||
/// 承载 SwiftTerm UIKit `TerminalView` 的 UIViewRepresentable,接本地回显。
|
||||
/// 形状即 T-iOS-11 正式 TerminalScreen 的雏形(去掉 SessionEngine)。
|
||||
private struct LocalEchoTerminal: UIViewRepresentable {
|
||||
func makeCoordinator() -> LocalEchoCoordinator {
|
||||
LocalEchoCoordinator()
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> TerminalView {
|
||||
let view = TerminalView(frame: .zero)
|
||||
view.terminalDelegate = context.coordinator
|
||||
view.feed(text: SpikeGreeting.text)
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: TerminalView, context: Context) {
|
||||
// 本地回显无外部状态可同步。
|
||||
}
|
||||
}
|
||||
|
||||
private enum SpikeGreeting {
|
||||
/// 行尾用 CRLF:PTY 语义下裸 LF 只下移一行不回车。
|
||||
static let text = "SwiftTerm local-echo spike (T-iOS-2)\r\n"
|
||||
+ "Typed bytes are echoed locally; CR -> CRLF.\r\n"
|
||||
+ "真机清单: 键盘弹出 / 中文 IME / accessory esc·ctrl / 文本选择\r\n\r\n> "
|
||||
}
|
||||
|
||||
/// 本地回显 delegate:SwiftTerm 要"发给宿主"的每个字节原样喂回终端。
|
||||
/// Enter 产生的 CR(0x0D)补一个 LF,光标才会回行首并换行。
|
||||
@MainActor
|
||||
private final class LocalEchoCoordinator: NSObject, @preconcurrency TerminalViewDelegate {
|
||||
private static let carriageReturn: UInt8 = 0x0D
|
||||
private static let lineFeed: UInt8 = 0x0A
|
||||
|
||||
func send(source: TerminalView, data: ArraySlice<UInt8>) {
|
||||
var echoed: [UInt8] = []
|
||||
echoed.reserveCapacity(data.count + 1)
|
||||
for byte in data {
|
||||
echoed.append(byte)
|
||||
if byte == Self.carriageReturn {
|
||||
echoed.append(Self.lineFeed)
|
||||
}
|
||||
}
|
||||
source.feed(byteArray: echoed[...])
|
||||
}
|
||||
|
||||
func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {}
|
||||
func setTerminalTitle(source: TerminalView, title: String) {}
|
||||
func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {}
|
||||
func scrolled(source: TerminalView, position: Double) {}
|
||||
func requestOpenLink(source: TerminalView, link: String, params: [String: String]) {}
|
||||
func clipboardCopy(source: TerminalView, content: Data) {}
|
||||
func rangeChanged(source: TerminalView, startY: Int, endY: Int) {}
|
||||
// bell / iTermContent 用 SwiftTerm 的默认实现。
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SpikeTerminalScreen()
|
||||
}
|
||||
24
ios/App/WebTerm/WebTermApp.swift
Normal file
24
ios/App/WebTerm/WebTermApp.swift
Normal file
@@ -0,0 +1,24 @@
|
||||
import SwiftUI
|
||||
|
||||
/// T-iOS-1 scaffold: empty-window placeholder. Navigation assembly
|
||||
/// (Pairing → SessionList → Terminal) is wired in T-iOS-15.
|
||||
@main
|
||||
struct WebTermApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
HelloPlaceholderView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct HelloPlaceholderView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "terminal")
|
||||
.font(.largeTitle)
|
||||
Text("Hello WebTerm")
|
||||
.font(.title2)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user