docs: document native clients — full iOS/iPad feature README + root Clients section

ios/README: add a comprehensive Features section (P0 pocket cockpit, P1 walk-away
complete, iPad adaptive split, design system, verification status); note min iOS
17, both-idiom test commands, P1 server touch-points.
root README: add a Clients section covering the iOS/iPad app, desktop Electron
shell, and the pre-production relay stack (with Tailscale as the remote-access
path today).
This commit is contained in:
Yaojia Wang
2026-07-06 08:10:33 +02:00
parent d01a69eb72
commit a887638557
2 changed files with 149 additions and 5 deletions

View File

@@ -1,20 +1,32 @@
# WebTerm iOS Client
Native iOS client for the web-terminal server in the repository root. It is a
**pure remote client**: it speaks the same WebSocket wire protocol and HTTP
endpoints as the web frontend and requires **zero server changes** in P0.
Native **iPhone + iPad** client for the web-terminal server in the repository
root. It is a **pure remote client**: it speaks the same WebSocket wire protocol
and HTTP endpoints as the web frontend. P0 needs **zero server changes**; P1 adds
exactly **two declared, additive server touch-points** (an APNs sender + token
endpoint, and an optional `lastOutputAt` field on `GET /live-sessions`) — nothing
else in `src/` / `public/` changes.
The whole app is a **single adaptive codebase**: iPhone (and iPad Slide Over /
small splits) get the compact stack layout; iPad regular width gets a
**NavigationSplitView** sidebar + detail — no forked UI. Min deployment **iOS
17.0**; Swift 6 language mode. The look matches the **desktop web theme**
(amber-gold accent `#E3A64A` on warm-dark, dark-appearance-first).
## Build & Run
Requirements: macOS 15, Xcode 16.3 (Swift 6.1), [XcodeGen](https://github.com/yonaskolb/XcodeGen),
iOS 18.4 simulator (iPhone 16). SwiftTerm 1.13 is resolved for the App target only.
an iOS 17+ simulator (CI tests on iPhone 16 **and** iPad Pro 11"). SwiftTerm 1.13
is resolved for the App target only.
```bash
cd ios
xcodegen generate # project.yml → WebTerm.xcodeproj
open WebTerm.xcodeproj # or run the full test suite headless:
open WebTerm.xcodeproj # or run the full test suite headless (both idioms):
xcodebuild -project WebTerm.xcodeproj -scheme WebTerm \
-destination 'platform=iOS Simulator,name=iPhone 16,arch=arm64' test
xcodebuild -project WebTerm.xcodeproj -scheme WebTerm \
-destination 'platform=iOS Simulator,name=iPad Pro 11-inch (M4)' test
```
Layout:
@@ -34,6 +46,108 @@ Per-package tests: `swift test --package-path Packages/<Name>`.
The server runs from the repo root: `npm install && npm start` (see the root
[README](../README.md)).
## Features
The product loop is **vibe coding**: kick off Claude Code on your Mac, walk away,
and check / steer it from your phone — approve or reject permission gates
remotely, watch status, and reattach with full scrollback.
### P0 — daily-usable pocket cockpit
- **Native terminal** — [SwiftTerm](https://github.com/migueldeicaza/SwiftTerm),
not a web view. Full ANSI/true-color, native selection, IME and keyboard. On
attach it replays the host's whole scrollback ring, then resumes the live
stream. The canvas matches the desktop (warm near-black `#100F0D`, gold caret).
- **Pairing** — scan the web UI's connect-device **QR**, or type the host URL.
A **confirm step** shows the parsed `scheme://host:port` and makes **zero
network calls until you confirm** (a scanned code is untrusted input). A
two-step probe verifies reachability + the `Origin` handshake before saving the
host to the **Keychain**. Per-`§5.4` warning tiers (public host = blocking
warning, plaintext LAN = notice, Tailscale = fine).
- **Session list (chooser + dashboard in one)** — every running session with a
semantic **status badge** (working / waiting / idle / stuck — colour **and**
shape, never colour alone), live **telemetry chips** (cost / context / PR, SF
Mono, greyed when stale), a **preview thumbnail**, cols×rows, swipe-to-kill,
pull-to-refresh, and multi-host switching.
- **Remote approve / reject** — the core steering primitive. A tool gate shows a
prominent **Approve / Reject** card (≥44 pt); a plan gate shows the **three-way**
sheet (Approve+Auto → `acceptEdits` / Approve+Review → `default` / Keep
Planning → `reject`, matching the web client exactly). Haptic on arrival.
- **Away digest** — on reattach, a summary of what happened while you were gone
("ran N tools · waiting · done"), expandable into the full timeline.
- **Sessions survive everything** — kill the app / background / lose the network
→ foreground reattaches and replays. One live WS for the foreground session;
the rest are HTTP-polled. Reconnect uses 1s→30s backoff; a 25 s ping keeps the
socket honest.
- **Mobile key-bar** — Esc / Esc·Esc / ⇧Tab / arrows / Enter (`\r`) / ^C / ^R /
^O / ^L / ^T / ^B / ^D / Tab / `/`, byte-for-byte from the web key-bar, sent
raw (no soft-keyboard pop). Hardware-keyboard chords via `UIKeyCommand`.
- **Privacy shade** — the terminal is covered whenever the app isn't active
(`scenePhase != .active`), so the multitasking snapshot never leaks terminal
bytes.
- **Notifications (P0)** — reuses the server's existing **ntfy bridge** (see
below), zero new code, default-off.
### P1 — walk-away complete
- **APNs push + lock-screen Allow / Deny** — the host notifies your phone on a
held gate; from the **lock screen** you Allow (behind **Face ID / passcode**,
`.authenticationRequired`) or Deny **without opening the app**. The decision is
a single-use capability token; it's validated then discarded, never persisted,
never logged. (Needs a paid Apple account + `.p8` on the server — see
[`PLAN_IOS_CLIENT.md`](../docs/PLAN_IOS_CLIENT.md) §T-iOS-20.)
- **Deep links** — `webterminal://open?host=…&join=…` (strict whitelist, UUID-
validated) opens straight to a gated session, cold or warm; push taps use the
same router.
- **Projects** — auto-discovered git repos grouped by namespace, favourites +
collapsed state synced through the server's `/prefs` (unknown keys preserved so
iOS never clobbers web-written prefs), dirty badge, detail page (sessions /
worktrees / CLAUDE.md), and **"open Claude in this repo"** (`attach(cwd)` +
inject `claude\r`).
- **Multi-session switcher** — unread dots (from `lastOutputAt`) and sanitized
**OSC titles** (bidi / zero-width stripped, length-capped — titles are
attacker-influenced).
- **Activity timeline** — the full `/events` drill-down, class → icon + colour.
- **Diff viewer** — read-only git diff, staged/unstaged, per-file hunks.
- **Quick-reply chips** — tap-to-send snippets + an editable palette (floats while
a gate is waiting).
- **Session thumbnails** — off-screen SwiftTerm renders, concurrency-capped and
cached by `(sessionId, lastOutputAt)`.
### iPad
- **Adaptive split view** — regular width shows a sidebar (session list +
projects, continue-last banner) beside the terminal detail; compact width (incl.
Slide Over) collapses to the iPhone stack. One code path (`LayoutPolicy`), the
privacy shade covers both.
- **Hardware keyboard** — the on-screen key-bar auto-hides when a hardware
keyboard is attached.
- **Pointer** — right-click / long-press context menu on the terminal (open in
cwd / kill / copy), routed through the same guarded endpoints.
### Look & feel
A single frozen **design system** (`App/WebTerm/DesignSystem/`): one amber-gold
accent token (`#E3A64A` dark / `#C9892F` light) matching the desktop `--accent`,
semantic status colours from the web palette, an 8-pt spacing scale, SF Mono
tabular numbers, reduce-motion-gated animation and haptics, and reusable
primitives (`StatusBadge` / `TelemetryChip` / `Card` / …). The app is
**dark-appearance-first** to match the desktop's dark theme (gold reads best on
warm-dark). Every view consumes tokens — no hardcoded colours.
### Status & what's verified
Built and green: **290 app tests** (iPhone 16 + iPad Pro 11"), **261 package
tests**, **10 integration tests** against a real Node server, plus one XCUITest
happy-path (pair → attach → type → approve). Coverage ≥ 80 % on the four logic
packages. **Not yet done / needs hardware:** on-device gesture / IME / camera-QR /
haptics / lock-screen-Allow walkthroughs are **DEFERRED to a real device**; APNs
end-to-end needs a paid Apple account. The client is on the `feat/ios-client`
branch (not yet merged). Design & task detail live in
[`PLAN_IOS_CLIENT.md`](../docs/PLAN_IOS_CLIENT.md) and
[`PLAN_IOS_IPAD.md`](../docs/PLAN_IOS_IPAD.md); progress in
[`PROGRESS_LOG.md`](../docs/PROGRESS_LOG.md).
## ntfy notification bridge (P0 "host finds the phone")
Until APNs push lands (P1, T-iOS-20/21), the phone is notified via the