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
40 changes: 31 additions & 9 deletions desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function AgentSessionThreadPanel({
const avatarUrl = profiles?.[agent.pubkey.toLowerCase()]?.avatarUrl ?? null;
const isOverlay = useIsThreadPanelOverlay();
const isFloatingOverlay = isOverlay && !isSinglePanelView;
const usesChannelSplitChrome = !isOverlay && !isSinglePanelView;
useEscapeKey(onClose, isOverlay || isSinglePanelView);

const { ref: scrollRef, onScroll } = useStickToBottom<HTMLDivElement>();
Expand Down Expand Up @@ -111,7 +112,10 @@ export function AgentSessionThreadPanel({
{!isOverlay ? (
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[76px] bg-background/75 backdrop-blur-md before:absolute before:left-0 before:right-0 before:top-10 before:h-px before:bg-border/35 after:absolute after:bottom-0 after:-left-px after:top-10 after:w-px after:bg-border/80 supports-[backdrop-filter]:bg-background/65 dark:bg-background/45 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/35"
className={cn(
"pointer-events-none absolute inset-x-0 top-0 z-40 bg-background/75 backdrop-blur-md before:absolute before:left-0 before:right-0 before:top-10 before:h-px before:bg-border/35 after:absolute after:bottom-0 after:-left-px after:top-10 after:w-px after:bg-border/80 supports-[backdrop-filter]:bg-background/65 dark:bg-background/45 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/35",
usesChannelSplitChrome ? "h-[92px]" : "h-[76px]",
)}
/>
) : null}

Expand All @@ -122,7 +126,7 @@ export function AgentSessionThreadPanel({
? `relative ${PANEL_SINGLE_COLUMN_HEADER_LAYER_CLASS} -mb-[76px] min-h-[76px] shrink-0 gap-[10px] bg-background/80 pb-[4px] pl-[16px] pr-[8px] pt-[42px] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 sm:pl-[24px] sm:pr-[12px] dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55`
: isOverlay
? "relative z-50 min-h-[44px] shrink-0 gap-3 bg-background/80 px-3 py-[6px] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55"
: "absolute inset-x-0 top-[42px] z-50 min-h-[32px] gap-3 px-3 py-[4px]",
: "absolute inset-x-0 top-[48px] z-50 h-[32px] gap-[10px] py-0 pl-[16px] pr-[8px] sm:pr-[12px]",
)}
data-tauri-drag-region
>
Expand All @@ -139,7 +143,14 @@ export function AgentSessionThreadPanel({
displayName={agent.name}
size="xs"
/>
<h2 className="min-w-0 flex-1 translate-y-px truncate text-sm font-semibold leading-5 tracking-tight">
<h2
className={cn(
"min-w-0 flex-1 translate-y-px truncate font-semibold tracking-tight",
usesChannelSplitChrome
? "text-base leading-6"
: "text-sm leading-5",
)}
>
{agent.name}
</h2>
</div>
Expand Down Expand Up @@ -186,14 +197,22 @@ export function AgentSessionThreadPanel({
) : null}
<Button
aria-label="Close activity panel"
className="h-6 w-6 text-foreground hover:bg-muted/60 hover:text-foreground"
className={cn(
usesChannelSplitChrome
? "h-8 w-8 rounded-lg border border-border/40 text-muted-foreground hover:bg-muted/70 hover:text-foreground [&_svg]:size-5"
: "h-6 w-6 text-foreground hover:bg-muted/60 hover:text-foreground",
)}
data-testid="agent-session-close"
onClick={onClose}
size="icon"
type="button"
variant="ghost"
>
<X className="h-3.5 w-3.5" />
<X
className={cn(
usesChannelSplitChrome ? "size-5" : "h-3.5 w-3.5",
)}
/>
</Button>
</div>
</div>
Expand All @@ -204,10 +223,13 @@ export function AgentSessionThreadPanel({
onScroll={onScroll}
className={cn(
"min-h-0 flex-1 overflow-y-auto px-3 pb-4",
// Match MessageThreadPanel: single-panel mode has a 76px header
// (min-h-[76px] with -mb-[76px]), so the body must clear it. Only
// the floating overlay (44px header) uses the smaller pt-4.
isSinglePanelView ? "pt-[76px]" : isOverlay ? "pt-4" : "pt-[76px]",
// Single-panel mode keeps the 76px local header; split panes sit
// under the channel screen's 92px top chrome.
usesChannelSplitChrome
? "pt-[92px]"
: isOverlay
? "pt-4"
: "pt-[76px]",
)}
>
<ManagedAgentSessionPanel
Expand Down
22 changes: 11 additions & 11 deletions desktop/src/features/channels/ui/ChannelMembersBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,38 +84,38 @@ export function ChannelMembersBar({

return (
<React.Fragment>
<div className="flex items-center gap-1">
<div className="flex items-center gap-[6px]">
<Button
aria-label="Add agent"
className="h-7 w-7 rounded-full"
className="h-8 w-8 rounded-lg border border-border/40 text-muted-foreground hover:bg-muted/70 hover:text-foreground [&_svg]:size-5"
data-testid="channel-add-bot-trigger"
disabled={!canAddAgents}
onClick={() => {
setIsAddBotOpen(true);
}}
size="icon"
type="button"
variant="outline"
variant="ghost"
>
<Plus className="h-3 w-3" />
<Plus className="size-5" />
</Button>

<Button
aria-label={`View channel members (${memberCount})`}
className="h-7 gap-1 rounded-full px-2"
className="h-8 gap-1 rounded-lg border border-border/40 px-2.5 text-muted-foreground hover:bg-muted/70 hover:text-foreground [&_svg]:size-5"
data-testid="channel-members-trigger"
onClick={onToggleMembers}
type="button"
variant="outline"
variant="ghost"
>
<Users className="h-3 w-3" />
<Users className="size-5" />
<span className="min-w-[1ch] text-[11px] font-medium tabular-nums">
{memberCount}
</span>
</Button>

<HuddleIndicator
className="h-7 w-7"
className="h-8 w-8"
channelId={channel.id}
onStart={async () => {
try {
Expand All @@ -132,14 +132,14 @@ export function ChannelMembersBar({

<Button
aria-label="Manage channel"
className="h-7 w-7 rounded-full"
className="h-8 w-8 rounded-lg border border-border/40 text-muted-foreground hover:bg-muted/70 hover:text-foreground [&_svg]:size-5"
data-testid="channel-management-trigger"
onClick={onManageChannel}
size="icon"
type="button"
variant="outline"
variant="ghost"
>
<Settings2 className="h-3 w-3" />
<Settings2 className="size-5" />
</Button>
</div>

Expand Down
17 changes: 16 additions & 1 deletion desktop/src/features/channels/ui/ChannelScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ import { useChannelFind } from "@/features/search/useChannelFind";
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
import { AgentSessionProvider } from "@/shared/context/AgentSessionContext";
import { ProfilePanelProvider } from "@/shared/context/ProfilePanelContext";
import { useElementWidthBreakpoint } from "@/shared/hooks/use-mobile";
import {
useElementWidthBreakpoint,
useIsThreadPanelOverlay,
} from "@/shared/hooks/use-mobile";
import {
THREAD_PANEL_MIN_WIDTH_PX,
THREAD_PANEL_SINGLE_COLUMN_BREAKPOINT_PX,
useThreadPanelWidth,
} from "@/shared/hooks/useThreadPanelWidth";
Expand Down Expand Up @@ -87,6 +91,7 @@ export function ChannelScreen({
widthPx: threadPanelWidthPx,
} = useThreadPanelWidth();
const [isMembersSidebarOpen, setIsMembersSidebarOpen] = React.useState(false);
const isThreadPanelOverlay = useIsThreadPanelOverlay();
const [channelContentRef, isNarrowPanelViewport] =
useElementWidthBreakpoint<HTMLDivElement>(
THREAD_PANEL_SINGLE_COLUMN_BREAKPOINT_PX,
Expand Down Expand Up @@ -435,6 +440,15 @@ export function ChannelScreen({
Boolean(
openThreadHeadMessage || openAgentSessionPubkey || profilePanelPubkey,
);
const hasSplitRightPanel =
!isSinglePanelView &&
!isThreadPanelOverlay &&
Boolean(
openThreadHeadMessage || openAgentSessionPubkey || profilePanelPubkey,
);
const headerActionsRightInset = hasSplitRightPanel
? `min(${threadPanelWidthPx}px, calc(100% - ${THREAD_PANEL_MIN_WIDTH_PX}px))`
: undefined;

return (
<AgentSessionProvider onOpenAgentSession={handleOpenAgentSession}>
Expand All @@ -443,6 +457,7 @@ export function ChannelScreen({
activeChannel={activeChannel}
activeChannelEphemeralDisplay={activeChannelEphemeralDisplay}
activeChannelTitle={activeChannelTitle}
actionsRightInset={headerActionsRightInset}
activeDmPresenceStatus={activeDmPresenceStatus}
currentPubkey={currentPubkey}
isJoining={joinChannelMutation.isPending}
Expand Down
4 changes: 3 additions & 1 deletion desktop/src/features/channels/ui/ChannelScreenHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type ChannelScreenHeaderProps = {
activeChannel: Channel | null;
activeChannelEphemeralDisplay: EphemeralChannelDisplay | null;
activeChannelTitle: string;
actionsRightInset?: string;
activeDmPresenceStatus: PresenceStatus | null;
currentPubkey?: string;
isJoining?: boolean;
Expand All @@ -27,6 +28,7 @@ export function ChannelScreenHeader({
activeChannel,
activeChannelEphemeralDisplay,
activeChannelTitle,
actionsRightInset,
activeDmPresenceStatus,
currentPubkey,
isJoining = false,
Expand Down Expand Up @@ -79,10 +81,10 @@ export function ChannelScreenHeader({

return (
<ChatHeader
actionsPlacement="top-right"
belowSystemChrome
density="compact"
actions={actions}
actionsRightInset={actionsRightInset}
channelType={activeChannel?.channelType}
description={getChannelDescription(activeChannel)}
statusBadge={
Expand Down
19 changes: 12 additions & 7 deletions desktop/src/features/chat/ui/ChatHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { cn } from "@/shared/lib/cn";
type ChatHeaderProps = {
actions?: React.ReactNode;
actionsPlacement?: "inline" | "top-right";
actionsRightInset?: string;
belowSystemChrome?: boolean;
density?: "default" | "compact";
title: string;
Expand Down Expand Up @@ -80,6 +81,7 @@ function ChannelIcon({
export function ChatHeader({
actions,
actionsPlacement = "inline",
actionsRightInset,
belowSystemChrome = false,
density = "default",
title,
Expand All @@ -101,10 +103,8 @@ export function ChatHeader({
const header = (
<header
className={cn(
"relative z-30 flex min-w-0 shrink-0 cursor-default select-none items-center gap-[10px] bg-transparent pl-[16px] pr-[8px] transition-[margin,padding] duration-200 ease-linear sm:pl-[24px] sm:pr-[12px]",
density === "compact"
? "min-h-[32px] py-[4px]"
: "min-h-[44px] py-[6px]",
"pointer-events-auto relative z-30 flex min-w-0 shrink-0 cursor-default select-none items-center gap-[10px] bg-transparent pl-[16px] pr-[8px] transition-[margin,padding] duration-200 ease-linear sm:pl-[24px] sm:pr-[12px]",
density === "compact" ? "h-[32px] py-0" : "min-h-[44px] py-[6px]",
overlaysContent && !belowSystemChrome && "-mb-[44px]",
)}
data-testid="chat-header"
Expand All @@ -118,7 +118,7 @@ export function ChatHeader({
visibility={visibility}
/>
<h1
className="min-w-0 translate-y-px truncate text-sm font-semibold leading-5 tracking-tight"
className="min-w-0 translate-y-px truncate text-base font-semibold leading-6 tracking-tight"
data-testid="chat-title"
title={trimmedDescription || undefined}
>
Expand All @@ -137,7 +137,12 @@ export function ChatHeader({
createPortal(topRightActions, document.body)
)
) : (
<div className="flex shrink-0 items-center gap-1">
<div
className="flex shrink-0 items-center gap-1"
style={
actionsRightInset ? { marginRight: actionsRightInset } : undefined
}
>
<UpdateIndicator />
{actions ? <div className="shrink-0">{actions}</div> : null}
</div>
Expand All @@ -150,7 +155,7 @@ export function ChatHeader({
}

return (
<div className="relative z-30 h-[76px] -mb-[76px] bg-background/80 pt-[42px] backdrop-blur-md supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55">
<div className="pointer-events-none relative z-30 h-[92px] -mb-[92px] bg-background/80 pb-[9px] pt-[48px] backdrop-blur-md after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-border/35 after:content-[''] supports-[backdrop-filter]:bg-background/70 dark:bg-background/70 dark:backdrop-blur-xl dark:supports-[backdrop-filter]:bg-background/55">
{header}
</div>
);
Expand Down
20 changes: 13 additions & 7 deletions desktop/src/features/huddle/components/HuddleIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,18 @@ export function HuddleIndicator({
<>
<Button
aria-label="Start huddle"
className={cn("h-7 w-7 rounded-full", className)}
className={cn(
"h-8 w-8 rounded-lg border border-border/40 text-muted-foreground hover:bg-muted/70 hover:text-foreground [&_svg]:size-5",
className,
)}
data-testid="channel-start-huddle-trigger"
disabled={startDisabled || isStarting}
onClick={() => headphonesGate.gate(() => onStart())}
size="icon"
type="button"
variant="outline"
variant="ghost"
>
<Headphones className="h-3 w-3" />
<Headphones className="size-5" />
</Button>
{gateDialog}
</>
Expand Down Expand Up @@ -270,15 +273,18 @@ export function HuddleIndicator({
<TooltipTrigger asChild>
<Button
aria-label={`Join active huddle (${participantCount} participant${participantCount !== 1 ? "s" : ""})`}
className={cn("relative h-7 w-7 rounded-full", className)}
className={cn(
"relative h-8 w-8 rounded-lg border border-border/40 text-muted-foreground hover:bg-muted/70 hover:text-foreground [&_svg]:size-5",
className,
)}
disabled={isJoining || isStarting}
onClick={() => headphonesGate.gate(() => void doJoin())}
size="icon"
type="button"
variant="outline"
variant="ghost"
>
<Headphones className="h-3 w-3 text-muted-foreground" />
<span className="absolute inset-0 animate-pulse rounded-full ring-2 ring-border/70" />
<Headphones className="size-5 text-muted-foreground" />
<span className="absolute inset-0 animate-pulse rounded-lg ring-2 ring-border/70" />
{/* Participant count badge */}
{participantCount > 0 && (
<span className="absolute -right-1 -top-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full border border-border bg-background px-0.5 text-[9px] font-bold text-muted-foreground">
Expand Down
4 changes: 2 additions & 2 deletions desktop/src/features/messages/ui/DayDivider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ export function DayDivider({ label }: { label: string }) {
return (
<section
aria-label={label}
className="sticky top-[76px] z-[5] flex justify-center py-1"
className="sticky top-[92px] z-[5] flex justify-center py-1"
data-testid="message-timeline-day-divider"
data-day-label={label}
>
<p className="shrink-0 rounded-lg border border-border/70 bg-background/95 px-2 py-1 text-[10px] font-medium tracking-[0.02em] text-muted-foreground/70 shadow-xs backdrop-blur-sm">
<p className="relative z-10 shrink-0 rounded-lg border border-border/70 bg-background/95 px-2 py-1 text-[10px] font-medium tracking-[0.02em] text-muted-foreground/70 shadow-xs backdrop-blur-sm">
{label}
</p>
</section>
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/features/messages/ui/MessageReactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";

const REACTION_PILL_BASE_CLASSES =
"inline-flex h-8 items-center rounded-full border text-xs font-medium leading-none transition-colors";
const REACTION_GLYPH_CLASSES = "-translate-y-px h-3.5 w-3.5 text-sm";
const REACTION_GLYPH_CLASSES = "h-3.5 w-3.5 translate-y-px text-sm";
const REACTION_PILL_HOVER_CLASSES =
"hover:bg-primary/10 hover:text-foreground focus-visible:bg-primary/10 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring";

Expand Down
Loading