Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions packages/ui/src/features/canvas/components/ChannelFeedView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
memo,
type ReactNode,
useEffect,
useLayoutEffect,
useMemo,
useRef,
} from "react";
Expand Down Expand Up @@ -695,7 +696,17 @@ export function ChannelFeedView({
return merged;
}, [tasks, systemMessages]);

if (isLoading && entries.length === 0 && pending.length === 0) {
const viewportRef = useRef<HTMLDivElement>(null);
// 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
useLayoutEffect(() => {
if (isLoading) return;
const viewport = viewportRef.current;
if (viewport) viewport.scrollTop = viewport.scrollHeight;
}, [channelId, isLoading]);

// Wait for the complete feed: the scroller's initial end-scroll fires once,
// so mounting around partial rows would land it short of the latest message.
if (isLoading && pending.length === 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pending Row Mounts Incomplete Feed

If a kickoff is submitted while the initial tasks or system messages are still loading, pending.length > 0 mounts the scroller around partial content and consumes its one-shot end scroll. The remaining rows then arrive without changing channelId, so neither the default scroll nor the layout effect runs again and the channel can open above its latest message.

Rule Used: In cases where there are multiple states to handle... (source)

Learned From
PostHog/posthog#32610

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/features/canvas/components/ChannelFeedView.tsx
Line: 714

Comment:
**Pending Row Mounts Incomplete Feed**

If a kickoff is submitted while the initial tasks or system messages are still loading, `pending.length > 0` mounts the scroller around partial content and consumes its one-shot end scroll. The remaining rows then arrive without changing `channelId`, so neither the default scroll nor the layout effect runs again and the channel can open above its latest message.

**Rule Used:** In cases where there are multiple states to handle... ([source](https://app.greptile.com/posthog-org-19734/-/custom-context?memory=b99946fe-4cce-4bff-bdc0-8480d200548a))

**Learned From**
[PostHog/posthog#32610](https://github.com/PostHog/posthog/pull/32610)

How can I resolve this? If you propose a fix, please make it concise.

return (
<div className="flex flex-1 items-center justify-center">
<Spinner />
Expand All @@ -714,7 +725,7 @@ export function ChannelFeedView({
<ChatMessageScrollerProvider defaultScrollPosition="end">
<FollowOwnPost latestPendingId={latestPendingId} />
<ChatMessageScroller className="min-h-0 flex-1">
<ChatMessageScrollerViewport>
<ChatMessageScrollerViewport ref={viewportRef}>
{/* Horizontal padding is load-bearing: ThreadItem's actions float at
the row's top-right corner (absolute, past the row edge). Without a
gutter they hug the scroll container and get clipped. */}
Expand Down
19 changes: 12 additions & 7 deletions packages/ui/src/features/canvas/components/WebsiteChannelHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,23 @@ export function WebsiteChannelHome({ channelId }: { channelId: string }) {
const { tasks, isLoading: isLoadingFeed } = useChannelFeed(
backendChannel?.id,
);
// Until the backend channel resolves there's no feed to ask for, and the feed
// query is disabled — which reports isLoading:false, indistinguishable from
// "this channel is empty". useBackendChannel reports loading for the whole
// identity-resolution window (settling if the resolve fails), so fold it in:
// we can't call a channel empty until we know which channel it is.
const isLoading = isLoadingChannels || isResolvingChannel || isLoadingFeed;
// Marking this channel read lives in ChannelHeader (rendered by every channel
// surface), so opening Artifacts or CONTEXT.md counts as reading it too.

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