Skip to content

web: branch a chat at an answer into a new session - #174

Merged
adityak74 merged 5 commits into
mainfrom
feat/web-branch-chat
Sep 5, 2026
Merged

web: branch a chat at an answer into a new session#174
adityak74 merged 5 commits into
mainfrom
feat/web-branch-chat

Conversation

@adityak74

Copy link
Copy Markdown
Contributor

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/branch with {"answer": N}, next to the other session routes in zorp-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 check delete_session makes).
  • Store::branch_session in zorp-agent/src/session.rs does 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 with seq up 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 rule transcript uses.
  • task stays 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: AnswerCount counts answers the way the server does, and branchButton builds the control. Both draw paths meet in answerControls, 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) in web/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 in appendError. Branch is disabled while a turn runs, matching the 409. Everything drawn goes through textContent.

Docs

  • docs/DECISIONS.md 2026-09-05 entry: ordinal rather than seq and why, task copied verbatim, changes not copied, recall indexing the copy accepted for now.
  • One bullet each in CLAUDE.md and AGENTS.md, after the title bullet.

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

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
@adityak74
adityak74 merged commit d31e1a0 into main Sep 5, 2026
7 checks passed
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