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
52 changes: 47 additions & 5 deletions apps/web/src/rightPanelStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,12 +602,12 @@ describe("rightPanelStore", () => {
});
});

it("closing the final terminal pane removes its surface and closes the panel", () => {
it("closing the final terminal pane removes its surface and keeps the panel open", () => {
useRightPanelStore.getState().openTerminal(refA, "term-1");
useRightPanelStore.getState().closeTerminal(refA, "terminal:term-1", "term-1");

expect(selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, refA)).toEqual({
isOpen: false,
isOpen: true,
activeSurfaceId: null,
surfaces: [],
});
Expand All @@ -623,10 +623,52 @@ describe("rightPanelStore", () => {
);
});

it("closing the final surface closes the panel", () => {
it("closing the final surface keeps the panel open on the empty state", () => {
useRightPanelStore.getState().openTerminal(refA, "term-1");
useRightPanelStore.getState().closeSurface(refA, "terminal:term-1");

expect(selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, refA)).toEqual({
isOpen: true,
activeSurfaceId: null,
surfaces: [],
});
});

it("closing a file opened from the explorer keeps the panel open on the empty state", () => {
useRightPanelStore.getState().open(refA, "files");
useRightPanelStore.getState().openFile(refA, "src/index.ts");
useRightPanelStore.getState().closeSurface(refA, "file:src/index.ts");

expect(selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, refA)).toEqual({
isOpen: true,
activeSurfaceId: null,
surfaces: [],
});
});

it("closing the final surface of the pull-request list's shared panel closes it", () => {
const pullRequestsPanelRef = scopeThreadRef(
"env-1" as EnvironmentId,
ThreadId.make("pull-requests-panel"),
);
const target = { projectId: "project-1", repository: "owner/repo", number: 1 };
useRightPanelStore.getState().openPullRequest(pullRequestsPanelRef, target);
useRightPanelStore.getState().closeSurface(pullRequestsPanelRef, pullRequestSurfaceId(target));

expect(
selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, pullRequestsPanelRef),
).toEqual({
isOpen: false,
activeSurfaceId: null,
surfaces: [],
});
});

it("closing the final surface of a hidden panel leaves it hidden", () => {
useRightPanelStore.getState().openTerminal(refA, "term-1");
useRightPanelStore.getState().close(refA);
useRightPanelStore.getState().closeSurface(refA, "terminal:term-1");

expect(selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, refA)).toEqual({
isOpen: false,
activeSurfaceId: null,
Expand Down Expand Up @@ -670,14 +712,14 @@ describe("rightPanelStore", () => {
});
});

it("closing all surfaces closes the panel", () => {
it("closing all surfaces keeps the panel open on the empty state", () => {
useRightPanelStore.getState().openBrowser(refA, "tab-a");
useRightPanelStore.getState().openFile(refA, "src/index.ts");

useRightPanelStore.getState().closeAllSurfaces(refA);

expect(selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, refA)).toEqual({
isOpen: false,
isOpen: true,
activeSurfaceId: null,
surfaces: [],
});
Expand Down
34 changes: 30 additions & 4 deletions apps/web/src/rightPanelStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ const RIGHT_PANEL_STORAGE_VERSION = 11;
*/
const isPullRequestsPanelKey = (threadKey: string) => threadKey.endsWith(":pull-requests-panel");

/**
* Closing tabs never hides a thread's panel. An open panel with no surfaces is a
* real state that renders the empty-state picker, and it is already the state the
* panel opens in on a thread that has no tabs yet, so emptying it by closing the
* last tab lands there too instead of collapsing. Visibility stays owned by
* `close`/`toggleVisibility`.
*
* The pull-request list's shared panel is the exception: it renders only while a
* change request is selected, so emptying that one still closes it rather than
* leaving an open panel with nothing to show.
*/
const isOpenAfterClose = (
current: ThreadRightPanelState,
ref: ScopedThreadRef,
remaining: number,
): boolean => current.isOpen && (remaining > 0 || !isPullRequestsPanelKey(scopedThreadKey(ref)));

export interface ThreadRightPanelState {
isOpen: boolean;
activeSurfaceId: string | null;
Expand Down Expand Up @@ -471,7 +488,7 @@ export const useRightPanelStore = create<RightPanelStoreState>()(
const fallback = surfaces[Math.min(index, surfaces.length - 1)] ?? null;
return {
...current,
isOpen: surfaces.length > 0 && current.isOpen,
isOpen: isOpenAfterClose(current, ref, surfaces.length),
surfaces,
activeSurfaceId:
current.activeSurfaceId === surfaceId
Expand Down Expand Up @@ -511,12 +528,16 @@ export const useRightPanelStore = create<RightPanelStoreState>()(
if (index < 0) return current;
const surfaces = current.surfaces.filter((surface) => surface.id !== surfaceId);
if (current.activeSurfaceId !== surfaceId) {
return { ...current, isOpen: surfaces.length > 0 && current.isOpen, surfaces };
return {
...current,
isOpen: isOpenAfterClose(current, ref, surfaces.length),
surfaces,
};
}
const fallback = surfaces[Math.min(index, surfaces.length - 1)] ?? null;
return {
...current,
isOpen: surfaces.length > 0 && current.isOpen,
isOpen: isOpenAfterClose(current, ref, surfaces.length),
surfaces,
activeSurfaceId: fallback?.id ?? null,
};
Expand Down Expand Up @@ -556,7 +577,12 @@ export const useRightPanelStore = create<RightPanelStoreState>()(
byThreadKey: updateThread(state.byThreadKey, scopedThreadKey(ref), (current) =>
current.surfaces.length === 0
? current
: { ...current, isOpen: false, surfaces: [], activeSurfaceId: null },
: {
...current,
isOpen: isOpenAfterClose(current, ref, 0),
surfaces: [],
activeSurfaceId: null,
},
),
})),
reconcileBrowserSurfaces: (ref, tabIds) =>
Expand Down
Loading