feat(tui): copy session ID from tab menu (#50181)

Co-authored-by: kitlangton <7587245+kitlangton@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-09-20 19:15:37 +00:00
committed by GitHub
Unverified
parent c555559ac1
commit 58fcad77a8
2 changed files with 21 additions and 3 deletions
+13 -1
View File
@@ -23,6 +23,7 @@ import { Portal, useRenderer, useTerminalDimensions } from "@opentui/solid"
import { useConfig } from "../config"
import { useSessionTabs } from "../context/session-tabs"
import { useData } from "../context/data"
import { useClipboard } from "../context/clipboard"
import { useTheme } from "../context/theme"
import {
adaptiveSessionTabLayout,
@@ -44,6 +45,7 @@ import { SESSION_SIDEBAR_WIDTH, SESSION_TABS_COMPACT_BREAKPOINT } from "../ui/la
import { projectName } from "../util/project"
import { marqueeCycleWidth, marqueeOverflows, marqueeTextParts } from "../util/marquee"
import { useDialog } from "../ui/dialog"
import { useToast } from "../ui/toast"
import { DialogSessionRename } from "./dialog-session-rename"
import { Keymap } from "../context/keymap"
import { registerOpencodeSpinner } from "./register-spinner"
@@ -75,7 +77,7 @@ const FADE_WIDTH = 4
const ADD_TAB_WIDTH = 3
const MARQUEE_DELAY = 600
const MARQUEE_INTERVAL = 80
const CONTEXT_MENU_WIDTH = 16
const CONTEXT_MENU_WIDTH = 20
const MIDDLE_MOUSE_BUTTON = 1
const RIGHT_MOUSE_BUTTON = 2
const MOUSE_CLOSE_HOLD_MS = 5_000
@@ -370,6 +372,8 @@ function TabContextMenu(props: { state: TabContextMenuState; tabs: SessionTabsCo
const background = () => theme.background.raised.base
const actionHovered = () => theme.background.raised.high
const dialog = useDialog()
const clipboard = useClipboard()
const toast = useToast()
onCleanup(Keymap.use().mode.push("menu"))
Keymap.createLayer(() => ({
mode: "menu",
@@ -387,6 +391,14 @@ function TabContextMenu(props: { state: TabContextMenuState; tabs: SessionTabsCo
run: () =>
props.tabs.rename ? props.tabs.rename(sessionID) : DialogSessionRename.show(dialog, sessionID, title),
},
{
title: "Copy session ID",
run: () =>
void clipboard
.write(sessionID)
.then(() => toast.show({ message: "Session ID copied to clipboard", variant: "info" }))
.catch(toast.error),
},
{ title: "Close", run: () => props.tabs.close(sessionID) },
]
: []),
@@ -34,6 +34,7 @@ for (const orientation of ["horizontal", "vertical"] as const) {
const [active, setActive] = createSignal("second")
const [newTab, setNewTab] = createSignal(false)
const settings: Info = { tabs: { enabled: true } }
const copied: string[] = []
let config!: ReturnType<typeof useConfig>
let theme!: ReturnType<typeof useTheme>
function Colors() {
@@ -61,7 +62,10 @@ for (const orientation of ["horizontal", "vertical"] as const) {
} satisfies SessionTabsController
const app = await testRender(
() => (
<TestTuiContexts paths={{ state: temporary.path }}>
<TestTuiContexts
paths={{ state: temporary.path }}
clipboard={{ read: async () => undefined, write: async (text) => void copied.push(text) }}
>
<TuiAppProvider value={{ name: "test", version: "test", channel: "test" }}>
<StorageProvider>
<ConfigProvider
@@ -202,10 +206,12 @@ for (const orientation of ["horizontal", "vertical"] as const) {
await app.mockMouse.click(column, row, MouseButton.RIGHT)
await app.waitForFrame((frame) => frame.includes("Rename"))
expect(app.captureCharFrame().split("\n")[row + 1]!.indexOf("Rename")).toBe(column + 1)
expect(app.captureCharFrame()).toContain("Copy session ID")
expect(app.captureCharFrame()).toContain("Close")
expect(app.captureCharFrame()).not.toContain("Keep open")
expect(active()).toBe("second")
app.mockInput.pressKey("c", { ctrl: true })
await app.mockMouse.click(column + 1, row + 2)
expect(copied).toEqual(["first"])
await app.waitForFrame((frame) => !frame.includes("Rename"))
setNewTab(true)