From d6f74087c4ad785eb0ce203c8ab96de4a2cea055 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Thu, 8 Jan 2026 00:29:20 +0200 Subject: [PATCH] feat: update SessionSidebar to include new session creation and worktree handling --- .../src/components/session/SessionSidebar.tsx | 94 ++++++++++++++----- packages/ui/src/stores/useSessionStore.ts | 12 ++- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index 53d9875c..1e509d52 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -37,7 +37,6 @@ import { RiErrorWarningLine, RiFileCopyLine, RiGitBranchLine, - RiGitRepositoryLine, RiLinkUnlinkM, RiMore2Line, RiPencilAiLine, @@ -129,7 +128,7 @@ interface SortableProjectItemProps { mobileVariant: boolean; onToggle: () => void; onHoverChange: (hovered: boolean) => void; - onOpenWorktreeManager: () => void; + onNewSession: () => void; onOpenMultiRunLauncher: () => void; onClose: () => void; sentinelRef: (el: HTMLDivElement | null) => void; @@ -150,7 +149,7 @@ const SortableProjectItem: React.FC = ({ mobileVariant, onToggle, onHoverChange, - onOpenWorktreeManager, + onNewSession, onOpenMultiRunLauncher, onClose, sentinelRef, @@ -233,17 +232,15 @@ const SortableProjectItem: React.FC = ({ + + + New Session + {isRepo && !hideDirectoryControls && ( - <> - - - Manage Worktrees - - - - New Multi-Run - - + + + New Multi-Run + )} = ({ const worktreeMetadata = useSessionStore((state) => state.worktreeMetadata); const availableWorktreesByProject = useSessionStore((state) => state.availableWorktreesByProject); const getSessionsByDirectory = useSessionStore((state) => state.getSessionsByDirectory); + const openNewSessionDraft = useSessionStore((state) => state.openNewSessionDraft); const [isDesktopRuntime, setIsDesktopRuntime] = React.useState(() => { if (typeof window === 'undefined') { @@ -691,8 +689,30 @@ export const SessionSidebar: React.FC = ({ // Check if this is a worktree session - if so, show confirmation dialog const worktree = worktreeMetadata.get(session.id); if (worktree) { + // Find ALL sessions linked to this worktree (not just the triggered one) + const worktreePath = worktree.path; + const allWorktreeSessions: Session[] = []; + const sessionIdSet = new Set(); + + // Iterate through all sessions and find those linked to the same worktree + sessions.forEach((s) => { + const meta = worktreeMetadata.get(s.id); + if (meta && meta.path === worktreePath && !sessionIdSet.has(s.id)) { + allWorktreeSessions.push(s); + sessionIdSet.add(s.id); + // Also collect descendants of each worktree session + const sessionDescendants = collectDescendants(s.id); + sessionDescendants.forEach((desc) => { + if (!sessionIdSet.has(desc.id)) { + allWorktreeSessions.push(desc); + sessionIdSet.add(desc.id); + } + }); + } + }); + sessionEvents.requestDelete({ - sessions: [session, ...descendants], + sessions: allWorktreeSessions, mode: 'worktree', worktree, }); @@ -719,17 +739,7 @@ export const SessionSidebar: React.FC = ({ } } }, - [collectDescendants, deleteSession, deleteSessions, worktreeMetadata], - ); - - const handleOpenWorktreeManager = React.useCallback( - (projectId?: string | null) => { - if (projectId && projectId !== activeProjectId) { - setActiveProjectIdOnly(projectId); - } - sessionEvents.requestCreate({ worktreeMode: 'create', projectId: projectId ?? null }); - }, - [activeProjectId, setActiveProjectIdOnly], + [collectDescendants, deleteSession, deleteSessions, worktreeMetadata, sessions], ); const handleOpenDirectoryDialog = React.useCallback(() => { @@ -1250,6 +1260,24 @@ export const SessionSidebar: React.FC = ({ )} + {node.worktree ? ( + { + if (projectId && projectId !== activeProjectId) { + setActiveProject(projectId); + } + setActiveMainTab('chat'); + if (mobileVariant) { + setSessionSwitcherOpen(false); + } + openNewSessionDraft({ directoryOverride: node.worktree?.path ?? null }); + }} + className="[&>svg]:mr-1" + > + + New session in worktree + + ) : null} handleDeleteSession(session)} @@ -1289,6 +1317,11 @@ export const SessionSidebar: React.FC = ({ handleDeleteSession, copiedSessionId, mobileVariant, + activeProjectId, + setActiveProject, + setActiveMainTab, + setSessionSwitcherOpen, + openNewSessionDraft, ], ); @@ -1497,7 +1530,16 @@ export const SessionSidebar: React.FC = ({ mobileVariant={mobileVariant} onToggle={() => toggleProject(projectKey)} onHoverChange={(hovered) => setHoveredProjectId(hovered ? projectKey : null)} - onOpenWorktreeManager={() => handleOpenWorktreeManager(projectKey)} + onNewSession={() => { + if (projectKey !== activeProjectId) { + setActiveProject(projectKey); + } + setActiveMainTab('chat'); + if (mobileVariant) { + setSessionSwitcherOpen(false); + } + openNewSessionDraft({ directoryOverride: project.normalizedPath }); + }} onOpenMultiRunLauncher={() => { if (projectKey !== activeProjectId) { setActiveProject(projectKey); diff --git a/packages/ui/src/stores/useSessionStore.ts b/packages/ui/src/stores/useSessionStore.ts index 3cec172a..6516384f 100644 --- a/packages/ui/src/stores/useSessionStore.ts +++ b/packages/ui/src/stores/useSessionStore.ts @@ -14,6 +14,7 @@ import { usePermissionStore } from "./permissionStore"; import { opencodeClient } from "@/lib/opencode/client"; import { useDirectoryStore } from "./useDirectoryStore"; import { useConfigStore } from "./useConfigStore"; +import { useProjectsStore } from "./useProjectsStore"; import { EXECUTION_FORK_META_TEXT } from "@/lib/messages/executionMeta"; import { flattenAssistantTextParts } from "@/lib/messages/messageText"; @@ -114,10 +115,19 @@ export const useSessionStore = create()( loadSessions: () => useSessionManagementStore.getState().loadSessions(), openNewSessionDraft: (options) => { + // Use explicit directoryOverride if provided, otherwise use active project path + let directory: string | null = null; + if (options?.directoryOverride !== undefined) { + directory = options.directoryOverride; + } else { + const activeProject = useProjectsStore.getState().getActiveProject(); + directory = activeProject?.path ?? null; + } + set({ newSessionDraft: { open: true, - directoryOverride: options?.directoryOverride ?? null, + directoryOverride: directory, parentID: options?.parentID ?? null, title: options?.title, },