Stop and start a session's process from the composer

The composer's second button now says what pressing it would do to the
process behind the session, in one place that is always there: an orange
pause while a turn is running (interrupt, the process stays), a red stop
when it is not (end the process), a green play when it has exited (start it
again on the same conversation). Send is disabled while there is nothing to
send, rather than pressable and silent.

Behind it, two routes. `stop` signals the recorded process and says nothing
else -- the driver's own reader already reports a death correctly, and
announcing it here would be a guess ahead of the measurement. `start`
replaces the driver and nothing else, so the transcript, the pump and every
open phone's stream stay where they were and there is still one writer of
the transcript; it is refused unless the session is known to have exited,
since starting on `Unknown` is the two-CLIs-on-one-conversation fault.

That last rule found a bug in the launch path: a relaunched session took its
status from the transcript, so one whose process had died before a backend
restart reported `exited` while the launch had just started a new process --
which refuses every command and offers a phone the chance to start a second
CLI on a live conversation. A launch that leaves a process running now says
idle.

The icon font moves to the Mono face, where every glyph is one em square, so
two icon buttons are the same width without either being told one; the
proportional advances ran 0.46 to 0.92 em and Send came out visibly wider
than Stop. GLYPH_SIZE comes down to match, since a glyph that fills its em
draws bigger at the same point size.

Verified against a stand-in CLI on the emulator: idle -> stop -> exited ->
start -> idle, a turn interrupted from the pause button, and both buttons
measured at 171x105 device pixels.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-30 12:41:36 -04:00
1 parent 317ad29d85
commit 6154cb1949
13 files changed
+562 -79

No files matched your search

+14 -4
View File
@@ -89,7 +89,11 @@ repo is in PLAN.md's "Backend layout" section.
codepoint in the Kotlin that the script did not subset is a glyph that
silently isn't there. Rerun the script and commit its output when adding
one; it needs network access. `md-cog` and `md-refresh` are deliberately
the same codepoints dev-updater uses and must not drift from it.
the same codepoints dev-updater uses and must not drift from it. The
subset is the **Mono** face, where every glyph is one em square — that is
what makes two icon buttons the same width without either being given
one, and it is why `GLYPH_SIZE` is smaller than it looks like it should
be.
- `.dev-updater.ron` — what Dev Updater is asked to do with this checkout:
the server (built in `server/`, run as `service: Managed(...)`) and the
APK (built in `app/`), built in parallel. The project it serves is the
@@ -280,15 +284,21 @@ day:
- **Stopping the server no longer stops the sessions.** After `pkill
ai-server` the `claude` processes are still there, on purpose, and the
next start picks them up (`reattaching to the claude-cli it left
running` in the log). To actually end one, delete the session — that is
the only path that stops a process.
running` in the log). To end one, either `POST /sessions/{id}/stop` —
which keeps the session and its transcript, and `POST .../start` brings
the process back on the same conversation — or delete the session, which
ends the conversation too.
- **Each session directory now holds `process.json`, `stdin.fifo`,
`stdout.log` and `stderr.log`.** `stdout.log` is the driver's input, read
from the byte offset in `process.json`; removing either by hand while the
session is live loses output or replays it.
- **`--resume` only ever runs when nothing is running.** That check is the
fix for the incident below, and the reason there is one entry point
(`ClaudeDriver::launch`) rather than a spawn and an attach.
(`ClaudeDriver::launch`) rather than a spawn and an attach. The status a
launch reports obeys the same rule: a session recorded as `exited` whose
launch has just started a process reports `idle`, because `exited` is the
word that refuses every command and offers a phone the chance to start a
second CLI on a live conversation.
- Remote sessions are adopted too. The pid recorded for one is the **`ssh`
client's**, on this machine — that is the process the backend owns, and it
lives as long as the remote command does. (This said "local only" until