Skip to content

Commit 8db09ef

Browse files
authored
Simplify channel-switch scroll to an inline viewport ref
Drop the LandAtLatestOnChannelChange headless component. The same behavior is a ref on the scroller viewport plus one layout effect keyed on channelId that sets scrollTop = scrollHeight — the engine composes external viewport refs, and first mounts are still covered by defaultScrollPosition. Generated-By: PostHog Code Task-Id: d887d524-7d87-4d7a-b38c-b20289937ad1
1 parent d4253b3 commit 8db09ef

1 file changed

Lines changed: 11 additions & 20 deletions

File tree

packages/ui/src/features/canvas/components/ChannelFeedView.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -629,24 +629,6 @@ function FollowOwnPost({ latestPendingId }: { latestPendingId?: string }) {
629629
return null;
630630
}
631631

632-
// Land at the latest message when the feed swaps to another channel: the route
633-
// doesn't remount on a param change, so the provider's one-shot
634-
// defaultScrollPosition only covers the channel it mounted with. Imperative
635-
// rather than keying the provider — a remount would rebuild the entire feed
636-
// subtree on every switch. Layout effect so the jump happens in the same
637-
// commit as the swapped rows, before paint. Renders nothing.
638-
function LandAtLatestOnChannelChange({ channelId }: { channelId: string }) {
639-
const { scrollToEnd } = useChatMessageScroller();
640-
const prevRef = useRef(channelId);
641-
useLayoutEffect(() => {
642-
if (channelId !== prevRef.current) {
643-
prevRef.current = channelId;
644-
scrollToEnd({ behavior: "auto" });
645-
}
646-
}, [channelId, scrollToEnd]);
647-
return null;
648-
}
649-
650632
// A single feed entry, either a real task card or a synthetic system row, tagged
651633
// with the timestamp used to interleave the two.
652634
type FeedEntry =
@@ -714,6 +696,16 @@ export function ChannelFeedView({
714696
return merged;
715697
}, [tasks, systemMessages]);
716698

699+
// Open every channel at its latest message. The scroller handles the first
700+
// mount itself (defaultScrollPosition), but switching channels only swaps
701+
// the rows — no remount — so jump to the bottom before paint whenever the
702+
// channel changes. No-ops while the loading state has the viewport unmounted.
703+
const viewportRef = useRef<HTMLDivElement>(null);
704+
useLayoutEffect(() => {
705+
const viewport = viewportRef.current;
706+
if (viewport) viewport.scrollTop = viewport.scrollHeight;
707+
}, [channelId]);
708+
717709
// Hold the loading state even when some entries already exist (the "joined"
718710
// opener renders as soon as the channel resolves, before the task cards):
719711
// mounting the scroller around partial content spends its one-shot initial
@@ -736,10 +728,9 @@ export function ChannelFeedView({
736728

737729
return (
738730
<ChatMessageScrollerProvider defaultScrollPosition="end">
739-
<LandAtLatestOnChannelChange channelId={channelId} />
740731
<FollowOwnPost latestPendingId={latestPendingId} />
741732
<ChatMessageScroller className="min-h-0 flex-1">
742-
<ChatMessageScrollerViewport>
733+
<ChatMessageScrollerViewport ref={viewportRef}>
743734
{/* Horizontal padding is load-bearing: ThreadItem's actions float at
744735
the row's top-right corner (absolute, past the row edge). Without a
745736
gutter they hug the scroll container and get clipped. */}

0 commit comments

Comments
 (0)