agent pane: rest the newest message 56px above the composer, with no shift on load
Leo (2026-11-01): "in the next build, will we have more space between composer and latest messages? i'd like it... tho maybe not as much as claude code / idk why theirs is so big"
And: "there's currently some oddities where the gap between recent text and composer is bigger than the desired gap (which is rn like barely anything) i hope we reconcile that in the future so there's no shift on load and it's just a load"
Design spec: leo-workspace design/chat-transcript.md (new section "Rest gap above the composer"; this replaces the grid's composer gap token in design/grid.md).
What changes
The space under the chat's newest row (a turn footer's action row, or the running turn's live line) up to the top of the floating bottom stack (the running tray, else the composer card) is now REST_GAP = 56 px. Before, it was 16 px, which was only the last row's own turn gap.
model.ts:REST_GAPandrestRoom(), which is the room added under the last row on top of the turn gap that row already keeps. Adata-rest-gapdocument attribute overrides the value, but only the gallery sets it.App.tsx: the transcript's room under the last row (dockRoom) is now the stack's overlap plusrestRoom(). The follow, held-room, send and lift paths use it. The edge masks, Jump to latest and scroll-into-view still use the stack height alone. The composer, the tray, the gaps between messages and the status rows are unchanged.- How 56 was picked: Codex leaves about 2.4 body lines under its action row and Claude Code about 4.3. Leo found Claude Code's gap too big. With our 22.75 px line, Codex works out to about 55 px and Claude Code to about 98 px.
Gallery
agent-pane.composer-rest-gap shows 40, 56 and 72 px at three widths, and each variant's play asserts its own gap. 56 ships. 40 is still tighter than Codex. 72 heads toward Claude Code's size, which Leo said was too much.
Geometry (Chromium and WebKit, same harness, webviews/test/agent-pane-composer-rest-gap.test.ts)
The gap is measured from the newest row's line (its box bottom less its 16 px turn gap) to the first drawn part of the bottom stack.
| state | before (base 86c7515e08 + test) | after (fa182687) |
|---|---|---|
| rest, chat loaded | 16 px | 56 px |
| while a reply streams | 16 px; on wrap frames the line went under the stack, down to -24 px | 56 px; never outside (0, 56] |
| tray shown (running turn with a subagent) | 16 px | 56 px |
| composer grown two lines | 15.5 px | 55.5 px |
| shift frames on load (gap differs from settled on any painted frame, first paint to settled) | 0 (plain chat), 0 (running chat with tray) | 0, 0 |
Before run (test red in all 6 cases): https://github.com/manaflow-ai/cmux/actions/runs/41290739350. After run (rest-gap 6/6 green): https://github.com/manaflow-ai/cmux/actions/runs/41290760074. The same after run also covers the transcript-spacing, stream-jitter, soft-edges, bottom-stack and queue-scroll-bottom suites. Its only failure is the reply only moves up as a tool call, the working line and the queue card come and go (chromium and webkit), and that same test fails on base too.
Notes
- Load shift: neither fixture reproduced Leo's load shift on base (0 shift frames). The new test still guards every painted frame from first paint until settled, so a regression would show up there. Follow-up filed to get a repro of the case he saw.
- Streaming: while a long line wraps, the existing follow glide eases the line up across a few frames, so on those frames the gap dips below 56. Because of that, the streaming check is "the line stays inside the gap and never goes under the stack" instead of an exact 56 on every frame. On base the line went under the stack on those frames. Follow-up filed to hold the gap exactly during the glide.
- Performance: no new observer, timer or animation loop.
restRoom()is read once per transcript mount (useStateinitializer). No per-token work was added:dockRoomis plain arithmetic on the existingdockSpacemeasurement. - Risk: the space at the bottom of every agent chat gets larger. That is undoable: one constant.
Agent session: agent session