Hold channel loading state until the feed is complete so it opens at the latest message#3583
Conversation
…the latest message The channel scroller's one-shot initial end-scroll fired against partial content (the synthesized "joined" opener renders as soon as the backend channel resolves, before the task cards arrive), and the engine never re-scrolls for late rows unless autoScroll forces it. Fold the feed-messages loading state into the gate and hold the spinner while any feed data is still loading, so the scroller mounts once with complete data and lands at the most recent message. Generated-By: PostHog Code Task-Id: d887d524-7d87-4d7a-b38c-b20289937ad1
|
😎 Merged successfully - details. |
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
Channel-to-channel navigation swaps the feed content without a route remount, so the scroller provider persisted and its one-shot defaultScrollPosition never re-applied — a revisited channel kept the previous channel's scroll offset (often the top). Keying the provider by channelId fresh-mounts it per channel, re-firing the initial end-scroll. Generated-By: PostHog Code Task-Id: d887d524-7d87-4d7a-b38c-b20289937ad1
Keying the provider by channelId re-fired the initial end-scroll but rebuilt the entire feed subtree (rows, observers, reply polling) on every switch, making channel changes feel sluggish. Replace the key with a headless LandAtLatestOnChannelChange component that imperatively calls scrollToEnd in a layout effect when the channel id changes — same commit as the swapped rows, before paint, no remount. Generated-By: PostHog Code Task-Id: d887d524-7d87-4d7a-b38c-b20289937ad1
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
There was a problem hiding this comment.
Low-risk UI fix: holds the channel loading state until the full feed is available so the one-shot scroll-to-end lands correctly, plus a useLayoutEffect to handle channel switches without remount. Changes are confined to view layer, touch no business logic, API contracts, or data models.
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
packages/ui/src/features/canvas/components/ChannelFeedView.tsx:714
**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.
Reviews (1): Last reviewed commit: "Simplify channel-switch scroll to an inl..." | Re-trigger Greptile |
| // mounting the scroller around partial content spends its one-shot initial | ||
| // end-scroll on a near-empty list, and it never re-scrolls when the real | ||
| // cards arrive. Pending kickoffs bail out so an optimistic post shows now. | ||
| if (isLoading && pending.length === 0) { |
There was a problem hiding this 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)
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.CI's biome ci errored on the scroll effect's [channelId] dependency (not referenced in the body — it's the re-run trigger). Suppress with the repo's biome-ignore idiom and cut the added comments down to the essentials. Generated-By: PostHog Code Task-Id: d887d524-7d87-4d7a-b38c-b20289937ad1
New commits pushed (delta classified non_trivial_delta) — stamphog approval dismissed; re-review running automatically.
…ckoff Greptile P1: submitting a kickoff mid-load mounts the scroller around partial content via the pending escape hatch, consuming the one-shot end scroll; the remaining rows then arrive with no re-scroll. Extend the scroll effect to also fire when isLoading settles, so the feed lands at the latest message in that path too. Generated-By: PostHog Code Task-Id: d887d524-7d87-4d7a-b38c-b20289937ad1
New commits pushed (delta classified non_trivial_delta) — stamphog approval dismissed; re-review running automatically.
There was a problem hiding this comment.
Pure view-layer scroll fix confined to two UI components. The greptile bot comment about pending rows is from an older commit and describes a narrow edge-case scroll-position UX quirk — not a correctness, data, or crash risk. The useLayoutEffect dependency on isLoading means the scroll-to-end fires when loading completes, which covers the primary scenario.
Problem
Opening a channel from the sidebar feels slow and doesn't start you at the most recent message. The feed scroller (
ChatMessageScrollerProvider defaultScrollPosition="end") applies its initial end-scroll exactly once, as soon as it has any content — and by design it never re-scrolls when more rows arrive (no forced scroll). Today the scroller mounts around partial content: the synthesized "joined" opener renders the moment the backend channel resolves, while the task cards and system messages are still fetching (the loading gate also ignoreduseChannelFeedMessages' loading state). The one-shot end-scroll spends itself on that near-empty list, so the real cards land off-screen below.Changes
WebsiteChannelHome: folduseChannelFeedMessages'isLoading(already returned, previously unused) into the feed loading gate.ChannelFeedView: scroll to the latest message on channel switch — channel-to-channel navigation swaps content without a route remount, so the scroller's one-shotdefaultScrollPositiononly covers the channel it mounted with and a revisited channel kept the previous channel's scroll offset (often the top). Implemented as a ref on the scroller viewport plus a layout effect keyed on the channel id that setsscrollTop = scrollHeightbefore paint; no remount, no extra components.ChannelFeedView: hold the loading spinner while the feed is still loading even if some entries already exist, so the scroller mounts once with the complete feed and its initial end-scroll lands at the latest message. Optimistic pending kickoffs still bail out of the spinner immediately, and warm reopens render instantly from cache with no repeat spinner.No new scroll code — verified against the scroller engine source that
defaultScrollPosition="end"handles the landing correctly once the mount isn't premature.Why: raised in project-bluebird — opening a channel should render at the most recent message immediately instead of showing a half-loaded feed scrolled to the top.
How did you test this?
pnpm --filter @posthog/ui typecheckandpnpm --filter @posthog/ui test(200 files / 1755 tests pass),pnpm lintclean on changed files.Automatic notifications
Created with PostHog Code