fix(tui): show canonical projects in open dialog (#50674)

This commit is contained in:
Dax
2026-09-22 11:59:27 -04:00
committed by GitHub
parent ad1a4a6539
commit 3584eca0eb
2 changed files with 16 additions and 44 deletions
+6 -22
View File
@@ -7,7 +7,7 @@ import { dialogWidth, useDialog } from "../ui/dialog"
import { DialogSelect, dialogSelectContentWidth, type DialogSelectRef } from "../ui/dialog-select"
import { DialogPrompt } from "../ui/dialog-prompt"
import { useRoute } from "../context/route"
import { locationKey, useData } from "../context/data"
import { useData } from "../context/data"
import { useClient } from "../context/client"
import { useLocation } from "../context/location"
import { useSessionTabs } from "../context/session-tabs"
@@ -205,26 +205,10 @@ export function DialogOpen(props: { sessions: SessionInfo[]; onLoad: (sessions:
})
const current = location.ref ?? data.location.default()
const seen = new Set<string>()
const projectOptions = [
...data.project.list().flatMap((project) =>
[project.canonical, ...project.sandboxes].map((directory) => ({
directory,
project,
})),
),
...sessions().flatMap((session) => {
const project = data.project.get(session.projectID)
if (project && project.canonical !== "/") return []
return [{ directory: session.location.directory, project }]
}),
]
.filter((item) => {
const key = locationKey(item)
if (item.directory === "/" || seen.has(key)) return false
seen.add(key)
return true
})
const projectOptions = data.project
.list()
.filter((project) => project.canonical !== "/")
.map((project) => ({ directory: project.canonical, project }))
.map((item) => {
const title =
item.directory === item.project?.canonical
@@ -248,7 +232,7 @@ export function DialogOpen(props: { sessions: SessionInfo[]; onLoad: (sessions:
category: "Projects",
gutter:
item.directory === current.directory ||
(item.directory === location.current?.project.canonical && !seen.has(locationKey(current)))
item.directory === location.current?.project.canonical
? () => <text fg={theme.text.formfield.selected}>●</text>
: undefined,
}
+10 -22
View File
@@ -135,7 +135,7 @@ test("shows the current project and opens its root", async () => {
}
})
test("includes unique sandbox and recent session directories, including global projects", async () => {
test("shows only canonical projects while retaining sessions from other directories", async () => {
const fixture = await renderOpen((url) => {
if (url.pathname === "/api/project")
return json([
@@ -185,13 +185,15 @@ test("includes unique sandbox and recent session directories, including global p
)
expect(frame).toContain("standalone-notes")
expect(frame).toContain("OpenCode · feature-branch")
expect(frame.match(/\/tmp\/opencode\/feature-branch/g)).toHaveLength(1)
expect(frame).toContain("/tmp/opencode/project")
expect(frame).not.toContain("/tmp/opencode/feature-branch")
expect(frame).not.toContain("/tmp/standalone-notes")
await fixture.app.mockInput.typeText("standalone-notes")
await fixture.app.waitForFrame((value) => value.includes("standalone-notes"))
fixture.app.mockInput.pressEnter()
await fixture.app.waitFor(() => fixture.route.data.type === "home")
expect(fixture.route.data).toEqual({ type: "home", location: { directory: "/tmp/standalone-notes" } })
await fixture.app.waitFor(() => fixture.route.data.type === "session")
expect(fixture.route.data).toEqual({ type: "session", sessionID: "ses_global" })
} finally {
await fixture.dispose()
}
@@ -233,13 +235,14 @@ test("keeps nested Git session directories out of projects and in their session
)
expect(frame).toContain("OpenCode · dashboard")
expect(frame).not.toContain("browse directories")
expect(frame).toContain("/tmp/opencode/project")
expect(frame).not.toContain("/tmp/opencode/project/packages/dashboard")
} finally {
await fixture.dispose()
}
})
test("loads Git worktrees only when drilling into a project or its associated directory", async () => {
test("loads Git worktrees only when drilling into the canonical project", async () => {
const root = path.resolve("/tmp/opencode/project")
const current = path.resolve("/tmp/opencode/current-branch")
const other = path.resolve("/tmp/opencode/other-branch")
@@ -276,9 +279,10 @@ test("loads Git worktrees only when drilling into a project or its associated di
try {
const projects = await fixture.app.waitForFrame(
(frame) => frame.includes("OpenCode") && frame.includes("current-branch") && frame.includes("→"),
(frame) => frame.includes("OpenCode") && frame.includes(root) && frame.includes("→"),
)
expect(projects).not.toContain("Browse directories")
expect(projects).not.toContain(current)
expect(requests).toBe(0)
fixture.app.mockInput.pressArrow("right")
@@ -291,22 +295,6 @@ test("loads Git worktrees only when drilling into a project or its associated di
expect(worktrees.indexOf("OpenCode")).toBeLessThan(worktrees.indexOf("current-branch"))
expect(worktrees.indexOf("current-branch")).toBeLessThan(worktrees.indexOf("other-branch"))
fixture.app.mockInput.pressArrow("left")
await fixture.app.waitForFrame((frame) => frame.includes("Search sessions and projects"))
await fixture.app.mockInput.typeText("current-branch")
await fixture.app.waitForFrame((frame) => frame.includes("current-branch") && !frame.includes("OpenCode"))
fixture.app.mockInput.pressArrow("right")
await fixture.app.waitForFrame((frame) => frame.includes("other-branch") && frame.includes("ctrl+n"))
expect(requests).toBe(2)
fixture.app.mockInput.pressEscape()
const restored = await fixture.app.waitForFrame(
(frame) => frame.includes("current-branch") && !frame.includes("Worktrees"),
)
expect(restored).toContain("current-branch")
expect(restored).not.toContain("OpenCode")
fixture.app.mockInput.pressArrow("right")
await fixture.app.waitForFrame((frame) => frame.includes("other-branch") && frame.includes("ctrl+n"))
await fixture.app.mockInput.typeText("other-branch")
fixture.app.mockInput.pressEnter()
await fixture.app.waitFor(() => fixture.route.data.type === "home")