mirror of
https://github.com/block/buzz.git
synced 2026-11-04 00:12:02 +00:00
fix(desktop): show nested thread replies inline
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -81,7 +81,6 @@ type ChannelPaneProps = {
|
||||
onDelete?: (message: TimelineMessage) => void;
|
||||
onEdit?: (message: TimelineMessage) => void;
|
||||
onEditSave?: (content: string) => Promise<void>;
|
||||
onExpandThreadReplies: (message: TimelineMessage) => void;
|
||||
onJoinChannel?: () => Promise<void>;
|
||||
onOpenAgentSession: (pubkey: string) => void;
|
||||
onOpenDm?: (pubkeys: string[]) => void;
|
||||
@@ -143,7 +142,6 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
onDelete,
|
||||
onEdit,
|
||||
onEditSave,
|
||||
onExpandThreadReplies,
|
||||
onJoinChannel,
|
||||
onOpenAgentSession,
|
||||
onOpenDm,
|
||||
@@ -418,7 +416,6 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
onDelete={onDelete}
|
||||
onEdit={onEdit}
|
||||
onEditSave={onEditSave}
|
||||
onExpandReplies={onExpandThreadReplies}
|
||||
onSelectReplyTarget={onSelectThreadReplyTarget}
|
||||
onSend={onSendThreadReply}
|
||||
onScrollTargetResolved={onThreadScrollTargetResolved}
|
||||
|
||||
@@ -296,14 +296,8 @@ export function ChannelScreen({
|
||||
timelineMessages,
|
||||
openThreadHeadId,
|
||||
threadReplyTargetId,
|
||||
expandedThreadReplyIds,
|
||||
),
|
||||
[
|
||||
expandedThreadReplyIds,
|
||||
openThreadHeadId,
|
||||
threadReplyTargetId,
|
||||
timelineMessages,
|
||||
],
|
||||
[openThreadHeadId, threadReplyTargetId, timelineMessages],
|
||||
);
|
||||
const openThreadHeadMessage = threadPanelData.threadHead;
|
||||
const threadMessages = threadPanelData.visibleReplies;
|
||||
@@ -322,7 +316,6 @@ export function ChannelScreen({
|
||||
handleDelete,
|
||||
handleEdit,
|
||||
handleEditSave,
|
||||
handleExpandThreadReplies,
|
||||
handleOpenThread,
|
||||
handleSendMessage,
|
||||
handleSendThreadReply,
|
||||
@@ -496,7 +489,6 @@ export function ChannelScreen({
|
||||
onDelete={handleDelete}
|
||||
onEdit={handleEdit}
|
||||
onEditSave={handleEditSave}
|
||||
onExpandThreadReplies={handleExpandThreadReplies}
|
||||
onOpenAgentSession={handleOpenAgentSession}
|
||||
onOpenDm={handleOpenDm}
|
||||
onCloseProfilePanel={handleCloseProfilePanel}
|
||||
|
||||
@@ -150,67 +150,56 @@ function buildSummaryForDirectReplies(
|
||||
};
|
||||
}
|
||||
|
||||
function appendExpandedReplies(params: {
|
||||
function appendThreadPanelReplies(params: {
|
||||
entries: MainTimelineEntry[];
|
||||
parentId: string;
|
||||
depth: number;
|
||||
directChildrenByParentId: Map<string, TimelineMessage[]>;
|
||||
descendantStatsByMessageId: Map<string, ThreadDescendantStats>;
|
||||
expandedReplyIds: ReadonlySet<string>;
|
||||
visitedMessageIds: Set<string>;
|
||||
}) {
|
||||
const {
|
||||
entries,
|
||||
parentId,
|
||||
depth,
|
||||
directChildrenByParentId,
|
||||
descendantStatsByMessageId,
|
||||
expandedReplyIds,
|
||||
visitedMessageIds,
|
||||
} = params;
|
||||
const directReplies = directChildrenByParentId.get(parentId) ?? [];
|
||||
|
||||
for (const reply of directReplies) {
|
||||
if (visitedMessageIds.has(reply.id)) {
|
||||
continue;
|
||||
}
|
||||
visitedMessageIds.add(reply.id);
|
||||
|
||||
entries.push({
|
||||
message: normalizeInlineReplyMessage(reply, depth),
|
||||
summary: buildSummaryForDirectReplies(
|
||||
reply.id,
|
||||
descendantStatsByMessageId,
|
||||
),
|
||||
summary: null,
|
||||
});
|
||||
|
||||
if (expandedReplyIds.has(reply.id)) {
|
||||
appendExpandedReplies({
|
||||
entries,
|
||||
parentId: reply.id,
|
||||
depth: depth + 1,
|
||||
directChildrenByParentId,
|
||||
descendantStatsByMessageId,
|
||||
expandedReplyIds,
|
||||
});
|
||||
}
|
||||
appendThreadPanelReplies({
|
||||
entries,
|
||||
parentId: reply.id,
|
||||
depth: depth + 1,
|
||||
directChildrenByParentId,
|
||||
visitedMessageIds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildVisibleThreadReplies(params: {
|
||||
openThreadHeadId: string;
|
||||
directChildrenByParentId: Map<string, TimelineMessage[]>;
|
||||
descendantStatsByMessageId: Map<string, ThreadDescendantStats>;
|
||||
expandedReplyIds: ReadonlySet<string>;
|
||||
}) {
|
||||
const {
|
||||
openThreadHeadId,
|
||||
directChildrenByParentId,
|
||||
descendantStatsByMessageId,
|
||||
expandedReplyIds,
|
||||
} = params;
|
||||
const { openThreadHeadId, directChildrenByParentId } = params;
|
||||
const entries: MainTimelineEntry[] = [];
|
||||
|
||||
appendExpandedReplies({
|
||||
appendThreadPanelReplies({
|
||||
entries,
|
||||
parentId: openThreadHeadId,
|
||||
depth: 0,
|
||||
directChildrenByParentId,
|
||||
descendantStatsByMessageId,
|
||||
expandedReplyIds,
|
||||
visitedMessageIds: new Set([openThreadHeadId]),
|
||||
});
|
||||
|
||||
return entries;
|
||||
@@ -238,7 +227,6 @@ export function buildThreadPanelData(
|
||||
messages: TimelineMessage[],
|
||||
openThreadHeadId: string | null,
|
||||
threadReplyTargetId: string | null,
|
||||
expandedReplyIds: ReadonlySet<string>,
|
||||
): ThreadPanelData {
|
||||
if (!openThreadHeadId) {
|
||||
return {
|
||||
@@ -267,8 +255,6 @@ export function buildThreadPanelData(
|
||||
const visibleReplies = buildVisibleThreadReplies({
|
||||
openThreadHeadId,
|
||||
directChildrenByParentId,
|
||||
descendantStatsByMessageId,
|
||||
expandedReplyIds,
|
||||
});
|
||||
|
||||
const replyTargetInBranch =
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
} from "@/shared/ui/OverlayPanelBackdrop";
|
||||
import { MessageComposer } from "./MessageComposer";
|
||||
import { MessageRow } from "./MessageRow";
|
||||
import { MessageThreadSummaryRow } from "./MessageThreadSummaryRow";
|
||||
import { TypingIndicatorRow } from "./TypingIndicatorRow";
|
||||
import { useTimelineScrollManager } from "./useTimelineScrollManager";
|
||||
|
||||
@@ -35,7 +34,6 @@ type MessageThreadPanelProps = {
|
||||
onDelete?: (message: TimelineMessage) => void;
|
||||
onEdit?: (message: TimelineMessage) => void;
|
||||
onEditSave?: (content: string) => Promise<void>;
|
||||
onExpandReplies: (message: TimelineMessage) => void;
|
||||
onResetWidth: () => void;
|
||||
onResizeStart: (event: React.PointerEvent<HTMLButtonElement>) => void;
|
||||
onScrollTargetResolved: () => void;
|
||||
@@ -87,7 +85,6 @@ export function MessageThreadPanel({
|
||||
onDelete,
|
||||
onEdit,
|
||||
onEditSave,
|
||||
onExpandReplies,
|
||||
onResetWidth,
|
||||
onResizeStart,
|
||||
onScrollTargetResolved,
|
||||
@@ -244,15 +241,6 @@ export function MessageThreadPanel({
|
||||
onToggleReaction={onToggleReaction}
|
||||
profiles={profiles}
|
||||
/>
|
||||
{entry.summary ? (
|
||||
<MessageThreadSummaryRow
|
||||
depth={entry.message.depth}
|
||||
layoutVariant="thread-reply"
|
||||
message={entry.message}
|
||||
onOpenThread={onExpandReplies}
|
||||
summary={entry.summary}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -424,8 +424,7 @@ test("opens a single-level thread panel with inline expansion", async ({
|
||||
const firstReplySummaryRow = threadReplies.locator(
|
||||
`[data-thread-head-id="${firstReplyId}"]`,
|
||||
);
|
||||
await expect(firstReplySummaryRow).toHaveCount(1);
|
||||
await expect(firstReplySummaryRow).toContainText("2 replies");
|
||||
await expect(firstReplySummaryRow).toHaveCount(0);
|
||||
|
||||
await expect(rootSummaryRow).toContainText("18 replies");
|
||||
await expect(
|
||||
@@ -448,16 +447,6 @@ test("opens a single-level thread panel with inline expansion", async ({
|
||||
});
|
||||
})
|
||||
.toBeLessThanOrEqual(160);
|
||||
|
||||
await firstReplySummaryRow.click();
|
||||
await expect(
|
||||
threadReplies.getByTestId("message-row").filter({ hasText: nestedReply }),
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
threadReplies
|
||||
.getByTestId("message-row")
|
||||
.filter({ hasText: nestedReplyFromBob }),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("thread panel width uses session storage and reset handle", async ({
|
||||
|
||||
Reference in New Issue
Block a user