Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 39 additions & 39 deletions desktop/src/features/channels/ui/ChannelPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,16 +328,6 @@ export const ChannelPane = React.memo(function ChannelPane({
searchQuery={channelFind.query}
targetMessageId={targetMessageId}
/>
{hasTypingActivity ? (
<div className="relative bg-background">
<TypingIndicatorRow
channel={activeChannel}
currentPubkey={currentPubkey}
profiles={profiles}
typingPubkeys={typingPubkeys}
/>
</div>
) : null}
{isNonMemberView ? (
<div
data-testid="join-banner"
Expand Down Expand Up @@ -365,37 +355,47 @@ export const ChannelPane = React.memo(function ChannelPane({
</Button>
</div>
) : (
<div className="relative z-10 shrink-0">
<MessageComposer
channelId={activeChannel?.id ?? null}
channelName={activeChannel?.name ?? "channel"}
disabled={isComposerDisabled}
editTarget={editTarget}
isSending={isSending}
onCancelEdit={onCancelEdit}
onEditSave={onEditSave}
onSend={onSendMessage}
profiles={profiles}
toolbarExtraActions={
<BotActivityComposerAction
agents={activityAgents}
onOpenAgentSession={onOpenAgentSession}
openAgentSessionPubkey={openAgentSessionPubkey}
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-10">
<div className="pointer-events-auto">
{hasTypingActivity ? (
<TypingIndicatorRow
channel={activeChannel}
currentPubkey={currentPubkey}
profiles={profiles}
typingBotPubkeys={composerBotTypingPubkeys}
typingPubkeys={typingPubkeys}
/>
}
placeholder={
activeChannel?.archivedAt
? "Archived channels are read-only."
: activeChannel?.channelType === "forum"
? "Forum posting is not wired in this pass."
: activeChannel
? `Message #${activeChannel.name}`
: "Select a channel"
}
showTopBorder={false}
/>
) : null}
<MessageComposer
channelId={activeChannel?.id ?? null}
channelName={activeChannel?.name ?? "channel"}
disabled={isComposerDisabled}
editTarget={editTarget}
isSending={isSending}
onCancelEdit={onCancelEdit}
onEditSave={onEditSave}
onSend={onSendMessage}
profiles={profiles}
toolbarExtraActions={
<BotActivityComposerAction
agents={activityAgents}
onOpenAgentSession={onOpenAgentSession}
openAgentSessionPubkey={openAgentSessionPubkey}
profiles={profiles}
typingBotPubkeys={composerBotTypingPubkeys}
/>
}
placeholder={
activeChannel?.archivedAt
? "Archived channels are read-only."
: activeChannel?.channelType === "forum"
? "Forum posting is not wired in this pass."
: activeChannel
? `Message #${activeChannel.name}`
: "Select a channel"
}
showTopBorder={false}
/>
</div>
</div>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/features/messages/ui/DayDivider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export function DayDivider({ label }: { label: string }) {
return (
<section
aria-label={label}
className="sticky top-0 z-30 flex justify-center py-1"
className="sticky top-0 z-[1] flex justify-center py-1"
data-testid="message-timeline-day-divider"
data-day-label={label}
>
Expand Down
10 changes: 7 additions & 3 deletions desktop/src/features/messages/ui/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -544,13 +544,17 @@ export function MessageComposer({
return (
<footer
className={cn(
"shrink-0 bg-transparent px-4 pb-4 pt-0",
"relative shrink-0 bg-transparent px-4 pb-4 pt-0",
showTopBorder ? "border-t border-border/40 pt-3" : "",
)}
>
<div className="mx-auto flex w-full max-w-4xl flex-col gap-3">
<div
aria-hidden="true"
className="absolute inset-x-0 bottom-0 h-5 bg-background"
/>
<div className="relative mx-auto flex w-full max-w-4xl flex-col gap-3">
<form
className="relative isolate rounded-2xl border border-border/50 bg-background/25 px-3 py-4 shadow-[0_4px_24px_rgba(0,0,0,0.08)] backdrop-blur-xl supports-[backdrop-filter]:bg-background/20 dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] sm:px-4"
className="relative isolate rounded-2xl border border-border/50 bg-background/70 px-3 py-3 shadow-[0_4px_24px_rgba(0,0,0,0.08)] backdrop-blur-xl supports-[backdrop-filter]:bg-background/55 dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] sm:px-4"
data-testid="message-composer"
onDragOver={media.handleDragOver}
onDrop={(e) => {
Expand Down
4 changes: 2 additions & 2 deletions desktop/src/features/messages/ui/MessageComposerToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export const MessageComposerToolbar = React.memo(
sendDisabled: boolean;
}) {
return (
<div className="mt-3 flex flex-wrap items-center justify-between gap-3">
<div className="flex min-w-0 flex-1 items-center gap-1 py-1 min-h-11">
<div className="mt-2 flex flex-wrap items-center justify-between gap-3">
<div className="flex min-h-10 min-w-0 flex-1 items-center gap-1 py-1">
{/*
* AnimatePresence with mode="popLayout" — exiting elements
* are popped out of flow immediately so entering elements
Expand Down
8 changes: 4 additions & 4 deletions desktop/src/features/messages/ui/MessageRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const MessageRow = React.memo(
const guideBleedPx = isThreadReplyLayout ? 4 : 0;
const avatarSizeClass = isThreadReplyLayout
? "!h-5 !w-5 !rounded-md"
: "!h-8 !w-8";
: "!h-9 !w-9";
const avatarButtonRadiusClass = isThreadReplyLayout
? "rounded-md"
: "rounded-xl";
Expand Down Expand Up @@ -321,8 +321,8 @@ export const MessageRow = React.memo(
) : (
<div className="flex shrink-0 items-start">{avatarNode}</div>
)}
<div className="-mt-1 min-w-0 flex-1 space-y-0.5">
<div className="flex min-w-0 flex-wrap items-start gap-x-2 gap-y-0.5">
<div className="-mt-1 min-w-0 flex-1 space-y-0">
<div className="flex min-w-0 flex-wrap items-start gap-x-2 gap-y-0">
{message.pubkey ? (
<UserProfilePopover
pubkey={message.pubkey}
Expand All @@ -347,7 +347,7 @@ export const MessageRow = React.memo(
) : null}
{metadataNode}
</div>
{messageBodyNode}
<div className="-mt-0.5">{messageBodyNode}</div>
</div>
</>
)}
Expand Down
61 changes: 32 additions & 29 deletions desktop/src/features/messages/ui/MessageThreadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,12 @@ export function MessageThreadPanel({
</div>

<div
className="min-h-0 flex-1 overflow-y-auto pb-6"
className="min-h-0 flex-1 overflow-y-auto pb-24"
data-testid="message-thread-body"
onScroll={syncScrollState}
ref={threadBodyRef}
>
<div ref={contentRef}>
<div className="pb-8" ref={contentRef}>
<div className="px-3 pb-1 pt-0" data-testid="message-thread-head">
<div className="rounded-2xl">
<MessageRow
Expand Down Expand Up @@ -273,46 +273,49 @@ export function MessageThreadPanel({
</div>

{!isAtBottom ? (
<div className="pointer-events-none absolute inset-x-0 bottom-16 flex justify-center px-4">
<div className="pointer-events-none absolute inset-x-0 bottom-32 z-20 flex justify-center px-4">
<Button
className="pointer-events-auto rounded-full shadow-lg"
className="pointer-events-auto h-7 min-h-7 gap-1.5 rounded-full border-border/50 bg-background/85 px-2.5 text-[11px] font-medium text-muted-foreground shadow-sm backdrop-blur-sm hover:bg-muted/70 hover:text-foreground [&_svg]:size-3.5"
data-testid="thread-scroll-to-latest"
onClick={() => scrollToBottom("smooth")}
size="sm"
type="button"
variant="outline"
>
<ArrowDown className="h-4 w-4" />
<ArrowDown aria-hidden />
{newMessageCount > 0
? `${newMessageCount} new message${newMessageCount === 1 ? "" : "s"}`
: "Jump to latest"}
</Button>
</div>
) : null}

<div>
<TypingIndicatorRow
channel={channel}
currentPubkey={currentPubkey}
profiles={profiles}
typingPubkeys={threadTypingPubkeys}
/>
<MessageComposer
channelId={channelId}
channelName={channelName}
disabled={disabled || isSending || !channelId}
draftKey={`thread:${threadHead.id}`}
editTarget={editTarget}
isSending={isSending}
onCancelEdit={onCancelEdit}
onCancelReply={composerReplyTarget ? onCancelReply : undefined}
onEditSave={onEditSave}
onSend={onSend}
placeholder={`Reply in thread to ${threadHead.author}`}
replyTarget={composerReplyTarget}
toolbarExtraActions={toolbarExtraActions}
typingParentEventId={threadHead.id}
typingRootEventId={threadHead.rootId}
/>
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-10">
<div className="pointer-events-auto">
<TypingIndicatorRow
channel={channel}
currentPubkey={currentPubkey}
profiles={profiles}
typingPubkeys={threadTypingPubkeys}
/>
<MessageComposer
channelId={channelId}
channelName={channelName}
disabled={disabled || isSending || !channelId}
draftKey={`thread:${threadHead.id}`}
editTarget={editTarget}
isSending={isSending}
onCancelEdit={onCancelEdit}
onCancelReply={composerReplyTarget ? onCancelReply : undefined}
onEditSave={onEditSave}
onSend={onSend}
placeholder={`Reply in thread to ${threadHead.author}`}
replyTarget={composerReplyTarget}
toolbarExtraActions={toolbarExtraActions}
typingParentEventId={threadHead.id}
typingRootEventId={threadHead.rootId}
/>
</div>
</div>
</aside>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function MessageThreadSummaryRow({
);

return (
<div className="relative pb-1">
<div className="relative pb-1 pt-1">
{depthGuideOffsets.length > 0 ? (
<div
aria-hidden
Expand All @@ -71,7 +71,7 @@ export function MessageThreadSummaryRow({
) : null}

<button
className="-mt-1 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"
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"
data-thread-head-id={message.id}
data-testid="message-thread-summary"
onClick={() => onOpenThread(message)}
Expand Down
4 changes: 2 additions & 2 deletions desktop/src/features/messages/ui/MessageTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({
<TooltipProvider delayDuration={200}>
<div className="relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
<div
className="absolute inset-0 overflow-y-auto overflow-x-hidden overscroll-contain px-4 pb-12 pt-1 [overflow-anchor:none] sm:px-6"
className="absolute inset-0 overflow-y-auto overflow-x-hidden overscroll-contain px-4 pb-24 pt-1 [overflow-anchor:none] sm:px-6"
data-scroll-restoration-id="message-timeline"
data-testid="message-timeline"
onScroll={syncScrollState}
Expand Down Expand Up @@ -197,7 +197,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({
</div>

{!isAtBottom ? (
<div className="pointer-events-none absolute inset-x-0 bottom-12 z-20 flex justify-center px-4">
<div className="pointer-events-none absolute inset-x-0 bottom-32 z-20 flex justify-center px-4">
<Button
className="pointer-events-auto h-7 min-h-7 gap-1.5 rounded-full border-border/50 bg-background/85 px-2.5 text-[11px] font-medium text-muted-foreground shadow-sm backdrop-blur-sm hover:bg-muted/70 hover:text-foreground [&_svg]:size-3.5"
data-testid="message-scroll-to-latest"
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/features/messages/ui/SystemMessageRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export const SystemMessageRow = React.memo(function SystemMessageRow({
<div className="flex items-start gap-2.5">
<UserAvatar
avatarUrl={resolveAvatarUrl(avatarPubkey, profiles)}
className="!h-8 !w-8 shrink-0 text-[10px]"
className="!h-9 !w-9 shrink-0 text-[10px]"
displayName={avatarLabel}
testId="system-message-avatar"
/>
Expand Down
6 changes: 3 additions & 3 deletions desktop/tests/e2e/messaging.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ test("opens a single-level thread panel with inline expansion", async ({
return body.scrollHeight - body.clientHeight;
});
})
.toBeGreaterThan(160);
.toBeGreaterThanOrEqual(160);

await expect(
timeline.getByTestId("message-row").filter({ hasText: firstReply }),
Expand Down Expand Up @@ -393,7 +393,7 @@ test("opens a single-level thread panel with inline expansion", async ({
return rowRect.top - bodyRect.top;
});
})
.toBeLessThan(160);
.toBeLessThanOrEqual(160);

const firstReplyId = await firstReplyRow.getAttribute("data-message-id");
if (!firstReplyId) {
Expand Down Expand Up @@ -447,7 +447,7 @@ test("opens a single-level thread panel with inline expansion", async ({
return rowRect.top - bodyRect.top;
});
})
.toBeLessThan(160);
.toBeLessThanOrEqual(160);

await firstReplySummaryRow.click();
await expect(
Expand Down
2 changes: 1 addition & 1 deletion desktop/tests/e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ test("does not shift the timeline when the composer grows", async ({
await page.waitForTimeout(1200);

const after = await getTimelineMetrics(page);
expect(after.clientHeight).toBeLessThan(before.clientHeight);
expect(after.clientHeight).toBeLessThanOrEqual(before.clientHeight);
expect(Math.abs(after.scrollTop - before.scrollTop)).toBeLessThanOrEqual(2);
expect(after.distanceFromBottom).toBeGreaterThan(160);
});
Loading