SPB Git

Repository Public

mobile-experience.md 6.7 KB

# Mobile experience

The same static web application serves phones, tablets and desktops. Below 768 px it switches to a purpose-built phone layout. Source: apps/web/src/components/shell/mobile-shell.tsx, components/terminal/{mobile-terminal,mobile-toolbar,keys,touch,controller}.ts(x), hooks/use-viewport.ts, public/{sw.js,manifest.webmanifest}.

Verification status: WebKit iPhone 15 emulation in Playwright only. Nothing in this document has been verified on a physical iPhone, in Safari or as an installed PWA.

# Layout

Breakpoint Width Shell
mobile < 768 px phone shell: top bar, full-bleed content, bottom navigation
tablet 768–1023 px desktop shell components at a narrower width
desktop ≥ 1024 px sidebar, tabbed terminal workspace, right panel

Phone shell:

  • The root element is position: fixed and sized from window.visualViewport (CSS variables --vvh, --vvt), so its bottom edge is the top of the software keyboard. iOS scrolls the layout viewport to reveal a focused field; the shell undoes that scroll.
  • Bottom navigation: Terminal, Projects, Files, Tasks, Cluster, plus a "More" entry (Sessions, Skills, MCP, System, Settings). It hides while the keyboard is open.
  • viewport-fit=cover; touch targets are at least 44 px high on coarse pointers.
  • Files use a drill-down list and a full-screen editor instead of the desktop split view.

# Terminal on a phone

MobileTerminal: a top bar (session title, state, connection indicator), the terminal edge to edge, and the key toolbar directly above the keyboard.

  • A session sheet lists the sessions for switching; a swipe from the left edge opens it.
  • The terminal font size comes from the mobileFontSize setting (default 12, range 8–28) and can be changed by pinching.
  • On iOS the terminal uses xterm.js's DOM renderer (the WebGL renderer is used elsewhere when available).
  • One WebSocket per session, same protocol as on desktop; the terminal is re-fitted on rotation and when the keyboard opens or closes, and the new size is sent to the server.

# Key toolbar

Default keys (mobileToolbar in the user settings — the list is configurable):

esc tab ctrl alt shift ↑ ↓ ← → enter / | ~ -, followed by Paste (reads the clipboard) and Hide keyboard.

  • Buttons act on pointerdown with preventDefault and are not focusable, so the terminal keeps focus and the keyboard stays open.
  • Sticky modifiers (ctrl, alt, shift): one tap = applies to the next key only; double tap = locked; tap while locked = off. They apply both to toolbar keys and to characters typed on the software keyboard (for example ctrl then c sends 0x03).
  • Arrow keys honour the application cursor mode reported by xterm.js (DECCKM), so they work in programs that switch it.
  • Keys that make sense repeated auto-repeat when held (after 420 ms, every 70 ms).
  • The toolbar scrolls horizontally if it does not fit.

# Touch gestures

Gesture Effect
Tap focuses the terminal (opens the keyboard), clears a previous selection
One-finger drag scrolls the scrollback with momentum; when the application inside tracks the mouse, wheel reports are sent instead
Long press (420 ms), then drag starts and extends a text selection
Two-finger pinch changes the font size

# Connection behaviour on mobile networks

Identical to desktop, and designed for it (see session-persistence.md):

  • The session keeps running on the server whatever the phone does.
  • Reconnect with backoff (0.5 s → 10 s) and immediately when the page becomes visible, the network comes back, or the page is restored from the back/forward cache.
  • A connection that looks open after a suspension is probed with a ping and replaced if it does not answer within 2.5 s.
  • Every re-attach replays the history and repaints the screen.
  • While offline the terminal shows "Offline — the session keeps running on the server"; keystrokes are not queued.

# Installable shell (PWA)

  • manifest.webmanifest: name "Claude Remote", display: standalone, start_url: /terminal/, 192/512 px icons (regular and maskable); Apple touch icon and apple-mobile-web-app-capable metadata are set in app/layout.tsx.
  • sw.js (registered in production builds only) caches the static application shell: the HTML shells of the routes, /_next/static/*, fonts, icons and the manifest. Navigations are network-first with the cached shell as fallback; content-hashed assets are cache-first.
  • /api/* is never intercepted and never cached; WebSockets cannot be. No terminal data, API response or credential is stored by the service worker.
  • Offline, the cached shell loads and shows an explicit offline state. Nothing can be done offline.

# Notifications on mobile

In-app only: a notification list and toasts while the application is open, fed by the events WebSocket (session.waiting, session.exited, task.succeeded, task.failed). There is no push notification when the application is closed or suspended.

# Tests

apps/web/e2e/mobile.spec.ts, Playwright project mobile (devices['iPhone 15'], WebKit), against the dev mock API — 4 tests, all passing on 2026-11-10:

  1. login, purpose-built layout, toolbar above the terminal, no horizontal overflow;
  2. toolbar keys send real sequences; sticky modifiers (one-shot, lock); focus is kept;
  3. session sheet, switching, font size, files drill-down with full-screen editor;
  4. larger phone (430 × 932) and landscape refit.

Key sequences and sticky-modifier logic are also unit-tested (apps/web/src/__tests__/keys.test.ts). apps/web/e2e/setup-offline.spec.ts covers the offline shell.

# Limitations / not verified

  • No real-device test. Not verified: Safari on a real iPhone, the installed PWA in standalone mode, the real iOS software keyboard and visualViewport behaviour, iOS suspension and resume, clipboard permission prompts for Paste, safe-area insets on real hardware, Android browsers.
  • Emulation runs WebKit on macOS with a touch-enabled viewport; it does not reproduce the iOS keyboard, Safari's toolbar, or background suspension.
  • The mobile tests run against the mock API, not the real backend.
  • Web Push is not implemented. The webPush setting is stored, but the client does not subscribe and the server does not send. A phone will not be alerted that a session is waiting unless the application is open.
  • iOS may evict a suspended PWA; the application then reloads and re-attaches (the session is unaffected), but this path was not observed on a device.
  • Hardware keyboards on tablets, split view and stage manager were not tested.
  • The laptop-independence test (laptop off, phone on cellular) has not been run.
Files main
Menu

Theme