Skip to content

Commit 3973080

Browse files
Refine conversation visual affordances (#615)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 50b350d commit 3973080

10 files changed

Lines changed: 246 additions & 53 deletions

File tree

desktop/src/features/messages/lib/threadPanel.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type TimelineThreadSummaryParticipant = {
1616
export type TimelineThreadSummary = {
1717
threadHeadId: string;
1818
replyCount: number;
19+
lastReplyAt: number | null;
1920
participants: TimelineThreadSummaryParticipant[];
2021
};
2122

@@ -26,6 +27,7 @@ export type MainTimelineEntry = {
2627

2728
type ThreadDescendantStats = {
2829
descendantCount: number;
30+
lastReplyAt: number | null;
2931
recentParticipantsNewestFirst: TimelineThreadSummaryParticipant[];
3032
};
3133

@@ -80,6 +82,7 @@ function buildDescendantStatsByMessageId(
8082
message.id,
8183
{
8284
descendantCount: 0,
85+
lastReplyAt: null,
8386
recentParticipantsNewestFirst: [],
8487
},
8588
]),
@@ -115,6 +118,10 @@ function buildDescendantStatsByMessageId(
115118
}
116119

117120
ancestorStats.descendantCount += 1;
121+
ancestorStats.lastReplyAt = Math.max(
122+
ancestorStats.lastReplyAt ?? 0,
123+
message.createdAt,
124+
);
118125

119126
if (
120127
ancestorStats.recentParticipantsNewestFirst.length <
@@ -146,6 +153,7 @@ function buildSummaryForDirectReplies(
146153
return {
147154
threadHeadId: messageId,
148155
replyCount: descendantStats.descendantCount,
156+
lastReplyAt: descendantStats.lastReplyAt,
149157
participants: [...descendantStats.recentParticipantsNewestFirst].reverse(),
150158
};
151159
}

desktop/src/features/messages/ui/MessageRow.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import { cn } from "@/shared/lib/cn";
1010
import { UserAvatar } from "@/shared/ui/UserAvatar";
1111
import { useChannelNavigation } from "@/shared/context/ChannelNavigationContext";
1212
import { parseImetaTags } from "@/features/messages/lib/parseImeta";
13-
import { resolveMentionNames } from "@/shared/lib/resolveMentionNames";
13+
import {
14+
resolveMentionNames,
15+
resolveMentionPubkeysByName,
16+
} from "@/shared/lib/resolveMentionNames";
1417
import { Markdown } from "@/shared/ui/markdown";
1518
import { MessageActionBar } from "./MessageActionBar";
1619
import { MessageTimestamp } from "./MessageTimestamp";
@@ -64,6 +67,10 @@ export const MessageRow = React.memo(
6467
() => resolveMentionNames(message.tags, profiles),
6568
[profiles, message.tags],
6669
);
70+
const mentionPubkeysByName = React.useMemo(
71+
() => resolveMentionPubkeysByName(message.tags, profiles),
72+
[profiles, message.tags],
73+
);
6774

6875
const imetaByUrl = React.useMemo(
6976
() => (message.tags ? parseImetaTags(message.tags) : undefined),
@@ -119,6 +126,7 @@ export const MessageRow = React.memo(
119126
content={message.body}
120127
imetaByUrl={imetaByUrl}
121128
mentionNames={mentionNames}
129+
mentionPubkeysByName={mentionPubkeysByName}
122130
searchQuery={searchQuery}
123131
tight
124132
/>

desktop/src/features/messages/ui/MessageThreadSummaryRow.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ import type {
55
import type { TimelineMessage } from "@/features/messages/types";
66
import { UserAvatar } from "@/shared/ui/UserAvatar";
77

8+
function formatRelativeTime(unixSeconds: number): string {
9+
const now = Date.now() / 1_000;
10+
const diff = now - unixSeconds;
11+
12+
if (diff < 60) return "just now";
13+
if (diff < 3_600) return `${Math.floor(diff / 60)}m`;
14+
if (diff < 86_400) return `${Math.floor(diff / 3_600)}h`;
15+
if (diff < 604_800) return `${Math.floor(diff / 86_400)}d`;
16+
17+
return new Date(unixSeconds * 1_000).toLocaleDateString(undefined, {
18+
month: "short",
19+
day: "numeric",
20+
});
21+
}
22+
823
function ParticipantAvatar({
924
participant,
1025
index,
@@ -71,7 +86,7 @@ export function MessageThreadSummaryRow({
7186
) : null}
7287

7388
<button
74-
className="inline-flex w-fit max-w-full items-center gap-1 rounded-full border border-border/70 bg-muted/70 py-0.5 pl-0.5 pr-2 text-left text-xs font-medium text-foreground/90 transition-colors hover:bg-accent hover:text-accent-foreground"
89+
className="group inline-flex w-fit max-w-full items-center gap-1 text-left text-xs font-medium text-muted-foreground"
7590
data-thread-head-id={message.id}
7691
data-testid="message-thread-summary"
7792
onClick={() => onOpenThread(message)}
@@ -89,10 +104,15 @@ export function MessageThreadSummaryRow({
89104
</div>
90105
<div className="min-w-0">
91106
<div className="font-medium">
92-
<span>
107+
<span className="transition-colors group-hover:text-foreground">
93108
{summary.replyCount}{" "}
94109
{summary.replyCount === 1 ? "reply" : "replies"}
95110
</span>
111+
{summary.lastReplyAt ? (
112+
<span className="ml-1 text-muted-foreground/70">
113+
last {formatRelativeTime(summary.lastReplyAt)}
114+
</span>
115+
) : null}
96116
</div>
97117
</div>
98118
</button>

desktop/src/features/messages/ui/MessageTimeline.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({
135135
ref={scrollContainerRef}
136136
>
137137
<div
138-
className="flex w-full flex-col gap-2 pb-10 pt-16"
138+
className="flex w-full flex-col gap-2 pb-10 pt-12"
139139
ref={contentRef}
140140
>
141141
<div ref={topSentinelRef} aria-hidden className="h-px" />
@@ -152,7 +152,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({
152152
data-testid="message-timeline-beginning"
153153
>
154154
<Separator className="flex-1" />
155-
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-muted-foreground">
155+
<p className="text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground/75">
156156
Beginning of conversation
157157
</p>
158158
<Separator className="flex-1" />

0 commit comments

Comments
 (0)