@@ -6,19 +6,21 @@ import {
66 useState ,
77 type PointerEvent as ReactPointerEvent ,
88} from "react" ;
9- import { confirm , open } from "@tauri-apps/plugin-dialog" ;
9+ import { invoke } from "@tauri-apps/api/core" ;
10+ import { open } from "@tauri-apps/plugin-dialog" ;
1011import styles from "./App.module.css" ;
1112import Sidebar from "./components/Sidebar/Sidebar" ;
1213import MainPane from "./components/MainPane/MainPane" ;
1314import NewSessionDialog from "./components/NewSessionDialog/NewSessionDialog" ;
1415import SettingsDialog from "./components/SettingsDialog/SettingsDialog" ;
1516import RenameDialog from "./components/RenameDialog/RenameDialog" ;
1617import CloseDialog from "./components/CloseDialog/CloseDialog" ;
18+ import TerminateSessionDialog from "./components/TerminateSessionDialog/TerminateSessionDialog" ;
1719import OnboardingDialog from "./components/OnboardingDialog/OnboardingDialog" ;
1820import { useAppState } from "./hooks/useAppState" ;
1921import { useToasts } from "./hooks/useToasts" ;
2022import Toasts from "./components/Toasts/Toasts" ;
21- import type { AgentId , CloseConfirmPayload , CloseConfirmResult , EnvVar , RepoConfig , PaneKind } from "./types" ;
23+ import type { AgentId , CloseConfirmPayload , CloseConfirmResult , EnvVar , RepoConfig , PaneKind , Session } from "./types" ;
2224import { getRepoName , groupSessionsByRepo , validateEnvVars } from "./utils/session" ;
2325import { defineHotkey , runHotkeys , type HotkeyBinding } from "./utils/hotkeys" ;
2426import {
@@ -30,6 +32,15 @@ import { agentCatalog } from "./constants";
3032
3133const emptyEnv : EnvVar [ ] = [ { key : "" , value : "" } ] ;
3234
35+ function canDeleteWorktreeForSession ( session : Session | null | undefined ) {
36+ if ( ! session ?. repo . worktree ?. enabled ) {
37+ return false ;
38+ }
39+ const cwd = session . cwd ?. trim ( ) ;
40+ const repoRoot = session . repo . repoPath . trim ( ) ;
41+ return Boolean ( cwd && repoRoot && cwd !== repoRoot ) ;
42+ }
43+
3344function buildModifierSessionSelectHotkeys (
3445 shortcutModifier : string ,
3546 selectSessionByHotkeyIndex : ( index : number ) => void
@@ -72,6 +83,8 @@ export default function App() {
7283 const closeDialogResolveRef = useRef < ( ( result : CloseConfirmResult ) => void ) | null > ( null ) ;
7384 const closeDialogPromiseRef = useRef < Promise < CloseConfirmResult > | null > ( null ) ;
7485 const previousActiveSessionIdRef = useRef < string | null > ( null ) ;
86+ const [ terminateDialogSessionId , setTerminateDialogSessionId ] = useState < string | null > ( null ) ;
87+ const [ terminateDialogDeleteWorktree , setTerminateDialogDeleteWorktree ] = useState ( false ) ;
7588
7689 function focusSearch ( ) {
7790 if ( searchInputRef . current ) {
@@ -334,6 +347,14 @@ export default function App() {
334347 ( ) => visibleSessions . find ( ( session ) => session . id === activeSessionId ) ?? null ,
335348 [ activeSessionId , visibleSessions ]
336349 ) ;
350+ const terminateDialogSession = useMemo (
351+ ( ) => sessions . find ( ( session ) => session . id === terminateDialogSessionId ) ?? null ,
352+ [ sessions , terminateDialogSessionId ]
353+ ) ;
354+ const canDeleteDialogWorktree = useMemo (
355+ ( ) => canDeleteWorktreeForSession ( terminateDialogSession ) ,
356+ [ terminateDialogSession ]
357+ ) ;
337358
338359 const canRestartActiveAgent = useMemo ( ( ) => {
339360 if ( activePaneKind !== "agent" || ! activeSession ) {
@@ -361,19 +382,73 @@ export default function App() {
361382 openRename ( activeSessionId ) ;
362383 } , [ activeSessionId , openRename ] ) ;
363384
364- const terminateActiveSession = useCallback ( async ( ) => {
365- if ( ! activeSessionId ) {
385+ const openTerminateDialog = useCallback ( ( sessionId : string ) => {
386+ setTerminateDialogSessionId ( sessionId ) ;
387+ setTerminateDialogDeleteWorktree ( false ) ;
388+ } , [ ] ) ;
389+
390+ const closeTerminateDialog = useCallback ( ( ) => {
391+ setTerminateDialogSessionId ( null ) ;
392+ setTerminateDialogDeleteWorktree ( false ) ;
393+ } , [ ] ) ;
394+
395+ const confirmTerminateDialog = useCallback ( async ( ) => {
396+ const session = sessions . find ( ( item ) => item . id === terminateDialogSessionId ) ;
397+ const targetSessionId = terminateDialogSessionId ;
398+ if ( ! session || ! targetSessionId ) {
399+ closeTerminateDialog ( ) ;
366400 return ;
367401 }
368- const confirmed = await confirm (
369- "Terminate this session? This will close the tab and stop ongoing shell sessions." ,
370- { title : "Codelegate" , kind : "warning" }
371- ) ;
372- if ( ! confirmed ) {
402+ const shouldDeleteWorktree = terminateDialogDeleteWorktree && canDeleteWorktreeForSession ( session ) ;
403+ closeTerminateDialog ( ) ;
404+ try {
405+ let cleanupWorktree :
406+ | {
407+ repoPath : string ;
408+ worktreePath : string ;
409+ branch ?: string ;
410+ }
411+ | undefined ;
412+ if ( shouldDeleteWorktree ) {
413+ let branchName : string | undefined ;
414+ const branchPath = session . cwd ?. trim ( ) ;
415+ if ( branchPath ) {
416+ try {
417+ const resolvedBranch = await invoke < string > ( "get_git_branch" , { path : branchPath } ) ;
418+ const trimmedBranch = resolvedBranch . trim ( ) ;
419+ if ( trimmedBranch ) {
420+ branchName = trimmedBranch ;
421+ }
422+ } catch ( error ) {
423+ // Keep branch optional; backend cleanup will still remove worktree directory.
424+ pushToast ( { message : `Failed to resolve branch from git: ${ String ( error ) } ` , tone : "error" } ) ;
425+ }
426+ }
427+ cleanupWorktree = {
428+ repoPath : session . repo . repoPath ,
429+ worktreePath : session . cwd ?? "" ,
430+ branch : branchName || undefined ,
431+ } ;
432+ }
433+ await terminateSession ( targetSessionId , cleanupWorktree ? { cleanupWorktree } : undefined ) ;
434+ } catch ( error ) {
435+ pushToast ( { message : `Failed to terminate session: ${ String ( error ) } ` , tone : "error" } ) ;
436+ }
437+ } , [
438+ closeTerminateDialog ,
439+ pushToast ,
440+ sessions ,
441+ terminateDialogDeleteWorktree ,
442+ terminateDialogSessionId ,
443+ terminateSession ,
444+ ] ) ;
445+
446+ const terminateActiveSession = useCallback ( ( ) => {
447+ if ( ! activeSessionId ) {
373448 return ;
374449 }
375- terminateSession ( activeSessionId ) ;
376- } , [ activeSessionId , terminateSession ] ) ;
450+ openTerminateDialog ( activeSessionId ) ;
451+ } , [ activeSessionId , openTerminateDialog ] ) ;
377452
378453 const openSettings = useCallback ( ( ) => {
379454 setFontFamily ( config . settings . terminalFontFamily ) ;
@@ -749,7 +824,7 @@ export default function App() {
749824 onNewSession = { handleOpenDialog }
750825 onOpenSettings = { openSettings }
751826 onRenameSession = { openRename }
752- onTerminateSession = { terminateSession }
827+ onTerminateSession = { openTerminateDialog }
753828 agentOutputting = { agentOutputting }
754829 searchRef = { searchInputRef }
755830 showShortcutHints = { showShortcutHints }
@@ -835,6 +910,17 @@ export default function App() {
835910 onClose = { handleCloseConfirmCancel }
836911 onConfirm = { handleCloseConfirmSubmit }
837912 />
913+ < TerminateSessionDialog
914+ open = { Boolean ( terminateDialogSession ) }
915+ sessionLabel = { terminateDialogSession ?. branch ?. trim ( ) || getRepoName ( terminateDialogSession ?. repo . repoPath ?? "" ) }
916+ canDeleteWorktree = { canDeleteDialogWorktree }
917+ deleteWorktree = { terminateDialogDeleteWorktree }
918+ onDeleteWorktreeChange = { setTerminateDialogDeleteWorktree }
919+ onClose = { closeTerminateDialog }
920+ onConfirm = { ( ) => {
921+ void confirmTerminateDialog ( ) ;
922+ } }
923+ />
838924 < Toasts toasts = { toasts } onDismiss = { removeToast } />
839925 </ div >
840926 ) ;
0 commit comments