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:
30
README.md
30
README.md
@@ -49,6 +49,36 @@ Sessions survive disconnects: the shell (and whatever's running in it) keeps goi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Clients
|
||||||
|
|
||||||
|
The browser is the reference client; two native clients and a remote-access
|
||||||
|
service are also in the repo (all consume the same wire protocol — the server
|
||||||
|
stays a byte-shuttle).
|
||||||
|
|
||||||
|
- **iOS / iPad app** ([`ios/`](ios/), branch `feat/ios-client`) — a native
|
||||||
|
SwiftUI + SwiftTerm **pure remote client** built for the walk-away loop:
|
||||||
|
native terminal with scrollback replay, QR/manual pairing (Keychain), the
|
||||||
|
remote **Approve / Reject** gate + three-way plan gate, **APNs push with
|
||||||
|
lock-screen Allow / Deny behind Face ID**, deep links, Projects, multi-session
|
||||||
|
switcher, timeline, diff, quick-reply, and an **adaptive iPhone/iPad split
|
||||||
|
layout**. Looks like the desktop (amber-gold, dark-first). P0 needs zero server
|
||||||
|
changes; P1 adds two declared additive touch-points. See
|
||||||
|
[`ios/README.md`](ios/README.md). *(Not yet merged.)*
|
||||||
|
- **Desktop app** ([`desktop/`](desktop/)) — a Mac/Windows **Electron shell that
|
||||||
|
embeds this Node server + node-pty** (all-in-one), for native notifications,
|
||||||
|
tray, deep links and launch-at-login. Design in
|
||||||
|
[`docs/DESKTOP_PLAN.md`](docs/DESKTOP_PLAN.md). *(Built; packaged & launch-
|
||||||
|
verified on Apple Silicon.)*
|
||||||
|
- **Rendezvous relay** (`term-relay/` · `agent/` · `relay-e2e/` · `relay-web/` ·
|
||||||
|
`relay-auth/` · `relay-contracts/`) — an "ngrok-for-Claude-Code" with
|
||||||
|
**end-to-end encryption** to reach a self-hosted host from anywhere, without a
|
||||||
|
VPN or port-forwarding. Security-audited but **pre-production / not deployed**;
|
||||||
|
see [`docs/PLAN_RELAY_INDEX.md`](docs/PLAN_RELAY_INDEX.md) and
|
||||||
|
[`docs/DEPLOY_RELAY.md`](docs/DEPLOY_RELAY.md). For remote access **today**, use
|
||||||
|
**Tailscale** (§ Security & deployment below).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|||||||
124
ios/README.md
124
ios/README.md
@@ -1,20 +1,32 @@
|
|||||||
# WebTerm iOS Client
|
# WebTerm iOS Client
|
||||||
|
|
||||||
Native iOS client for the web-terminal server in the repository root. It is a
|
Native **iPhone + iPad** client for the web-terminal server in the repository
|
||||||
**pure remote client**: it speaks the same WebSocket wire protocol and HTTP
|
root. It is a **pure remote client**: it speaks the same WebSocket wire protocol
|
||||||
endpoints as the web frontend and requires **zero server changes** in P0.
|
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
|
## Build & Run
|
||||||
|
|
||||||
Requirements: macOS 15, Xcode 16.3 (Swift 6.1), [XcodeGen](https://github.com/yonaskolb/XcodeGen),
|
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
|
```bash
|
||||||
cd ios
|
cd ios
|
||||||
xcodegen generate # project.yml → WebTerm.xcodeproj
|
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 \
|
xcodebuild -project WebTerm.xcodeproj -scheme WebTerm \
|
||||||
-destination 'platform=iOS Simulator,name=iPhone 16,arch=arm64' test
|
-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:
|
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
|
The server runs from the repo root: `npm install && npm start` (see the root
|
||||||
[README](../README.md)).
|
[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")
|
## ntfy notification bridge (P0 "host finds the phone")
|
||||||
|
|
||||||
Until APNs push lands (P1, T-iOS-20/21), the phone is notified via the
|
Until APNs push lands (P1, T-iOS-20/21), the phone is notified via the
|
||||||
|
|||||||
Reference in New Issue
Block a user