The patch path did a DOM replaceWith and returned early, skipping the
focus-restoration logic in rebuildTranscript. Now saves and restores
tool-head focus within the surgical swap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Queued prompts now appear inline in the transcript as faded user bubbles
instead of a pill badge above the composer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uploaded images now render as thumbnails in the user bubble instead of
showing only the server file path as text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reuse existing tool-group DOM elements in rebuildTranscript instead of
tearing them down — preserves CSS transition state and focus. Defer
config-menu DOM rebuild while the menu is open so <select> dropdowns
survive background state updates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "Running 1m ..." status now sits at the tail of the chat
history instead of pinned above the input, matching Claude Code's
placement.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Intermediate narration was hidden inside the collapsed tool group
while the agent was still working. The last AgentText/AgentThought
now stays visible as current status, absorbed only when the turn ends
or newer text replaces it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolved permission boxes vanish from the transcript instead of
lingering as "answered" cards. When a turn has 2+ tools the resolved
approval is absorbed into the tool group as a compact italic line;
otherwise it is hidden entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Patch deltas on existing rows now swap just the affected element via
replaceWith() instead of tearing down and rebuilding the entire
transcript. Stops the visible flash and preserves text selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep icon cancel button, always-"Send" label, and queue status span
from main. Drop unused `active` local after label simplification.
Update test expectation to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move max-width constraint from .structured-rows up to .structured-main
so the activity banner and composer match the transcript width. Replace
text cancel button with a stop-sign icon inside a spinning ring. Drop
the accent border-top on composer focus.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Intermediate agent text/thought rows between tool calls in the same
turn now render inside the collapsed tool group instead of after it,
preserving chronological order. The last agent text (no subsequent
tool) stays outside as the prominent final answer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three more fixes from acceptance review:
- structured.ts's header omitted the .sessions-btn attach.ts's terminal
header carries. The sidebar is off-canvas on mobile, so a structured
session had no way to reach it — add the same button and
requestToggleDrawer wiring.
- ConnectionPool.reconcileViews always remounted a repaired connection
with focus:false. If the mistyped pane was the one actually focused,
disposing it dropped DOM focus while `focusedId` still named it. Pass
`focus: this.focusedId === id` instead — `focusedId` only ever names a
real focus (multiview.ts skips setFocus while previewing), so a
preview or a merely-visible split neighbour still comes back
unfocused.
- onMount's deferred requestAnimationFrame focus had no way to tell it
had been superseded by an unmount (or a remount elsewhere) before it
fired, so a parked pane could steal focus a frame later. A token
bumped on every mount/unmount lets the callback recognize it's stale
and no-op.
Regression tests: 7 new (structured.test.ts, connections.test.ts). 495
tests total.
Three fixes from acceptance review:
- ConnectionPool.mount cached a connection's kind (terminal/structured)
permanently from whatever `info` it saw first. A deep link or restored
layout mounts before the catalog snapshot names a session's view, so
`info` is undefined and a structured session got an xterm connection
forever. The pool now tracks the view each cached connection was built
for, subscribes to the catalog itself, and replaces a mistyped
connection in place the moment the real view resolves — without
downgrading a resolved connection just because a later caller couldn't
resolve the session, and without treating a session merely absent from
a snapshot (mid-removal) as "Terminal".
- The approval card offered all three decisions unconditionally. Per
the daemon's decision mapping (session.rs's permission_outcome), a
decision with no matching PermissionOptionKind in the request's own
options resolves Cancelled silently — so "Allow for session" could
look like it worked while the daemon withdrew it. Buttons are now
gated on the options actually offered.
- Clicking a decision disabled the buttons and never re-enabled them if
the POST failed. The decision path is async now: a rejection restores
the buttons and surfaces the error only if the approval is still the
pending one — a superseded/resolved approval's stale rejection is a
no-op, not a wrongly revived control.
Regression tests: 12 new (connections.test.ts, structured.test.ts,
structuredRender.test.ts). 488 tests total.
Adds a transcript surface for a Structured-view session alongside the
existing xterm attach view: a WS /api/structured/:id connection
(structured.ts) mounted by ConnectionPool whenever a session's `view`
is `"Structured"`, and pure per-row/rail renderers (structuredRender.ts,
structuredDiff.ts) covering all eight transcript row kinds.
Hierarchy follows the design reference (artifact 88ecd690): the
approval is the only lifted, card-like element; a user turn is a
tinted block with an accent rail; agent prose is plain text; tool
calls are compact bordered rows expanding to output or a coloured,
horizontally-scrolling diff computed from ACP's full old/new text.
The client renders `reduced_state` and rows exactly as received, with
no raw-event folding or turn tracking of its own.
Extracted attach.ts's pane-header title building into paneTitle.ts and
its connection interface into pane.ts (PaneConnection) so both
connection kinds share one contract and one header.