From 5d844efa094ec2843af447d15f5e40373e6daf72 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Wed, 16 Sep 2026 22:50:37 +0200 Subject: [PATCH] fix(tutor): the log stopped following a reply sent from answer mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Submitting from answer mode brings back the earlier messages, the composer and the nav in one layout. That reset the log's scrollTop to 0, and the scroll event it fired read as the learner scrolling up — undoing the follow send() had just armed, so the reply landed out of view, 1,812px below. A move made while the log's content or box changed size is now taken for layout, not for the learner. Scrolling up during a reply still lets go. Co-Authored-By: Claude Opus 5 (1M context) --- app/src/ui/tutor/TutorTab.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/ui/tutor/TutorTab.tsx b/app/src/ui/tutor/TutorTab.tsx index 78dd3f7..234ab9d 100644 --- a/app/src/ui/tutor/TutorTab.tsx +++ b/app/src/ui/tutor/TutorTab.tsx @@ -206,8 +206,9 @@ export function TutorTab() { const log = useRef(null); /** The log follows new text while this is set; see the follow effect. */ const stick = useRef(true); - /** Where the log was last scrolled to; see onLogScroll. */ + /** Where the log was last scrolled to, and how tall it was; see onLogScroll. */ const lastTop = useRef(0); + const lastHeights = useRef({ content: 0, box: 0 }); const menuButton = useRef(null); /* ── the keyboard's field, and answer mode ── */ @@ -716,15 +717,24 @@ export function TutorTab() { stream has added more than STICK_PX below it — judged by distance alone, that read as the learner scrolling away, and the log stopped following mid-reply. Growing content never moves scrollTop up; a - finger does. */ + finger does. + + And a finger does not change the log's height. Leaving answer mode + brings back the earlier messages, the composer and the nav in one + layout, which reset scrollTop to 0 — an upward jump that read as + scrolling away, just as the answer was sent. A move made while the + content or the box changed size is layout, not the learner. */ const onLogScroll = useCallback(() => { const el = log.current; if (!el || !active) return; const top = el.scrollTop; + const resized = + el.scrollHeight !== lastHeights.current.content || el.clientHeight !== lastHeights.current.box; if (el.scrollHeight - top - el.clientHeight < STICK_PX) stick.current = true; - else if (top < lastTop.current - 1) stick.current = false; + else if (top < lastTop.current - 1 && !resized) stick.current = false; lastTop.current = top; + lastHeights.current = { content: el.scrollHeight, box: el.clientHeight }; }, [active]); const follow = useCallback(() => { @@ -732,6 +742,7 @@ export function TutorTab() { if (!el || !stick.current) return; el.scrollTop = el.scrollHeight; lastTop.current = el.scrollTop; + lastHeights.current = { content: el.scrollHeight, box: el.clientHeight }; }, []); useEffect(() => {