From 78f81e89f4c8be86c148149d7b3e175a4519844a Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Mon, 15 Jun 2026 22:27:27 -0700 Subject: [PATCH] fix(timeline): render channel intro header flush at the top of the list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The channel intro block (the `#` avatar, channel title, "beginning of…" line, and Create agent / Add people cards) was bottom-pinned: it lived in a `min-h-full` flex column with `mt-auto`, which Slack-style pushes it to the viewport bottom when the list is short. tho wants it flush at the actual TOP of the virtualized list, since it's an in-list header — not a floating element. Gate the push-down on `topAlignIntro = showChannelIntro` and turn it off for the channel-intro case in 4 spots: the outer wrapper `min-h-full`, the SkeletonReveal `className` + `contentClassName` `min-h-full`, and the intro div's own `mt-auto`. Natural top-down flow then places the header at the top. The DM intro and the generic empty state keep their existing bottom-pin behavior. Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../features/messages/ui/MessageTimeline.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/desktop/src/features/messages/ui/MessageTimeline.tsx b/desktop/src/features/messages/ui/MessageTimeline.tsx index 46967673f..c184b54c4 100644 --- a/desktop/src/features/messages/ui/MessageTimeline.tsx +++ b/desktop/src/features/messages/ui/MessageTimeline.tsx @@ -326,6 +326,13 @@ export const MessageTimeline = React.memo(function MessageTimeline({ const showChannelIntro = !isLoading && channelIntro !== null && directMessageIntro === null; const showIntro = showDirectMessageIntro || showChannelIntro; + // The channel intro is an in-list HEADER: it must sit FLUSH at the top of the + // virtualized list (the `#` avatar / title / "beginning of…" / action cards), + // not float bottom-pinned in the viewport like the DM intro / empty state. So + // for the channel-intro case we drop the `min-h-full` fill + the intro's + // `mt-auto`, letting natural top-down flow place it at the top. Everything + // else (DM intro, generic empty) keeps the existing bottom-pin behavior. + const topAlignIntro = showChannelIntro; const showGenericEmpty = !isLoading && deferredMessages.length === 0 && @@ -355,7 +362,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({ className={cn( "flex w-full flex-col gap-2", channelChrome.contentPadding, - (showIntro || showGenericEmpty) && "min-h-full", + (showIntro || showGenericEmpty) && !topAlignIntro && "min-h-full", )} >
@@ -369,12 +376,16 @@ export const MessageTimeline = React.memo(function MessageTimeline({ } @@ -402,7 +413,12 @@ export const MessageTimeline = React.memo(function MessageTimeline({ {showChannelIntro ? (