feat: add new session button and integrate worktree session creation logic

This commit is contained in:
Bohdan Triapitsyn
2026-01-08 01:48:42 +02:00
parent 8902662f74
commit cc09f802ce
2 changed files with 33 additions and 31 deletions
+12 -6
View File
@@ -4,12 +4,18 @@ All notable changes to this project will be documented in this file.
## [Unreleased] ## [Unreleased]
- Multi Run / Agent Manager: Select the Agent you want to run for the Worktree sessions (thanks to @wienans). - Agent Manager / Multi Run: select agent per worktree session (thanks to @wienans).
- Agent Manager now submits the prompt if valid, similar to the Chat interfaces. - Agent Manager / Multi Run: worktree actions to delete group or individual worktrees, or keep only selected one (thanks to @wienans).
- Added worktree management actions in Agent Manager: delete the group or individual worktrees or keep only selected one (thanks to @wienans). - Agent Manager: added "Copy Worktree Path" action in the more menu (thanks to @wienans).
- Fixed IME (Input Method Editor) composition handling for CJK input methods, preventing accidental message send during character conversion (thanks to @madebyjun). - Worktrees: added session creation flow with loading screen, auto-create worktree setting, and setup commands management.
- Added project management with multi-project support and per-project settings for agents/commands/skills. - Session sidebar: refactoring with unified view for sessions in worktrees.
- Enhanced SSE event stream with heartbeat management, permission bootstrap on connect, and improved reconnection logic. - Settings: added ability to create new session in worktree by default
- Git view: added branch rename for worktree.
- Chat: fixed IME composition for CJK input to prevent accidental send (thanks to @madebyjun).
- Projects: added multi-project support with per-project settings for agents/commands/skills.
- Event stream: improved SSE with heartbeat management, permission bootstrap on connect, and reconnection logic.
- Tunnel: added QR code and password URL for Cloudflare tunnel (thanks to @martindonadieu).
- Model selector: fixed dropdowns not responding to viewport size.
## [1.4.3] - 2026-01-04 ## [1.4.3] - 2026-01-04
@@ -36,6 +36,7 @@ import {
RiDeleteBinLine, RiDeleteBinLine,
RiErrorWarningLine, RiErrorWarningLine,
RiFileCopyLine, RiFileCopyLine,
RiFolderAddLine,
RiGitBranchLine, RiGitBranchLine,
RiLinkUnlinkM, RiLinkUnlinkM,
RiMore2Line, RiMore2Line,
@@ -50,10 +51,12 @@ import { useSessionStore } from '@/stores/useSessionStore';
import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { useProjectsStore } from '@/stores/useProjectsStore'; import { useProjectsStore } from '@/stores/useProjectsStore';
import { useUIStore } from '@/stores/useUIStore'; import { useUIStore } from '@/stores/useUIStore';
import { useConfigStore } from '@/stores/useConfigStore';
import type { WorktreeMetadata } from '@/types/worktree'; import type { WorktreeMetadata } from '@/types/worktree';
import { opencodeClient } from '@/lib/opencode/client'; import { opencodeClient } from '@/lib/opencode/client';
import { checkIsGitRepository } from '@/lib/gitApi'; import { checkIsGitRepository } from '@/lib/gitApi';
import { getSafeStorage } from '@/stores/utils/safeStorage'; import { getSafeStorage } from '@/stores/utils/safeStorage';
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
const PROJECT_COLLAPSE_STORAGE_KEY = 'oc.sessions.projectCollapse'; const PROJECT_COLLAPSE_STORAGE_KEY = 'oc.sessions.projectCollapse';
const SESSION_EXPANDED_STORAGE_KEY = 'oc.sessions.expandedParents'; const SESSION_EXPANDED_STORAGE_KEY = 'oc.sessions.expandedParents';
@@ -232,10 +235,6 @@ 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={onOpenMultiRunLauncher}>
<ArrowsMerge className="mr-1.5 h-4 w-4" /> <ArrowsMerge className="mr-1.5 h-4 w-4" />
@@ -252,18 +251,17 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
{/* Collapse arrow */} {/* New session button */}
<button <button
type="button" type="button"
onClick={onToggle} onClick={(e) => {
className="inline-flex h-6 w-6 items-center justify-center text-muted-foreground hover:text-foreground flex-shrink-0" e.stopPropagation();
aria-label={isCollapsed ? 'Expand project' : 'Collapse project'} onNewSession();
}}
className="inline-flex h-6 w-6 items-center justify-center text-muted-foreground hover:text-foreground flex-shrink-0 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
aria-label="New session"
> >
{isCollapsed ? ( <RiAddLine className="h-4 w-4" />
<RiArrowRightSLine className="h-4 w-4" />
) : (
<RiArrowDownSLine className="h-4 w-4" />
)}
</button> </button>
</div> </div>
</div> </div>
@@ -278,13 +276,11 @@ const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
interface ProjectDragOverlayProps { interface ProjectDragOverlayProps {
projectLabel: string; projectLabel: string;
isActiveProject: boolean; isActiveProject: boolean;
isCollapsed: boolean;
} }
const ProjectDragOverlay: React.FC<ProjectDragOverlayProps> = ({ const ProjectDragOverlay: React.FC<ProjectDragOverlayProps> = ({
projectLabel, projectLabel,
isActiveProject, isActiveProject,
isCollapsed,
}) => { }) => {
return ( return (
<div <div
@@ -299,11 +295,7 @@ const ProjectDragOverlay: React.FC<ProjectDragOverlayProps> = ({
{projectLabel} {projectLabel}
</span> </span>
<span className="inline-flex h-6 w-6 items-center justify-center text-muted-foreground ml-auto"> <span className="inline-flex h-6 w-6 items-center justify-center text-muted-foreground ml-auto">
{isCollapsed ? ( <RiAddLine className="h-4 w-4" />
<RiArrowRightSLine className="h-4 w-4" />
) : (
<RiArrowDownSLine className="h-4 w-4" />
)}
</span> </span>
</div> </div>
</div> </div>
@@ -361,6 +353,8 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen); const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen);
const openMultiRunLauncher = useUIStore((state) => state.openMultiRunLauncher); const openMultiRunLauncher = useUIStore((state) => state.openMultiRunLauncher);
const settingsAutoCreateWorktree = useConfigStore((state) => state.settingsAutoCreateWorktree);
const sessions = useSessionStore((state) => state.sessions); const sessions = useSessionStore((state) => state.sessions);
const sessionsByDirectory = useSessionStore((state) => state.sessionsByDirectory); const sessionsByDirectory = useSessionStore((state) => state.sessionsByDirectory);
const currentSessionId = useSessionStore((state) => state.currentSessionId); const currentSessionId = useSessionStore((state) => state.currentSessionId);
@@ -1420,9 +1414,8 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|| project.normalizedPath || project.normalizedPath
), ),
isActive: project.id === activeProjectId, isActive: project.id === activeProjectId,
isCollapsed: collapsedProjects.has(project.id),
}; };
}, [activeDragId, projectSections, homeDirectory, activeProjectId, collapsedProjects]); }, [activeDragId, projectSections, homeDirectory, activeProjectId]);
return ( return (
<div <div
@@ -1450,7 +1443,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
aria-label="Add project" aria-label="Add project"
title="Add project" title="Add project"
> >
<RiAddLine className="h-5 w-5" /> <RiFolderAddLine className="h-4.5 w-4.5" />
</button> </button>
</div> </div>
</div> </div>
@@ -1538,7 +1531,11 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
if (mobileVariant) { if (mobileVariant) {
setSessionSwitcherOpen(false); setSessionSwitcherOpen(false);
} }
openNewSessionDraft({ directoryOverride: project.normalizedPath }); if (settingsAutoCreateWorktree && isRepo) {
createWorktreeSession();
} else {
openNewSessionDraft({ directoryOverride: project.normalizedPath });
}
}} }}
onOpenMultiRunLauncher={() => { onOpenMultiRunLauncher={() => {
if (projectKey !== activeProjectId) { if (projectKey !== activeProjectId) {
@@ -1567,7 +1564,6 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
<ProjectDragOverlay <ProjectDragOverlay
projectLabel={activeDragProject.label} projectLabel={activeDragProject.label}
isActiveProject={activeDragProject.isActive} isActiveProject={activeDragProject.isActive}
isCollapsed={activeDragProject.isCollapsed}
/> />
) : null} ) : null}
</DragOverlay> </DragOverlay>