Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions packages/app/src/pages/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ import {
createSessionTabs,
createSizing,
focusTerminalById,
resolveReviewChangeMode,
reviewChangeOptions,
type ReviewChangeMode,
shouldFocusTerminalOnKeyDown,
shouldShowFileTree,
} from "@/pages/session/helpers"
Expand All @@ -82,13 +85,17 @@ type FollowupItem = FollowupDraft & { id: string }
type FollowupEdit = Pick<FollowupItem, "id" | "prompt" | "context">
const emptyFollowups: FollowupItem[] = []

type ChangeMode = "git" | "branch" | "turn"
type ChangeMode = ReviewChangeMode
type VcsMode = "git" | "branch"

const sessionViewState = () => ({
messageId: undefined as string | undefined,
mobileTab: "session" as "session" | "changes",
changes: "git" as ChangeMode,
// Until the user explicitly picks a review mode, the default is auto-selected
// from changesOptions() so feature-branch sessions open on the full branch diff
// rather than working-tree-only ("git") changes.
changesTouched: false,
})

async function runPromptRollbackMutation<T, R>(input: {
Expand Down Expand Up @@ -358,17 +365,13 @@ export default function Page() {
const project = sync().project
return !!project && project.vcs !== "git"
})
const changesOptions = createMemo<ChangeMode[]>(() => {
const list: ChangeMode[] = []
const project = sync().project
const vcs = sync().data.vcs
if (project?.vcs === "git") list.push("git")
if (project?.vcs === "git" && vcs?.branch && vcs?.default_branch && vcs.branch !== vcs.default_branch) {
list.push("branch")
}
list.push("turn")
return list
})
const changesOptions = createMemo<ChangeMode[]>(() =>
reviewChangeOptions({
vcs: sync().project?.vcs,
branch: sync().data.vcs?.branch,
defaultBranch: sync().data.vcs?.default_branch,
}),
)
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
const wantsReview = createMemo(() =>
isDesktop()
Expand Down Expand Up @@ -736,11 +739,12 @@ export default function Page() {

createEffect(() => {
if (!sync().project) return
const list = changesOptions()
if (list.includes(store.changes)) return
const next = list[0]
if (!next) return
setStore("changes", next)
const next = resolveReviewChangeMode({
options: changesOptions(),
current: store.changes,
touched: store.changesTouched,
})
if (store.changes !== next) setStore("changes", next)
})

createEffect(
Expand Down Expand Up @@ -820,7 +824,7 @@ export default function Page() {
options={changesOptions()}
current={store.changes}
label={label}
onSelect={(option) => option && setStore("changes", option)}
onSelect={(option) => option && setStore({ changes: option, changesTouched: true })}
variant="ghost"
size="small"
valueClass="text-14-medium"
Expand Down
50 changes: 50 additions & 0 deletions packages/app/src/pages/session/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
createSessionTabs,
focusTerminalById,
getTabReorderIndex,
resolveReviewChangeMode,
reviewChangeOptions,
shouldFocusTerminalOnKeyDown,
shouldShowFileTree,
} from "./helpers"
Expand Down Expand Up @@ -125,6 +127,54 @@ describe("getTabReorderIndex", () => {
})
})

describe("reviewChangeOptions", () => {
test("prefers the branch diff on a feature-branch git session", () => {
expect(reviewChangeOptions({ vcs: "git", branch: "forge-task--abc", defaultBranch: "main" })).toEqual([
"branch",
"git",
"turn",
])
})

test("omits branch when on the default branch", () => {
expect(reviewChangeOptions({ vcs: "git", branch: "main", defaultBranch: "main" })).toEqual(["git", "turn"])
})

test("omits branch when branch metadata is missing", () => {
expect(reviewChangeOptions({ vcs: "git" })).toEqual(["git", "turn"])
})

test("offers only turn diffs without git", () => {
expect(reviewChangeOptions({ vcs: "none" })).toEqual(["turn"])
expect(reviewChangeOptions({})).toEqual(["turn"])
})
})

describe("resolveReviewChangeMode", () => {
const options = reviewChangeOptions({ vcs: "git", branch: "forge-task--abc", defaultBranch: "main" })

test("auto-selects the branch diff when the user has not chosen a mode", () => {
expect(resolveReviewChangeMode({ options, current: "git", touched: false })).toBe("branch")
})

test("re-tracks the default even after an unrelated store default", () => {
expect(resolveReviewChangeMode({ options, current: "turn", touched: false })).toBe("branch")
})

test("keeps an explicit user choice", () => {
expect(resolveReviewChangeMode({ options, current: "git", touched: true })).toBe("git")
expect(resolveReviewChangeMode({ options, current: "turn", touched: true })).toBe("turn")
})

test("falls back to the default when the explicit choice is unavailable", () => {
expect(resolveReviewChangeMode({ options: ["git", "turn"], current: "branch", touched: true })).toBe("git")
})

test("returns the current mode when no options exist", () => {
expect(resolveReviewChangeMode({ options: [], current: "git", touched: false })).toBe("git")
})
})

describe("createSessionTabs", () => {
test("normalizes the effective file tab", () => {
createRoot((dispose) => {
Expand Down
34 changes: 34 additions & 0 deletions packages/app/src/pages/session/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,40 @@ type TabsInput = {

export const getSessionKey = (dir: string | undefined, id: string | undefined) => `${dir ?? ""}${id ? `/${id}` : ""}`

export type ReviewChangeMode = "git" | "branch" | "turn"

// Available review-tab change modes, in auto-default priority order. "branch"
// (current branch vs default branch = the session's committed work) comes first
// so a feature-branch session opens on the full task diff instead of
// working-tree-only ("git") changes, which otherwise surface unrelated startup
// drift (e.g. tooling that rewrites .opencode/package.json) as the "only" change.
export function reviewChangeOptions(input: {
vcs?: string
branch?: string
defaultBranch?: string
}): ReviewChangeMode[] {
const list: ReviewChangeMode[] = []
const isGit = input.vcs === "git"
if (isGit && input.branch && input.defaultBranch && input.branch !== input.defaultBranch) list.push("branch")
if (isGit) list.push("git")
list.push("turn")
return list
}

// Resolve the review mode to display. Until the user explicitly picks one
// (`touched`), track the best default (options[0]); after that, keep their
// choice and only fall back to the default when it is no longer available.
export function resolveReviewChangeMode(input: {
options: ReviewChangeMode[]
current: ReviewChangeMode
touched: boolean
}): ReviewChangeMode {
const next = input.options[0]
if (!next) return input.current
if (input.touched) return input.options.includes(input.current) ? input.current : next
return next
}

export function shouldShowFileTree(input: { visible: boolean; opened: boolean }) {
return input.opened && input.visible
}
Expand Down
Loading