Skip to content

Commit ef9c7b2

Browse files
authored
Hold channel loading state until the feed is complete so it opens at the latest message (#3583)
1 parent d55d24e commit ef9c7b2

2 files changed

Lines changed: 25 additions & 9 deletions

File tree

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
memo,
6464
type ReactNode,
6565
useEffect,
66+
useLayoutEffect,
6667
useMemo,
6768
useRef,
6869
} from "react";
@@ -722,7 +723,17 @@ export function ChannelFeedView({
722723
return merged;
723724
}, [tasks, systemMessages]);
724725

725-
if (isLoading && entries.length === 0 && pending.length === 0) {
726+
const viewportRef = useRef<HTMLDivElement>(null);
727+
// biome-ignore lint/correctness/useExhaustiveDependencies: channelId is a trigger — switching channels or finishing the initial load swaps/completes the rows without a remount, so re-land at the latest message
728+
useLayoutEffect(() => {
729+
if (isLoading) return;
730+
const viewport = viewportRef.current;
731+
if (viewport) viewport.scrollTop = viewport.scrollHeight;
732+
}, [channelId, isLoading]);
733+
734+
// Wait for the complete feed: the scroller's initial end-scroll fires once,
735+
// so mounting around partial rows would land it short of the latest message.
736+
if (isLoading && pending.length === 0) {
726737
return (
727738
<div className="flex flex-1 items-center justify-center">
728739
<Spinner />
@@ -741,7 +752,7 @@ export function ChannelFeedView({
741752
<ChatMessageScrollerProvider defaultScrollPosition="end">
742753
<FollowOwnPost latestPendingId={latestPendingId} />
743754
<ChatMessageScroller className="min-h-0 flex-1">
744-
<ChatMessageScrollerViewport>
755+
<ChatMessageScrollerViewport ref={viewportRef}>
745756
{/* Horizontal padding is load-bearing: ThreadItem's actions float at
746757
the row's top-right corner (absolute, past the row edge). Without a
747758
gutter they hug the scroll container and get clipped. */}

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,23 @@ export function WebsiteChannelHome({ channelId }: { channelId: string }) {
6969
const { tasks, isLoading: isLoadingFeed } = useChannelFeed(
7070
backendChannel?.id,
7171
);
72-
// Until the backend channel resolves there's no feed to ask for, and the feed
73-
// query is disabled — which reports isLoading:false, indistinguishable from
74-
// "this channel is empty". useBackendChannel reports loading for the whole
75-
// identity-resolution window (settling if the resolve fails), so fold it in:
76-
// we can't call a channel empty until we know which channel it is.
77-
const isLoading = isLoadingChannels || isResolvingChannel || isLoadingFeed;
7872
// Marking this channel read lives in ChannelHeader (rendered by every channel
7973
// surface), so opening Artifacts or CONTEXT.md counts as reading it too.
8074

8175
// Durable "PostHog agent" rows (CONTEXT.md being built, …) live on the
8276
// backend channel — the same id the feed tasks use, not the folder id.
83-
const { messages: feedMessages } = useChannelFeedMessages(backendChannel?.id);
77+
const { messages: feedMessages, isLoading: isLoadingMessages } =
78+
useChannelFeedMessages(backendChannel?.id);
79+
// Until the backend channel resolves there's no feed to ask for, and the feed
80+
// query is disabled — which reports isLoading:false, indistinguishable from
81+
// "this channel is empty". useBackendChannel reports loading for the whole
82+
// identity-resolution window (settling if the resolve fails), so fold it in:
83+
// we can't call a channel empty until we know which channel it is.
84+
const isLoading =
85+
isLoadingChannels ||
86+
isResolvingChannel ||
87+
isLoadingFeed ||
88+
isLoadingMessages;
8489
// The Slack-style "joined" opener, derived from the channel row so it renders
8590
// (and sorts first) even where the feed endpoint isn't deployed.
8691
const systemMessages = useMemo(() => {

0 commit comments

Comments
 (0)