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:
124
ios/README.md
124
ios/README.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user