fix(timeline): your own new message always scrolls into view (#212)
CI / Build & Quality Checks (push) Successful in 1m34s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 7s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 3m1s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-19 12:37:54 -04:00
co-authored by Claude Opus 5
parent 6df160a7bf
commit 464951edf4
+28
View File
@@ -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));