Skip to content
Open
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
17 changes: 17 additions & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ const clientSettings: ClientSettings = {
proactivePanelsEnabled: true,
showSkillsInSlashMenu: false,
providerModelPreferences: {},
sidebarCompactThreadRows: false,
sidebarThreadRowLayoutMode: "custom",
sidebarActiveThreadLayoutId: "daily",
sidebarSavedThreadLayouts: [
{
id: "daily",
name: "Daily",
layout: [
{ component: "title", row: 1, alignment: "left" },
{ component: "status", row: 2, alignment: "right" },
],
},
],
sidebarThreadRowLayout: [
{ component: "title", row: 1, alignment: "left" },
{ component: "status", row: 2, alignment: "right" },
],
sidebarProjectGroupingMode: "repository_path",
sidebarProjectGroupingOverrides: {
"environment-1:/tmp/project-a": "separate",
Expand Down
110 changes: 72 additions & 38 deletions apps/web/src/components/AppSidebarLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
} from "../panelAnimations";
import LegacyThreadSidebar from "./LegacySidebar";
import ThreadSidebar from "./Sidebar";
import { ThreadListPreviewContext } from "./settings/ThreadListPreviewContext";
import { Button } from "./ui/button";
import { SettingsSidebarNav } from "./settings/SettingsSidebarNav";
import { SidebarChromeHeader } from "./sidebar/SidebarChrome";
import {
Expand All @@ -46,6 +48,26 @@ import {
} from "./ui/sidebar";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";

function ThreadListPreviewNavigation({ onClose }: { onClose: () => void }) {
const { isMobile, setOpenMobile } = useSidebar();
return (
<div className="border-b border-sidebar-border p-3">
<p className="mb-2 text-xs text-muted-foreground">Your threads · layout preview</p>
<Button
className="w-full"
size="sm"
variant="outline"
onClick={() => {
onClose();
if (isMobile) setOpenMobile(false);
}}
>
Back to settings navigation
</Button>
</div>
);
}

const MACOS_TRAFFIC_LIGHTS_LEFT_INSET = "90px";

function subscribeToViewportWidth(onChange: () => void): () => void {
Expand Down Expand Up @@ -153,6 +175,11 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) {
const routePanelAnimationsActive = panelAnimationsActive && !panelAnimationsSuppressed;
const isOnSettings = pathname === "/settings" || pathname.startsWith("/settings/");
const isMacosDesktop = isElectron && isMacPlatform(navigator.platform);
const [showThreadListPreview, setShowThreadListPreview] = useState(false);
const previewing = isOnSettings && showThreadListPreview;
useEffect(() => {
setShowThreadListPreview(false);
}, [pathname]);
const [sidebarWidth, setSidebarWidth] = useState(readInitialThreadSidebarWidth);
// Subscribed rather than read once: the clamp must track live window size,
// and a clamped drag ends with an unchanged width, which skips the re-render
Expand Down Expand Up @@ -219,44 +246,51 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) {
}, [navigate, pathname]);

return (
<PanelAnimationSuppressionProvider value={panelAnimationsSuppressed}>
<SidebarProvider
className="h-dvh! min-h-0!"
data-panel-animations={routePanelAnimationsActive ? "true" : "false"}
defaultOpen
style={sidebarProviderStyle}
>
<ProjectProjectionRetention />
<Sidebar
side="left"
collapsible="offcanvas"
data-app-sidebar=""
className="border-r border-sidebar-border bg-sidebar text-sidebar-foreground"
resizable={{
maxWidth: sidebarMaximumWidth,
minWidth: THREAD_SIDEBAR_MIN_WIDTH,
shouldAcceptWidth: ({ currentWidth, nextWidth, wrapper }) =>
nextWidth <= currentWidth ||
wrapper.clientWidth - nextWidth >= THREAD_MAIN_CONTENT_MIN_WIDTH,
storageKey: THREAD_SIDEBAR_WIDTH_STORAGE_KEY,
onResize: setSidebarWidth,
}}
<ThreadListPreviewContext value={{ showing: previewing, setShowing: setShowThreadListPreview }}>
<PanelAnimationSuppressionProvider value={panelAnimationsSuppressed}>
<SidebarProvider
className="h-dvh! min-h-0!"
data-panel-animations={routePanelAnimationsActive ? "true" : "false"}
defaultOpen
style={sidebarProviderStyle}
>
{isOnSettings ? (
<>
<SidebarChromeHeader isElectron={isElectron} />
<SettingsSidebarNav pathname={pathname} />
</>
) : legacySidebarEnabled ? (
<LegacyThreadSidebar />
) : (
<ThreadSidebar />
)}
<SidebarRail onDoubleClick={resetSidebarWidth} />
</Sidebar>
{children}
<SidebarControl />
</SidebarProvider>
</PanelAnimationSuppressionProvider>
<ProjectProjectionRetention />
<Sidebar
side="left"
collapsible="offcanvas"
data-app-sidebar=""
className="border-r border-sidebar-border bg-sidebar text-sidebar-foreground"
resizable={{
maxWidth: sidebarMaximumWidth,
minWidth: THREAD_SIDEBAR_MIN_WIDTH,
shouldAcceptWidth: ({ currentWidth, nextWidth, wrapper }) =>
nextWidth <= currentWidth ||
wrapper.clientWidth - nextWidth >= THREAD_MAIN_CONTENT_MIN_WIDTH,
storageKey: THREAD_SIDEBAR_WIDTH_STORAGE_KEY,
onResize: setSidebarWidth,
}}
>
{previewing ? (
<>
<ThreadListPreviewNavigation onClose={() => setShowThreadListPreview(false)} />
<ThreadSidebar />
</>
) : isOnSettings ? (
<>
<SidebarChromeHeader isElectron={isElectron} />
<SettingsSidebarNav pathname={pathname} />
</>
) : legacySidebarEnabled ? (
<LegacyThreadSidebar />
) : (
<ThreadSidebar />
)}
<SidebarRail onDoubleClick={resetSidebarWidth} />
</Sidebar>
{children}
<SidebarControl />
</SidebarProvider>
</PanelAnimationSuppressionProvider>
</ThreadListPreviewContext>
);
}
Loading
Loading