فهرست منبع

fix(tui): preserve tab context on home and close (#39421)

Kit Langton 2 هفته پیش
والد
کامیت
1c8175a61a

+ 22 - 7
packages/tui/src/context/session-tabs-model.ts

@@ -75,15 +75,30 @@ export function adaptiveSessionTabLayout(
 ) {
   if (tabs.length === 0) return { tabs: [], widths: [], before: 0, after: 0, start: 0, total: 0 }
 
-  const activeIndex = Math.max(
-    0,
-    tabs.findIndex((tab) => tab.sessionID === active),
-  )
+  const activeIndex = tabs.findIndex((tab) => tab.sessionID === active)
   const fit = (width: number) =>
-    Math.min(tabs.length, Math.max(1, 1 + Math.floor((Math.max(0, width) - SESSION_TAB_WIDTH) / SESSION_TAB_MIN_WIDTH)))
+    Math.min(
+      tabs.length,
+      Math.max(
+        1,
+        activeIndex === -1
+          ? Math.floor(Math.max(0, width) / SESSION_TAB_MIN_WIDTH)
+          : 1 + Math.floor((Math.max(0, width) - SESSION_TAB_WIDTH) / SESSION_TAB_MIN_WIDTH),
+      ),
+    )
   const solve = (count: number, start: number, attempts: number): { count: number; start: number } => {
+    const boundedStart = Math.min(Math.max(0, start), tabs.length - count)
     const nextStart = Math.min(
-      Math.max(0, activeIndex < start ? activeIndex : activeIndex >= start + count ? activeIndex - count + 1 : start),
+      Math.max(
+        0,
+        activeIndex === -1
+          ? boundedStart
+          : activeIndex < boundedStart
+            ? activeIndex
+            : activeIndex >= boundedStart + count
+              ? activeIndex - count + 1
+              : boundedStart,
+      ),
       tabs.length - count,
     )
     const markers =
@@ -103,7 +118,7 @@ export function adaptiveSessionTabLayout(
   )
   const roomy = contentWidth >= SESSION_TAB_WIDTH * visible.length
   const total = roomy ? Math.min(contentWidth, SESSION_TAB_MAX_WIDTH * visible.length) : contentWidth
-  if (roomy) {
+  if (roomy || activeIndex === -1) {
     const width = Math.floor(total / visible.length)
     const remainder = total - width * visible.length
     return {

+ 7 - 2
packages/tui/src/context/session-tabs.tsx

@@ -202,11 +202,16 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
       const target = root(sessionID)
       const closed = closeSessionTab(store.tabs, target)
       if (closed.tabs.length === store.tabs.length) return
+      const selected = navigate && current() === target
+      const previous = selected
+        ? moveSessionTabHistory(recordSessionTabHistory(history, target), closed.tabs, target, -1)
+        : { history, sessionID: undefined }
+      const next = previous.sessionID ?? closed.next
+      history = previous.history
       batch(() => {
         setStore("tabs", reconcile(closed.tabs))
         clearUnread(target)
-        if (navigate && current() === target)
-          route.navigate(closed.next ? { type: "session", sessionID: closed.next } : { type: "home" })
+        if (selected) route.navigate(next ? { type: "session", sessionID: next } : { type: "home" })
       })
       save()
     }

+ 27 - 0
packages/tui/test/context/session-tabs-model.test.ts

@@ -75,6 +75,15 @@ describe("session tabs", () => {
     expect(moveSessionTabHistory(history, tabs, undefined, -1).sessionID).toBe("b")
   })
 
+  test("returns to the previous selected open tab after closing the active tab", () => {
+    const tabs = ["a", "b", "c"].map((sessionID) => ({ sessionID }))
+    const history = ["a", "c"].reduce(recordSessionTabHistory, { entries: [], index: -1 })
+    const closed = closeSessionTab(tabs, "b")
+    const current = recordSessionTabHistory(history, "b")
+
+    expect(moveSessionTabHistory(current, closed.tabs, "b", -1).sessionID).toBe("c")
+  })
+
   test("reveals completion activity only after session work becomes idle", () => {
     expect(sessionTabComplete("activity", true)).toBe(false)
     expect(sessionTabComplete("activity", false)).toBe(true)
@@ -90,6 +99,24 @@ describe("session tabs", () => {
     expect(layout.widths.reduce((total, width) => total + width, 0)).toBe(76)
   })
 
+  test("does not reserve an active tab slot on the new session page", () => {
+    const tabs = ["a", "b", "c", "d", "e"].map((sessionID) => ({ sessionID }))
+    const layout = adaptiveSessionTabLayout(tabs, "dummy", 40)
+
+    expect(layout.tabs).toEqual(tabs)
+    expect(layout.widths).toEqual([8, 8, 8, 8, 8])
+    expect(layout.widths.reduce((total, width) => total + width, 0)).toBe(layout.total)
+  })
+
+  test("keeps the visible tab window stable on the new session page", () => {
+    const tabs = Array.from({ length: 10 }, (_, index) => ({ sessionID: String(index) }))
+    const selected = adaptiveSessionTabLayout(tabs, "7", 70)
+    const home = adaptiveSessionTabLayout(tabs, undefined, 70, selected.start)
+
+    expect(selected.start).toBeGreaterThan(0)
+    expect(home.start).toBe(selected.start)
+  })
+
   test("only swaps old and new active width inside a sticky window", () => {
     const tabs = ["a", "b", "c", "d", "e", "f", "g"].map((sessionID) => ({ sessionID }))
     const before = adaptiveSessionTabLayout(tabs, "c", 76)