Fix thread reply pill behavior

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Thomas Petersen
2026-05-04 11:28:21 -04:00
parent 607c77d6ca
commit bb905767db
5 changed files with 41 additions and 9 deletions

View File

@@ -265,6 +265,23 @@ export function ChannelScreen({
(messageId: string) => directReplyIdsByParentId.get(messageId)?.[0] ?? null,
[directReplyIdsByParentId],
);
const getReplyDescendantIdsForMessage = React.useCallback(
(messageId: string) => {
const descendantIds: string[] = [];
const pendingIds = [...(directReplyIdsByParentId.get(messageId) ?? [])];
while (pendingIds.length > 0) {
const currentId = pendingIds.pop();
if (!currentId) continue;
descendantIds.push(currentId);
pendingIds.push(...(directReplyIdsByParentId.get(currentId) ?? []));
}
return descendantIds;
},
[directReplyIdsByParentId],
);
const threadPanelData = React.useMemo(
() =>
buildThreadPanelData(
@@ -309,6 +326,7 @@ export function ChannelScreen({
editTargetId,
expandedThreadReplyIds,
getFirstReplyIdForMessage,
getReplyDescendantIdsForMessage,
openThreadHeadId,
sendMessageMutation,
setExpandedThreadReplyIds,

View File

@@ -21,6 +21,7 @@ export function useChannelPaneHandlers({
editTargetId,
expandedThreadReplyIds,
getFirstReplyIdForMessage,
getReplyDescendantIdsForMessage,
openThreadHeadId,
sendMessageMutation,
setExpandedThreadReplyIds,
@@ -36,6 +37,7 @@ export function useChannelPaneHandlers({
editTargetId: string | null;
expandedThreadReplyIds: ReadonlySet<string>;
getFirstReplyIdForMessage: (messageId: string) => string | null;
getReplyDescendantIdsForMessage: (messageId: string) => string[];
openThreadHeadId: string | null;
sendMessageMutation: ReturnType<typeof useSendMessageMutation>;
setExpandedThreadReplyIds: React.Dispatch<React.SetStateAction<Set<string>>>;
@@ -158,21 +160,33 @@ export function useChannelPaneHandlers({
const handleExpandThreadReplies = React.useCallback(
(message: { id: string }) => {
const firstReplyId = getFirstReplyIdForMessage(message.id);
if (!expandedThreadReplyIdsRef.current.has(message.id)) {
if (expandedThreadReplyIdsRef.current.has(message.id)) {
const descendantIds = getReplyDescendantIdsForMessage(message.id);
setExpandedThreadReplyIds((current) => {
const next = new Set(current);
next.add(message.id);
next.delete(message.id);
for (const descendantId of descendantIds) {
next.delete(descendantId);
}
return next;
});
return;
}
const firstReplyId = getFirstReplyIdForMessage(message.id);
setExpandedThreadReplyIds((current) => {
const next = new Set(current);
next.add(message.id);
return next;
});
if (firstReplyId) {
setThreadScrollTargetId(firstReplyId);
}
},
[
getFirstReplyIdForMessage,
getReplyDescendantIdsForMessage,
setExpandedThreadReplyIds,
setThreadScrollTargetId,
],

View File

@@ -220,10 +220,6 @@ export function MessageThreadPanel({
{threadReplies.length > 0 ? (
<div className="space-y-2">
{threadReplies.map((entry, index) => {
const nextDepth =
threadReplies[index + 1]?.message.depth ?? -1;
const isExpanded = nextDepth > entry.message.depth;
return (
<div key={entry.message.id}>
<MessageRow
@@ -246,9 +242,10 @@ export function MessageThreadPanel({
onToggleReaction={onToggleReaction}
profiles={profiles}
/>
{entry.summary && !isExpanded ? (
{entry.summary ? (
<MessageThreadSummaryRow
depth={entry.message.depth}
layoutVariant="thread-reply"
message={entry.message}
onOpenThread={onExpandReplies}
summary={entry.summary}

View File

@@ -30,17 +30,19 @@ function ParticipantAvatar({
export function MessageThreadSummaryRow({
depth = 0,
layoutVariant = "default",
message,
onOpenThread,
summary,
}: {
depth?: number;
layoutVariant?: "default" | "thread-reply";
message: TimelineMessage;
onOpenThread: (message: TimelineMessage) => void;
summary: TimelineThreadSummary;
}) {
const visibleDepth = Math.min(Math.max(depth, 0), 6);
const messageTextOffsetPx = 50;
const messageTextOffsetPx = layoutVariant === "thread-reply" ? 8 : 50;
const marginLeftPx = visibleDepth * 28 + messageTextOffsetPx;
const depthGuideOffsets = Array.from(
{ length: visibleDepth },

View File

@@ -104,6 +104,7 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
profiles={profiles}
/>
<MessageThreadSummaryRow
depth={message.depth}
message={message}
onOpenThread={onReply}
summary={summary}