feat: update SessionSidebar to include new session creation and worktree handling
This commit is contained in:
@@ -37,7 +37,6 @@ import {
|
|||||||
RiErrorWarningLine,
|
RiErrorWarningLine,
|
||||||
RiFileCopyLine,
|
RiFileCopyLine,
|
||||||
RiGitBranchLine,
|
RiGitBranchLine,
|
||||||
RiGitRepositoryLine,
|
|
||||||
RiLinkUnlinkM,
|
RiLinkUnlinkM,
|
||||||
RiMore2Line,
|
RiMore2Line,
|
||||||
RiPencilAiLine,
|
RiPencilAiLine,
|
||||||
@@ -129,7 +128,7 @@ interface SortableProjectItemProps {
|
|||||||
mobileVariant: boolean;
|
mobileVariant: boolean;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
onHoverChange: (hovered: boolean) => void;
|
onHoverChange: (hovered: boolean) => void;
|
||||||
onOpenWorktreeManager: () => void;
|
onNewSession: () => void;
|
||||||
onOpenMultiRunLauncher: () => void;
|
onOpenMultiRunLauncher: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
sentinelRef: (el: HTMLDivElement | null) => void;
|
sentinelRef: (el: HTMLDivElement | null) => void;
|
||||||
@@ -150,7 +149,7 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
|||||||
mobileVariant,
|
mobileVariant,
|
||||||
onToggle,
|
onToggle,
|
||||||
onHoverChange,
|
onHoverChange,
|
||||||
onOpenWorktreeManager,
|
onNewSession,
|
||||||
onOpenMultiRunLauncher,
|
onOpenMultiRunLauncher,
|
||||||
onClose,
|
onClose,
|
||||||
sentinelRef,
|
sentinelRef,
|
||||||
@@ -233,17 +232,15 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="min-w-[180px]">
|
<DropdownMenuContent align="end" className="min-w-[180px]">
|
||||||
|
<DropdownMenuItem onClick={onNewSession}>
|
||||||
|
<RiAddLine className="mr-1.5 h-4 w-4" />
|
||||||
|
New Session
|
||||||
|
</DropdownMenuItem>
|
||||||
{isRepo && !hideDirectoryControls && (
|
{isRepo && !hideDirectoryControls && (
|
||||||
<>
|
<DropdownMenuItem onClick={onOpenMultiRunLauncher}>
|
||||||
<DropdownMenuItem onClick={onOpenWorktreeManager}>
|
<ArrowsMerge className="mr-1.5 h-4 w-4" />
|
||||||
<RiGitRepositoryLine className="mr-1.5 h-4 w-4" />
|
New Multi-Run
|
||||||
Manage Worktrees
|
</DropdownMenuItem>
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={onOpenMultiRunLauncher}>
|
|
||||||
<ArrowsMerge className="mr-1.5 h-4 w-4" />
|
|
||||||
New Multi-Run
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -377,6 +374,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
const worktreeMetadata = useSessionStore((state) => state.worktreeMetadata);
|
const worktreeMetadata = useSessionStore((state) => state.worktreeMetadata);
|
||||||
const availableWorktreesByProject = useSessionStore((state) => state.availableWorktreesByProject);
|
const availableWorktreesByProject = useSessionStore((state) => state.availableWorktreesByProject);
|
||||||
const getSessionsByDirectory = useSessionStore((state) => state.getSessionsByDirectory);
|
const getSessionsByDirectory = useSessionStore((state) => state.getSessionsByDirectory);
|
||||||
|
const openNewSessionDraft = useSessionStore((state) => state.openNewSessionDraft);
|
||||||
|
|
||||||
const [isDesktopRuntime, setIsDesktopRuntime] = React.useState<boolean>(() => {
|
const [isDesktopRuntime, setIsDesktopRuntime] = React.useState<boolean>(() => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
@@ -691,8 +689,30 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
// Check if this is a worktree session - if so, show confirmation dialog
|
// Check if this is a worktree session - if so, show confirmation dialog
|
||||||
const worktree = worktreeMetadata.get(session.id);
|
const worktree = worktreeMetadata.get(session.id);
|
||||||
if (worktree) {
|
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<string>();
|
||||||
|
|
||||||
|
// 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({
|
sessionEvents.requestDelete({
|
||||||
sessions: [session, ...descendants],
|
sessions: allWorktreeSessions,
|
||||||
mode: 'worktree',
|
mode: 'worktree',
|
||||||
worktree,
|
worktree,
|
||||||
});
|
});
|
||||||
@@ -719,17 +739,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[collectDescendants, deleteSession, deleteSessions, worktreeMetadata],
|
[collectDescendants, deleteSession, deleteSessions, worktreeMetadata, sessions],
|
||||||
);
|
|
||||||
|
|
||||||
const handleOpenWorktreeManager = React.useCallback(
|
|
||||||
(projectId?: string | null) => {
|
|
||||||
if (projectId && projectId !== activeProjectId) {
|
|
||||||
setActiveProjectIdOnly(projectId);
|
|
||||||
}
|
|
||||||
sessionEvents.requestCreate({ worktreeMode: 'create', projectId: projectId ?? null });
|
|
||||||
},
|
|
||||||
[activeProjectId, setActiveProjectIdOnly],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenDirectoryDialog = React.useCallback(() => {
|
const handleOpenDirectoryDialog = React.useCallback(() => {
|
||||||
@@ -1250,6 +1260,24 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{node.worktree ? (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
if (projectId && projectId !== activeProjectId) {
|
||||||
|
setActiveProject(projectId);
|
||||||
|
}
|
||||||
|
setActiveMainTab('chat');
|
||||||
|
if (mobileVariant) {
|
||||||
|
setSessionSwitcherOpen(false);
|
||||||
|
}
|
||||||
|
openNewSessionDraft({ directoryOverride: node.worktree?.path ?? null });
|
||||||
|
}}
|
||||||
|
className="[&>svg]:mr-1"
|
||||||
|
>
|
||||||
|
<RiGitBranchLine className="mr-1 h-4 w-4" />
|
||||||
|
New session in worktree
|
||||||
|
</DropdownMenuItem>
|
||||||
|
) : null}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="text-destructive focus:text-destructive [&>svg]:mr-1"
|
className="text-destructive focus:text-destructive [&>svg]:mr-1"
|
||||||
onClick={() => handleDeleteSession(session)}
|
onClick={() => handleDeleteSession(session)}
|
||||||
@@ -1289,6 +1317,11 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
handleDeleteSession,
|
handleDeleteSession,
|
||||||
copiedSessionId,
|
copiedSessionId,
|
||||||
mobileVariant,
|
mobileVariant,
|
||||||
|
activeProjectId,
|
||||||
|
setActiveProject,
|
||||||
|
setActiveMainTab,
|
||||||
|
setSessionSwitcherOpen,
|
||||||
|
openNewSessionDraft,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1497,7 +1530,16 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
|||||||
mobileVariant={mobileVariant}
|
mobileVariant={mobileVariant}
|
||||||
onToggle={() => toggleProject(projectKey)}
|
onToggle={() => toggleProject(projectKey)}
|
||||||
onHoverChange={(hovered) => setHoveredProjectId(hovered ? projectKey : null)}
|
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={() => {
|
onOpenMultiRunLauncher={() => {
|
||||||
if (projectKey !== activeProjectId) {
|
if (projectKey !== activeProjectId) {
|
||||||
setActiveProject(projectKey);
|
setActiveProject(projectKey);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { usePermissionStore } from "./permissionStore";
|
|||||||
import { opencodeClient } from "@/lib/opencode/client";
|
import { opencodeClient } from "@/lib/opencode/client";
|
||||||
import { useDirectoryStore } from "./useDirectoryStore";
|
import { useDirectoryStore } from "./useDirectoryStore";
|
||||||
import { useConfigStore } from "./useConfigStore";
|
import { useConfigStore } from "./useConfigStore";
|
||||||
|
import { useProjectsStore } from "./useProjectsStore";
|
||||||
import { EXECUTION_FORK_META_TEXT } from "@/lib/messages/executionMeta";
|
import { EXECUTION_FORK_META_TEXT } from "@/lib/messages/executionMeta";
|
||||||
import { flattenAssistantTextParts } from "@/lib/messages/messageText";
|
import { flattenAssistantTextParts } from "@/lib/messages/messageText";
|
||||||
|
|
||||||
@@ -114,10 +115,19 @@ export const useSessionStore = create<SessionStore>()(
|
|||||||
loadSessions: () => useSessionManagementStore.getState().loadSessions(),
|
loadSessions: () => useSessionManagementStore.getState().loadSessions(),
|
||||||
|
|
||||||
openNewSessionDraft: (options) => {
|
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({
|
set({
|
||||||
newSessionDraft: {
|
newSessionDraft: {
|
||||||
open: true,
|
open: true,
|
||||||
directoryOverride: options?.directoryOverride ?? null,
|
directoryOverride: directory,
|
||||||
parentID: options?.parentID ?? null,
|
parentID: options?.parentID ?? null,
|
||||||
title: options?.title,
|
title: options?.title,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user