Cloud chat: a pause or resume stays invisible for 1.5 s and moves nothing
Leo, 2026-11-03: "The model sometimes sees a delay and you sometimes see it after sending but it evens out to not much at all just a tiny bit of extra time that you mostly perceive as "thinking" / But it's all still responsive on the ui side". Also: "the user acquisition has to come, and it has to come FAST aka we need it to work well feel good".
Cloud machines are about to pause on their own, first when idle and then between tool calls. A Cloud chat on a paused or resuming machine should feel exactly like one on a running machine.
What changes
One threshold, RESUME_NOTE_DELAY_MS = 1.5 s, gates every resume note in the agent pane. It lives in afterDelay.ts as useAfterDelay(active, ms). The hook runs one timer per stretch of active and clears it when the wait ends or the component unmounts, so nothing runs at idle.
- Paused chat with nothing to draw (
ChatUnavailableState):- For the first 1.5 s it shows the empty loading layout, so a quick wake never flashes a card.
- After that it shows a calm card that fades in. The card has
role=status, neveralert. - The resume spinner's slot exists from the first frame. The spinner fades into it only once the resume passes 1.5 s, so nothing moves.
- A chat with rows was already never replaced (
cloudChatRecoveryActive). That is unchanged.
- Send to a paused machine (
ComposerQueue):- The held prompt leaves the composer at once, as before (#33281).
- Its "Waking the machine…" line is drawn from the same frame, empty. The text fades in only past 1.5 s.
- Peer copy banner (
PeerCopyBanner):- Its text shows only past 1.5 s, and an ssh peer's shows at once. A paused machine therefore no longer reads "offline" before the account's machine list answers.
- It takes its place above the transcript from the first frame, as on base: empty and out of the accessibility tree until the text fades in. Nothing shifts beyond what base already does, and it never covers a row.
Where the slow-resume note goes (gallery: agent-pane.cloud-resume-designs)
I built three variants. queue ships: the line sits over the held prompt in the composer queue.
- queue: sits on the thing that is waiting, its line already exists, and it adds no new surface.
- footer: a line under the composer input. It reads as a composer state, not as the state of the sent prompt.
- transcript: a line under the last reply. It is closest to "thinking", but the prompt it waits on isn't in the transcript yet, so the line points at nothing.
New states in agent-pane.cloud-chat-recovery: paused-quick (invisible), slow-resume, resume-failed. The existing banner variants now pass noteDelayMs: 0, or wait for the banner, so their captures stay settled.
No design reference matched this state in cmux-design-references. The nearest, t3-code/live/approval-resume-motion, shows a gate that gives way to the live composer without a blocking card.
Budgets
- Instant: sends, the composer, opening the chat and history all render from local state on the first frame, unchanged.
- No layout shift: every note fills a slot that existed before it. The banner's place is taken from the first frame, so its text fading in moves nothing.
- Low resources: one
setTimeoutper wait, cleared on end or unmount. There is no interval and no idle work.
Out of scope (other PRs)
- Native: an ended link reads as a calm wait until the machine list answers (#34284).
- Composer chip, picker, host line and New Tab wording (the wording helper's PR).
- Terminals and preview URLs (#25668).
- Follow-up #34815: the banner's drop-in entrance plays while it is still hidden.
Changelog
Changed: a Cloud chat whose machine is paused or waking shows nothing for the first 1.5 s, then one quiet note, and nothing on screen shifts.
Observed / Not run
- Ran on the fleet (
cmux-ci test --setup webviews) at an earlier head:bun run check(its remaining reds,transcript-spacing-kinds.test.tsafterandupdateSurfaces.css, are also red on base), andbun testforafterDelay.test.tsx,ChatUnavailableState.test.tsx,ComposerQueue.test.tsxandstopHeldMessages.test.tsx. - Tests use fake timers (
check:fixed-waitsis clean). The settled-state fixtures incloud-chat-recovery,cloud-chat-resumeandagent-pane-peer-copy-pausedpassnoteDelayMs0. - Not run: gallery captures (advisory) and a DEV build on a paused machine.
Risk: normal. Web-only UI timing; no data, protocol or native change.
🤖 Generated with Claude Code
Agent session: agent session