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
42 lines
2.1 KiB
Swift
42 lines
2.1 KiB
Swift
import Foundation
|
|
|
|
/// Client → server WS frames (frozen contract, plan §3.1; mirrors
|
|
/// `src/types.ts:87-92` `ClientMessage`). Immutable value type.
|
|
///
|
|
/// Server-side validation the caller must respect (the server SILENTLY DISCARDS
|
|
/// invalid frames, `src/protocol.ts:45-86` — no error reply comes back):
|
|
/// - `attach` must be the FIRST frame on a connection (src/server.ts:707-711).
|
|
/// - `resize` cols/rows must be integers in `WireConstants.resizeRange` (1...1000).
|
|
/// - `attach.cwd`, when present, must be an absolute path (`Validation.isAbsoluteCwd`).
|
|
/// - `input.data` is raw keyboard bytes, passed through verbatim — never filtered.
|
|
public enum ClientMessage: Sendable, Equatable {
|
|
/// First frame. `sessionId == nil` spawns a new session (encoded as an
|
|
/// explicit JSON `"sessionId":null` — the key is REQUIRED by the server,
|
|
/// src/protocol.ts:132-134). `cwd` = "new tab here" spawn directory (M6).
|
|
case attach(sessionId: UUID?, cwd: String?)
|
|
/// Raw keyboard bytes, verbatim (invariant #9 — no content filtering).
|
|
case input(data: String)
|
|
/// Own message type so the server can `ioctl(TIOCSWINSZ)` → SIGWINCH.
|
|
case resize(cols: Int, rows: Int)
|
|
/// Resolve a held permission gate with allow. `mode` is only meaningful for
|
|
/// a `plan` gate and is encoded as a TOP-LEVEL `mode` key — the server's WS
|
|
/// wiring re-parses the raw frame for it (src/server.ts:91-102).
|
|
case approve(mode: ApproveMode?)
|
|
/// Resolve a held permission gate with deny.
|
|
case reject
|
|
}
|
|
|
|
/// Permission mode written back when resolving a `plan` gate. Raw values mirror
|
|
/// the server whitelist `PERMISSION_MODES` (src/server.ts:76 / src/types.ts:365).
|
|
///
|
|
/// Note: the plan-gate three-way UI only ever sends `.acceptEdits` / `.default`
|
|
/// (mirroring public/tabs.ts:345-347). Raw `.auto` is gated by ALLOW_AUTO_MODE
|
|
/// (default false) and is server-downgraded to `default` otherwise
|
|
/// (src/server.ts:765-766); it is reserved for a future permission-mode switcher.
|
|
public enum ApproveMode: String, Sendable, CaseIterable {
|
|
case `default`
|
|
case acceptEdits
|
|
case plan
|
|
case auto
|
|
}
|