Skip to content

Commit 4757422

Browse files
fix(mothership): single drain cadence and a settle-window gesture kill switch
1 parent ecacda7 commit 4757422

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,29 +340,37 @@ export function MothershipChat({
340340
if (!sizer || !el) return
341341
if (!floorActive) {
342342
if (sizerFloorAppliedRef.current === 0) return
343+
// A drain already in flight keeps its own rAF cadence — settle-burst
344+
// commits re-enter this branch and must not add extra steps in layout,
345+
// which would accelerate the release past the eased rate.
346+
if (floorDrainRafRef.current !== 0) return
343347
scrollerPaddingRef.current = null
344-
cancelAnimationFrame(floorDrainRafRef.current)
345348
const drain = () => {
346349
const target = virtualizer.getTotalSize()
347350
const current = sizerFloorAppliedRef.current
348-
if (current === 0) return
351+
if (current === 0) {
352+
floorDrainRafRef.current = 0
353+
return
354+
}
349355
// Re-checked per frame: a user scrolling away mid-drain makes the
350356
// remaining shrink invisible, so finish instantly instead of clamping.
351357
const pinned = el.scrollHeight - el.scrollTop - el.clientHeight <= 2
352358
const next = Math.floor(current - Math.max(1, (current - target) * SMOOTH_CHASE_RATE))
353359
if (!pinned || next - target <= 1) {
354360
sizerFloorAppliedRef.current = 0
361+
floorDrainRafRef.current = 0
355362
sizer.style.minHeight = ''
356363
return
357364
}
358365
sizerFloorAppliedRef.current = next
359366
sizer.style.minHeight = `${next}px`
360367
floorDrainRafRef.current = requestAnimationFrame(drain)
361368
}
362-
drain()
369+
floorDrainRafRef.current = requestAnimationFrame(drain)
363370
return
364371
}
365372
cancelAnimationFrame(floorDrainRafRef.current)
373+
floorDrainRafRef.current = 0
366374
if (!scrollerPaddingRef.current) {
367375
const style = getComputedStyle(el)
368376
scrollerPaddingRef.current = {

apps/sim/hooks/use-auto-scroll.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,20 @@ export function useAutoScroll(isStreaming: boolean) {
214214
// behind the input; a user who scrolled away stays put via the sticky
215215
// check.
216216
chase.kickUntil(POST_STOP_SETTLE_WINDOW)
217+
// The main gesture listeners are gone, so give the settle window its own
218+
// kill switch: the chase's clamp-aware interrupt catches wheel-scale
219+
// moves, but a slow trackpad glide during a concurrent shrink could slip
220+
// under it for up to the window's duration.
221+
const cancelOnGesture = (event: Event) => {
222+
if (event.type === 'wheel' && (event as WheelEvent).deltaY >= 0) return
223+
chase.cancel()
224+
}
225+
el.addEventListener('wheel', cancelOnGesture, { passive: true })
226+
el.addEventListener('touchmove', cancelOnGesture, { passive: true })
227+
setTimeout(() => {
228+
el.removeEventListener('wheel', cancelOnGesture)
229+
el.removeEventListener('touchmove', cancelOnGesture)
230+
}, POST_STOP_SETTLE_WINDOW + 100)
217231
}
218232
}, [isStreaming])
219233

0 commit comments

Comments
 (0)