fix(web): collapse the resting composer without replacing it - #7855
fix(web): collapse the resting composer without replacing it#7855chrisdeeming wants to merge 6 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 |
There was a problem hiding this comment.
Reviewed the resting-composer layout against the shared composer/editor contracts. Two concrete regressions in the changed behavior are noted inline: the resting footer overlay can overlap the prompt line, and the new desktop pointerdown reconciliation can collapse the composer while the caret is still inside it.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One finding on the resting layout: the reserved right gutter is padding, not a clip, so a long resting line can still paint under the absolutely positioned footer. The earlier inline-badge and pointerdown concerns look addressed (hasInlineAccessories, and pointerdown now defers to scheduleComposerCollapseCheck).
Posted via Macroscope — UI Consistency
ApprovabilityVerdict: Skipped Macroscope did not run approvability analysis for this PR. Macroscope could not determine whether this PR modifies its approvability configuration, so the PR was not approved automatically. A PR that may change the rules that govern approval is never approved automatically. Not approved because:
|
There was a problem hiding this comment.
Reviewed the resting/scroll-collapse composer changes for UI consistency and interaction preservation. Two concrete issues in apps/web/src/components/chat/ChatComposer.tsx; the earlier clipping concern (prompt text painting under the resting send button) looks resolved now that pr-12 sits on the positioned wrapper rather than the editor, and hasInlineAccessories keeps the inline tasks/stash badges out of the resting overlay.
Posted via Macroscope — UI Consistency
| environmentUnavailable !== null || | ||
| composerSubmissionError !== null || | ||
| providerInputSubmissionError !== null; | ||
| const isComposerResting = shouldUseRestingComposerLayout({ |
There was a problem hiding this comment.
🟡 Medium chat/ChatComposer.tsx:2602
In an idle desktop thread without the branch context strip, restingControlsHost is null while isComposerResting still becomes true, so the footer hides composerControls and the model, runtime-mode, and traits controls disappear. Gate the resting state on restingControlsHost (or mount the host independently) so controls remain rendered until they have a portal destination.
Also found in 1 other location(s)
apps/web/src/components/ChatView.tsx:6676
restingControlsHostis always passed toChatComposer, but its host is only mounted insideBranchToolbarwhenshowComposerContextStripis true. For an existing idle desktop thread in a non-Git project with no environment indicator,showComposerContextStripis false whileshouldUseRestingComposerLayoutcan still return true. In that stateChatComposerhidescomposerControlsfrom its footer, but cannot portal them becauserestingControlsHostisnull, so the provider/model, runtime-mode, and traits controls disappear. Either prevent resting without a host or mount the host independently of the context strip.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/chat/ChatComposer.tsx around line 2602:
In an idle desktop thread without the branch context strip, `restingControlsHost` is `null` while `isComposerResting` still becomes `true`, so the footer hides `composerControls` and the model, runtime-mode, and traits controls disappear. Gate the resting state on `restingControlsHost` (or mount the host independently) so controls remain rendered until they have a portal destination.
Also found in 1 other location(s):
- apps/web/src/components/ChatView.tsx:6676 -- `restingControlsHost` is always passed to `ChatComposer`, but its host is only mounted inside `BranchToolbar` when `showComposerContextStrip` is true. For an existing idle desktop thread in a non-Git project with no environment indicator, `showComposerContextStrip` is false while `shouldUseRestingComposerLayout` can still return true. In that state `ChatComposer` hides `composerControls` from its footer, but cannot portal them because `restingControlsHost` is `null`, so the provider/model, runtime-mode, and traits controls disappear. Either prevent resting without a host or mount the host independently of the context strip.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c806486. Configure here.
| </div>, | ||
| restingControlsHost, | ||
| ) | ||
| : null} |
There was a problem hiding this comment.
Resting controls vanish without host
High Severity
When the composer is resting, footer model/traits/mode controls are hidden and only reappear via a portal into restingControlsHost. That host only exists when BranchToolbar mounts under showComposerContextStrip. If the strip is absent (non-git primary-only projects, or toolbar returns null), resting hides those controls with nowhere to render them.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit c806486. Configure here.
| resetAccumulatedScroll(); | ||
| }; | ||
| }, [canScrollCollapseComposer]); | ||
|
|
There was a problem hiding this comment.
Picker remounts on resting open
Medium Severity
Opening the model picker from the resting strip sets isComposerModelPickerOpen, which clears resting and moves composerControls from the toolbar portal back into the footer. That fully remounts ProviderModelPicker under a new trigger while open is already true, so the popover jumps from the strip to the expanding footer (and can flicker or briefly lose its anchor).
Additional Locations (2)
Reviewed by Cursor Bugbot for commit c806486. Configure here.
There was a problem hiding this comment.
UI consistency review of the latest commit (c806486, "keep resting composer controls inline"). Two issues where the resting layout can leave the composer with no visible provider/model, plan-build, or runtime controls, plus one shared-primitive default change that also affects the non-resting composer. Earlier findings on the scroll-collapse focus loss and the stale transition height look addressed.
Posted via Macroscope — UI Consistency
| ref={composerControlsHostRef} | ||
| data-composer-context-control | ||
| data-chat-resting-composer-controls-host="true" | ||
| className="hidden min-w-0 flex-1 items-center justify-start overflow-hidden empty:hidden md:flex" |
There was a problem hiding this comment.
The host is visible from md up, but the composer's resting layout starts at sm: BranchToolbar uses useIsMobile() (max-md) for its mobile row, while ChatComposer derives isMobileViewport from useMediaQuery("max-sm").
Between 640px and 767px the composer therefore rests, the inline footer controls get hidden, and this portal target is display: none — the model picker, plan/build toggle and runtime select are neither visible nor reachable (the measurement hook also bails out on clientWidth === 0). That window is easy to hit in a narrow Electron window or split view.
Consider making the two breakpoints agree: gate the resting layout on max-md as well, or expose this host (and a strip layout that fits it) from sm up.
Posted via Macroscope — UI Consistency
| <div | ||
| className={cn( | ||
| "-m-1 -ms-3.5 flex min-w-0 flex-1 items-center gap-1 overflow-x-auto p-1 ps-3.5 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", | ||
| isComposerResting && "hidden", |
There was a problem hiding this comment.
When restingControlsHost is null the resting layout still hides the inline footer controls here (and renders null at line 3853) while the portal at line 3367 renders nothing — so the model picker, plan/build toggle and runtime-mode select disappear from the UI entirely until the user clicks into the composer.
This is reachable: BranchToolbar is the only host and it is gated by showComposerContextStrip, i.e. hasActiveProject && (isGitRepo || showEnvironmentIndicator), which is false for a non-git project folder with a single primary environment.
Consider feeding host availability into the resting decision (e.g. an extra hasControlsHost: restingControlsHost !== null input to shouldUseRestingComposerLayout) so the composer only rests when it has somewhere to put its controls.
Posted via Macroscope — UI Consistency
|
|
||
| const composerControlClassName = | ||
| "h-7 min-h-7 gap-1.5 rounded-[var(--control-radius)] px-2.5 text-secondary-label transition-none hover:text-foreground [&_svg[data-composer-control-icon]]:mx-0 [&_svg[data-composer-control-chevron]]:-mx-0.5"; | ||
| "rounded-[var(--control-radius)] text-secondary-label transition-none hover:text-foreground [&_svg[data-composer-control-icon]]:mx-0 [&_svg[data-composer-control-chevron]]:-mx-0.5"; |
There was a problem hiding this comment.
Dropping h-7 min-h-7 px-2.5 hands geometry back to buttonVariants, but the default here is size="sm" = h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:h-7. Below sm every existing composer control (model picker, plan/build toggle, runtime select) now renders 32px tall instead of 28px, so the mobile footer this PR otherwise preserves changes height.
Same pattern for ComposerControlChevron (lines 45-51): the new size-3 + opacity-50 default also applies to the expanded composer, replacing the semantic text-icon-muted token with an opacity hack, while the newly added className param is never passed by any caller.
Consider keeping the previous defaults and opting into the smaller/muted resting treatment per size — e.g. size === "xs" ? undefined : "h-7 min-h-7" inside ComposerControl/ComposerSelectControl, and passing the resting chevron tone through the new className prop.
Posted via Macroscope — UI Consistency


What Changed
Why
The resting composer occupies more vertical space than it needs. Compacting the existing controls reclaims that space without introducing duplicate editor state or focus handoffs, while scroll collapse creates room when reading longer threads.
UI Changes
Before
Empty composer
Parked draft
After
Empty composer
Parked draft
Video
browser-recording-mt4ye3nq.mp4
Checklist
Built with GPT-5.6-Sol via Codex in T3 Code.
Note
Medium Risk
Complex focus, scroll-collapse, portal, and height-animation logic in the chat composer can cause layout or focus regressions, but it is UI-only with no auth or data-path changes.
Overview
Idle desktop composers now compact to a single-line layout instead of keeping the full editor chrome. The same prompt editor and send actions stay mounted; only layout and control placement change.
On existing threads, the composer collapses on blur or a meaningful timeline wheel while the prompt is focused, and expands again on typing, pointer interaction, or focus. Height changes animate (~180ms) and honor reduced motion.
Model/mode/traits controls move into the branch context strip when resting (with overflow into the compact menu). Full height is kept for mobile, attachments, drawers/menus, inline badges, errors, and blocking env states. Toolbar controls are restyled to match the quieter strip.
Reviewed by Cursor Bugbot for commit c806486. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add desktop resting composer layout that collapses without replacing the composer
BranchToolbarand height animates via the Web Animations APIshouldUseRestingComposerLayoutin composerFooterLayout.ts gates the layout based on focus, attachments, expanded chrome, and inline accessoriesuseRestingComposerControlsLayoutmeasures available host width and hides trailing control blocks behind an overflow menu when space is constraineddata-chat-messages-timeline) auto-collapse the composer after a small threshold; key/pointer/focus interactions expand it backProviderModelPicker,TraitsPicker,BranchToolbarBranchSelector,BranchToolbarEnvModeSelector,BranchToolbarEnvironmentSelector,MobileRunContextSelector) switch fromfont-mediumtofont-normaland support a compactxssizeChatComposerportals resting controls intoprops.restingControlsHost; if the host ref is not provided byChatView, resting controls will not render.useLabelsOverflowin BranchToolbar.tsx excludes the host from width calculations — any new toolbar children must avoid matchingdata-chat-resting-composer-controls-hostMacroscope summarized c806486.