Skip to content
Merged
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
31 changes: 20 additions & 11 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1658,7 +1658,6 @@ function ChatViewContent(props: ChatViewProps) {
// multiple chats share one on-disk tree, matching the sidebar's
// "New thread on {branch}" affordance.
const newChatWorktreePath = activeThread?.worktreePath ?? null;
const newChatWorktreeBranch = activeThread?.branch ?? null;
// File-diff "content tabs" live in the chat-column tab strip and are scoped to
// the worktree, so they stay visible while switching between its chats. Key
// them off the stable route ref (resolved via the shell/draft worktree) rather
Expand All @@ -1674,16 +1673,6 @@ function ChatViewContent(props: ChatViewProps) {
// The browser preview is shown as a content tab in the chat column (beside the
// file/diff viewers), not in the right panel.
const previewTabActive = activeContentTab?.view === "preview";
const handleNewChatInScope = useCallback(() => {
if (!activeProjectRef) return;
void handleNewThread(activeProjectRef, {
branch: newChatWorktreeBranch,
worktreePath: newChatWorktreePath,
envMode: newChatWorktreePath ? "worktree" : "local",
forceNew: true,
preservePreviousDraft: true,
});
}, [activeProjectRef, newChatWorktreeBranch, newChatWorktreePath, handleNewThread]);
const activeEnvironmentShell = useEnvironmentQuery(
activeThread ? environmentShell.stateAtom(activeThread.environmentId) : null,
);
Expand Down Expand Up @@ -2509,6 +2498,26 @@ function ChatViewContent(props: ChatViewProps) {
input: { cwd: gitStatusCwd },
}),
);
// The reference a review or new worktree chat targets must follow the
// worktree's *actual* checked-out branch — the one shown in the footer chip —
// not the thread's stored `branch`, which drifts after a checkout, PR
// checkout, or branch switch inside the worktree. Mirror the footer's
// resolution (live git ref, then stored branch) for worktree-backed chats so
// a review never runs against a stale reference; local chats keep the stored
// value.
const newChatWorktreeBranch = newChatWorktreePath
? (gitStatusQuery.data?.refName ?? activeThread?.branch ?? null)
: (activeThread?.branch ?? null);
const handleNewChatInScope = useCallback(() => {
if (!activeProjectRef) return;
void handleNewThread(activeProjectRef, {
branch: newChatWorktreeBranch,
worktreePath: newChatWorktreePath,
envMode: newChatWorktreePath ? "worktree" : "local",
forceNew: true,
preservePreviousDraft: true,
});
}, [activeProjectRef, newChatWorktreeBranch, newChatWorktreePath, handleNewThread]);
const keybindings = useAtomValue(primaryServerKeybindingsAtom);
const availableEditors = useAtomValue(primaryServerAvailableEditorsAtom);
// Prefer an instance-id match so a custom Codex instance (e.g.
Expand Down
61 changes: 41 additions & 20 deletions apps/web/src/components/DiffPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from "lucide-react";
import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useOpenInPreferredEditor } from "../editorPreferences";
import { type DraftId } from "../composerDraftStore";
import { type DraftId, useComposerDraftStore } from "../composerDraftStore";
import { openDiffFilePrimaryAction } from "../diffFileActions";
import { useCheckpointDiff } from "~/lib/checkpointDiffState";
import { cn } from "~/lib/utils";
Expand Down Expand Up @@ -309,27 +309,48 @@ export default function DiffPanel({
// thread yet, so this resolves to the representative sibling that owns the
// worktree; for an ordinary chat it's the same worktree either way.
const workspaceThread = useThread(workspaceThreadRef) ?? activeThread;
const activeProjectId = workspaceThread?.projectId ?? null;
// A worktree can hold only a not-yet-sent draft (no server thread), so
// `workspaceThread` is null even though the draft already targets a worktree
// on disk. Read that draft's worktree/environment/project so the shared diff,
// git status, and branch preview load on open instead of only after the first
// message promotes the draft to a server thread. Drafts with no worktree keep
// their empty per-chat panel, matching the pre-draft behavior.
const draftThreadsByThreadKey = useComposerDraftStore((state) => state.draftThreadsByThreadKey);
const workspaceDraft = useMemo(() => {
if (workspaceThread || !workspaceThreadRef) return null;
const draft = Object.values(draftThreadsByThreadKey).find(
(candidate) =>
candidate.environmentId === workspaceThreadRef.environmentId &&
candidate.threadId === workspaceThreadRef.threadId,
);
return draft?.worktreePath != null ? draft : null;
}, [draftThreadsByThreadKey, workspaceThread, workspaceThreadRef]);
// The resolved worktree data source: a real thread when one exists, otherwise
// the worktree-targeting draft. Everything below keys off these instead of
// `workspaceThread` so a draft-only worktree behaves like a real one.
const workspaceEnvironmentId =
workspaceThread?.environmentId ?? workspaceDraft?.environmentId ?? null;
const hasWorkspaceSource = workspaceThread != null || workspaceDraft != null;
const activeProjectId = workspaceThread?.projectId ?? workspaceDraft?.projectId ?? null;
const activeProject = useProject(
workspaceThread && activeProjectId
workspaceEnvironmentId && activeProjectId
? {
environmentId: workspaceThread.environmentId,
environmentId: workspaceEnvironmentId,
projectId: activeProjectId,
}
: null,
);
const activeCwd = workspaceThread?.worktreePath ?? activeProject?.workspaceRoot;
const serverConfig = useAtomValue(
serverEnvironment.configValueAtom(workspaceThread?.environmentId ?? null),
);
const activeCwd =
workspaceThread?.worktreePath ?? workspaceDraft?.worktreePath ?? activeProject?.workspaceRoot;
const serverConfig = useAtomValue(serverEnvironment.configValueAtom(workspaceEnvironmentId));
const openInPreferredEditor = useOpenInPreferredEditor(
workspaceThread?.environmentId ?? null,
workspaceEnvironmentId,
serverConfig?.availableEditors ?? [],
);
const gitStatusQuery = useEnvironmentQuery(
workspaceThread != null && activeCwd != null
workspaceEnvironmentId != null && activeCwd != null
? vcsEnvironment.status({
environmentId: workspaceThread.environmentId,
environmentId: workspaceEnvironmentId,
input: { cwd: activeCwd },
})
: null,
Expand Down Expand Up @@ -442,9 +463,9 @@ export default function DiffPanel({
{ enabled: isGitRepo && selectedTurn !== undefined },
);
const primaryBranchDiffPreview = useEnvironmentQuery(
selectedTurnId === null && workspaceThread && activeCwd
selectedTurnId === null && workspaceEnvironmentId && activeCwd
? reviewEnvironment.diffPreview({
environmentId: workspaceThread.environmentId,
environmentId: workspaceEnvironmentId,
input: {
cwd: activeCwd,
...(effectiveBaseRef ? { baseRef: effectiveBaseRef } : {}),
Expand All @@ -465,9 +486,9 @@ export default function DiffPanel({
serverConfig?.cwd !== undefined &&
serverConfig.cwd !== activeCwd;
const fallbackBranchDiffPreview = useEnvironmentQuery(
shouldRetryBranchDiffAtEnvironmentCwd && workspaceThread && serverConfig
shouldRetryBranchDiffAtEnvironmentCwd && workspaceEnvironmentId && serverConfig
? reviewEnvironment.diffPreview({
environmentId: workspaceThread.environmentId,
environmentId: workspaceEnvironmentId,
input: {
cwd: serverConfig.cwd,
...(effectiveBaseRef ? { baseRef: effectiveBaseRef } : {}),
Expand Down Expand Up @@ -537,10 +558,10 @@ export default function DiffPanel({
const localBranchRefs = useEnvironmentQuery(
selectedTurnId === null &&
selectedGitScope === "branch" &&
workspaceThread &&
workspaceEnvironmentId &&
branchDiffPreview.data?.cwd
? vcsEnvironment.listRefs({
environmentId: workspaceThread.environmentId,
environmentId: workspaceEnvironmentId,
input: {
cwd: branchDiffPreview.data.cwd,
includeMatchingRemoteRefs: true,
Expand All @@ -554,10 +575,10 @@ export default function DiffPanel({
const remoteBranchRefs = useEnvironmentQuery(
selectedTurnId === null &&
selectedGitScope === "branch" &&
workspaceThread &&
workspaceEnvironmentId &&
branchDiffPreview.data?.cwd
? vcsEnvironment.listRefs({
environmentId: workspaceThread.environmentId,
environmentId: workspaceEnvironmentId,
input: {
cwd: branchDiffPreview.data.cwd,
includeMatchingRemoteRefs: true,
Expand Down Expand Up @@ -1191,7 +1212,7 @@ export default function DiffPanel({

return (
<DiffPanelShell mode={mode} header={headerRow}>
{!workspaceThread ? (
{!hasWorkspaceSource ? (
<div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70">
Select a thread to inspect turn diffs.
</div>
Expand Down