Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/data/blogPosts.json
Original file line number Diff line number Diff line change
Expand Up @@ -1708,5 +1708,15 @@
"tags": ["shell-online", "terminal-sharing", "end-to-end-encryption", "coding-agents", "team-collaboration"],
"banner": "banners/share-a-terminal-session-password-with-a-teammate-without-the-service-seeing-it.svg",
"iso_date": "2026-09-23"
},
{
"slug": "watch-claude-code-from-your-phone-as-a-conversation-not-an-80-column-grid",
"title": "Watch Claude Code From Your Phone as a Conversation, Not an 80-Column Grid",
"description": "How to watch Claude Code from your phone with the shell.online chat renderer, what it reads from the screen, and where the transcript is kept.",
"date": "Sep 24",
"category": "Engineering",
"tags": ["shell-online", "claude-code", "coding-agents", "mobile", "end-to-end-encryption"],
"banner": "banners/watch-claude-code-from-your-phone-as-a-conversation-not-an-80-column-grid.svg",
"iso_date": "2026-09-24"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
import BlogLayout from '../../layouts/BlogLayout.astro';

const bodyContent = `<p>Two of the most used coding agents shipped terminal-rendering work this week. Codex 0.156.0 added an optional fullscreen interface, six themes, and inline Mermaid diagrams and display equations. Claude Code 2.1.282, released today, added a setting that caps how wide the model's prose may run in a wide terminal. Both point the same way: an agent's output is prose, and a terminal is a grid. The gap is widest when you try to watch Claude Code from your phone. The <span class="brand-shell">shell.online</span> app now has a renderer that reads the session as the conversation it is.</p>

<section>
<h2>An agent session is a conversation drawn on an 80-column grid</h2>
<p>The <a href="https://developers.openai.com/codex/changelog">Codex changelog for 0.156.0</a> describes an optional fullscreen UI behind a /tui command, with transcript search and mouse selection, plus the ability to "view supported Mermaid diagrams and display equations directly in responses." The <a href="https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md">Claude Code changelog for 2.1.282</a> adds a maxProseWidth setting "that caps the width of Claude's prose in wide terminals while tables and code blocks keep the full width", next to fixes for garbled rows in the non-fullscreen renderer.</p>
<p>The agent writes headings, lists, fenced code, and paragraphs. The terminal turns them into rows of cells, and the agent's interface works hard to make those rows look like a document again. On a big monitor that mostly works. On a phone an 80-column grid is either unreadably small or wrapped into pieces that no longer line up. A terminal share does not change this by itself: the default xterm.js view shows the real screen byte for byte, which is right for anything interactive and poor for reading three paragraphs and a code block in a queue.</p>
</section>

<section>
<h2>How to watch Claude Code from your phone as a conversation</h2>
<p>The chat renderer lives in the <span class="brand-shell">shell.online</span> app, the optional accounts layer, so the one-time setup is linking the machine. Everything else is the normal command.</p>
<pre><code>curl -fsSL https://shell.online/install | sh
shell auth
shell --name "Refactor auth" claude</code></pre>
<p>Linking publishes the share URL, the command name, the host name, and timing. It never publishes terminal contents or the browser password. On the phone, open <a href="https://app.shell.online">app.shell.online</a>, pick the session, and enter the eight-character password the CLI printed, or use the saved copy in your vault. In the terminal tab, change the Renderer selector from xterm.js to Chat. The choice is remembered per browser, and switching does not restart the process or touch the encryption.</p>
<p>What you get is a thread. Your prompts appear as sent messages. Claude Code's replies appear as received messages, with the Markdown it wrote rendered as headings, lists, inline code, quotes, links, and code blocks that carry their language. While the agent is working there is a Thinking line under the last message, driven by the spinner the program itself draws. Before this week a busy session, a finished session, and a broken session all looked identical on a phone: the thread simply stopped.</p>
<p>As the <a href="https://shell.online/mobile/">mobile guide</a> says, closing the browser does not stop the host; the session ends when its process exits.</p>
</section>

<section>
<h2>What the renderer reads, and what it declines to guess</h2>
<p>The design is written up in the <a href="https://github.com/TeoSlayer/shell.online/blob/main/app/src/terminal/chat/README.md">chat renderer README in the repository</a>. It never parses the byte stream directly. It builds an xterm.js Terminal that is never opened, so it has no canvas and no DOM, and reads finished rows out of that emulator's buffer. Stripping escape codes and splitting on newlines fails within a minute of real use: a progress bar becomes two hundred lines, a wrapped line is cut in half, and the shell prompt becomes output. An emulator already solves all of that.</p>
<p>Two rules decide what is finished. The cursor row is never released, because whatever is on it is still being written. A wrapped line is released whole, because the emulator knows which rows continue the row above. Where a shell publishes OSC 133 command markers, the boundary between answers is exact.</p>
<p>Claude Code draws its own interface, so it needs an adapter on top, and that adapter is written against captured frames from the real program rather than an assumed shape. Three merges today tightened it. The title match now includes the rotating glyphs Claude Code puts in front of the window title once it has a task, so a session is still recognised after the header has scrolled away. And the repaint reader anchors on the tail of what it has already emitted rather than the head of the screen. In the replay test added with that change, three real exchanges went from 71 emitted utterances to 9.</p>
<p>The Markdown rendering is hand-written on purpose. The input is output from somebody else's machine, so every node is built with textContent and there is no path from the input to innerHTML. A link only becomes a link when its scheme is http or https. Raw HTML, images, reference links, and tables are left exactly as typed. The whole thing is in <a href="https://github.com/TeoSlayer/shell.online/blob/main/app/src/terminal/chat/markdown.ts">markdown.ts</a>.</p>
<p>When a program takes the whole screen and no adapter can read it, such as vim, top, or a pager, the renderer prints one line instead of squeezing a grid into a bubble: "vim notes.md is running. Switch this session to the terminal renderer to see it."</p>
</section>

<section>
<h2>Where the transcript lives after you reload the page</h2>
<p>Reload the page and the renderer starts from whatever the relay replays, which is the current screen, not the conversation. The change that merged today keeps the conversation on the device that watched it, and the reasoning matters more than the mechanism.</p>
<p>The relay cannot read a session. The <a href="https://shell.online/e2ee/">encryption page</a> spells out what it does see: timing, addresses, encrypted sizes, labels, and lifecycle metadata. A server-side history would mean shipping the conversation somewhere it is not supposed to exist. The device that watched already has the plaintext on its screen, so it is the honest place to keep it.</p>
<p>The store is IndexedDB rather than localStorage. localStorage is synchronous, so every write would land on the thread that draws the conversation, and per <a href="https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria">MDN's storage quota documentation</a> browsers allow about 5 MiB of local storage per origin. The cache keeps the most recent 400 messages and writes once a burst has settled, so an answer arriving a line at a time is one write rather than forty.</p>
<p>At rest, the cache is encrypted whenever the session is. The key is derived from the secret in the share link with HKDF-SHA256, per <a href="https://www.rfc-editor.org/rfc/rfc5869">RFC 5869</a>, under its own salt and info strings, so the cache key is never the session key. A cached conversation is therefore readable by exactly the people who could read the session it came from, and by nobody else with access to the disk. Reload with a different link and the cache cannot be decrypted, so it is treated as no history. A session started with <code>--no-e2ee</code> has nothing to derive from and is stored as plaintext, which is no worse than the session itself. The implementation is <a href="https://github.com/TeoSlayer/shell.online/blob/main/app/src/terminal/chat/chat-history.ts">chat-history.ts</a>.</p>
<p>A device that was not there says so. The first time a browser opens a session with an empty cache, the thread starts with "Nothing from this session is kept on this device. What follows starts here." That notice is not saved, because it is true of that moment and no moment after it. There is no syncing between devices: the relay broadcasts to every viewer, so each cache reflects every interaction that device was present for.</p>
</section>

<section>
<h2>What access looks like once the session reads as chat</h2>
<div class="callout"><p>The URL and the password together are a bearer credential. Anyone holding both can read the session and, unless it was started with <code>--read-only</code>, type into it with the permissions of the process you wrapped. A conversation view does not narrow that in any way. It is the same session with a different renderer.</p></div>
<p>If you want a teammate to read the agent's reasoning from their phone without steering it, start the session read-only. A colleague who opens a session you handed over, through the organisation features covered in the <a href="https://pilotprotocol.network/blog/shell-online-live-terminal-browser-link">earlier post on live terminal links</a>, starts their own cache from that moment, and their thread will say so.</p>
<p>One note on versions. The CLI release is v0.23.1, published September 23, and none of the above needs a CLI update, because the chat renderer runs entirely in the browser. The adapter, Markdown, and history changes merged to main on September 24. The repository README is explicit that a merged change is not proof that every hosted component has been updated, so if the Renderer selector does not yet behave as described, the source linked above is the authoritative account of what shipped.</p>
</section>

<div class="cta"><h3>Read your agent, not its screen</h3><p>Start Claude Code with one command, open the link on your phone, and switch the renderer to Chat. The process stays on your machine, the relay sees ciphertext, and the transcript stays on the device that watched it.</p><a href="https://shell.online/">Try shell.online</a></div>
`;
---
<BlogLayout
title="Watch Claude Code From Your Phone as a Conversation, Not an 80-Column Grid"
description="How to watch Claude Code from your phone with the shell.online chat renderer, what it reads from the screen, and where the transcript is kept."
date="September 24, 2026"
tags={["shell-online", "claude-code", "coding-agents", "mobile", "end-to-end-encryption"]}
canonicalPath="/blog/watch-claude-code-from-your-phone-as-a-conversation-not-an-80-column-grid"
author={{ name: 'Artemii Amelin', type: 'Person' }}
bannerImage="/blog/banners/watch-claude-code-from-your-phone-as-a-conversation-not-an-80-column-grid.svg"
>
<Fragment set:html={bodyContent} />
</BlogLayout>
Loading