diff --git a/app/src/components/LogViewer.tsx b/app/src/components/LogViewer.tsx index 75161ab..b7a4921 100644 --- a/app/src/components/LogViewer.tsx +++ b/app/src/components/LogViewer.tsx @@ -1,5 +1,5 @@ import { useEffect, useImperativeHandle, useRef, useState, forwardRef } from "react" -import { Maximize2, Minimize2, Trash2 } from "lucide-react" +import { Maximize2, Minimize2, Pause, Play, Trash2 } from "lucide-react" import { apiClient } from "@/services/api/client" interface LogViewerProps { @@ -19,7 +19,11 @@ export const LogViewer = forwardRef(function Lo const [logs, setLogs] = useState([]) const [connected, setConnected] = useState(false) const [maximized, setMaximized] = useState(false) + // When true, new log lines scroll the view to the bottom. Scrolling up to read + // pauses it; scrolling back to the bottom (or the play button) resumes. + const [autoScroll, setAutoScroll] = useState(true) const bottomRef = useRef(null) + const preRef = useRef(null) useEffect(() => { if (!follow) { @@ -53,9 +57,20 @@ export const LogViewer = forwardRef(function Lo return () => window.removeEventListener("keydown", onKey) }, [maximized]) + // Jump to the newest line only while auto-scroll is on. Instant (not smooth) so + // the scroll-position check below never sees a mid-animation false "scrolled up". useEffect(() => { - bottomRef.current?.scrollIntoView({ behavior: "smooth" }) - }, [logs]) + if (autoScroll) bottomRef.current?.scrollIntoView() + }, [logs, autoScroll]) + + // Keep auto-scroll in sync with where the user is: away from the bottom pauses, + // back at the bottom resumes — so the button and the scroll position never disagree. + const onScroll = () => { + const el = preRef.current + if (!el) return + const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 40 + setAutoScroll(atBottom) + } const btn = "p-1 rounded text-[var(--muted)] hover:text-[var(--foreground)] hover:bg-white/10 transition-colors" @@ -76,8 +91,16 @@ export const LogViewer = forwardRef(function Lo {connected ? "streaming" : "disconnected"} )} + {follow && !autoScroll && paused} + @@ -91,6 +114,8 @@ export const LogViewer = forwardRef(function Lo