diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx
index 531a13724b..784d4d7fbc 100644
--- a/desktop/src/features/channels/ui/ChannelPane.tsx
+++ b/desktop/src/features/channels/ui/ChannelPane.tsx
@@ -84,6 +84,7 @@ export const ChannelPane = React.memo(function ChannelPane({
header,
idleAuxiliaryPanel = null,
idleAuxiliaryHeaderActions,
+ idleAuxiliaryOverridesThread = false,
idleAuxiliaryTitle = "",
hasOlderMessages,
historyExhausted,
@@ -487,11 +488,15 @@ export const ChannelPane = React.memo(function ChannelPane({
hasThreadSurface: Boolean(threadHeadMessage) || shouldShowThreadSkeleton,
useSplitAuxiliaryPane,
});
+ const priorityIdleAuxiliary =
+ idleAuxiliaryOverridesThread && useFocusIdleDrawer;
const { channelIsCovered, markExitComplete } = useFocusDrawerPresence(
useFocusThreadDrawer || useFocusIdleDrawer,
- useFocusThreadDrawer
- ? onCloseThread
- : (onCloseIdleAuxiliaryPanel ?? onCloseThread),
+ priorityIdleAuxiliary
+ ? (onCloseIdleAuxiliaryPanel ?? onCloseThread)
+ : useFocusThreadDrawer
+ ? onCloseThread
+ : (onCloseIdleAuxiliaryPanel ?? onCloseThread),
);
const { changeThreadViewMode, layoutScrollTargetId, resolveScrollTarget } =
useThreadViewModeSwitch({
@@ -551,6 +556,25 @@ export const ChannelPane = React.memo(function ChannelPane({
) : (
wrapAux(panel, "idle-auxiliary-panel")
);
+ const idleAuxiliarySurface =
+ idleAuxiliaryPanel && onCloseIdleAuxiliaryPanel
+ ? wrapIdlePanel(
+
+ {idleAuxiliaryPanel}
+ ,
+ )
+ : null;
const threadHeaderLeading = useSplitAuxiliaryPane ? (
) : undefined;
@@ -799,14 +823,7 @@ export const ChannelPane = React.memo(function ChannelPane({
) : null}
- {/*
- * `AnimatePresence` keeps the focus thread drawer mounted through its exit
- * animation — without it the drawer's own existence condition
- * (`useFocusThreadDrawer`, which is derived from `threadHeadMessage`) goes
- * false on the same frame as the close, and there is nothing left to
- * animate. It can hold the real thread through the exit rather than a
- * frozen snapshot because the panel is fully prop-driven.
- */}
+ {/* Keep focus drawers mounted until their exit animation finishes. */}
{channelManagementOpen && activeChannel ? (
+ ) : priorityIdleAuxiliary && idleAuxiliarySurface ? (
+ idleAuxiliarySurface
) : threadHeadMessage ? (
(() => {
const panel = (
@@ -975,24 +994,9 @@ export const ChannelPane = React.memo(function ChannelPane({
);
return wrapAux(panel, "user-profile-panel");
})()
- ) : idleAuxiliaryPanel && onCloseIdleAuxiliaryPanel ? (
- wrapIdlePanel(
-
- {idleAuxiliaryPanel}
- ,
- )
- ) : null}
+ ) : (
+ idleAuxiliarySurface
+ )}
);
diff --git a/desktop/src/features/channels/ui/ChannelPane.types.ts b/desktop/src/features/channels/ui/ChannelPane.types.ts
index 3295113306..83a9794e5a 100644
--- a/desktop/src/features/channels/ui/ChannelPane.types.ts
+++ b/desktop/src/features/channels/ui/ChannelPane.types.ts
@@ -49,11 +49,13 @@ export type ChannelPaneProps = {
header?: React.ReactNode;
/**
* Idle-state body for the right auxiliary pane (project extras, etc.).
- * Shown only when no thread, profile, agent session, or channel-management
- * panel is open — the same slot as those panels.
+ * Uses the same slot as thread, profile, agent-session, and management panels.
+ * By default it yields to those surfaces; callers may opt into thread override.
*/
idleAuxiliaryPanel?: React.ReactNode;
idleAuxiliaryHeaderActions?: IdleAuxiliaryHeaderControls;
+ /** Show the idle auxiliary surface ahead of an already-open thread. */
+ idleAuxiliaryOverridesThread?: boolean;
idleAuxiliaryTitle?: string;
hasOlderMessages?: boolean;
/** True when the loaded window provably starts at the channel's beginning. */
diff --git a/desktop/src/features/channels/ui/ChannelScreen.tsx b/desktop/src/features/channels/ui/ChannelScreen.tsx
index 795647ec2f..1a92135e9c 100644
--- a/desktop/src/features/channels/ui/ChannelScreen.tsx
+++ b/desktop/src/features/channels/ui/ChannelScreen.tsx
@@ -91,6 +91,7 @@ export function ChannelScreen({
headerEndActions,
idleAuxiliaryPanel,
idleAuxiliaryHeaderActions,
+ idleAuxiliaryOverridesThread,
idleAuxiliaryTitle,
onAddFiles,
onCloseIdleAuxiliaryPanel,
@@ -855,6 +856,7 @@ export function ChannelScreen({
header={channelHeader}
idleAuxiliaryPanel={idleAuxiliaryPanel}
idleAuxiliaryHeaderActions={idleAuxiliaryHeaderActions}
+ idleAuxiliaryOverridesThread={idleAuxiliaryOverridesThread}
idleAuxiliaryTitle={idleAuxiliaryTitle}
hasOlderMessages={hasOlderMessages}
historyExhausted={historyExhausted}
diff --git a/desktop/src/features/channels/ui/ChannelScreen.types.ts b/desktop/src/features/channels/ui/ChannelScreen.types.ts
index 33ac2375ce..e555066704 100644
--- a/desktop/src/features/channels/ui/ChannelScreen.types.ts
+++ b/desktop/src/features/channels/ui/ChannelScreen.types.ts
@@ -21,6 +21,7 @@ export type ChannelScreenProps = {
currentProfile?: Profile;
idleAuxiliaryPanel?: ReactNode;
idleAuxiliaryHeaderActions?: IdleAuxiliaryHeaderControls;
+ idleAuxiliaryOverridesThread?: boolean;
idleAuxiliaryTitle?: string;
headerEndActions?: ReactNode;
onAddFiles?: () => void;
diff --git a/desktop/src/features/projects/ui/ProjectChannelHome.tsx b/desktop/src/features/projects/ui/ProjectChannelHome.tsx
index e259ca2487..7d4619096e 100644
--- a/desktop/src/features/projects/ui/ProjectChannelHome.tsx
+++ b/desktop/src/features/projects/ui/ProjectChannelHome.tsx
@@ -352,6 +352,7 @@ export function ProjectChannelHome({
backLabel: workspaceDetail?.backLabel,
onBack: workspaceDetail?.onBack,
}}
+ idleAuxiliaryOverridesThread={workspaceSheetOpen}
idleAuxiliaryTitle={
workspaceSheetTab
? projectHomeWorkspaceSheetTitle(workspaceSheetTab)
diff --git a/desktop/tests/e2e/project-commit-detail.spec.ts b/desktop/tests/e2e/project-commit-detail.spec.ts
index 5064527d4a..dd5501dbf2 100644
--- a/desktop/tests/e2e/project-commit-detail.spec.ts
+++ b/desktop/tests/e2e/project-commit-detail.spec.ts
@@ -791,6 +791,14 @@ test("commit detail opens from the commits feed with a diff", async ({
.first();
await expect(projectEntry).toBeVisible({ timeout: 10_000 });
await projectEntry.click();
+ const rootMessage = page
+ .getByTestId("message-timeline")
+ .getByTestId("message-row")
+ .first();
+ await expect(rootMessage).toBeVisible();
+ await rootMessage.hover();
+ await rootMessage.getByRole("button", { name: "Reply" }).click();
+ await expect(page.getByTestId("focus-thread-drawer")).toBeVisible();
await page.getByTestId("project-home-context-tasks").click();
await expect(page.getByTestId("project-home-workspace-sheet")).toBeVisible();
const workspaceSheet = page.getByTestId("project-home-workspace-sheet");