# Nucleic — iPhone UX (v0)

The phone is a **thin remote client** to the Mac host (SYNC_PROTOCOL): no local git or CLI,
holds no canonical state, scope **`approve`** in v1. Its whole job: tell you *which session
needs you* and let you answer — from a notification, a glance, or in the app — wherever you
are. Everything here is a projection of the same authority the Mac UX renders (UX_MACOS §8).

**Status:** design draft.

---

## 1. Invariants

1. **Notification-first.** The primary entry point is a push, not app-launch. The app must be
   great when opened *cold from a notification straight to one approval*.
2. **Projection, not authority.** Every screen is a `subscribe` over the sync protocol; every
   action is an intent (`approvalRespond`, `sendInput`). No state is invented on-device.
3. **Glanceable.** "What needs me, and how many" must be answerable from the lock screen / a
   Live Activity / a widget — without unlocking into the app.
4. **Honest about connectivity.** The phone is often disconnected or on a flaky link; stale
   data is always labeled, and actions disable rather than silently fail.
5. **Risk-gated.** Destructive/network approvals require an extra deliberate step (biometric),
   matching the Mac's "open detail" friction (UX_MACOS §4).

---

## 2. Capabilities in v1 (scope = approve)

| Can | Cannot (scope = control, later) |
| --- | --- |
| View session list + status across projects | Start a new session |
| Read transcript (read-friendly) | Merge / rebase / squash |
| Read diff | Discard a session |
| **Answer approvals** (the point) | Manage projects / pairing-of-others |
| Send a **follow-up prompt** (unblock `awaitingInput`) | Interrupt a run |

`sendInput` is allowed at `approve` (SYNC §5.1) — typing the next step to unblock a paused
agent is a core mobile use; structural git actions stay on the Mac until `scope=control` ships.

---

## 3. Navigation

A 2-tab `TabView` (a phone doesn't need the Mac's three columns):

```
┌─────────────────────────┐        Tab 1: Sessions (attention-first home)
│        Sessions          │        Tab 2: Settings (host, device, connection)
│  ────────────────────    │
│  ⚠ NEEDS YOU (2)         │   ← pinned section, top
│  ⚠ auth-refactor  ProjA  │
│  ◔ payment-flow   ProjC  │
│  ─ RUNNING (4) ───────   │
│  ◐ flaky-tests +312/−40  │
│  ◐ graphql     +88/−12   │
│  ─ DONE (3) ──────────   │
│  ✓ docs-pass             │
│ ┌─────────────────────┐ │
│ │ ● Connected · LAN    │ │   ← persistent connection chip
│ └─────────────────────┘ │
│   [ Sessions ] [ ⚙ ]    │
└─────────────────────────┘
```

Same status glyphs/semantics as the Mac (UX_MACOS §1) so the two devices read identically.
`NEEDS YOU` is pinned and badge-counted (= app icon badge). Tapping a row pushes detail.

---

## 4. Session detail

`NavigationStack` push. A segmented control swaps three read views; the bottom is the action
area.

```
┌─────────────────────────┐
│ ‹ auth-refactor   ProjA  │
│ Claude · ⚠ awaiting      │
│ [ Transcript | Diff ]    │   (Log is power-user; behind ⋯)
│ ───────────────────────  │
│  🤖 I'll update the auth  │
│     middleware…           │
│  ▸ Bash  git status       │   tap to expand input/result
│  ▸ Edit  auth/mw.ts       │   → jumps to Diff
│  …                        │
│ ───────────────────────  │
│  ⚠ Permission requested   │   ← action area (§5)
│  Bash · destructive       │
│  $ rm -rf build/          │
│  [Deny]   [Allow ▾]       │
└─────────────────────────┘
```

- **Transcript**: streamed markdown + collapsible tool rows; verbosity defaults to
  `.coalesced` on cellular, `.full` on Wi‑Fi/foreground (SYNC §5.5) to save battery/data.
- **Diff**: read-only, unified (narrow screen), file picker, syntax highlight. Sourced from the
  host snapshot's `diffStat` + on-demand diff fetch.
- **Action area** is status-driven: `awaitingApproval`→approval card; `awaitingInput`/
  `finished`→a compact prompt composer (`sendInput`); `running`→live activity line (no
  interrupt at this scope).

---

## 5. The approval interaction (the defining feature)

Three entry paths, one resolution model (first-responder-wins, SYNC §5.4 — "Approved on Mac"
collapses the card if someone beats you).

### 5.1 From a notification (primary path)

```
Lock screen / banner:
┌─────────────────────────────────┐
│ Nucleic · auth-refactor          │
│ ⚠ Bash wants to run: rm -rf build│
│ [ Deny ]            [ Allow ]    │   ← actionable (low/medium risk only)
└─────────────────────────────────┘
```

- **Low/medium risk** (`readOnly`/`write`/`execute`): inline notification actions resolve
  directly via a background task — no unlock needed beyond the OS's own auth on the action.
- **High risk** (`destructive`/`network`): the notification has **no inline Allow**. It opens
  the app to the approval card, which requires **Face ID/Touch ID** before `Allow` is enabled.
  Deliberate friction mirrors the Mac.
- Payload is minimal (title + reason, no code/diff — SYNC §6); the app pulls the real
  `ApprovalRequest` over the E2EE channel on open.

### 5.2 In-app approval card

```
┌─ ⚠ Permission requested ──────┐
│ Bash · execute · destructive  │
│   $ rm -rf build/ && npm pub… │   tap → full input sheet
│                               │
│ [   Deny   ] [   Allow   ]    │
│ Allow always ▾                │
│  ├ this command, this session │
│  ├ any Bash, this session     │
│  └ Bash matching `npm *`      │
│ 🔒 Face ID required (destruct.)│
└───────────────────────────────┘
```

- `Allow always` writes the same `always_rule` the Mac would (RUNTIME §5) at the chosen scope.
- **Modify-and-allow** appears only if `BackendCapabilities.canModifyToolInput` (Claude) — the
  card reads capabilities just like the Mac, so Codex hides it.

### 5.3 Live Activity / Dynamic Island

For a session that's `running` or has a pending approval, a Live Activity keeps it on the lock
screen / Island:

```
Dynamic Island (expanded):
  ◐ auth-refactor   ·   ⚠ 1 approval waiting   [ Open ]
```

Glance = status + pending-approval count; tap routes straight to the card. (Apple Watch
approve-from-wrist is a natural later add; same intent.)

---

## 6. Connectivity states (must be unambiguous)

The connection chip (and detail action area) reflect transport truth:

| State | Chip | Behavior |
| --- | --- | --- |
| Connected · LAN | ● green "LAN" | full interactivity, `.full` verbosity allowed |
| Connected · Relay | ● green "Relay" | full interactivity, default `.coalesced` |
| Reconnecting | ◐ amber | last snapshot shown, **stamped "as of HH:MM"**, actions disabled |
| Disconnected | ○ gray | read-only stale view; actions disabled with "reconnect to act" |
| Host asleep / unreachable | ○ "Mac offline" | explains the Mac must be awake (LAN) / relay needed |

- **No optimistic actions.** An approval/`sendInput` requires a live channel; if it drops
  mid-send, the UI shows it failed and the request is still pending on the host (idempotent
  re-send on reconnect, deduped on the host).
- **Reconnect is automatic** with `sinceSeq` catch-up (SYNC §7); the user sees a brief "catching
  up…" then live state.

---

## 7. Pairing & security UX

- **Pair:** Settings → "Add this iPhone" shows instructions; the **Mac displays a QR**, the
  phone scans it (SYNC §4.2). Success → "Paired with <Mac name>." Identity keys live in the
  Secure Enclave.
- **Biometric gate:** Face ID/Touch ID required for high-risk approvals (§5.1) and, optionally
  (setting), for *all* approvals and for opening the app.
- **Revoke:** either side can unpair; the phone shows "This device was removed on the Mac" and
  drops to the pairing screen.
- **Privacy posture surfaced:** a short note that code/diffs are end-to-end encrypted and the
  relay can't read them — trust is part of the UX for a tool touching proprietary source.

---

## 8. Ambient surfaces

- **App icon badge / Dock-equivalent:** count of `NEEDS YOU` sessions.
- **Home/Lock-screen widget:** attention count + the top waiting session; tap → that approval.
- **Focus/notification tuning:** per-project notification levels (e.g. only `destructive`
  approvals break through a Focus) — set on the phone, scoped to this device.

---

## 9. Mapping to sync + runtime

| iOS surface | Sync message | Notes |
| --- | --- | --- |
| Sessions home | `listSessions` → `sessionList`, live `sessionUpdated` | attention sort client-side |
| Open a session | `subscribe(sessionID, sinceSeq, verbosity)` → `snapshot` + `events` | verbosity by transport/foreground |
| Approval card / notification | push wake → pull `approvalRequested`; `approvalRespond(id, Decision)` | first-responder-wins; biometric on high-risk |
| Prompt composer | `sendInput(sessionID, AgentInput)` | scope ≥ approve |
| Diff view | snapshot `diffStat` + on-demand diff fetch | read-only |
| Connection chip | `SecureChannel` transport state | drives action enablement |

The phone never holds canonical state — identical to the Mac (UX_MACOS §8). Two renderers, one
host authority.

---

## 10. Differences from the Mac (by design)

- **No create / merge / discard / interrupt** (scope `approve`; those are `control`, later).
- **Notification-first, not window-first**; Live Activity + widget replace the menu-bar item.
- **Verbosity defaults down** (battery/data) and gates up only when foregrounded on a fast link.
- **Biometric** on risky approvals; the Mac uses open-detail friction instead.

---

## 11. Open questions

1. **High-risk on mobile** — biometric-gated in-app `Allow` (proposed) vs. *no* mobile approve
   for `destructive` at all (force it to the Mac)? Trades convenience against blast radius.
2. **Follow-up input scope** — keep `sendInput` at `approve`, or is "type a new prompt" really a
   control action that belongs behind `scope=control`? (Leaning: keep it; it's the natural
   mobile unblock.)
3. **Multi-host** — v1 assumes one Mac; when a user pairs two Macs, does the phone show a host
   switcher or a merged cross-host list?
4. **Live Activity budget** — one Activity per running session vs. a single aggregate Activity
   ("3 running, 1 waiting") to respect iOS limits.
5. **Offline approval queueing** — confirm we never queue an approval to auto-send on reconnect
   (stale, possibly dangerous); current design refuses and requires a live channel.
