fix(web): stop a tall terminal drawer pushing the composer out of the chat area - #5207
fix(web): stop a tall terminal drawer pushing the composer out of the chat area#5207carlosricojr wants to merge 3 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Approved 154ad23 This PR is a well-tested UI layout bug fix that prevents the terminal drawer from pushing the composer out of view. Changes consist of pure calculation functions with comprehensive unit tests (103+ lines), CSS layout adjustments, and measurement logic - all self-contained with no runtime behavior changes beyond the specific fix. You can customize Macroscope's approvability policy. Learn more. |
0929a90 to
3a8c2e0
Compare
… chat area The composer is an overlay, and the chat area it overlays could shrink to nothing (`min-h-0`) while the terminal drawer held an explicit height of up to 75% of the window. Once a tall drawer left the chat area shorter than the composer, nothing clipped the overlay — it spilled out of the area instead. Docked, it spilled upward over the thread header and off the top of the viewport. On a new thread it is worse: the hero composer is centred in the chat area, and a centred flex item that outgrows its container overflows *both* ends, so it covered the header and ran back down across the top of the terminal, with the headline pushed off-screen above. Its glass surface let the header and the terminal read through it either way. The chat area now reserves the height the composer actually needs, so flex layout shrinks the drawer rather than the composer's room. Docked, that is the overlay's own height. In the hero state the overlay spans the whole area, so the reserve is the composer stack, plus twice any banner — centring mirrors a banner's space below the composer, and banners carry actions. The hero headline is deliberately not reserved. It is decorative, and paying for it costs the drawer 138px it should keep; it hides instead once the area cannot hold it, and comes back when the drawer shrinks. Hidden means `visibility: hidden`, since the decision reads its measured height and unmounting it would oscillate. The drawer now keeps 644 of its 675px cap at a 900px window instead of 506, and is untouched above ~1024px. The drawer became shrinkable to allow the reserve (its wrapper is `display: contents` so the drawer itself is the flex child that yields) and keeps a floor at MIN_DRAWER_HEIGHT so the resize handle stays reachable. Drags now start from the height layout actually granted, so the divider keeps tracking the pointer while the drawer is being squeezed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3a8c2e0 to
e4d5290
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e4d5290. Configure here.
…ed drags Two follow-ups from review of the reserve. The measuring effect only re-ran when the overlay node or the hero state changed, but the overlay node is reused across threads. Switching to a thread with a taller composer left the previous thread's reserve in place until the resize observer caught up a frame later, which is long enough for the composer to spill again with the drawer open. The active thread key now re-measures it in the same layout pass. A drag while the reserve squeezes the drawer anchored on the rendered height and then persisted the result, so nudging the divider up by 10px rewrote a stored 675px down to 454px — the user asked for more room and silently lost their terminal size, with no visible feedback because the squeeze caps what renders either way. A drag that ends above where it started now keeps the larger stored height; only a drag that ends below it is treated as a request to shrink. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
resolveDrawerResizeStoredHeight took the shrink branch on equality. Because the drag re-anchors on the squeezed rendered height, the first pointer move sets that height even with no net movement, so pressing the divider and returning to the starting point persisted the squeezed height over a taller stored one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

The composer is an overlay, and the chat area it overlays could shrink to nothing (
min-h-0) while the terminal drawer held an explicit height of up to 75% of the window. Once a tall drawer left the chat area shorter than the composer, nothing clipped the overlay — it spilled out of the area instead.Docked, it spilled upward over the thread header and off the top of the viewport. On a new thread it is worse: the hero composer is centred in the chat area, and a centred flex item that outgrows its container overflows both ends — so it covered the header and ran back down across the top of the terminal, with the headline pushed off-screen above. Its glass surface let the header and the terminal read through it either way.
The fix
The chat area reserves the height the composer actually needs, so flex layout shrinks the drawer rather than the composer's room. Docked that is the overlay's own height; in the hero state the overlay spans the whole area, so the reserve is the composer stack plus twice any banner (centring mirrors a banner's space below the composer, and banners carry actions).
The hero headline is deliberately not reserved. It is decorative, and reserving it costs the drawer 138px it should keep — it hides once the area cannot hold it and returns when the drawer shrinks. Hidden means
visibility: hidden, because the decision reads its measured height and unmounting it would oscillate.The drawer became shrinkable to allow the reserve — its wrapper is
display: contentsso the drawer itself is the flex child that yields — and it keeps a floor atMIN_DRAWER_HEIGHTso the resize handle stays reachable. Drags now start from the height layout actually granted, so the divider keeps tracking the pointer while the drawer is being squeezed.What it costs the terminal
A visible composer and an uncapped drawer are mutually exclusive — the bug exists exactly when they don't both fit — so the goal was to make the drawer give up as little as possible:
Measured live, not derived. At 900px with an ordinary draft the drawer holds its full cap with 6px to spare.
Before / after
New thread, 1280×700 (the tightest case above), terminal dragged to its cap, long draft in the composer.
Same run at 1280×900, where the drawer barely moves (675 → 644): before · after.
Geometry at 1280×700, same 204px composer:
Reverse state checked: dragging the divider back down brings the headline straight back (drawer 280 → headline shown, 675 → hidden, 203 → shown). The docked case was verified separately — composer top at 52px, bottom exactly on the drawer edge, 0px overlap either side.
Notes
mode="panel") are unchanged — they size withh-full flex-1and never carried the pixel height.GhosttyTerminalSurfacealready observes its own container and refits synchronously, so a drawer shrunk by layout re-fits itself with no extra plumbing.Verified with
tsgo --noEmit,vp lint, andvp test runover the touched files (57 tests), plus the live passes above.Review round: Bugbot raised three findings. Two were real and are fixed in
466ce94— the reserve went stale for a frame when switching threads (the overlay node is reused, so the measuring effect never re-ran), and a drag taken while the reserve squeezed the drawer could persist a smaller height than the user had stored while they were dragging to make it bigger. The third was already resolved on the current head; replies are inline.Claude Opus 5 via Claude Code, driven from T3 Code.
Note
Medium Risk
Touches core thread layout flex behavior and persisted terminal drawer height during squeeze; logic is covered by unit tests but regressions could affect composer/terminal coexistence on small viewports.
Overview
Fixes the composer overlay spilling over the thread header and terminal when a tall drawer left the chat area shorter than the composer.
The chat row now applies
minHeightfromchatAreaReservedComposerHeight(docked overlay height vs draft-hero stack plus twice banner height).ChatViewmeasures stack, headline, and banners viaResizeObserver, tracks chat area height, and hides the draft hero headline withvisibility: hiddenwhenshouldShowDraftHeroHeadlinesays there isn’t room—without unmounting, so measurements stay stable.The terminal drawer wrapper uses
display: contentswhen visible so the drawer is the flex child that shrinks; the drawer dropsshrink-0, keeps aminHeightfloor, and resize drags anchor on rendered height (resolveDrawerResizeStartHeight) and persist height without clobbering a taller stored preference when squeezed (resolveDrawerResizeStoredHeight).Reviewed by Cursor Bugbot for commit 154ad23. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix tall terminal drawer pushing the composer out of the chat area
chatAreaReservedComposerHeightcalculation, causing the terminal drawer to shrink under flex layout instead of overlapping the composer.shrink-0and gains aminHeightstyle so it can yield space while remaining resizable; its wrapper div usescontentswhen hidden to keep the drawer as the direct flex child.resolveDrawerResizeStartHeight), and height persistence viaresolveDrawerResizeStoredHeightavoids overwriting a taller stored preference when the drawer is squeezed.shouldShowDraftHeroHeadlinewhen the chat area lacks room to display it above and below the composer.Macroscope summarized 154ad23.