fix(app): show the main checkout's branch for Local in new sessions (#62061)

This commit is contained in:
usrnk1
2026-11-05 14:04:34 +01:00
committed by GitHub
Unverified
parent 893e8dc43f
commit fa2843f270
5 changed files with 56 additions and 21 deletions
@@ -92,6 +92,35 @@ test("the location shortcuts choose worktrees from every state and keep cycling"
await expect(view.editor).toBeFocused()
})
test("Local shows and targets the main checkout when the draft opened in a worktree", async ({ page }) => {
const root = "C:/OpenCode/LocalFromWorktree"
const worktree = `${root}/.worktrees/quiet-river`
const created: unknown[] = []
const view = await openDraft(page, {
name: "LocalFromWorktree",
project: { sandboxes: [worktree] },
worktrees: () => [{ directory: root }, { directory: worktree, strategy: "git" }],
vcs: (directory) =>
directory === worktree
? { current: "worktree-branch", default: "dev" }
: { current: "checkout-branch", default: "dev" },
onSessionCreate: (body) => void created.push(body.location),
seed: { tabs: [{ draft: "draft_localfromworktree", directory: worktree }] },
})
const screen = page.locator('[data-component="new-session"]')
await expect(newSessionWorktree(page)).toHaveText("Local")
await expect(screen.getByText("checkout-branch", { exact: true })).toBeVisible()
await expect(screen.getByText("worktree-branch", { exact: true })).toHaveCount(0)
await view.editor.fill("Run in the main checkout")
await submit(page).click()
await expect.poll(() => created).toEqual([{ directory: root }])
})
for (const binding of ["custom", "none"]) {
test(`the picker tooltips reflect ${binding} shortcuts`, async ({ page }) => {
const view = await openDraft(page, {
@@ -11,7 +11,6 @@ import { useLocal } from "@/providers/models/selection"
import { useData, useServer } from "@/runtime/server/current"
import { useServerSDK } from "@/runtime/server/client"
import { useTabs } from "@/shell/tabs/tabs"
import { useWorkspaceLocation } from "@/workspaces/location"
import { createWorktree } from "@/workspaces/create"
import { showToast } from "@/shell/notifications/toast"
import type { DraftMcpControls } from "./mcp"
@@ -72,6 +71,7 @@ type Started = Awaited<ReturnType<NewSessionComposerAdapter["start"]>>
export function createNewSessionComposerAdapter(props: {
draftID: string
worktree: () => string
root: () => string
branch: () => string | undefined
submitted: () => void
mcp: DraftMcpControls
@@ -84,7 +84,6 @@ export function createNewSessionComposerAdapter(props: {
const server = useServer()
const serverSDK = useServerSDK()
const tabs = useTabs()
const location = useWorkspaceLocation()
const language = useLanguage()
const model = createComposerModelSelection({ agent: () => local.agent.current() })
const controls = createComposerControls({ model })
@@ -97,11 +96,7 @@ export function createNewSessionComposerAdapter(props: {
working: () => false,
submitted: props.submitted,
start(selection, submission, message) {
const currentDirectory = location().directory
const projectDirectory =
data.location.info({ directory: currentDirectory })?.project.canonical ?? currentDirectory
const projectDirectory = props.root()
const worktree = props.worktree()
const directory = worktree === "main" ? projectDirectory : worktree
const sessionID = Session.ID.create()
+1
View File
@@ -44,6 +44,7 @@ export default function NewSessionPage(props: { draftId: string }) {
const composer = createNewSessionComposerAdapter({
draftID: props.draftId,
worktree: workspace.selection.value,
root: workspace.project.root,
branch: workspace.bar.branch,
submitted: workspace.selection.remember,
mcp,
@@ -38,19 +38,24 @@ describe("new session workspace selection", () => {
expect(resolveNewSessionWorktree(input)).toBe(expected)
})
const branch = (worktree: string) => (worktree === "/project/feature" ? "feature" : undefined)
const branches = new Map([
["/project", "dev"],
["/project/feature", "feature"],
])
const branch = (worktree: string) => branches.get(worktree)
test.each([
{ worktree: "main", directory: "/project/feature", createBranch: undefined, expected: "feature" },
{ worktree: "create", directory: "/project/feature", createBranch: undefined, expected: "feature" },
{ worktree: "/project/feature", directory: "/project", createBranch: undefined, expected: "feature" },
{ worktree: "/missing", directory: "/project/feature", createBranch: undefined, expected: undefined },
{ worktree: "create", directory: "/project/feature", createBranch: "release", expected: "release" },
])("resolves the branch for $worktree in $directory (new branch: $createBranch)", (row) => {
{ worktree: "main", root: "/project", createBranch: undefined, expected: "dev" },
{ worktree: "create", root: "/project", createBranch: undefined, expected: "dev" },
{ worktree: "/project/feature", root: "/project", createBranch: undefined, expected: "feature" },
{ worktree: "/missing", root: "/project", createBranch: undefined, expected: undefined },
{ worktree: "create", root: "/project", createBranch: "release", expected: "release" },
])("resolves the branch for $worktree from $root (new branch: $createBranch)", (row) => {
expect(
resolveNewSessionBranch({
worktree: row.worktree,
directory: row.directory,
root: row.root,
createBranch: row.createBranch,
worktreeBranch: branch,
}),
@@ -26,12 +26,12 @@ export function resolveNewSessionWorktree(input: { enabled: boolean; selected?:
export function resolveNewSessionBranch(input: {
worktree: string
directory: string
root: string
createBranch?: string
worktreeBranch: (worktree: string) => string | undefined
}) {
if (input.worktree === "create" && input.createBranch) return input.createBranch
const directory = input.worktree === "main" || input.worktree === "create" ? input.directory : input.worktree
const directory = input.worktree === "main" || input.worktree === "create" ? input.root : input.worktree
return input.worktreeBranch(directory)
}
@@ -167,8 +167,13 @@ export function createNewSessionWorkspaceController(input: {
}),
)
const projectRoot = createMemo(() => currentProject()?.worktree ?? sdk().directory)
const branchDirectory = () => (value() === "create" ? projectRoot() : sdk().directory)
// A draft opened from a worktree session keeps that worktree as its directory; Local still runs in the main checkout.
const projectRoot = createMemo(
() =>
data.location.info({ directory: sdk().directory })?.project.canonical ??
currentProject()?.worktree ??
sdk().directory,
)
createEffect(() => {
void Promise.all([
@@ -178,7 +183,7 @@ export function createNewSessionWorkspaceController(input: {
]).catch(() => undefined)
})
createEffect(() => {
if (value() !== "create" || sameDirectory(projectRoot(), sdk().directory)) return
if ((value() !== "main" && value() !== "create") || sameDirectory(projectRoot(), sdk().directory)) return
void data.location.vcs.sync({ directory: projectRoot() }).catch(() => undefined)
})
// Only the selected worktree feeds the branch label. Syncing every worktree in the inventory boots
@@ -197,7 +202,7 @@ export function createNewSessionWorkspaceController(input: {
const branch = createMemo(() =>
resolveNewSessionBranch({
worktree: value(),
directory: branchDirectory(),
root: projectRoot(),
createBranch: input.selectedBranch(),
worktreeBranch: (worktree) => data.location.vcs.info({ directory: worktree })?.branch.current,
}),