#233

Phone: real sessions on the home, and a read-only live thread

Opencalledforthwants to merge intofrom

First slice of the phone's sessions work: real sessions in the shell, and opening a session to a read-only live thread. Sending, interrupt, delete, rename and settle, and the designed session page, are later slices.

What changes

The home reads the paired environment. src/home/source.ts now takes the shared sidebar providers (SidebarDataContext, SidebarSessionsContext, useSidebarDrafts) and normalises them into the shell's shapes. The workspace groups become one list of rows that each know their workspace. Every status, title and settle passes through as the environment served it: done and ready are statuses, read by the shared sessionBusyTone. The environment line comes from the phone's controller: the saved label, the route in use (LAN or tunnel), and whether it is connected. The Inbox badge, tab counts and search follow, because they all read the same source.

Drafts. PhoneEnvironmentProvider now gives the application providers a draft page (/draft/<id>). Only hosts with draft pages get the shared draft cards, so this is what makes the Drafts tab list the environment's real drafts.

The fixture. It is now the source only when nothing is paired and the build sets EXPO_PUBLIC_FIXTURE=1. Otherwise an unpaired phone shows the empty home.

A session opens read-only. src/session/useReadOnlySession.ts opens a session the way web's session route does:

  • It sends session.open once the socket is up.
  • The snapshot and then the live events come through the shared active-thread provider (environment-thread).
  • When the page goes, it calls setActiveSession(null), which releases the session's subscriptions.
  • It never asks for session.history.
  • A done session on screen is acknowledged on the environment (acknowledgeSessionDone), so tapping a Done row in the Inbox clears it there. Nothing is cleared locally, and a clear that did not land is asked again.
  • The page owns the client's one active session only while it is the page in front (useIsFocused). It opens the session on focus and releases it on blur or on leaving, but only if the session is still its own.
  • An open that a dropped connection cut off is asked again on the next connect.
  • An environment that does not advertise subscription.replay is never opened: without replay, the shared client fills the transcript from session.history. The page says the environment needs updating instead.

The page. src/session/SessionThread.tsx has a back icon, the title and project, and the status line (the home's mark and line). The transcript is a FlatList that follows its end while the turn streams, unless you scroll up; a glass button brings you back to the latest. Each row reads its own message from the shared stores, so a streamed token re-renders only that row. Rows cover:

  • markdown
  • reasoning, collapsed
  • bare tool rows: the tool's name and status
  • images, fetched through fetchArtifact

There is no composer yet: a glass bar at the bottom says the session is read-only on the phone for now. If the session cannot be kept current (it failed, is gone, or the environment is too old), a fill above that bar says so over the transcript, with a retry icon. A child-session row opens the child the same way. With the fixture, the page renders a thread recorded from a live run (src/session/recorded-thread.ts).

Removed, because nothing used them any more:

  • the Convex-era data hooks (src/data/*)
  • the old session screen, with its Composer, PermissionBanner, lib/dialogs and StatusDot
  • SessionCard
  • the session page placeholder

No server changes.

Checks

  • tests/home-source.test.ts (12 tests) covers the source's normalisation:
    • every status passed through (done, ready, busy and others)
    • row identity
    • child nesting across groups
    • unavailable workspaces
    • missing titles and times
    • drafts
    • when the fixture applies
    • the environment line
  • tests/read-only-thread.test.tsx (13 tests, jsdom) mounts the shared providers over the mock environment client and checks:
    • the snapshot
    • a turn started elsewhere, streaming into the rows
    • the acknowledgement
    • a child session
    • the release on leaving
    • draft cards
    • a session deleted while on screen
    • the page in front owning the session (blur, refocus, a page under another)
    • an environment without replay, at open and after a reconnect
    • an open cut off by a dropped connection
    • a retried acknowledgement
    • that loadSessionHistory is never called
  • live/sessions.live.ts runs the same hooks against a real server:
    • It connects through the phone's controller, and the home's rows match the server's sessions and draft.
    • It opens a session and streams two live Claude Code turns into it (a Read, then a shell command, reasoning and a reply).
    • It leaves, and the session is unsubscribed.
    • On the wire: one session.open, zero session.history.
  • The web export was screenshotted in dark and light: the fixture home, the read-only page from the recorded thread (including scrolled up, with the button), and the unpaired empty home.
  • npx expo export --platform android bundles.

The Android glass check on a device is still open from the shell PR.

🤖 Generated with Claude Code

Activity

11
cloudflare-workers-and-pages[bot]8d ago

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

StatusDeployment URLCommitUpdated (UTC)See this deployment's details
  • Build: Failed ❌

View logs ↗
eace1812026-10-22T07:08:34.983ZView logs ↗
  • Build: Failed ❌

View logs ↗
8aca1602026-10-22T02:02:48.533ZView logs ↗
  • Build: Failed ❌

View logs ↗
1aae6842026-10-22T01:54:27.521ZView logs ↗
  • Build: In progress 🔵

View logs ↗
1250c092026-10-22T01:51:11.519ZView logs ↗
  • Build: Failed ❌

View logs ↗
c63d2e22026-10-22T01:37:07.890ZView logs ↗
greptile-apps[bot]8d ago


[Medium impact] The changes affect the paired mobile session workflow, from the home list through viewing a live session.

Fix the hidden failure and retry state before merging.

Findings

  1.  Existing messages hide failures ▶
  2.  Read sessions stay Done ▶

Summary

The phone now reads real sessions and drafts from the paired environment and opens sessions as read-only live threads.

  • The phone home shows sessions and drafts from its paired environment.
  • Opening a session shows its read-only live thread.
  • Session transcripts render live messages and follow new content.
  • Mobile provider tests use the same React setup as the phone.

Scope acknowledged by calledforth: sending, interrupt, delete, rename, settle, and the designed session page are deferred to later slices. The Android glass check on a device remains open.

Diagram

sequenceDiagram
  participant Phone as Phone session page
  participant Client as Environment client
  participant Server as Environment
  participant Stores as Shared thread stores
  Phone->>Client: openSession(sessionId)
  Client->>Server: session.open
  Server-->>Client: Snapshot
  Client-->>Stores: Messages and content
  Server-->>Client: Live events
  Client-->>Stores: Updated row content
  Stores-->>Phone: Render changed rows
  Phone->>Client: setActiveSession(null) on leaving
  Client->>Server: Unsubscribe

Reviews (1) · Last reviewed commit: "Show the environment's sessions on the p..." · Reviewed by Greptile

greptile-apps[bot]reviewed8d ago
greptile-apps[bot]reviewed8d ago
greptile-apps[bot]reviewed8d ago
greptile-apps[bot]reviewed8d ago
greptile-apps[bot]8d ago

Your organization has used all 50 credits included in the free plan this billing period. To keep receiving reviews, upgrade your plan.

calledforthreviewed8d ago
calledforthreviewed8d ago
calledforthreviewed8d ago
calledforthreviewed8d ago