fix(desktop): keep floating composer on-screen, scoped to the thread area#50977
Merged
Conversation
… off-screen The popped-out composer is position:fixed, but the chat content wrapper sets `contain: layout paint`, which makes it a containing block for — and clips — fixed descendants. Inline, the floating composer was positioned/clipped relative to the chat column (which shifts with the sidebars), not the viewport, so the viewport-based bounds clamp from #50466 couldn't keep it reachable: users still lost it off-screen. Portal it to <body> when popped out so fixed positioning and the clamp finally share the viewport as their reference. Docked stays inline (it's absolute within the chat column by design).
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
run_agent.py:2984: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
✅ Fixed issues (1):
| Rule | Count |
|---|---|
invalid-assignment |
1 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
Unchanged: 6018 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
Replaces the body-portal approach: render ChatBar as a sibling of the contain:[layout paint] chat wrapper (inside the same runtime boundary) rather than portaling the floating instance to <body>. The wrapper is a containing block for — and clips — position:fixed descendants, which is what stranded the popped-out composer off-screen. As a sibling it anchors to the outer relative container: docked stays absolute (identical placement), floating resolves against the viewport. Both states stay mounted, so dock<->float no longer remounts the editor (the portal toggle did).
…le window Now that the popped-out composer is fixed to the viewport, clamping against the window let it slide under a pinned sidebar. Confine it to the thread region (data-slot="composer-bounds") instead — its rect already excludes a pinned sidebar and the header — falling back to the full window before it's measured. This subsumes the old titlebar top-margin (the thread rect starts below the header).
Re-clamp once more on the next frame after pop-out so layout (sidebar widths, fonts) has settled, and treat a degenerate pre-layout bounds rect as "unknown" (fall back to the window) so we never clamp the box into a collapsed area. Net: anyone who loads in with a stranded position is pulled back on-screen and the fix is persisted, even if the first measure was premature.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second fast-follow to the composer pop-out (#49488, bounds #50466). Users on the latest build still reported losing the floating composer off-screen even after #50466 made the bounds clamp account for the box size.
Root cause
The popped-out composer is
position: fixed, but its ancestor — the chat content wrapper — setscontain: layout paint:contain: layout/paintmakes that element a containing block for fixed descendants andpaintclips them to its box. So the floating composer was positioned/clipped relative to the chat column, not the viewport. That column resizes/shifts with the sidebars, so the viewport-based clamp measured a different rectangle than the box lived in, and anything outside the column got clipped → invisible. Matches the field report + the earlier "sidebars hidden" symptom.Fix
ChatBaras a sibling of the contained wrapper, inside the sameChatRuntimeBoundary(pure context, no DOM). Its nearest positioned ancestor becomes the outerrelative isolatecontainer (no transform/contain/filter), so docked staysabsolute(identical placement) and floatingfixedresolves against the viewport. The composer stays mounted across dock⇄float — no remount, no portal. (VerifiedPaneMain+ the shell chain aboveChatViewuse no transform/contain/filter.)data-slot="composer-bounds"; the clamp confines the box to that rect (which already excludes a pinned sidebar and the header), falling back to the full window before it's measured. Threaded through every entry point: mount, resize, per-frame drag, peel-off, release-persist. This subsumes the old titlebar top-margin.Test plan
hermes.desktop.composerPopout.positionin localStorage, reload → snaps back on-screen and the value is rewritten.Related: #50466, #49903.