Compare commits

..

1 Commits

Author SHA1 Message Date
Kit Langton 74e7137c10 fix(tui): keep closed tabs closed 2026-08-02 15:38:19 -04:00
2 changed files with 57 additions and 14 deletions
+32 -14
View File
@@ -1,4 +1,4 @@
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
import { isDeepEqual } from "remeda"
import { createSimpleContext } from "./helper"
import { useClient } from "./client"
@@ -126,22 +126,40 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
})
}
createEffect(
on(
[
() => (enabled() && route.data.type === "session" ? route.data.sessionID : undefined),
() => config.tabs?.scope,
() => paths.cwd,
],
([routed]) => {
if (!routed || routed === "dummy") return
const sessionID = root(routed)
history = recordSessionTabHistory(history, sessionID)
const fallback = newTab() ? NEW_SESSION_TAB_TITLE : undefined
update((draft) => {
draft.tabs = openSessionTab(draft.tabs, {
sessionID,
title: title(sessionID, draft.tabs.find((tab) => tab.sessionID === sessionID)?.title, fallback),
})
delete draft.unread[sessionID]
})
},
),
)
createEffect(() => {
if (!enabled()) return
if (route.data.type !== "session" || route.data.sessionID === "dummy") return
if (!enabled() || route.data.type !== "session" || route.data.sessionID === "dummy") return
const sessionID = root(route.data.sessionID)
history = recordSessionTabHistory(history, sessionID)
const fallback = newTab() ? NEW_SESSION_TAB_TITLE : undefined
const tabs = openSessionTab(state().tabs, {
sessionID,
title: title(sessionID, state().tabs.find((tab) => tab.sessionID === sessionID)?.title, fallback),
})
if (tabs === state().tabs && !state().unread[sessionID]) return
const tab = state().tabs.find((tab) => tab.sessionID === sessionID)
if (!tab) return
const nextTitle = title(sessionID, tab.title)
if ((!nextTitle || nextTitle === tab.title) && !state().unread[sessionID]) return
update((draft) => {
draft.tabs = openSessionTab(draft.tabs, {
sessionID,
title: title(sessionID, draft.tabs.find((tab) => tab.sessionID === sessionID)?.title, fallback),
})
const tab = draft.tabs.find((tab) => tab.sessionID === sessionID)
if (!tab) return
draft.tabs = openSessionTab(draft.tabs, { sessionID, title: title(sessionID, tab.title) })
delete draft.unread[sessionID]
})
})
@@ -170,6 +170,31 @@ test("concurrent TUIs do not alternate shared tab titles from divergent session
}
})
test("closing a tab is not undone by another TUI viewing the same session", async () => {
const state = stateDir("opencode-session-tabs-shared-close-")
const first = await renderSessionTabs("shared", { state })
const second = await renderSessionTabs("shared", { state })
try {
await wait(() => first.tabs.tabs().some((tab) => tab.sessionID === "shared"))
await wait(() => second.tabs.tabs().some((tab) => tab.sessionID === "shared"))
first.tabs.close()
await wait(() => first.route.data.type === "home")
await wait(() => !second.tabs.tabs().some((tab) => tab.sessionID === "shared"))
await Bun.sleep(50)
expect(first.tabs.tabs().some((tab) => tab.sessionID === "shared")).toBe(false)
second.route.navigate({ type: "home" })
await wait(() => second.route.data.type === "home")
second.route.navigate({ type: "session", sessionID: "shared" })
await wait(() => first.tabs.tabs().some((tab) => tab.sessionID === "shared"))
} finally {
first.destroy()
second.destroy()
}
})
test("user prompt admissions pulse an already-busy background tab", async () => {
const setup = await renderSessionTabs("background")
const admitted = (sessionID: string, inputID: string): OpenCodeEvent => ({