Skip to content
Open
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
1 change: 1 addition & 0 deletions apps/web/src/components/ChatView.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export function collectUserMessageBlobPreviewUrls(message: ChatMessage): string[
}

export interface PullRequestDialogState {
open: boolean;
initialReference: string | null;
key: number;
}
Expand Down
46 changes: 34 additions & 12 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1278,7 +1278,12 @@ function ChatViewContent(props: ChatViewProps) {
const localComposerRef = useRef<ChatComposerHandle | null>(null);
const composerRef = useComposerHandleContext() ?? localComposerRef;
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
const [expandedImage, setExpandedImage] = useState<ExpandedImagePreview | null>(null);
const [expandedImageDialog, setExpandedImageDialog] = useState<{
readonly open: boolean;
readonly preview: ExpandedImagePreview;
readonly generation: number;
} | null>(null);
const expandedImage = expandedImageDialog?.preview ?? null;
const [optimisticUserMessages, setOptimisticUserMessages] = useState<ChatMessage[]>([]);
const optimisticUserMessagesRef = useRef(optimisticUserMessages);
optimisticUserMessagesRef.current = optimisticUserMessages;
Expand Down Expand Up @@ -1742,6 +1747,7 @@ function ChatViewContent(props: ChatViewProps) {
return;
}
setPullRequestDialogState({
open: true,
initialReference: reference ?? null,
key: Date.now(),
});
Expand All @@ -1750,7 +1756,7 @@ function ChatViewContent(props: ChatViewProps) {
);

const closePullRequestDialog = useCallback(() => {
setPullRequestDialogState(null);
setPullRequestDialogState((current) => (current ? { ...current, open: false } : current));
}, []);

const openOrReuseProjectDraftThread = useCallback(
Expand Down Expand Up @@ -3908,11 +3914,11 @@ function ChatViewContent(props: ChatViewProps) {
return [];
});
resetLocalDispatch();
setExpandedImage(null);
setExpandedImageDialog(null);
}, [draftId, resetLocalDispatch, threadId]);

const closeExpandedImage = useCallback(() => {
setExpandedImage(null);
setExpandedImageDialog((current) => (current ? { ...current, open: false } : current));
}, []);

const activeWorktreePath = activeThread?.worktreePath ?? null;
Expand Down Expand Up @@ -5600,7 +5606,11 @@ function ChatViewContent(props: ChatViewProps) {
};

const onExpandTimelineImage = useCallback((preview: ExpandedImagePreview) => {
setExpandedImage(preview);
setExpandedImageDialog((current) => ({
open: true,
preview,
generation: (current?.generation ?? 0) + 1,
}));
}, []);
const onOpenTurnDiff = useCallback(
(turnId: TurnId, filePath?: string) => {
Expand Down Expand Up @@ -6077,7 +6087,7 @@ function ChatViewContent(props: ChatViewProps) {
{pullRequestDialogState ? (
<PullRequestThreadDialog
key={pullRequestDialogState.key}
open
open={pullRequestDialogState.open}
environmentId={activeThread.environmentId}
threadId={activeThread.id}
cwd={activeProject?.workspaceRoot ?? null}
Expand All @@ -6087,6 +6097,9 @@ function ChatViewContent(props: ChatViewProps) {
closePullRequestDialog();
}
}}
onOpenChangeComplete={(open) => {
if (!open) setPullRequestDialogState(null);
}}
onPrepared={handlePreparedPullRequestThread}
/>
) : null}
Expand Down Expand Up @@ -6141,8 +6154,11 @@ function ChatViewContent(props: ChatViewProps) {
{rightPanelContent}
</RightPanelTabs>
) : null}
{shouldUsePlanSidebarSheet && rightPanelOpen && activeThreadRef ? (
<RightPanelSheet open onClose={planSidebarOpen ? closePlanSidebar : closePreviewPanel}>
{shouldUsePlanSidebarSheet && activeThreadRef ? (
<RightPanelSheet
open={rightPanelOpen}
Comment on lines +6157 to +6159

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop treating the closed preview sheet as visible

On narrow desktop layouts, this now keeps RightPanelSheet mounted after rightPanelOpen becomes false; because the sheet uses keepMounted and rightPanelContent still passes visible unconditionally to PreviewPanel, the hidden preview continues subscribing to preview actions and retaining its browser surface. Consequently, refresh/zoom/focus actions can target a closed panel and the preview remains active in the background after the exit animation. Retain it only through the close transition, then unmount it or mark the preview not visible.

AGENTS.md reference: AGENTS.md:L15-L17

Useful? React with 👍 / 👎.

onClose={planSidebarOpen ? closePlanSidebar : closePreviewPanel}
>
<RightPanelTabs
mode="sheet"
layoutControls={panelToggleControls}
Expand Down Expand Up @@ -6170,13 +6186,19 @@ function ChatViewContent(props: ChatViewProps) {
</RightPanelSheet>
) : null}

{expandedImage && (
{expandedImageDialog ? (
<ExpandedImageDialog
key={`${expandedImage.images[expandedImage.index]?.src ?? "image"}:${expandedImage.index}`}
key={expandedImageDialog.generation}
open={expandedImageDialog.open}
preview={expandedImage}
onClose={closeExpandedImage}
onOpenChange={(open) => {
if (!open) closeExpandedImage();
}}
onOpenChangeComplete={(open) => {
if (!open) setExpandedImageDialog(null);
}}
/>
)}
) : null}
</div>
);
}
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/CommandPalette.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("reduceCommandPaletteUiState", () => {

expect(
reduceCommandPaletteUiState(contentOpen, { _tag: "ToggleMode", mode: "content" }),
).toEqual({ open: false, mode: "command", openIntent: null });
).toEqual({ open: false, mode: "content", openIntent: null });
});

it("switches between open modes without closing", () => {
Expand Down Expand Up @@ -62,15 +62,15 @@ describe("reduceCommandPaletteUiState", () => {
});
});

it("resets to command mode for dialog-driven opens and closes", () => {
it("preserves the mode on close and resets it on open", () => {
const filesOpen = reduceCommandPaletteUiState(closedState, {
_tag: "ToggleMode",
mode: "files",
});

expect(reduceCommandPaletteUiState(filesOpen, { _tag: "SetOpen", open: false })).toEqual({
open: false,
mode: "command",
mode: "files",
openIntent: null,
});
expect(reduceCommandPaletteUiState(filesOpen, { _tag: "SetOpen", open: true })).toEqual({
Expand Down
10 changes: 4 additions & 6 deletions apps/web/src/components/CommandPalette.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,12 @@ export function reduceCommandPaletteUiState(
): CommandPaletteUiState {
switch (action._tag) {
case "SetOpen":
return {
open: action.open,
mode: "command",
openIntent: action.open ? state.openIntent : null,
};
return action.open
? { open: true, mode: "command", openIntent: state.openIntent }
: { ...state, open: false, openIntent: null };
case "ToggleMode":
return state.open && state.mode === action.mode
? { open: false, mode: "command", openIntent: null }
? { ...state, open: false, openIntent: null }
: { open: true, mode: action.mode, openIntent: null };
case "OpenAddProject":
return { open: true, mode: "command", openIntent: { kind: "add-project" } };
Expand Down
49 changes: 29 additions & 20 deletions apps/web/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,13 +366,23 @@ export function CommandPalette({ children }: { children: ReactNode }) {
mode: "command",
openIntent: null,
});
const setOpen = useCallback((open: boolean) => dispatch({ _tag: "SetOpen", open }), []);
const toggleMode = useCallback(
(mode: SearchOverlayMode) => dispatch({ _tag: "ToggleMode", mode }),
[],
);
const openAddProject = useCallback(() => dispatch({ _tag: "OpenAddProject" }), []);
const openNewThreadIn = useCallback(() => dispatch({ _tag: "OpenNewThreadIn" }), []);
const [dialogContentMounted, setDialogContentMounted] = useState(false);
const setOpen = useCallback((open: boolean) => {
if (open) setDialogContentMounted(true);
dispatch({ _tag: "SetOpen", open });
}, []);
const toggleMode = useCallback((mode: SearchOverlayMode) => {
setDialogContentMounted(true);
dispatch({ _tag: "ToggleMode", mode });
}, []);
const openAddProject = useCallback(() => {
setDialogContentMounted(true);
dispatch({ _tag: "OpenAddProject" });
}, []);
const openNewThreadIn = useCallback(() => {
setDialogContentMounted(true);
dispatch({ _tag: "OpenNewThreadIn" });
}, []);
const clearOpenIntent = useCallback(() => dispatch({ _tag: "ClearOpenIntent" }), []);
const keybindings = useAtomValue(primaryServerKeybindingsAtom);
const composerHandleRef = useRef<ChatComposerHandle | null>(null);
Expand Down Expand Up @@ -455,23 +465,26 @@ export function CommandPalette({ children }: { children: ReactNode }) {
}
setOpen(open);
}}
onOpenChangeComplete={(open) => {
if (!open) setDialogContentMounted(false);
}}
>
{children}
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
<CommandPaletteDialog
open={state.open}
mode={state.mode}
openIntent={state.openIntent}
setOpen={setOpen}
openOverlayMode={toggleMode}
clearOpenIntent={clearOpenIntent}
/>
{dialogContentMounted ? (
<CommandPaletteDialog
mode={state.mode}
openIntent={state.openIntent}
setOpen={setOpen}
openOverlayMode={toggleMode}
clearOpenIntent={clearOpenIntent}
/>
) : null}
</CommandDialog>
</ComposerHandleContext>
);
}

function CommandPaletteDialog(props: {
readonly open: boolean;
readonly mode: SearchOverlayMode;
readonly openIntent: CommandPaletteOpenIntent | null;
readonly setOpen: (open: boolean) => void;
Expand All @@ -480,10 +493,6 @@ function CommandPaletteDialog(props: {
}) {
const composerHandleRef = useComposerHandleContext();

if (!props.open) {
return null;
}

return (
<CommandDialogPopup
aria-label={
Expand Down
Loading
Loading