From 464951edf49b2c8b83a3554f9c7cb274008998d2 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 19 Sep 2026 12:37:54 -0400 Subject: [PATCH] fix(timeline): your own new message always scrolls into view (#212) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-scroll on a live event only ran while the at-bottom flag was true, so a stale flag (in-place growth like a poll gaining a row briefly pushes the anchor out of view; or simply having scrolled up) left your own just-sent message below the fold behind 'Jump to Latest'. An own non-relation, non-state, non-reaction event now always brings the live end into view: advance the range + smooth scroll when the window is at the live end, otherwise reset to the live timeline like the Jump to Latest button. Others' messages keep the current behaviour. Verified headless: scrolled up 600 px → Send → message visible, no Jump pill (before: Jump pill, message off-screen). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- src/app/features/room/RoomTimeline.tsx | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index adbb2e9a0..1336b45f4 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -705,6 +705,34 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli })); return; } + // [Gitea #212] Your OWN new message always comes into view — that is + // what pressing Send means — even if the at-bottom flag went stale + // (in-place growth like a poll gaining a row briefly pushes the anchor + // out of the viewport) or you had scrolled up to read. + const ownNewMessage = + mEvt.getSender() === mx.getUserId() && + !mEvt.isRelation() && + !mEvt.isRedaction() && + !mEvt.isState() && + mEvt.getType() !== MessageEvent.Reaction; + if (ownNewMessage) { + if (atLiveEndRef.current) { + setAtBottom(true); + scrollToBottomRef.current.count += 1; + scrollToBottomRef.current.smooth = true; + setTimeline((ct) => ({ + ...ct, + range: { start: ct.range.start + 1, end: ct.range.end + 1 }, + })); + } else { + // Paginated back into history: jump to the live end like the + // "Jump to Latest" button does. + setTimeline(getInitialTimeline(room)); + scrollToBottomRef.current.count += 1; + scrollToBottomRef.current.smooth = false; + } + return; + } setTimeline((ct) => ({ ...ct })); if (!unreadInfo) { setUnreadInfo(getRoomUnreadInfo(room));