atagen

malice

public
Star0
Watch0

Commit e72926bcca9a

tarball

atagen <boss@atagen.co>2026-10-27 05:07 UTC

1 file changed+10-2
web: pin the timeline bottom instantly when its content resizes

While pinned to the bottom, every resize from images decoding, previews arriving or rows settling started a fresh 220ms smooth scroll, so during load-in the view kept chasing a moving bottom and visibly shook. Resizes now snap, and only a new newest message after the room has settled animates.
Modified file: web/src/App.svelte+10 additions−2 deletions
@@ -807,7 +807,7 @@
807807 timelineViewport = viewport;
808808 const observer = new ResizeObserver(() => {
809809 if (timelineViewport !== viewport || viewport.roomId !== $selectedRoomId) return;
810- if (stickToBottom) viewport.scrollToBottom(!motionActive());
810+ if (stickToBottom) viewport.scrollToBottom(false);
811811 else {
812812 viewport.restore();
813813 measureLatest();
@@ -822,14 +822,22 @@
822822 } };
823823 }
824824
825+ const SETTLE_MS = 1500;
826+ let followedTail = '';
827+ let followedSince = 0;
828+
825829 $: void trackTimeline($selectedRoomId, items);
826830 async function trackTimeline(roomId: string | null, list: TimelineItem[]): Promise<void> {
827831 if (!roomId || roomId !== $selectedRoomId) return;
828832 await tick();
829833 if (roomId !== $selectedRoomId) return;
830834 if (list.length > 0 && timelineViewport?.roomId === roomId) {
831- if (stickToBottom) timelineViewport.scrollToBottom(!motionActive());
835+ const tail = `${roomId}|${lastEventId(list)}`;
836+ if (!followedTail.startsWith(`${roomId}|`)) followedSince = performance.now();
837+ const arrived = tail !== followedTail && performance.now() - followedSince > SETTLE_MS;
838+ if (stickToBottom) timelineViewport.scrollToBottom(arrived && !motionActive());
832839 else timelineViewport.restore();
840+ followedTail = tail;
833841 queueReadReceipt();
834842 }
835843 fillTimeline(roomId);