mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 12:41:37 +03:00
fix(tui): scope session picker to active location (#43264)
Co-authored-by: neriousy <34747899+neriousy@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
98c717cb5b
commit
bac474aaa0
@@ -23,6 +23,7 @@ import { useStorage } from "../context/storage"
|
||||
import { useConfig } from "../config"
|
||||
import { withTimestampedFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
import { projectName } from "../util/project"
|
||||
import { useLocation } from "../context/location"
|
||||
|
||||
export function DialogSessionList() {
|
||||
const dialog = useDialog()
|
||||
@@ -36,6 +37,7 @@ export function DialogSessionList() {
|
||||
const sessionTabs = useSessionTabs()
|
||||
const config = useConfig().data
|
||||
const toast = useToast()
|
||||
const activeLocation = useLocation()
|
||||
const [filter, setFilter] = createSignal("")
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
const [search, setSearch] = createDebouncedSignal("", 150)
|
||||
@@ -44,13 +46,21 @@ export function DialogSessionList() {
|
||||
initial: { allProjects: config.tabs?.scope !== "cwd" },
|
||||
})
|
||||
const allProjects = () => prefs.allProjects
|
||||
const pickerLocation = () =>
|
||||
(route.data.type === "session" ? data.session.get(route.data.sessionID)?.location : undefined) ??
|
||||
activeLocation.ref ??
|
||||
data.location.default()
|
||||
|
||||
const [searchResults, { mutate: setSearchResults }] = createResource(
|
||||
() => ({ query: search().trim(), allProjects: allProjects() }),
|
||||
async ({ query, allProjects }) => {
|
||||
() => ({
|
||||
query: search().trim(),
|
||||
allProjects: allProjects(),
|
||||
location: pickerLocation(),
|
||||
}),
|
||||
async ({ query, allProjects, location }) => {
|
||||
try {
|
||||
if (!data.location.info()) await data.location.sync()
|
||||
const current = data.location.info()
|
||||
if (!data.location.info(location)) await data.location.sync(location)
|
||||
const current = data.location.info(location)
|
||||
if (!current) throw new Error("Location unavailable")
|
||||
const response = await client.api.session.list({
|
||||
...(allProjects
|
||||
@@ -78,7 +88,7 @@ export function DialogSessionList() {
|
||||
const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
|
||||
const localSessions = createMemo(() => {
|
||||
const query = filter().trim().toLowerCase()
|
||||
const current = data.location.info()
|
||||
const current = data.location.info(pickerLocation())
|
||||
const sessions = data.session
|
||||
.list()
|
||||
.filter(
|
||||
@@ -125,7 +135,7 @@ export function DialogSessionList() {
|
||||
return hint && local.session.slots().length > 0 ? [{ title: "switch", label: hint }] : []
|
||||
})
|
||||
const currentProjectName = createMemo(() => {
|
||||
const current = data.location.info()
|
||||
const current = data.location.info(pickerLocation())
|
||||
if (!current) return ""
|
||||
const project = data.project.get(current.project.id)
|
||||
return projectName(project) ?? ""
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { expect, test } from "bun:test"
|
||||
import { testRender } from "@opentui/solid"
|
||||
import { onMount } from "solid-js"
|
||||
import { DialogSessionList } from "../../../src/component/dialog-session-list"
|
||||
import { ConfigProvider } from "../../../src/config"
|
||||
import { ArgsProvider } from "../../../src/context/args"
|
||||
import { ClientProvider } from "../../../src/context/client"
|
||||
import { DataProvider, useData } from "../../../src/context/data"
|
||||
import { Keymap } from "../../../src/context/keymap"
|
||||
import { LocalProvider } from "../../../src/context/local"
|
||||
import { LocationProvider } from "../../../src/context/location"
|
||||
import { PermissionProvider } from "../../../src/context/permission"
|
||||
import { RouteProvider, useRoute } from "../../../src/context/route"
|
||||
import { TuiAppProvider } from "../../../src/context/runtime"
|
||||
import { SessionTabsProvider } from "../../../src/context/session-tabs"
|
||||
import { StorageProvider, useStorage } from "../../../src/context/storage"
|
||||
import { ThemeProvider } from "../../../src/context/theme"
|
||||
import { DialogProvider, useDialog } from "../../../src/ui/dialog"
|
||||
import { ToastProvider } from "../../../src/ui/toast"
|
||||
import { createApi, createEventStream, createFetch, json } from "../../fixture/tui-client"
|
||||
import { emptyThemeSource, tmpdir } from "../../fixture/fixture"
|
||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
|
||||
test("scopes sessions to the active session location", async () => {
|
||||
const active = "/tmp/opencode/project-b"
|
||||
const events = createEventStream()
|
||||
const requestedProjects: string[] = []
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname === "/api/location") {
|
||||
const directory = url.searchParams.get("location[directory]") ?? process.cwd()
|
||||
const project = directory === active ? "proj_b" : "proj_a"
|
||||
return json({ directory, project: { id: project, directory, canonical: directory } })
|
||||
}
|
||||
if (url.pathname !== "/api/session") return undefined
|
||||
const project = url.searchParams.get("project") ?? ""
|
||||
requestedProjects.push(project)
|
||||
return json({
|
||||
data: [
|
||||
{
|
||||
id: project === "proj_b" ? "ses_b" : "ses_a",
|
||||
projectID: project,
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
time: { created: 1, updated: 2 },
|
||||
title: project === "proj_b" ? "Project B session" : "Project A session",
|
||||
location: { directory: project === "proj_b" ? active : process.cwd() },
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
})
|
||||
}, events)
|
||||
const temporary = await tmpdir()
|
||||
let storage!: ReturnType<typeof useStorage>
|
||||
|
||||
function Probe() {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const route = useRoute()
|
||||
storage = useStorage()
|
||||
onMount(() => {
|
||||
data.session.remember({
|
||||
id: "ses_active",
|
||||
projectID: "proj_b",
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
time: { created: 1, updated: 3 },
|
||||
title: "Active session",
|
||||
location: { directory: active },
|
||||
})
|
||||
route.navigate({ type: "session", sessionID: "ses_active" })
|
||||
dialog.replace(() => <DialogSessionList />)
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const app = await testRender(
|
||||
() => (
|
||||
<TestTuiContexts paths={{ state: temporary.path }}>
|
||||
<TuiAppProvider value={{ name: "test", version: "test", channel: "test" }}>
|
||||
<StorageProvider>
|
||||
<ArgsProvider>
|
||||
<ConfigProvider config={createTuiResolvedConfig()}>
|
||||
<Keymap.Provider>
|
||||
<ToastProvider>
|
||||
<RouteProvider>
|
||||
<ClientProvider api={createApi(calls.fetch)}>
|
||||
<PermissionProvider>
|
||||
<DataProvider>
|
||||
<LocationProvider>
|
||||
<SessionTabsProvider>
|
||||
<ThemeProvider mode="dark" source={emptyThemeSource}>
|
||||
<LocalProvider>
|
||||
<DialogProvider>
|
||||
<Probe />
|
||||
</DialogProvider>
|
||||
</LocalProvider>
|
||||
</ThemeProvider>
|
||||
</SessionTabsProvider>
|
||||
</LocationProvider>
|
||||
</DataProvider>
|
||||
</PermissionProvider>
|
||||
</ClientProvider>
|
||||
</RouteProvider>
|
||||
</ToastProvider>
|
||||
</Keymap.Provider>
|
||||
</ConfigProvider>
|
||||
</ArgsProvider>
|
||||
</StorageProvider>
|
||||
</TuiAppProvider>
|
||||
</TestTuiContexts>
|
||||
),
|
||||
{ width: 100, height: 30, kittyKeyboard: true },
|
||||
)
|
||||
app.renderer.start()
|
||||
|
||||
try {
|
||||
const frame = await app.waitForFrame((value) => value.includes("Project B session"))
|
||||
expect(frame).not.toContain("Project A session")
|
||||
expect(requestedProjects.at(-1)).toBe("proj_b")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
await storage.flush()
|
||||
await temporary[Symbol.asyncDispose]()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user