feat(ios): W3 UI layer + integration CI + ntfy docs

T-iOS-11: TerminalScreen/KeyBar/TerminalViewModel + KeyByteMap (byte-for-byte keybar.ts, arrows excluded from UIKeyCommand to preserve DECCKM)
T-iOS-12: PairingScreen/VM — confirm-before-network (zero-call assertions), §5.4 four-tier warnings, Host construction per contract ruling
T-iOS-13: SessionListScreen/VM — Tunables-paced polling with leak-free teardown, optimistic kill+rollback, pending via overlay (LiveSessionInfo has no pending field)
T-iOS-14: GateBanner/PlanGateSheet/AwayDigestView/GateViewModel — three-way mapping from SessionCore Affordance single source, tap-epoch guard, per-epoch haptics
T-iOS-16: IntegrationTests vs real Node server (10 tests: origin guards, mirror, kill-close vs exit-frame differential, 16MiB+ESC/C0 replay) + ios.yml own-sources coverage gate (red-once demoed)
T-iOS-17: ios/README.md ntfy chapter (read-only verification, file:line cites)
Verified: 224 unit + 10 integration tests green; 5/5 semantic spot-checks; zero Owns violations
This commit is contained in:
Yaojia Wang
2026-07-05 00:13:14 +02:00
parent 5c8c87fb7a
commit 5098643355
33 changed files with 5946 additions and 616 deletions

View File

@@ -0,0 +1,57 @@
/// Key-name terminal byte-string table (T-iOS-11; pure data, no I/O).
///
/// Byte-for-byte mirror of the web client's `public/keybar.ts` `KEY_MAP`
/// the SINGLE source of truth for every labelbytes lookup on iOS: the KeyBar
/// buttons AND the hardware `UIKeyCommand` mapping both resolve through here
/// (plan §7 T-iOS-11). Hand-writing an escape sequence anywhere in the App
/// layer is a review finding.
///
/// Gotcha carried over from the web client: Enter is `\r` (0x0D, carriage
/// return), NOT `\n` synthesized input with a linefeed breaks TUIs.
public enum KeyByteMap {
/// One key the bar/hardware mapping can send. Raw values mirror the web
/// `KEY_MAP` property names; declaration order mirrors the web table.
public enum Key: String, Sendable, CaseIterable {
case esc
case escEsc
case shiftTab
case arrowUp
case arrowDown
case arrowLeft
case arrowRight
case enter
case ctrlC
case ctrlR
case ctrlO
case ctrlL
case ctrlT
case ctrlB
case ctrlD
case tab
case slash
}
/// The exact byte string to send as `ClientMessage.input(data:)` for `key`.
/// Values are verbatim from public/keybar.ts KEY_MAP (see file doc).
public static func bytes(for key: Key) -> String {
switch key {
case .esc: return "\u{1B}"
case .escEsc: return "\u{1B}\u{1B}"
case .shiftTab: return "\u{1B}[Z"
case .arrowUp: return "\u{1B}[A"
case .arrowDown: return "\u{1B}[B"
case .arrowLeft: return "\u{1B}[D"
case .arrowRight: return "\u{1B}[C"
case .enter: return "\r" // NOT "\n" (CLAUDE.md gotcha)
case .ctrlC: return "\u{03}"
case .ctrlR: return "\u{12}"
case .ctrlO: return "\u{0F}"
case .ctrlL: return "\u{0C}"
case .ctrlT: return "\u{14}"
case .ctrlB: return "\u{02}"
case .ctrlD: return "\u{04}"
case .tab: return "\t"
case .slash: return "/"
}
}
}