fix(desktop): keep message actions hover-only (#844)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
thomaspblock
2026-06-04 11:07:36 -04:00
committed by GitHub
parent db46c42546
commit b3be9ecba7
9 changed files with 4 additions and 33 deletions

View File

@@ -91,7 +91,6 @@ type ChannelPaneProps = {
threadMessages: MainTimelineEntry[]; threadMessages: MainTimelineEntry[];
threadPanelWidthPx: number; threadPanelWidthPx: number;
threadTypingPubkeys: string[]; threadTypingPubkeys: string[];
threadReplyTargetId: string | null;
threadReplyTargetMessage: TimelineMessage | null; threadReplyTargetMessage: TimelineMessage | null;
threadScrollTargetId: string | null; threadScrollTargetId: string | null;
targetMessageId: string | null; targetMessageId: string | null;
@@ -160,7 +159,6 @@ export const ChannelPane = React.memo(function ChannelPane({
threadPanelWidthPx, threadPanelWidthPx,
threadScrollTargetId, threadScrollTargetId,
threadTypingPubkeys, threadTypingPubkeys,
threadReplyTargetId,
threadReplyTargetMessage, threadReplyTargetMessage,
typingPubkeys, typingPubkeys,
}: ChannelPaneProps) { }: ChannelPaneProps) {
@@ -307,7 +305,6 @@ export const ChannelPane = React.memo(function ChannelPane({
) : null} ) : null}
<MessageTimeline <MessageTimeline
channelId={activeChannel?.id} channelId={activeChannel?.id}
activeReplyTargetId={openThreadHeadId}
scrollContainerRef={timelineScrollRef} scrollContainerRef={timelineScrollRef}
currentPubkey={currentPubkey} currentPubkey={currentPubkey}
fetchOlder={fetchOlder} fetchOlder={fetchOlder}
@@ -456,7 +453,6 @@ export const ChannelPane = React.memo(function ChannelPane({
onToggleReaction={onToggleReaction} onToggleReaction={onToggleReaction}
onUnfollowThread={onUnfollowThread} onUnfollowThread={onUnfollowThread}
profiles={profiles} profiles={profiles}
replyTargetId={threadReplyTargetId}
replyTargetMessage={threadReplyTargetMessage} replyTargetMessage={threadReplyTargetMessage}
scrollTargetId={threadScrollTargetId} scrollTargetId={threadScrollTargetId}
canResetWidth={canResetThreadPanelWidth} canResetWidth={canResetThreadPanelWidth}

View File

@@ -551,7 +551,6 @@ export function ChannelScreen({
threadMessages={threadMessages} threadMessages={threadMessages}
threadPanelWidthPx={threadPanelWidthPx} threadPanelWidthPx={threadPanelWidthPx}
threadTypingPubkeys={threadTypingPubkeys} threadTypingPubkeys={threadTypingPubkeys}
threadReplyTargetId={threadReplyTargetId}
threadReplyTargetMessage={threadReplyTargetMessage} threadReplyTargetMessage={threadReplyTargetMessage}
threadScrollTargetId={threadScrollTargetId} threadScrollTargetId={threadScrollTargetId}
isJoining={joinChannelMutation.isPending} isJoining={joinChannelMutation.isPending}

View File

@@ -324,7 +324,6 @@ export function InboxDetailPane({
<div className="mx-6 my-3 border-t border-border/60" /> <div className="mx-6 my-3 border-t border-border/60" />
) : null} ) : null}
<InboxMessageRow <InboxMessageRow
activeReplyTargetId={replyTargetId}
canReply={canReply} canReply={canReply}
channelId={item.item.channelId} channelId={item.item.channelId}
isFocusHighlightVisible={isFocusHighlightVisible} isFocusHighlightVisible={isFocusHighlightVisible}

View File

@@ -27,7 +27,6 @@ function toTimelineMessage(message: InboxDisplayMessage): TimelineMessage {
} }
type InboxMessageRowProps = { type InboxMessageRowProps = {
activeReplyTargetId: string | null;
canReply: boolean; canReply: boolean;
/** Channel UUID for "Copy link" — passed straight through to MessageActionBar. */ /** Channel UUID for "Copy link" — passed straight through to MessageActionBar. */
channelId?: string | null; channelId?: string | null;
@@ -42,7 +41,6 @@ type InboxMessageRowProps = {
}; };
export function InboxMessageRow({ export function InboxMessageRow({
activeReplyTargetId,
canReply, canReply,
channelId = null, channelId = null,
isFocusHighlightVisible, isFocusHighlightVisible,
@@ -89,7 +87,6 @@ export function InboxMessageRow({
{canReply || canToggleReactions ? ( {canReply || canToggleReactions ? (
<div className="absolute right-2 top-1 z-10"> <div className="absolute right-2 top-1 z-10">
<MessageActionBar <MessageActionBar
activeReplyTargetId={activeReplyTargetId}
channelId={channelId} channelId={channelId}
message={timelineMessage} message={timelineMessage}
onReactionSelect={ onReactionSelect={

View File

@@ -257,7 +257,6 @@ function MoreActionsMenu({
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export function MessageActionBar({ export function MessageActionBar({
activeReplyTargetId = null,
channelId, channelId,
message, message,
onDelete, onDelete,
@@ -272,7 +271,6 @@ export function MessageActionBar({
reactionPending = false, reactionPending = false,
isFollowingThread, isFollowingThread,
}: { }: {
activeReplyTargetId?: string | null;
/** Channel UUID — required for the "Copy link" action; when omitted the /** Channel UUID — required for the "Copy link" action; when omitted the
* action is hidden (callers like the home inbox that lack the context). */ * action is hidden (callers like the home inbox that lack the context). */
channelId?: string | null; channelId?: string | null;
@@ -306,7 +304,6 @@ export function MessageActionBar({
return null; return null;
} }
const isReplyingToMessage = activeReplyTargetId === message.id;
const selectedReactionCount = reactions.filter( const selectedReactionCount = reactions.filter(
(reaction) => reaction.reactedByCurrentUser, (reaction) => reaction.reactedByCurrentUser,
).length; ).length;
@@ -318,7 +315,7 @@ export function MessageActionBar({
"opacity-100 sm:pointer-events-none sm:opacity-0", "opacity-100 sm:pointer-events-none sm:opacity-0",
"sm:group-hover/message:pointer-events-auto sm:group-hover/message:opacity-100", "sm:group-hover/message:pointer-events-auto sm:group-hover/message:opacity-100",
"sm:group-focus-within/message:pointer-events-auto sm:group-focus-within/message:opacity-100", "sm:group-focus-within/message:pointer-events-auto sm:group-focus-within/message:opacity-100",
isReplyingToMessage || isReactionPickerOpen || isDropdownOpen isReactionPickerOpen || isDropdownOpen
? "sm:pointer-events-auto sm:opacity-100" ? "sm:pointer-events-auto sm:opacity-100"
: "", : "",
)} )}
@@ -390,7 +387,7 @@ export function MessageActionBar({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
aria-label={isReplyingToMessage ? "Cancel reply" : "Reply"} aria-label="Reply"
className="h-6 w-6 rounded-full p-0" className="h-6 w-6 rounded-full p-0"
data-testid={`reply-message-${message.id}`} data-testid={`reply-message-${message.id}`}
onClick={() => { onClick={() => {
@@ -398,14 +395,12 @@ export function MessageActionBar({
}} }}
size="sm" size="sm"
type="button" type="button"
variant={isReplyingToMessage ? "secondary" : "ghost"} variant="ghost"
> >
<CornerUpLeft className="h-3 w-3" /> <CornerUpLeft className="h-3 w-3" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>Reply</TooltipContent>
{isReplyingToMessage ? "Cancel reply" : "Reply"}
</TooltipContent>
</Tooltip> </Tooltip>
) : null} ) : null}

View File

@@ -28,7 +28,6 @@ const NESTED_REPLY_OFFSET_PX = 28;
export const MessageRow = React.memo( export const MessageRow = React.memo(
function MessageRow({ function MessageRow({
activeReplyTargetId = null,
channelId = null, channelId = null,
highlighted = false, highlighted = false,
isFollowingThread, isFollowingThread,
@@ -44,7 +43,6 @@ export const MessageRow = React.memo(
profiles, profiles,
searchQuery, searchQuery,
}: { }: {
activeReplyTargetId?: string | null;
channelId?: string | null; channelId?: string | null;
highlighted?: boolean; highlighted?: boolean;
isFollowingThread?: boolean; isFollowingThread?: boolean;
@@ -212,7 +210,6 @@ export const MessageRow = React.memo(
const actionBarNode = ( const actionBarNode = (
<div className="absolute right-2 top-1 z-10"> <div className="absolute right-2 top-1 z-10">
<MessageActionBar <MessageActionBar
activeReplyTargetId={activeReplyTargetId}
channelId={channelId} channelId={channelId}
isFollowingThread={isFollowingThread} isFollowingThread={isFollowingThread}
message={message} message={message}
@@ -451,7 +448,6 @@ export const MessageRow = React.memo(
prev.message.role === next.message.role && prev.message.role === next.message.role &&
prev.message.personaDisplayName === next.message.personaDisplayName && prev.message.personaDisplayName === next.message.personaDisplayName &&
prev.highlighted === next.highlighted && prev.highlighted === next.highlighted &&
prev.activeReplyTargetId === next.activeReplyTargetId &&
prev.isFollowingThread === next.isFollowingThread && prev.isFollowingThread === next.isFollowingThread &&
prev.layoutVariant === next.layoutVariant && prev.layoutVariant === next.layoutVariant &&
prev.profiles === next.profiles && prev.profiles === next.profiles &&

View File

@@ -63,7 +63,6 @@ type MessageThreadPanelProps = {
remove: boolean, remove: boolean,
) => Promise<void>; ) => Promise<void>;
profiles?: UserProfileLookup; profiles?: UserProfileLookup;
replyTargetId: string | null;
replyTargetMessage: TimelineMessage | null; replyTargetMessage: TimelineMessage | null;
scrollTargetId: string | null; scrollTargetId: string | null;
threadHead: TimelineMessage | null; threadHead: TimelineMessage | null;
@@ -116,7 +115,6 @@ export function MessageThreadPanel({
onToggleReaction, onToggleReaction,
onUnfollowThread, onUnfollowThread,
profiles, profiles,
replyTargetId,
replyTargetMessage, replyTargetMessage,
scrollTargetId, scrollTargetId,
threadHead, threadHead,
@@ -275,7 +273,6 @@ export function MessageThreadPanel({
<div className="px-3 pb-1 pt-0" data-testid="message-thread-head"> <div className="px-3 pb-1 pt-0" data-testid="message-thread-head">
<div className="rounded-2xl"> <div className="rounded-2xl">
<MessageRow <MessageRow
activeReplyTargetId={replyTargetId}
channelId={channelId} channelId={channelId}
isFollowingThread={isFollowingThread} isFollowingThread={isFollowingThread}
layoutVariant="thread-reply" layoutVariant="thread-reply"
@@ -316,7 +313,6 @@ export function MessageThreadPanel({
key={entry.message.id} key={entry.message.id}
> >
<MessageRow <MessageRow
activeReplyTargetId={replyTargetId}
channelId={channelId} channelId={channelId}
layoutVariant="thread-reply" layoutVariant="thread-reply"
message={entry.message} message={entry.message}

View File

@@ -18,7 +18,6 @@ type MessageTimelineProps = {
isLoading?: boolean; isLoading?: boolean;
emptyTitle?: string; emptyTitle?: string;
emptyDescription?: string; emptyDescription?: string;
activeReplyTargetId?: string | null;
currentPubkey?: string; currentPubkey?: string;
fetchOlder?: () => Promise<void>; fetchOlder?: () => Promise<void>;
hasOlderMessages?: boolean; hasOlderMessages?: boolean;
@@ -58,7 +57,6 @@ export const MessageTimeline = React.memo(function MessageTimeline({
isLoading = false, isLoading = false,
emptyTitle = "No messages yet", emptyTitle = "No messages yet",
emptyDescription = "Send the first message to start the thread.", emptyDescription = "Send the first message to start the thread.",
activeReplyTargetId = null,
currentPubkey, currentPubkey,
fetchOlder, fetchOlder,
hasOlderMessages = true, hasOlderMessages = true,
@@ -192,7 +190,6 @@ export const MessageTimeline = React.memo(function MessageTimeline({
{!isLoading && messages.length > 0 ? ( {!isLoading && messages.length > 0 ? (
<TimelineMessageList <TimelineMessageList
activeReplyTargetId={activeReplyTargetId}
channelId={channelId} channelId={channelId}
currentPubkey={currentPubkey} currentPubkey={currentPubkey}
followThreadById={followThreadById} followThreadById={followThreadById}

View File

@@ -15,7 +15,6 @@ import { MessageThreadSummaryRow } from "./MessageThreadSummaryRow";
import { SystemMessageRow } from "./SystemMessageRow"; import { SystemMessageRow } from "./SystemMessageRow";
type TimelineMessageListProps = { type TimelineMessageListProps = {
activeReplyTargetId?: string | null;
channelId?: string | null; channelId?: string | null;
currentPubkey?: string; currentPubkey?: string;
followThreadById?: (rootId: string) => void; followThreadById?: (rootId: string) => void;
@@ -45,7 +44,6 @@ type TimelineMessageListProps = {
}; };
export const TimelineMessageList = React.memo(function TimelineMessageList({ export const TimelineMessageList = React.memo(function TimelineMessageList({
activeReplyTargetId = null,
channelId, channelId,
currentPubkey, currentPubkey,
followThreadById, followThreadById,
@@ -116,7 +114,6 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
)} )}
> >
<MessageRow <MessageRow
activeReplyTargetId={activeReplyTargetId}
channelId={channelId} channelId={channelId}
highlighted={false} highlighted={false}
isFollowingThread={ isFollowingThread={
@@ -165,7 +162,6 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
currentDayGroup?.elements.push( currentDayGroup?.elements.push(
<div key={message.id} className="flex flex-col gap-1"> <div key={message.id} className="flex flex-col gap-1">
<MessageRow <MessageRow
activeReplyTargetId={activeReplyTargetId}
channelId={channelId} channelId={channelId}
highlighted={message.id === highlightedMessageId || isSearchActive} highlighted={message.id === highlightedMessageId || isSearchActive}
message={message} message={message}