fix(chat): restore the 40px end-follow threshold
The half-viewport band made return-to-end detection too broad. Restore the fixed 40px threshold without changing gesture handling or follow transitions. Testing: 14 focused scroll tests pass; UI type-check passes; UI lint has one unrelated existing warning; dead-code report inspected. Live chat behavior not validated.
This commit is contained in:
@@ -94,11 +94,11 @@ describe('resolveRealContentEndOffset', () => {
|
||||
});
|
||||
|
||||
describe('resolveTimelineIsAtEnd', () => {
|
||||
test('counts half a viewport from the full content length as the end', () => {
|
||||
test('uses a 40px band regardless of viewport height', () => {
|
||||
expect(resolveTimelineIsAtEnd({ contentLength: 2000, scroll: 1400, scrollLength: 600 })).toBe(true);
|
||||
expect(resolveTimelineIsAtEnd({ contentLength: 2000, scroll: 1100, scrollLength: 600 })).toBe(true);
|
||||
expect(resolveTimelineIsAtEnd({ contentLength: 2000, scroll: 1099, scrollLength: 600 })).toBe(false);
|
||||
// Tiny viewports keep a 40px floor.
|
||||
expect(resolveTimelineIsAtEnd({ contentLength: 2000, scroll: 1360, scrollLength: 600 })).toBe(true);
|
||||
expect(resolveTimelineIsAtEnd({ contentLength: 2000, scroll: 1359, scrollLength: 600 })).toBe(false);
|
||||
expect(resolveTimelineIsAtEnd({ contentLength: 2000, scroll: 1100, scrollLength: 600 })).toBe(false);
|
||||
expect(resolveTimelineIsAtEnd({ contentLength: 2000, scroll: 1900, scrollLength: 60 })).toBe(true);
|
||||
expect(resolveTimelineIsAtEnd({ contentLength: 2000, scroll: 1899, scrollLength: 60 })).toBe(false);
|
||||
});
|
||||
|
||||
@@ -61,17 +61,8 @@ export const resolveRealContentEndOffset = ({
|
||||
return Math.max(0, lastBottom + Math.max(0, footerSize) - visibleLength);
|
||||
};
|
||||
|
||||
// "At the end" for follow purposes is half a viewport. Leaving the end is
|
||||
// only ever decided by a real gesture, so this band never yanks a reader who
|
||||
// is still on the end; what it decides is how close to the live edge a reader
|
||||
// who scrolled away must come back before follow re-arms and the pill hides.
|
||||
// Half a screen reads as "I am back at the bottom" without having to land on
|
||||
// the last pixel, and stray row growth or late measurements cannot push a
|
||||
// pinned reader out of it. Distance is measured against the full content
|
||||
// length.
|
||||
const FOLLOW_REARM_MIN_THRESHOLD_PX = 40;
|
||||
export const resolveFollowRearmThresholdPx = (scrollLength: number): number =>
|
||||
Math.max(FOLLOW_REARM_MIN_THRESHOLD_PX, scrollLength / 2);
|
||||
// Keep return-to-end detection in a tight band, rather than half a viewport.
|
||||
export const TIMELINE_FOLLOW_REARM_THRESHOLD_PX = 40;
|
||||
|
||||
export const resolveTimelineIsAtEnd = (
|
||||
state: {
|
||||
@@ -90,7 +81,7 @@ export const resolveTimelineIsAtEnd = (
|
||||
&& typeof scrollLength === 'number'
|
||||
&& Number.isFinite(contentLength)
|
||||
) {
|
||||
return contentLength - (scroll + scrollLength) <= resolveFollowRearmThresholdPx(scrollLength);
|
||||
return contentLength - (scroll + scrollLength) <= TIMELINE_FOLLOW_REARM_THRESHOLD_PX;
|
||||
}
|
||||
return state.isNearEnd ?? state.isAtEnd;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
getRowBottom,
|
||||
resolveRealContentEndOffset,
|
||||
resolveTimelineIsAtEnd,
|
||||
resolveFollowRearmThresholdPx,
|
||||
TIMELINE_FOLLOW_REARM_THRESHOLD_PX,
|
||||
type TimelineListMeasurementState,
|
||||
type TimelineScrollMode,
|
||||
} from '@/components/chat/lib/scroll/timelineScrollAnchoring';
|
||||
@@ -496,7 +496,7 @@ export const useChatTimelineScroll = ({
|
||||
const lastBottom = lastIndex >= 0 ? getRowBottom(state, lastIndex) : null;
|
||||
if (lastBottom !== null) {
|
||||
const visibleBottom = state.scroll + state.scrollLength - composerOverlayHeightRef.current;
|
||||
if (lastBottom - visibleBottom > resolveFollowRearmThresholdPx(state.scrollLength)) {
|
||||
if (lastBottom - visibleBottom > TIMELINE_FOLLOW_REARM_THRESHOLD_PX) {
|
||||
isAtEndRef.current = false;
|
||||
setIsPinned(false);
|
||||
scheduleShowScrollButton();
|
||||
|
||||
Reference in New Issue
Block a user