Designing a terminal for a phone: five problems and how we solved them

A terminal assumes a physical keyboard, a fixed window and a connection that doesn't drop. A phone has none of those. Here are the five problems that mattered most when we put a real terminal on a phone, and what we did about each.

1. The keyboard hides what you're typing

On iPhone, the on-screen keyboard slides over the bottom of the page without making the page smaller. A terminal's prompt lives at the bottom. So the moment you start typing, the line you're typing on disappears behind the keyboard.

The obvious fix is to shrink the terminal to the space above the keyboard. That changes the terminal's size, and every full-screen program (editors, top, coding agents) redraws itself on each size change, often leaving debris behind. Doing that every time the keyboard opens is worse than the original problem.

What we do instead: the browser reports the visible area through the visualViewport API. We fit the terminal screen to that area, so the key bar sits right on top of the keyboard, and then slide the terminal up so the cursor's line stays just above the key bar. The terminal keeps its size, so nothing redraws. When the keyboard closes, the slide goes away.

2. Phones have no Esc, Tab or Ctrl

A key bar above the keyboard provides Esc, Tab, the arrows and a dedicated Ctrl-C. Ctrl itself is sticky: tap it, then type a letter, and the letter is sent as a control character. Ctrl-D, Ctrl-R and friends all work without a key for each.

One detail matters more than it seems: tapping a key-bar button must not move focus. If it does, the keyboard closes on every tap. The buttons swallow the focus change, so Esc and the arrows work whether the keyboard is up or not, and only Ctrl opens the keyboard (because it needs the next letter).

3. Two screens, one terminal size

A terminal has exactly one size, in columns and rows, and programs lay out their output for that size. When the same session is open on a wide Mac window and a narrow phone, something has to give. We tried letting each device draw at its own size. The result was output cut off at the phone's edge, and lines wrapped into nonsense.

The rule we settled on: the device you last typed on sets the size, and every other device draws at exactly that size, never its own. A phone watching a Mac-sized session shows it scaled down, like a mirror, with a "tap to fit here" hint. Tap, and the terminal resizes to the phone. Just opening a session never changes its size, so looking doesn't disturb a running agent.

4. Scrolling inside full-screen programs

In a plain shell, swiping scrolls back through the terminal's history. Full-screen programs are different: they draw their own scrollable views and switch mouse reporting on, and they scroll when they receive mouse-wheel events. A trackpad produces those. A finger doesn't. So on a phone, swiping inside those programs did nothing at all.

The fix is small: when a full-screen or mouse-reporting program is running, we translate vertical swipes into wheel events, about one per line of finger movement, and let the terminal forward them to the program exactly as a trackpad would. In a plain shell, swipes keep their normal behaviour.

5. Reconnecting without starting over

Phones freeze web pages in the background and drop connections all the time. The naive reconnect replays the terminal's recent output, which flickers, duplicates text, and, worse, replays cursor movements that were recorded when the terminal was a different size, scattering characters across the screen.

We fixed it in two layers:

And because phones don't always notice a dead connection, the page checks as soon as you come back to it and reconnects straight away if the line has gone quiet, instead of waiting for a timeout.

The common thread

Almost every fix came from the same question: what is the terminal program on the Mac expecting, and how do we give it that while the phone does something phone-shaped? The shell never finds out it's being used from a phone. That's the point.