fix(agent): contain the floating assistant panel on open and resize (#1575)

A persisted float rect saved at the viewport edge, or on a larger
monitor, passes clampFloatRect (which only keeps 48px reachable so a
live drag may deliberately hang off an edge) and renders the panel as
a 48px sliver on every open. Add containFloatRect, which snaps the
whole window inside the viewport, and an AgentSheet effect that
validates the persisted rect on sheet mount and on viewport resize
and persists the corrected position. The rect is read through a ref
so drag commits do not re-trigger containment: parking the window
half off-screen still works within a session. The live drag path and
clampFloatRect are unchanged, and containFloatRect's output is always
a fixpoint of clampFloatRect, so the render clamp stays a no-op.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-13 15:14:34 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 78a581bca1
commit 7ccaab7a08
3 changed files with 101 additions and 0 deletions
+28
View File
@@ -25,6 +25,7 @@ import {
DOCK_WIDTH_MIN,
clampDockWidth,
clampFloatRect,
containFloatRect,
defaultFloatRect,
expandedDockWidth,
resizeFloatRect,
@@ -251,6 +252,33 @@ export default function AgentSheet({
)
: null
// Open/resize-time validation of the persisted float rect: a rect saved at
// the viewport edge (or on a larger monitor) passes clampFloatRect but
// renders as a 48px sliver, so snap it fully on screen and persist the
// corrected position. panelPrefs.float is read through a ref, NOT the deps:
// putting it in the deps would re-run this on every drag commit and snap
// the window back mid-session, killing the deliberate hang-off-the-edge
// allowance of the live drag path. The effect fires on sheet mount (every
// fresh open remounts, keyed by the provider) and on viewport resize only.
const floatPrefRef = useRef(panelPrefs.float)
useEffect(() => {
floatPrefRef.current = panelPrefs.float
}, [panelPrefs.float])
useEffect(() => {
if (!floating) return
const rect = floatPrefRef.current
if (!rect) return
const contained = containFloatRect(rect, viewport.w, viewport.h)
if (
contained.x !== rect.x ||
contained.y !== rect.y ||
contained.w !== rect.w ||
contained.h !== rect.h
) {
updatePanelPrefs({ float: contained })
}
}, [floating, viewport.w, viewport.h, updatePanelPrefs])
// Reserve page margin while docked, compact AND expanded: both reflow the
// page beside the panel instead of covering it (the original complaint).
// Floating and collapsed claim nothing; below md the margin variable is