Fix bar-click flicker: suppress collapse while the reveal scroll is in flight

Clicking the collapsed bar set expanded=true and started a smooth
scrollToBottom — but the scroll position was still legitimately inside
the collapse zone (that's why the bar was showing), so the hysteresis
listener re-collapsed the dock on the very next scroll event, then
re-opened it when the scroll arrived: open → snap shut → reopen.

The machine had no notion of intent. Now the bar click sets an
expandIntent flag that suppresses the collapse branch; it clears when
the scrollToBottom promise settles (completion or interruption) or after
a 2s safety net, so a user yanking the scroll away mid-reveal collapses
the dock again instead of wedging it open.

Verified live: click = exactly one transition (bar→open at ~26ms, stays
open, lands at d=1); collapse still healthy afterward; mid-reveal
interrupt re-collapses within ~300ms.
This commit is contained in:
Amos
2026-08-22 06:56:39 +03:00
parent d1d28d6bb4
commit 3c4eac8061
+22 -5
View File
@@ -235,6 +235,26 @@ function DockOverlay({
const expandedHRef = useRef(0);
const collapsedHRef = useRef(42);
// Explicit expand (bar click): the smooth scroll to the bottom takes
// hundreds of ms, during which the position is still legitimately inside
// the collapse zone — the machine would instantly re-collapse the dock
// (open → snap shut → reopen at the bottom, the flicker). Suppress the
// collapse branch until the reveal scroll settles or is interrupted.
const expandIntentRef = useRef(false);
const intentTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
const expandAndReveal = () => {
expandIntentRef.current = true;
setExpanded(true);
clearTimeout(intentTimerRef.current);
intentTimerRef.current = setTimeout(() => {
expandIntentRef.current = false;
}, 2000);
void Promise.resolve(scrollToBottom()).finally(() => {
clearTimeout(intentTimerRef.current);
expandIntentRef.current = false;
});
};
useEffect(() => {
const el = scrollRef.current;
if (!el) return;
@@ -243,7 +263,7 @@ function DockOverlay({
const d = el.scrollHeight - el.clientHeight - el.scrollTop;
const delta = Math.max(expandedHRef.current - collapsedHRef.current, 240);
if (expandedRef.current) {
if (d > delta + 64) setExpanded(false);
if (d > delta + 64 && !expandIntentRef.current) setExpanded(false);
} else if (d < 16) {
setExpanded(true);
void scrollToBottom();
@@ -310,10 +330,7 @@ function DockOverlay({
toolCallId={pending.toolCallId}
input={pending.input as never}
expanded={expanded}
onExpand={() => {
setExpanded(true);
void scrollToBottom();
}}
onExpand={expandAndReveal}
onSubmit={onSubmitAnswer}
/>
) : (