Skip to content

Read a session's conversation as a chat lens on the phone - #296

Draft
jiweiyuan wants to merge 1 commit into
mainfrom
feat/ios-chat-lens
Draft

Read a session's conversation as a chat lens on the phone#296
jiweiyuan wants to merge 1 commit into
mainfrom
feat/ios-chat-lens

Conversation

@jiweiyuan

@jiweiyuan jiweiyuan commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

The terminal stays the interface. This adds a second lens onto the same live session, for the times a phone is the only screen you have.

How it works

The host turns an agent's on-disk transcript into an AgentEvent stream that clients subscribe to with a cursor, so a phone reconnecting mid-turn asks for the gap rather than the whole conversation, and a dormant session still reads — the transcript outlives the process. A tool event is upserted rather than re-appended, so a card revises in place instead of jumping to the bottom when it finishes.

The phone renders those events natively — prose, tool cards, diffs, plans — and its composer types into the same PTY the Mac terminal owns. One session, two lenses, no local/remote mode switch and no session restart.

Sending

A prompt waits for the agent's screen to settle before it is typed. Bytes written while a CLI is still painting are read by whatever holds the PTY at that moment and are simply gone: the message disappears, no turn starts, and nothing streams back — so the lens looked broken in both directions from a single cause. This is a client-side approximation; the Mac is the side that actually knows when the agent is ready, and promoting prompt to a typed wire message with an acknowledgement is the follow-up.

Verified

Driven on device and in the simulator against a real Mac companion and a real Claude session: a prompt sent from the lens into a dormant session reconciles from pending to sent and the reply streams back. AgentEventStoreStreamTests pins the half that cold-replay tests miss — that a turn appended after a subscriber attaches reaches it.

Terminal first, chat one tap away

A session opens in the terminal — the product's position rather than a default nobody chose — and each view carries the switch to the other in the same header, so swapping never moves the chrome. Settings ▸ Appearance ▸ Sessions flips which one a session lands on, and back leaves the session from either view rather than making it a two-step.

Reading never starts anything. The Mac creates a session's process on first attach, so the chat subscribes without attaching and only the terminal — or a send — claims the PTY. Asking whether a session is live no longer goes through the call that spawns one either, which meant opening a finished conversation used to resurrect it.

The conversation itself

Events now carry the transcript's own clock, so a conversation read days later groups by the day it happened rather than the day you opened it: day pills between days, a time under your own messages, and grouping that tightens consecutive messages from one speaker. Around that, a jump-to-latest control, long-press to copy, a haptic on send, and a working… line under the title fed by the same hook-driven status the session list already broadcasts — the chat was simply not listening to it.

Measured row heights are fed back as estimates. With one fixed guess, a conversation full of forty-line diffs shifts under your finger every time a row turns out taller than the guess, which is the single loudest way a self-sizing table announces itself.

Three dialect details that were each wrong first: a plan is one row that revises in place, folding both shapes Claude Code writes (TodoWrite sends a whole list, TaskCreate / TaskUpdate one task at a time); a revised event keeps its place in the log and only takes a new seq, without which a cold replay puts every finished tool card below the diff it produced; and the user side of a transcript is filtered of the CLI's own plumbing — isMeta rows carrying a skill's body, <command-name> envelopes, <local-command-stdout> — because rendering those in a user bubble claims the human said them.

Not in this PR

Approval buttons (the PreToolUse interaction plane), the Codex dialect, the transcript / questions manifest blocks, an on-device event store so a chat opens from disk instead of re-downloading, and a turn-level progress row to collapse the tool traffic of a busy turn.

Release Notes

  • Read an agent session's conversation on your phone, with tool calls, diffs and plans rendered natively rather than scraped off the terminal.

…phone

The terminal stays the interface; this adds the second lens onto the same live
session. The host turns an agent's on-disk transcript into an AgentEvent stream
clients subscribe to with a cursor, so a phone that reconnects mid-turn asks for
the gap rather than the whole conversation, and a dormant session still reads.

The phone renders those events natively — prose, tool cards, diffs, plans — and
its composer types into the same PTY the Mac terminal owns, so there is one
session and no local/remote mode switch.

A prompt now waits for the agent's screen to settle before it is typed. Bytes
written while a CLI is still painting are read by whatever holds the PTY at the
time and are simply gone: the message disappears, no turn starts, and nothing
streams back, so the lens looks broken in both directions from one cause.
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
landing Ready Ready Preview Aug 13, 2026 5:33pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant