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
File renamed without changes.
2 changes: 1 addition & 1 deletion bin/pnpm
59 changes: 16 additions & 43 deletions desktop/src/app/AppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ChevronLeft, ChevronRight } from "lucide-react";
import * as React from "react";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { useQueryClient } from "@tanstack/react-query";
Expand All @@ -9,6 +8,7 @@ import {
AppShellOverlays,
type BrowseDialogType,
} from "@/app/AppShellOverlays";
import { AppTopChrome } from "@/app/AppTopChrome";
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { useBackForwardControls } from "@/app/navigation/useBackForwardControls";
import { useMarkAsReadShortcuts } from "@/app/useMarkAsReadShortcuts";
Expand Down Expand Up @@ -62,12 +62,7 @@ import type { Channel, RelayEvent, SearchHit } from "@/shared/api/types";
import { ChannelNavigationProvider } from "@/shared/context/ChannelNavigationContext";
import { hasPrimaryShortcutModifier } from "@/shared/lib/platform";
import { useMessageDeepLinks } from "@/shared/useMessageDeepLinks";
import { Button } from "@/shared/ui/button";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/shared/ui/sidebar";
import { SidebarInset, SidebarProvider } from "@/shared/ui/sidebar";

type AppView =
| "home"
Expand Down Expand Up @@ -174,7 +169,7 @@ export function AppShell() {

const [isChannelManagementOpen, setIsChannelManagementOpen] =
React.useState(false);
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
const [searchFocusRequest, setSearchFocusRequest] = React.useState(0);
const [browseDialogType, setBrowseDialogType] =
React.useState<BrowseDialogType>(null);
const [isNewDmOpen, setIsNewDmOpen] = React.useState(false);
Expand Down Expand Up @@ -365,7 +360,7 @@ export function AppShell() {
void refetchChannels();
}, [refetchChannels]);
const handleOpenSearch = React.useCallback(() => {
setIsSearchOpen(true);
setSearchFocusRequest((request) => request + 1);
void refetchChannels();
}, [refetchChannels]);

Expand Down Expand Up @@ -400,7 +395,6 @@ export function AppShell() {

const handleOpenSettings = React.useCallback(
(section: SettingsSection = DEFAULT_SETTINGS_SECTION) => {
setIsSearchOpen(false);
setIsChannelManagementOpen(false);
setSettingsSection(section);
setSettingsOpen(true);
Expand Down Expand Up @@ -659,36 +653,19 @@ export function AppShell() {
<HuddleProvider>
<div className="flex h-dvh flex-col overflow-hidden overscroll-none">
<SidebarProvider className="min-h-0 flex-1 overflow-hidden">
<div
aria-hidden="true"
className="fixed inset-x-0 top-0 z-20 h-10 cursor-default select-none"
data-tauri-drag-region
<AppTopChrome
canGoBack={canGoBack}
canGoForward={canGoForward}
channels={channels}
currentPubkey={identityQuery.data?.pubkey}
onGoBack={goBack}
onGoForward={goForward}
onOpenChannel={(channelId) => {
void goChannel(channelId);
}}
onOpenResult={handleOpenSearchResult}
searchFocusRequest={searchFocusRequest}
/>
<div className="fixed left-[80px] top-[9px] z-50 flex items-center gap-0.5">
<SidebarTrigger className="h-[22px] w-[22px] text-muted-foreground/70 hover:bg-muted/60 hover:text-foreground" />
<Button
aria-label="Go back"
className="h-[22px] w-[22px] text-muted-foreground/70 hover:bg-muted/60 hover:text-foreground"
data-testid="global-back"
disabled={!canGoBack}
onClick={goBack}
size="icon"
variant="ghost"
>
<ChevronLeft className="h-3 w-3" />
</Button>
<Button
aria-label="Go forward"
className="h-[22px] w-[22px] text-muted-foreground/70 hover:bg-muted/60 hover:text-foreground"
data-testid="global-forward"
disabled={!canGoForward}
onClick={goForward}
size="icon"
variant="ghost"
>
<ChevronRight className="h-3 w-3" />
</Button>
</div>
<AppSidebar
activeWorkspace={workspacesHook.activeWorkspace}
channels={sidebarChannels}
Expand Down Expand Up @@ -770,7 +747,6 @@ export function AppShell() {
});
await goChannel(directMessage.id);
}}
onOpenSearch={handleOpenSearch}
onSelectAgents={() => {
void goAgents();
}}
Expand Down Expand Up @@ -821,16 +797,13 @@ export function AppShell() {
channels={channels}
currentPubkey={identityQuery.data?.pubkey}
isChannelManagementOpen={isChannelManagementOpen}
isSearchOpen={isSearchOpen}
onBrowseChannelJoin={handleBrowseChannelJoin}
onBrowseDialogOpenChange={handleBrowseDialogOpenChange}
onChannelManagementOpenChange={setIsChannelManagementOpen}
onDeleteActiveChannel={() => {
setIsChannelManagementOpen(false);
void goHome({ replace: true });
}}
onOpenSearchResult={handleOpenSearchResult}
onSearchOpenChange={setIsSearchOpen}
onSelectChannel={(channelId) => {
void goChannel(channelId);
}}
Expand Down
26 changes: 1 addition & 25 deletions desktop/src/app/AppShellOverlays.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";

import type { Channel, SearchHit } from "@/shared/api/types";
import type { Channel } from "@/shared/api/types";

const ChannelBrowserDialog = React.lazy(async () => {
const module = await import("@/features/channels/ui/ChannelBrowserDialog");
Expand All @@ -12,11 +12,6 @@ const ChannelManagementSheet = React.lazy(async () => {
return { default: module.ChannelManagementSheet };
});

const SearchDialog = React.lazy(async () => {
const module = await import("@/features/search/ui/SearchDialog");
return { default: module.SearchDialog };
});

export type BrowseDialogType = "stream" | "forum" | null;

type AppShellOverlaysProps = {
Expand All @@ -25,13 +20,10 @@ type AppShellOverlaysProps = {
channels: Channel[];
currentPubkey?: string;
isChannelManagementOpen: boolean;
isSearchOpen: boolean;
onBrowseChannelJoin: (channelId: string) => Promise<void>;
onBrowseDialogOpenChange: (open: boolean) => void;
onChannelManagementOpenChange: (open: boolean) => void;
onDeleteActiveChannel: () => void;
onOpenSearchResult: (hit: SearchHit) => void;
onSearchOpenChange: (open: boolean) => void;
onSelectChannel: (channelId: string) => void;
};

Expand All @@ -41,13 +33,10 @@ export function AppShellOverlays({
channels,
currentPubkey,
isChannelManagementOpen,
isSearchOpen,
onBrowseChannelJoin,
onBrowseDialogOpenChange,
onChannelManagementOpenChange,
onDeleteActiveChannel,
onOpenSearchResult,
onSearchOpenChange,
onSelectChannel,
}: AppShellOverlaysProps) {
return (
Expand All @@ -65,19 +54,6 @@ export function AppShellOverlays({
</React.Suspense>
) : null}

{isSearchOpen ? (
<React.Suspense fallback={null}>
<SearchDialog
channels={channels}
currentPubkey={currentPubkey}
onOpenChannel={onSelectChannel}
onOpenResult={onOpenSearchResult}
onOpenChange={onSearchOpenChange}
open={true}
/>
</React.Suspense>
) : null}

{isChannelManagementOpen && activeChannel !== null ? (
<React.Suspense fallback={null}>
<ChannelManagementSheet
Expand Down
86 changes: 86 additions & 0 deletions desktop/src/app/AppTopChrome.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { ChevronLeft, ChevronRight } from "lucide-react";

import { TopbarSearch } from "@/features/search/ui/TopbarSearch";
import type { Channel, SearchHit } from "@/shared/api/types";
import { Button } from "@/shared/ui/button";
import { SidebarTrigger, useSidebar } from "@/shared/ui/sidebar";

type AppTopChromeProps = {
canGoBack: boolean;
canGoForward: boolean;
channels: Channel[];
currentPubkey?: string;
onGoBack: () => void;
onGoForward: () => void;
onOpenChannel: (channelId: string) => void;
onOpenResult: (hit: SearchHit) => void;
searchFocusRequest: number;
};

function GlobalTopDivider() {
const { state } = useSidebar();

return (
<div
aria-hidden="true"
className="pointer-events-none fixed right-0 top-10 z-50 h-px bg-border/35"
style={{ left: state === "expanded" ? "var(--sidebar-width)" : 0 }}
/>
);
}

export function AppTopChrome({
canGoBack,
canGoForward,
channels,
currentPubkey,
onGoBack,
onGoForward,
onOpenChannel,
onOpenResult,
searchFocusRequest,
}: AppTopChromeProps) {
return (
<>
<div
aria-hidden="true"
className="fixed inset-x-0 top-0 z-20 h-10 cursor-default select-none"
data-tauri-drag-region
/>
<GlobalTopDivider />
<div className="fixed left-[80px] top-[9px] z-[80] flex items-center gap-0.5">
<SidebarTrigger className="h-[22px] w-[22px] text-muted-foreground/70 hover:bg-muted/60 hover:text-foreground" />
<Button
aria-label="Go back"
className="h-[22px] w-[22px] text-muted-foreground/70 hover:bg-muted/60 hover:text-foreground"
data-testid="global-back"
disabled={!canGoBack}
onClick={onGoBack}
size="icon"
variant="ghost"
>
<ChevronLeft className="h-3 w-3" />
</Button>
<Button
aria-label="Go forward"
className="h-[22px] w-[22px] text-muted-foreground/70 hover:bg-muted/60 hover:text-foreground"
data-testid="global-forward"
disabled={!canGoForward}
onClick={onGoForward}
size="icon"
variant="ghost"
>
<ChevronRight className="h-3 w-3" />
</Button>
</div>
<TopbarSearch
channels={channels}
className="fixed left-1/2 top-[7px] z-[80] hidden w-[300px] max-w-[34vw] -translate-x-1/2 md:block lg:w-[360px] lg:max-w-[38vw] xl:w-[420px] xl:max-w-[42vw] 2xl:w-[480px] 2xl:max-w-[44vw]"
currentPubkey={currentPubkey}
focusRequest={searchFocusRequest}
onOpenChannel={onOpenChannel}
onOpenResult={onOpenResult}
/>
</>
);
}
3 changes: 3 additions & 0 deletions desktop/src/app/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ function HomeRouteComponent() {
<HomeScreen
availableChannelIds={availableChannelIds}
currentPubkey={identityQuery.data?.pubkey}
onOpenChannel={(channelId) => {
void goChannel(channelId);
}}
onOpenContext={(channelId, messageId) => {
void goChannel(channelId, { messageId });
}}
Expand Down
20 changes: 5 additions & 15 deletions desktop/src/features/agents/ui/AgentsScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from "react";

import { ChatHeader } from "@/features/chat/ui/ChatHeader";
import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";

const AgentsView = React.lazy(async () => {
Expand All @@ -10,19 +9,10 @@ const AgentsView = React.lazy(async () => {

export function AgentsScreen() {
return (
<>
<ChatHeader
description="Choose personas from Persona Catalog, create local ACP workers, and monitor the relay-visible agent directory."
mode="agents"
overlaysContent
title="Agents"
/>

<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
<React.Suspense fallback={<ViewLoadingFallback kind="agents" />}>
<AgentsView />
</React.Suspense>
</div>
</>
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
<React.Suspense fallback={<ViewLoadingFallback kind="agents" />}>
<AgentsView />
</React.Suspense>
</div>
);
}
28 changes: 21 additions & 7 deletions desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,7 @@ export function AgentSessionThreadPanel({
<>
{isOverlay && <OverlayPanelBackdrop onClose={onClose} />}
<aside
className={cn(
PANEL_BASE_CLASS,
!isOverlay && "pt-11",
isOverlay && PANEL_OVERLAY_CLASS,
)}
className={cn(PANEL_BASE_CLASS, isOverlay && PANEL_OVERLAY_CLASS)}
data-testid="agent-session-thread-panel"
style={{ width: `${widthPx}px` }}
>
Expand All @@ -94,7 +90,22 @@ export function AgentSessionThreadPanel({
</button>
)}

<div className="flex items-center gap-3 border-b border-border/70 px-4 py-2.5">
{!isOverlay ? (
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 top-0 z-40 h-[76px] bg-background/45 backdrop-blur-xl after:absolute after:bottom-0 after:left-0 after:top-10 after:w-px after:bg-border/80 supports-[backdrop-filter]:bg-background/35"
/>
) : null}

<div
className={cn(
"z-50 flex cursor-default select-none items-center gap-3 px-4",
isOverlay
? "relative min-h-[44px] shrink-0 border-b border-border/70 bg-background/70 py-2.5 backdrop-blur-xl supports-[backdrop-filter]:bg-background/55"
: "absolute inset-x-0 top-11 min-h-[32px] py-[4px]",
)}
data-tauri-drag-region
>
<Bot className="h-4 w-4 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1">
<h2 className="truncate text-sm font-semibold tracking-tight">
Expand Down Expand Up @@ -157,7 +168,10 @@ export function AgentSessionThreadPanel({
<div
ref={scrollRef}
onScroll={onScroll}
className="min-h-0 flex-1 overflow-y-auto px-3 py-4"
className={cn(
"min-h-0 flex-1 overflow-y-auto px-3 pb-4",
isOverlay ? "pt-4" : "pt-[76px]",
)}
>
<ManagedAgentSessionPanel
agent={agent}
Expand Down
Loading