From 0230f2d4383c8650bf8286727dae055be945145a Mon Sep 17 00:00:00 2001 From: Thomas Petersen Date: Sat, 9 May 2026 15:17:34 -0400 Subject: [PATCH] fix(desktop): refine thread panel reply layout Co-authored-by: Cursor --- .../messages/lib/threadPanel.test.mjs | 68 ++++++++++++++++++- .../src/features/messages/lib/threadPanel.ts | 2 +- .../src/features/messages/ui/MessageRow.tsx | 6 +- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/desktop/src/features/messages/lib/threadPanel.test.mjs b/desktop/src/features/messages/lib/threadPanel.test.mjs index 3ddf745bb1..6daadef439 100644 --- a/desktop/src/features/messages/lib/threadPanel.test.mjs +++ b/desktop/src/features/messages/lib/threadPanel.test.mjs @@ -1,7 +1,10 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { buildMainTimelineEntries } from "./threadPanel.ts"; +import { + buildMainTimelineEntries, + buildThreadPanelData, +} from "./threadPanel.ts"; function message(overrides) { return { @@ -56,3 +59,66 @@ test("buildMainTimelineEntries includes broadcast replies", () => { ["root", "broadcast-reply"], ); }); + +test("buildThreadPanelData keeps direct replies flat", () => { + const root = message({ id: "root", createdAt: 1 }); + const reply = message({ + id: "reply", + createdAt: 2, + parentId: "root", + rootId: "root", + depth: 1, + tags: [["e", "root", "", "reply"]], + }); + + const panel = buildThreadPanelData([root, reply], "root", null, new Set()); + + assert.deepEqual( + panel.visibleReplies.map((entry) => ({ + id: entry.message.id, + depth: entry.message.depth, + })), + [{ id: "reply", depth: 0 }], + ); +}); + +test("buildThreadPanelData indents expanded subthread replies", () => { + const root = message({ id: "root", createdAt: 1 }); + const reply = message({ + id: "reply", + createdAt: 2, + parentId: "root", + rootId: "root", + depth: 1, + tags: [["e", "root", "", "reply"]], + }); + const nestedReply = message({ + id: "nested-reply", + createdAt: 3, + parentId: "reply", + rootId: "root", + depth: 2, + tags: [ + ["e", "root", "", "root"], + ["e", "reply", "", "reply"], + ], + }); + + const panel = buildThreadPanelData( + [root, reply, nestedReply], + "root", + null, + new Set(["reply"]), + ); + + assert.deepEqual( + panel.visibleReplies.map((entry) => ({ + id: entry.message.id, + depth: entry.message.depth, + })), + [ + { id: "reply", depth: 0 }, + { id: "nested-reply", depth: 1 }, + ], + ); +}); diff --git a/desktop/src/features/messages/lib/threadPanel.ts b/desktop/src/features/messages/lib/threadPanel.ts index 71f67fc3e4..ad704f24cb 100644 --- a/desktop/src/features/messages/lib/threadPanel.ts +++ b/desktop/src/features/messages/lib/threadPanel.ts @@ -207,7 +207,7 @@ function buildVisibleThreadReplies(params: { appendExpandedReplies({ entries, parentId: openThreadHeadId, - depth: 1, + depth: 0, directChildrenByParentId, descendantStatsByMessageId, expandedReplyIds, diff --git a/desktop/src/features/messages/ui/MessageRow.tsx b/desktop/src/features/messages/ui/MessageRow.tsx index 69a4a58aa4..50ec9e332b 100644 --- a/desktop/src/features/messages/ui/MessageRow.tsx +++ b/desktop/src/features/messages/ui/MessageRow.tsx @@ -255,7 +255,7 @@ export const MessageRow = React.memo(
-
{messageBodyNode}
+
+ {messageBodyNode} +
) : ( <>