feat(plugin): decompose tab controls (#48129)

Co-authored-by: vimtor <36263538+vimtor@users.noreply.github.com>
Co-authored-by: Victor Navarro <vn4varro@gmail.com>
This commit is contained in:
opencode-agent[bot]
2026-09-09 18:14:33 +02:00
committed by GitHub
parent c0cb1c7a91
commit be2582f316
5 changed files with 43 additions and 7 deletions
+4 -2
View File
@@ -487,10 +487,12 @@ export interface UI {
readonly attention: boolean
readonly unread?: "activity" | "error"
}[]
/** Opens (or focuses) a tab for a session, adding it when not already open. Returns false when tabs are disabled. */
/** Opens a tab for a session without focusing it. Returns false when tabs are disabled. */
open(sessionID: string): boolean
/** Focuses an already-open tab and returns false when it is not open. */
/** Opens a tab when needed, then focuses it. Returns false when tabs are disabled. */
focus(sessionID: string): boolean
/** Moves an open tab to an index and returns false when it is not open. */
move(sessionID: string, index: number): boolean
/** Closes an open tab, or the active tab when omitted, and returns false when no tab matched. */
close(sessionID?: string): boolean
}
@@ -395,6 +395,15 @@ export const { use: useSessionTabs, provider: SessionTabsProvider } = createSimp
if (!enabled()) return
route.navigate({ type: "session", sessionID: root(sessionID) })
},
open(sessionID: string) {
if (!enabled()) return
const session = root(sessionID)
if (state().tabs.some((tab) => tab.sessionID === session)) return
cancelledTabs.delete(session)
update((draft) => {
draft.tabs = openSessionTab(draft.tabs, { sessionID: session, title: title(session) })
})
},
promote(sessionID: string) {
if (!enabled()) return
const session = root(sessionID)
+8 -2
View File
@@ -206,15 +206,21 @@ export function createPluginContext(input: {
}),
open(sessionID) {
if (!host.sessionTabs.enabled()) return false
host.sessionTabs.select(sessionID)
host.sessionTabs.open(sessionID)
return true
},
focus(sessionID) {
if (!host.sessionTabs.enabled()) return false
if (!host.sessionTabs.tabs().some((tab) => tab.sessionID === sessionID)) return false
host.sessionTabs.select(sessionID)
return true
},
move(sessionID, index) {
if (!host.sessionTabs.enabled()) return false
const target = host.data.session.root(sessionID)
if (!host.sessionTabs.tabs().some((tab) => tab.sessionID === target)) return false
host.sessionTabs.move(target, index)
return true
},
close(sessionID) {
if (!host.sessionTabs.enabled()) return false
const target = sessionID ?? host.sessionTabs.current()
@@ -265,6 +265,23 @@ test("loads VCS metadata for each persisted tab location", async () => {
}
})
test("opens a background tab without changing the current session", async () => {
const setup = await renderSessionTabs("first")
try {
await wait(() => setup.tabs.current() === "first" && setup.tabs.tabs().some((tab) => tab.sessionID === "first"))
setup.tabs.open("background")
await wait(() => setup.tabs.tabs().some((tab) => tab.sessionID === "background"))
expect(setup.tabs.current()).toBe("first")
expect(setup.tabs.isPreview("background")).toBe(false)
setup.tabs.move("background", 0)
await wait(() => setup.tabs.tabs()[0]?.sessionID === "background")
} finally {
await setup.destroy()
}
})
test("loads location metadata when an open session moves", async () => {
const destination = `${directory}/moved-worktree`
const setup = await renderSessionTabs("first")
@@ -375,13 +375,15 @@ context.ui.router.navigate({ type: "home" })
return unregister
```
Tabs can be listed, opened, focused, and closed when session tabs are enabled.
Tabs can be listed, opened, focused, moved, and closed when session tabs are enabled. `open` leaves focus unchanged;
`focus` opens the tab when needed.
```ts
if (context.ui.tabs.enabled()) {
context.ui.tabs.open(sessionID)
const tabs = context.ui.tabs.list()
context.ui.tabs.open(backgroundSessionID)
context.ui.tabs.focus(sessionID)
const tabs = context.ui.tabs.list()
context.ui.tabs.move(backgroundSessionID, tabs.length - 1)
context.ui.tabs.close(sessionID)
context.ui.tabs.close()
}