Skip to content

Commit 437370c

Browse files
fix: improve pull-to-refresh behavior and fix code review issues
- Add error handling to prevent UI from getting stuck on refresh failure - Fix dependency array issue by using ref to avoid unnecessary re-renders - Implement platform-specific refresh behavior: - Desktop/Web: scroll up when at top to refresh - Mobile: touch-based pull-to-refresh (unchanged) - Uses getPlatformInfo() to detect platform for appropriate behavior Co-authored-by: Anthony <AnthonyRonning@users.noreply.github.com>
1 parent ec7326f commit 437370c

1 file changed

Lines changed: 51 additions & 15 deletions

File tree

frontend/src/components/ChatHistoryList.tsx

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
CheckSquare,
1010
RefreshCw
1111
} from "lucide-react";
12+
import { getPlatformInfo } from "@/utils/platform";
1213
import {
1314
DropdownMenu,
1415
DropdownMenuContent,
@@ -87,6 +88,7 @@ export function ChatHistoryList({
8788
const [pullDistance, setPullDistance] = useState(0);
8889
const pullStartY = useRef(0);
8990
const isPulling = useRef(false);
91+
const pullDistanceRef = useRef(0);
9092

9193
// Fetch initial conversations from API using the OpenSecret SDK
9294
const { isPending, error } = useQuery({
@@ -182,19 +184,27 @@ export function ChatHistoryList({
182184
// Pull-to-refresh handler
183185
const handleRefresh = useCallback(async () => {
184186
setIsPullRefreshing(true);
185-
await pollForUpdates();
186-
// Add a small delay to show the refresh indicator
187-
setTimeout(() => {
188-
setIsPullRefreshing(false);
189-
setPullDistance(0);
190-
}, 300);
187+
try {
188+
await pollForUpdates();
189+
} catch (error) {
190+
console.error("Refresh failed:", error);
191+
} finally {
192+
// Add a small delay to show the refresh indicator
193+
setTimeout(() => {
194+
setIsPullRefreshing(false);
195+
setPullDistance(0);
196+
}, 300);
197+
}
191198
}, [pollForUpdates]);
192199

193200
// Pull-to-refresh event handlers
194201
useEffect(() => {
195202
const container = containerRef?.current;
196203
if (!container) return;
197204

205+
const platformInfo = getPlatformInfo();
206+
const isDesktopPlatform = platformInfo.isDesktop || platformInfo.isWeb;
207+
198208
const handleTouchStart = (e: TouchEvent) => {
199209
// Only start pull if we're at the top of the scroll
200210
if (container.scrollTop === 0 && !isPullRefreshing) {
@@ -216,6 +226,7 @@ export function ChatHistoryList({
216226
// Apply resistance: diminishing returns as you pull further
217227
const resistanceFactor = 0.4;
218228
const adjustedDistance = Math.min(distance * resistanceFactor, 80);
229+
pullDistanceRef.current = adjustedDistance;
219230
setPullDistance(adjustedDistance);
220231
}
221232
};
@@ -226,7 +237,7 @@ export function ChatHistoryList({
226237
isPulling.current = false;
227238

228239
// Trigger refresh if pulled far enough (threshold: 60px)
229-
if (pullDistance > 60) {
240+
if (pullDistanceRef.current > 60) {
230241
handleRefresh();
231242
} else {
232243
// Reset if not pulled far enough
@@ -235,6 +246,9 @@ export function ChatHistoryList({
235246
};
236247

237248
const handleMouseDown = (e: MouseEvent) => {
249+
// Only for mobile platforms - desktop uses wheel event
250+
if (isDesktopPlatform) return;
251+
238252
// Only start pull if we're at the top of the scroll
239253
if (container.scrollTop === 0 && !isPullRefreshing) {
240254
pullStartY.current = e.clientY;
@@ -243,7 +257,7 @@ export function ChatHistoryList({
243257
};
244258

245259
const handleMouseMove = (e: MouseEvent) => {
246-
if (!isPulling.current || isPullRefreshing) return;
260+
if (!isPulling.current || isPullRefreshing || isDesktopPlatform) return;
247261

248262
const currentY = e.clientY;
249263
const distance = currentY - pullStartY.current;
@@ -253,41 +267,63 @@ export function ChatHistoryList({
253267
// Apply resistance: diminishing returns as you pull further
254268
const resistanceFactor = 0.4;
255269
const adjustedDistance = Math.min(distance * resistanceFactor, 80);
270+
pullDistanceRef.current = adjustedDistance;
256271
setPullDistance(adjustedDistance);
257272
}
258273
};
259274

260275
const handleMouseUp = () => {
261-
if (!isPulling.current) return;
276+
if (!isPulling.current || isDesktopPlatform) return;
262277

263278
isPulling.current = false;
264279

265280
// Trigger refresh if pulled far enough (threshold: 60px)
266-
if (pullDistance > 60) {
281+
if (pullDistanceRef.current > 60) {
267282
handleRefresh();
268283
} else {
269284
// Reset if not pulled far enough
270285
setPullDistance(0);
271286
}
272287
};
273288

274-
// Add event listeners for touch (mobile) and mouse (desktop)
289+
// Desktop: detect scroll up when already at top (overscroll)
290+
const handleWheel = (e: WheelEvent) => {
291+
if (!isDesktopPlatform || isPullRefreshing) return;
292+
293+
// Check if we're at the top and trying to scroll up
294+
if (container.scrollTop === 0 && e.deltaY < 0) {
295+
// Prevent default to avoid browser overscroll bounce
296+
e.preventDefault();
297+
// Trigger refresh on upward scroll attempt
298+
handleRefresh();
299+
}
300+
};
301+
302+
// Add event listeners based on platform
275303
container.addEventListener("touchstart", handleTouchStart, { passive: true });
276304
container.addEventListener("touchmove", handleTouchMove, { passive: false });
277305
container.addEventListener("touchend", handleTouchEnd);
278-
container.addEventListener("mousedown", handleMouseDown);
279-
window.addEventListener("mousemove", handleMouseMove);
280-
window.addEventListener("mouseup", handleMouseUp);
306+
307+
if (isDesktopPlatform) {
308+
// Desktop: use wheel event for scroll-to-refresh
309+
container.addEventListener("wheel", handleWheel, { passive: false });
310+
} else {
311+
// Mobile: use mouse events for pull-to-refresh
312+
container.addEventListener("mousedown", handleMouseDown);
313+
window.addEventListener("mousemove", handleMouseMove);
314+
window.addEventListener("mouseup", handleMouseUp);
315+
}
281316

282317
return () => {
283318
container.removeEventListener("touchstart", handleTouchStart);
284319
container.removeEventListener("touchmove", handleTouchMove);
285320
container.removeEventListener("touchend", handleTouchEnd);
321+
container.removeEventListener("wheel", handleWheel);
286322
container.removeEventListener("mousedown", handleMouseDown);
287323
window.removeEventListener("mousemove", handleMouseMove);
288324
window.removeEventListener("mouseup", handleMouseUp);
289325
};
290-
}, [containerRef, isPullRefreshing, pullDistance, handleRefresh]);
326+
}, [containerRef, isPullRefreshing, handleRefresh]);
291327

292328
// Set up polling every 60 seconds
293329
useEffect(() => {

0 commit comments

Comments
 (0)