fix(desktop): float composer chips over the transcript and resume chats quietly
Summary
- The chip row above the chat composer (Changes, Subagents, Background) floats over the bottom of the transcript and takes no space, so the transcript no longer jumps when a chip appears or goes away.
- The row stays on one line. In narrow panes the chips show icon, count and age, and the scroll-to-bottom arrow sits above them.
- The chat has no status line above the composer; a check that keeps failing shows in the host pill at the top (Part 7).
Why / Context
From the review thread: the Subagents chip took its own row and broke the text flow. The first version floated the row. Review then found four ways the overlay got in the way: it covered the last line when it wrapped, it sat under the arrow (at 900px too, with three chips), it blocked clicks in its gaps, and it covered the connection notice. Options were compared and measured here: https://app.superset.sh/page/composer-chips-the-fix-that-keeps-the-transcript-s-t4oh1i
Reserving space for the row was rejected: when the chips appear, the transcript moves up 26px (one row) or 52px (two rows), which is the jump this PR removes.
The resume notice: useChatSessionLiveness keys its check on the host connection status, including since. When a chat's host socket has closed (nothing else held it, as with cloud boxes), coming back means a new socket, a new key, and a fresh check, so the notice showed on every return. The check is needed (a box that slept may have lost its agent), but the hold already queues prompts, so the notice adds nothing when the check is quick.
How It Works
ComposerChips: zero-height anchor, row isabsolute bottom-0, one line (flex max-w-full, no wrap),[&>*]:pointer-events-autoso only the chips take clicks. The column is a@container.RunningTasksChip/LiveChangesPill: the full label ishidden @[28rem]:inline; below that, aformatNumbercount. The full label is onaria-label(andtitle). Existing messages are reused; the catalogs do not change.Transcript: arrow wrapperbottom-3→bottom-9, above the chip band. The arrow shows only when scrolled up.SessionView:<ComposerChips>renders above the connection notice.
Part 3: from cloud workspace creation to the first reply
Before and after, every stage: https://app.superset.sh/page/starting-a-cloud-chat-before-and-after-bgaf6b
Creating a cloud workspace with a prompt went through five screens from five components. In the chat, the prompt moved from a bottom bubble (y 884) to a queue item (y 948), vanished while attaching, came back, then jumped to the transcript top (y 140), and the status line changed text four times, including "Resuming the conversation…" for a chat that never ran.
Now there are two screens: the provisioning card (unchanged), then the chat with the prompt at the transcript top from the first frame.
layout.tsx: the provisioning card stays up until the real chat pane mounts.PendingCloudChatPreview(the fake chat with a blank composer) is removed.- New
LaunchPromptViewdraws the launch prompt with the transcript's ownUserMessageRow(pending) in the transcript's wrappers.DraftChat(viaPendingCloudChatPaneandAcpChatPane) andSessionView's launch hold use it. - No loading line while a launch is in flight; the faded prompt carries that. Real resumes keep "Resuming the conversation…" after 1 s, and chats opened without a prompt keep "Starting the agent…".
Checked: in stages 3–5 the prompt is at (892, 140) with no notice; stage 6 (a real send) lands at the same spot. Stages 1–5 were staged with a temporary flag-guarded hook feeding the real layout/pane code (cloud workspaces can't be created from the sandbox; hook removed before commit). Not checked: a real cloud workspace end to end.
Part 4: workspace activity button shows when its panel is open
The top-bar activity button only took its active style for the popover form of the panel. When the panel was docked into the pane, the button looked idle even though clicking it hides the panel. The style now follows open (docked or popover), the same flag aria-expanded and the tooltip already use. Checked in the dev app with real clicks: open → active, hide → resting, show → active.
Part 5: Stop while a chat launch is starting
While the launch prompt was on its way (stages 3–5), the composer offered Send as if the chat were idle. It now shows Stop from the first frame, disabled with a "Starting the agent…" tooltip until there is a turn to stop; the session's normal Stop takes over once the turn runs. Typing still switches it to "Queue message".
Composer: newlaunchingprop puts it in streaming status without a stop handler. Set byDraftChat(launch prompt present) andSessionView(launch held, transcript empty).ComposerBody(chat-ui): Stop with noonStoprenders disabled, in place of a filled button whose click did nothing.
Checked in the dev app: stages 3–5 (staged) show Stop disabled with the tooltip, "Queue message" after typing, Stop again when cleared; an idle chat shows Send. A real send showed Stop enabled ~150 ms after sending, then Send once the turn ended. The tooltip reuses an existing message, so no catalog changes.
Part 6: "More actions" open state
Its data-[state=open]: style never applied: the Tooltip and DropdownMenu triggers merge onto one button and the tooltip's data-state ("closed", "delayed-open") overrides the menu's "open". It now keys on aria-expanded, which only the menu trigger sets, with the same fill as the activity button (bg-foreground/15). Checked with a real click: no fill closed, 15% fill open, cleared on Escape.
Possibly the same bug, not checked or changed here (files combining data-[state=open] with a Tooltip and a menu/popover trigger): AutomationRow, PortsDropdown, SkillPreviewDialog, FileRow (changes list), ContextWindowRing.
Part 7: no status line in the chat; errors in the host pill
Reopening a chat runs a liveness check and holds the chat until it passes: prompts queue above the composer and go out after, and a dead session gets the recovery screen. The chat no longer has a status line above the composer at all.
ConnectionNoticeis removed with all its uses: "Resuming the conversation…" (replaces the 1 suseShowAfterdelay from Part 2, also removed), "Starting the agent…" while a session boots or in the placeholder chat, and "Attaching to the running session…".- The one failure the old line covered, a check that keeps failing while the host socket is open, now shows in
HostConnectionPillas "Can't reach the host" with the pill's detail (existing messages for this device / other hosts / cloud), Host settings and Retry. Retry re-runs the failing checks.useChatSessionChecksFailing(hostUrl)reads them from the query cache (keyacp-chat-session, that host, mounted, statuserror); a closed socket is still the pill's existing path. - The liveness fixture asserts on a test marker and on
useChatSessionChecksFailinginstead of app copy. The catalogs drop "Resuming the conversation…" and "Attaching to the running session…"; "Starting the agent…" stays (Stop tooltip, Part 5).
Checked in the dev app (staged with a temporary hook, removed before commit): a starting chat showed "Attaching to the running session…" before and nothing after; a failing check showed "Can't reach the host" above the composer before and in the top pill after. Frames are on the page. Not checked: a real failing host, and the pill's Retry against a real failure.
Part 8: messages sent during a chat's session check stay visible
Right after a chat opens or you switch back to it, sends wait for its session check, which in cloud takes a few round trips (often 3–5 s). A message sent in that window left the composer for the queue strip attached to it, so it read as gone: not in the input, not in the conversation.
Measured locally with CDP network emulation (1 s added latency): in a settled chat prompt goes out at 269 ms and the message is solid at 849 ms; right after switching back, prompt only leaves at 2681 ms (liveness getSession 837→1979 ms, then the stream subscribe), solid at 2901 ms.
- Held prompts render at the bottom of the transcript as queued outbox rows (
heldOutboxEntries, launch prompt first until the transcript has it), faded like any undelivered message; when the hold ends they are sent from the same spot. - The placeholder chat shows its queued prompts the same way via
PendingPromptsView(wasLaunchPromptView). - The composer's queue strip is left for prompts queued behind a running turn.
heldPromptQueueis removed; its test moves toheldOutboxEntries. Composer'slaunchingis nowpromptPending: the disabled Stop shows for any prompt on its way.ChatPanememoizespendingChatPrompts, which built a new list on every render and now feeds transcript rows.
Checked in the dev app (hold staged with a temporary hook, removed before commit): while held the message sits in the conversation at y 892, faded; after release it stays at y 892 and turns solid ~300 ms later. Not changed: the round trips themselves. Keeping recent chat tabs mounted, or letting the outbox send before the stream reports open, would shorten the wait; both are left for a separate change.
Known issue, not from this PR: forcing a held launch prompt on a freshly opened chat with the staging hook hits "Maximum update depth exceeded" (a ref callback calling setState). It reproduces identically on main's code (2e42c29e), so it is either pre-existing or an artifact of the staging hook; not investigated further here.
Manual QA Checklist
Dev desktop app in the cloud workspace, driven over CDP. The real RunningTasksChip was mounted into the real row (temporary mount file, removed); the transcript text was filler, because the sandbox Claude agent is not signed in.
- 320 / 420 / 900px panes: one line; compact (
2 now,1 3m) below 28rem, full labels at 900; last line covered 0px; arrow over a chip 0px; a click on the arrow's lower half hits the arrow; a click in a gap reaches the transcript - A real click on the Subagents chip opens its list
- Notice placement: with the anchor above the notice, chips cover it by 0px (was 14px)
-
LiveChangesPillcompact form in the app: not checked (needs a live turn with changes); same markup asRunningTasksChip - Delayed resume notice end to end (switching between cloud chats): not checked; covered by the
useShowAfterunit test
Testing
bun test useShowAfter ComposerChips useChatSessionLiveness SessionView DraftChat PendingCloudChat UserMessageRow pendingCloudChat(79 pass);bun testin packages/chat-ui (84 pass)bunx tsc --noEmit -p apps/desktop: no errors in the changed filesbiome check: cleanbun run check:i18n: passes; drops the unused "Resuming the conversation…" and "Attaching to the running session…"
Known Limitations
- The 28rem cut-off suits three chips. With one or two, the compact form shows in panes where full labels would fit.
- The resume check still runs after every reconnect; only its notice is quieter. Keeping a recently used host socket open for a short time would avoid the recheck when switching back and forth, but that touches
packages/workspace-clientand keeps cloud boxes awake longer, so it is left out.
Summary by CodeRabbit
-
New Features
- Launch prompts now appear in the chat view while a session is starting, replacing the separate cloud-chat preview.
- Held-session notices appear after a brief delay.
- Activity controls remain visibly active while their panel is open, including when docked.
-
Improvements
- Change and task chips adapt their labels to available space, showing counts in narrower layouts.
- The scroll-to-bottom button sits higher in the transcript.
- The Stop control indicates when an agent is starting and remains disabled until stopping is available.