@@ -198,17 +198,17 @@ struct RepositoriesFeature {
198198 )
199199 case consumeSetupScript( Worktree . ID )
200200 case consumeTerminalFocus( Worktree . ID )
201- case runScriptCompleted( worktreeID: Worktree . ID , exitCode: Int ? )
201+ case runScriptCompleted( worktreeID: Worktree . ID , exitCode: Int ? , tabId : TerminalTabID ? )
202202 case requestArchiveWorktree( Worktree . ID , Repository . ID )
203203 case requestArchiveWorktrees( [ ArchiveWorktreeTarget ] )
204204 case archiveWorktreeConfirmed( Worktree . ID , Repository . ID )
205- case archiveScriptCompleted( worktreeID: Worktree . ID , exitCode: Int ? )
205+ case archiveScriptCompleted( worktreeID: Worktree . ID , exitCode: Int ? , tabId : TerminalTabID ? )
206206 case archiveWorktreeApply( Worktree . ID , Repository . ID )
207207 case unarchiveWorktree( Worktree . ID )
208208 case requestDeleteWorktree( Worktree . ID , Repository . ID )
209209 case requestDeleteWorktrees( [ DeleteWorktreeTarget ] )
210210 case deleteWorktreeConfirmed( Worktree . ID , Repository . ID )
211- case deleteScriptCompleted( worktreeID: Worktree . ID , exitCode: Int ? )
211+ case deleteScriptCompleted( worktreeID: Worktree . ID , exitCode: Int ? , tabId : TerminalTabID ? )
212212 case deleteWorktreeApply( Worktree . ID , Repository . ID )
213213 case worktreeDeleted(
214214 Worktree . ID ,
@@ -285,6 +285,7 @@ struct RepositoriesFeature {
285285 case confirmDeleteWorktree( Worktree . ID , Repository . ID )
286286 case confirmDeleteWorktrees( [ DeleteWorktreeTarget ] )
287287 case confirmRemoveRepository( Repository . ID )
288+ case viewTerminalTab( Worktree . ID , tabId: TerminalTabID )
288289 }
289290
290291 enum PullRequestAction : Equatable {
@@ -305,6 +306,7 @@ struct RepositoriesFeature {
305306 case openRepositorySettings( Repository . ID )
306307 case worktreeCreated( Worktree )
307308 case runBlockingScript( Worktree , repositoryID: Repository . ID , kind: BlockingScriptKind , script: String )
309+ case selectTerminalTab( Worktree . ID , tabId: TerminalTabID )
308310 }
309311
310312 @Dependency ( AnalyticsClient . self) private var analyticsClient
@@ -1371,12 +1373,16 @@ struct RepositoriesFeature {
13711373 }
13721374 )
13731375
1374- case . runScriptCompleted ( let worktreeID, _ ) :
1376+ case . runScriptCompleted ( let worktreeID, let exitCode , let tabId ) :
13751377 guard state. runScriptWorktreeIDs. contains ( worktreeID) else {
13761378 repositoriesLogger. debug ( " Ignoring runScriptCompleted for \( worktreeID) : not in runScriptWorktreeIDs " )
13771379 return . none
13781380 }
13791381 state. runScriptWorktreeIDs. remove ( worktreeID)
1382+ guard let exitCode, exitCode != 0 else { return . none }
1383+ state. alert = blockingScriptFailureAlert (
1384+ kind: . run, exitCode: exitCode, worktreeID: worktreeID, tabId: tabId, state: state
1385+ )
13801386 return . none
13811387
13821388 case . archiveWorktreeConfirmed ( let worktreeID, let repositoryID) :
@@ -1400,7 +1406,7 @@ struct RepositoriesFeature {
14001406 return . send(
14011407 . delegate( . runBlockingScript( worktree, repositoryID: repositoryID, kind: . archive, script: script) ) )
14021408
1403- case . archiveScriptCompleted ( let worktreeID, let exitCode) :
1409+ case . archiveScriptCompleted ( let worktreeID, let exitCode, let tabId ) :
14041410 guard state. archivingWorktreeIDs. contains ( worktreeID) else {
14051411 repositoriesLogger. debug ( " Ignoring archiveScriptCompleted for \( worktreeID) : not in archivingWorktreeIDs " )
14061412 return . none
@@ -1424,9 +1430,8 @@ struct RepositoriesFeature {
14241430 repositoriesLogger. debug ( " Archive script cancelled or tab closed for worktree \( worktreeID) " )
14251431 return . none
14261432 case let code? :
1427- state. alert = messageAlert (
1428- title: " Archive script failed " ,
1429- message: " \( blockingScriptExitMessage ( code) ) \n Check the Archive Script tab for details. "
1433+ state. alert = blockingScriptFailureAlert (
1434+ kind: . archive, exitCode: code, worktreeID: worktreeID, tabId: tabId, state: state
14301435 )
14311436 return . none
14321437 }
@@ -1652,7 +1657,7 @@ struct RepositoriesFeature {
16521657 return . send(
16531658 . delegate( . runBlockingScript( worktree, repositoryID: repositoryID, kind: . delete, script: script) ) )
16541659
1655- case . deleteScriptCompleted ( let worktreeID, let exitCode) :
1660+ case . deleteScriptCompleted ( let worktreeID, let exitCode, let tabId ) :
16561661 guard state. deleteScriptWorktreeIDs. contains ( worktreeID) else {
16571662 repositoriesLogger. debug ( " Ignoring deleteScriptCompleted for \( worktreeID) : not in deleteScriptWorktreeIDs " )
16581663 return . none
@@ -1676,9 +1681,8 @@ struct RepositoriesFeature {
16761681 repositoriesLogger. debug ( " Delete script cancelled or tab closed for worktree \( worktreeID) " )
16771682 return . none
16781683 case let code? :
1679- state. alert = messageAlert (
1680- title: " Delete script failed " ,
1681- message: " \( blockingScriptExitMessage ( code) ) \n Check the Delete Script tab for details. "
1684+ state. alert = blockingScriptFailureAlert (
1685+ kind: . delete, exitCode: code, worktreeID: worktreeID, tabId: tabId, state: state
16821686 )
16831687 return . none
16841688 }
@@ -2661,6 +2665,12 @@ struct RepositoriesFeature {
26612665 case . openRepositorySettings ( let repositoryID) :
26622666 return . send( . delegate( . openRepositorySettings( repositoryID) ) )
26632667
2668+ case . alert ( . presented( . viewTerminalTab( let worktreeID, let tabId) ) ) :
2669+ return . merge(
2670+ . send( . selectWorktree( worktreeID, focusTerminal: true ) ) ,
2671+ . send( . delegate( . selectTerminalTab( worktreeID, tabId: tabId) ) )
2672+ )
2673+
26642674 case . alert ( . dismiss) :
26652675 state. alert = nil
26662676 return . none
@@ -2891,6 +2901,37 @@ struct RepositoriesFeature {
28912901 )
28922902 }
28932903
2904+ private func blockingScriptFailureAlert(
2905+ kind: BlockingScriptKind ,
2906+ exitCode: Int ,
2907+ worktreeID: Worktree . ID ,
2908+ tabId: TerminalTabID ? ,
2909+ state: State
2910+ ) -> AlertState < Alert > {
2911+ let worktreeName = state. worktree ( for: worktreeID) ? . name
2912+ let repoName = state. repositoryID ( containing: worktreeID)
2913+ . flatMap { state. repositories [ id: $0] ? . name }
2914+ let parts = [ repoName, worktreeName] . compactMap ( \. self)
2915+ if parts. isEmpty {
2916+ repositoriesLogger. debug ( " blockingScriptFailureAlert: worktree \( worktreeID) not found in state " )
2917+ }
2918+ let subtitle = parts. isEmpty ? " Unknown worktree " : parts. joined ( separator: " — " )
2919+ return AlertState {
2920+ TextState ( " \( kind. tabTitle) failed " )
2921+ } actions: {
2922+ if let tabId {
2923+ ButtonState ( action: . viewTerminalTab( worktreeID, tabId: tabId) ) {
2924+ TextState ( " View Terminal " )
2925+ }
2926+ }
2927+ ButtonState ( role: . cancel) {
2928+ TextState ( " Dismiss " )
2929+ }
2930+ } message: {
2931+ TextState ( " \( subtitle) \n \n \( blockingScriptExitMessage ( exitCode) ) " )
2932+ }
2933+ }
2934+
28942935 private func messageAlert( title: String , message: String ) -> AlertState < Alert > {
28952936 AlertState {
28962937 TextState ( title)
0 commit comments