web: branch a chat at an answer into a new session - #174
Merged
Conversation
A long run's transcript is the answers, not the plumbing. Consecutive tool lines now sit under one native details whose summary reads "Working on it" and the latest line's phrase while the turn is adding to it, and the count, "3 steps" or "3 steps, 1 failed", once it is closed. The lines themselves are built exactly as before and are one click away. Every place that used to null the group now goes through closeActivityGroup, so the summary is finalised in one place, and a replayed group is closed as soon as it is drawn. The approval card is a details too: open and refusing a click on its head while it waits, folded to its head with the outcome and the tool name once settled, with the arguments and the note under it. Both pieces of DOM building move out of main.ts into activity-group.ts and approval-card.ts so they can be tested without a page. The phrase on the summary is read off the line and written through textContent after the same clamp the line used. Nothing about what is stored or sent changed. Claude-Session: https://claude.ai/code/session_01PG2vuux5tMsjnZBLWJzMyp
POST /api/sessions/:id/branch takes {"answer": N} and Store::branch_session copies the stored messages up to and including the Nth answer, with their images and tool calls, seq for seq, into a new session in one transaction. The source is not touched. The session row is copied verbatim, task included, for the reason the title entry gives; file changes are not copied. A running turn gets the same 409 as delete.
The browser puts a Branch button next to Copy under every answer. The answer is named by ordinal, the Nth assistant message with text, which the page counts as it draws on both the replay and the live path, skipping a row the store never got. Pressing it opens the copy.
Claude-Session: https://claude.ai/code/session_01PG2vuux5tMsjnZBLWJzMyp
…it reports The browser's tool line no longer writes its status word beside the phrase. The result is the bullet's colour, from exactly one of activity-ok, activity-fail or activity-running on the line, with the word kept in the line's title and, for a shell call, under the verbatim command in the details. stateForStatus in web/src/activity-line.ts is the one mapping: exit 0 and passed are ok, the tools' failure vocabulary is fail, and the other tools' own success summaries stay ok, the same split the agent loop uses for succeeded. The verify line keeps its word and takes the same classes. Renderer::tool_starting is a new trait method with an empty default, called from Agent::run_tool after approval and before dispatch, so a denied call never draws as running. WebRenderer sends it as a tool_started frame with the name and phrase the tool frame will carry; the browser appends a running line on the first and settles it in place on the second. A replayed session has every result, so no replayed line is in progress, and a turn that ends with a line pending fails it with the reason in its title. Claude-Session: https://claude.ai/code/session_01PG2vuux5tMsjnZBLWJzMyp
…y-and-approval Resolves the overlap in web/src/main.ts: both imports, both pieces of state, the tool line comment says colour and collapse, and every reset site closes the group and drops the pending tool line. Both decision entries are kept. Claude-Session: https://claude.ai/code/session_01PG2vuux5tMsjnZBLWJzMyp
Resets in one place: the answer count, the activity group and the pending tool line all clear together. All three decision entries are kept. Claude-Session: https://claude.ai/code/session_01PG2vuux5tMsjnZBLWJzMyp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Under every assistant answer, next to Copy and Copy for…, a Branch button starts a new chat whose history is this chat up to and including that answer. The original is untouched. It works on a chat reopened from the sidebar and on answers that just streamed in.
Backend
POST /api/sessions/:id/branchwith{"answer": N}, next to the other session routes inzorp-web/src/api.rs. Answers{"id": ...}, 404 for an unknown session, 400 when there is no such answer, 409 while a turn is running on the source (the same checkdelete_sessionmakes).Store::branch_sessioninzorp-agent/src/session.rsdoes the copy in one transaction, in SQL: the session row verbatim (task,repo,model,display_title, reasoning mode, status as a new session gets), then every message withsequp to and including the Nth answer, with its images and tool calls, seq for seq. File changes are not copied. An answer is an assistant message with non-empty text, the same ruletranscriptuses.taskstays the verbatim first message. It is the same first message, and the title decision says why nothing generated may go in that column.Browser
web/src/branch.ts:AnswerCountcounts answers the way the server does, andbranchButtonbuilds the control. Both draw paths meet inanswerControls, which is where each answer takes its number. A row cut off by an error, a stop, or the end of the turn was never recorded, so it gets no number and no button, and the count does not drift.branchSession(id, answer)inweb/src/api.ts. On click the button goes down, the new session is opened the way the sidebar opens one, and the list is refreshed; a failure lands inappendError. Branch is disabled while a turn runs, matching the 409. Everything drawn goes throughtextContent.Docs
docs/DECISIONS.md2026-09-05 entry: ordinal rather than seq and why,taskcopied verbatim, changes not copied, recall indexing the copy accepted for now.CLAUDE.mdandAGENTS.md, after thetitlebullet.Checks
cargo fmt --all --check,cargo build --workspace,cargo test --workspace,cargo test -p zorp-web --features research,cargo clippy --workspace --all-targets --locked -- -D warnings: all green.web/:npm run check,npm test(363 pass, 0 fail),npm run build.https://claude.ai/code/session_01PG2vuux5tMsjnZBLWJzMyp