@@ -10,6 +10,7 @@ import { uuid } from "@/utils/uuid"
1010import { SessionTabsRemovedDetail } from "@/components/titlebar-session-events"
1111import { sessionHref } from "@/utils/session-route"
1212import { createTabMemory } from "./tab-memory"
13+ import { nextTabAfterClose , pushClosedTab , removeClosedTabs , takeClosedTab , type ClosedTab } from "./closed-tabs"
1314
1415export type SessionTab = {
1516 type : "session"
@@ -63,6 +64,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
6364 createStore < Tab [ ] > ( [ ] ) ,
6465 )
6566 const [ recent , setRecent , , recentReady ] = persisted ( Persist . window ( "tabs.recent" ) , createStore < RecentTab > ( { } ) )
67+ const [ closed , setClosed , , closedReady ] = persisted ( Persist . window ( "tabs.closed" ) , createStore < ClosedTab [ ] > ( [ ] ) )
6668
6769 const params = useParams ( )
6870 const navigate = useNavigate ( )
@@ -87,6 +89,15 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
8789 } )
8890 }
8991
92+ const updateClosed = ( update : ( stack : ClosedTab [ ] ) => ClosedTab [ ] ) => {
93+ const apply = ( ) => setClosed ( ( stack ) => update ( stack ) )
94+ if ( closedReady ( ) ) {
95+ apply ( )
96+ return
97+ }
98+ void closedReady . promise ?. then ( apply )
99+ }
100+
90101 const removeDraftPersisted = ( draftID : string ) => {
91102 for ( const key of draftPersistedKeys ( ) ) removePersisted ( Persist . draft ( draftID , key ) , platform )
92103 }
@@ -106,6 +117,13 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
106117 if ( recent . key && ! next . some ( ( tab ) => tabKey ( tab ) === recent . key ) ) setRecentKey ( undefined )
107118 } )
108119
120+ createEffect ( ( ) => {
121+ if ( ! closedReady ( ) ) return
122+ const servers = new Set ( server . list . map ( ServerConnection . key ) )
123+ const next = closed . filter ( ( entry ) => servers . has ( entry . tab . server ) )
124+ if ( next . length !== closed . length ) setClosed ( ( ) => next )
125+ } )
126+
109127 const navigateTab = ( tab : Tab ) => {
110128 const href = tabHref ( tab )
111129 setRecentKey ( tabKey ( tab ) )
@@ -117,17 +135,19 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
117135 if ( ! tab ) return
118136 const key = tabKey ( tab )
119137 const draftID = tab . type === "draft" ? tab . draftID : undefined
120- const nextTab = store [ index + 1 ] ?? store [ index - 1 ]
138+ const nextTab = nextTabAfterClose ( store , index , recentKey ( ) === key && location . pathname !== "/" )
121139 closing . add ( key )
122140 void startTransition ( ( ) => {
123141 setStore (
124142 produce ( ( tabs ) => {
125143 tabs . splice ( index , 1 )
126144 } ) ,
127145 )
128- if ( recent . key === key ) setRecentKey ( nextTab && tabKey ( nextTab ) )
146+ if ( nextTab === null ) {
147+ setRecentKey ( undefined )
148+ navigate ( "/" )
149+ }
129150 if ( nextTab ) navigateTab ( nextTab )
130- else navigate ( "/" )
131151 } ) . finally ( ( ) => closing . delete ( key ) )
132152 memory . remove ( key )
133153 if ( draftID ) removeDraftPersisted ( draftID )
@@ -201,13 +221,45 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
201221 removeDraftPersisted ( draftID )
202222 } ,
203223 removeTab,
224+ // User-initiated close: records the tab so it can be reopened.
225+ // Cleanup paths (missing sessions, archive, server removal) go through
226+ // removeTab and friends directly and are not recorded.
227+ closeTab ( index : number ) {
228+ const tab = store [ index ]
229+ if ( ! tab ) return
230+ if ( tab . type === "session" ) updateClosed ( ( stack ) => pushClosedTab ( stack , tab , index ) )
231+ removeTab ( index )
232+ } ,
233+ reopenClosedTab ( ) {
234+ if ( ! closedReady ( ) ) {
235+ void closedReady . promise ?. then ( ( ) => actions . reopenClosedTab ( ) )
236+ return
237+ }
238+ const result = takeClosedTab ( closed , store )
239+ if ( result . stack . length === closed . length ) return
240+ setClosed ( ( ) => result . stack )
241+ const entry = result . entry
242+ if ( ! entry ) return
243+ const index = Math . min ( entry . index , store . length )
244+ void startTransition ( ( ) => {
245+ setStore (
246+ produce ( ( tabs ) => {
247+ if ( tabs . some ( ( item ) => tabKey ( item ) === tabKey ( entry . tab ) ) ) return
248+ tabs . splice ( index , 0 , entry . tab )
249+ } ) ,
250+ )
251+ navigateTab ( entry . tab )
252+ } )
253+ } ,
204254 removeSessionTab ( input : Omit < SessionTab , "type" > ) {
255+ updateClosed ( ( stack ) => removeClosedTabs ( stack , input . server , [ input . sessionId ] ) )
205256 const index = store . findIndex (
206257 ( tab ) => tab . type === "session" && tab . server === input . server && tab . sessionId === input . sessionId ,
207258 )
208259 if ( index !== - 1 ) removeTab ( index )
209260 } ,
210261 removeServer ( key : ServerConnection . Key ) {
262+ updateClosed ( ( stack ) => stack . filter ( ( entry ) => entry . tab . server !== key ) )
211263 const drafts = store . flatMap ( ( tab ) => ( tab . type === "draft" && tab . server === key ? [ tab . draftID ] : [ ] ) )
212264 const removed = store . filter ( ( tab ) => tab . server === key ) . map ( tabKey )
213265 setStore ( ( tabs ) => tabs . filter ( ( tab ) => tab . server !== key ) )
@@ -218,6 +270,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
218270 } ,
219271 removeSessions : ( input : SessionTabsRemovedDetail ) => {
220272 const targetServer = input . server ?? server . key
273+ updateClosed ( ( stack ) => removeClosedTabs ( stack , targetServer , input . sessionIDs ) )
221274 const removed = store
222275 . filter (
223276 ( tab ) => tab . type === "session" && tab . server === targetServer && input . sessionIDs . includes ( tab . sessionId ) ,
0 commit comments