diff --git a/docs/explorer-sidebar.md b/docs/explorer-sidebar.md index 606a82391a..9415abd85a 100644 --- a/docs/explorer-sidebar.md +++ b/docs/explorer-sidebar.md @@ -31,7 +31,8 @@ switcher keeps only agents and terminals. A panel kind opts in with `compactShee sheet and tab, so callers open targets the same way on every layout. Each sheet holds one ordinary `WorkspaceTab`, and sheets stack: a target opened from a sheet covers it, and closing the top sheet (drag, Close, backdrop, or Android Back) uncovers the one below. Revealing a target that already has -a sheet drops the sheets above it. Opening a sheet as a tab dismisses the sheets above it, since the +a sheet brings that sheet back to the top and closes the sheets above it, except ones holding unsaved +work. Opening a sheet as a tab dismisses the sheets above it, since the tab would sit under them. The stack is not persisted. Sheets are modal and cover the whole compact column, header included. A sheet's header is its drag diff --git a/packages/app/src/stores/workspace-layout-store.test.ts b/packages/app/src/stores/workspace-layout-store.test.ts index 73000b8bc8..fc973f442d 100644 --- a/packages/app/src/stores/workspace-layout-store.test.ts +++ b/packages/app/src/stores/workspace-layout-store.test.ts @@ -4878,19 +4878,26 @@ describe("compact workspace sheet", () => { }); }); - it("opens a stacked target as a tab rather than dropping a modified sheet above it", () => { + it("brings a covered sheet back to the top and keeps a modified sheet above it beneath", () => { const store = createCompactStore(); const otherTarget = { kind: "file" as const, path: "src/other.ts" }; - store.getState().openTab({ workspaceKey, target: otherTarget, intent: "reveal" }); + const otherTabId = store + .getState() + .openTab({ workspaceKey, target: otherTarget, intent: "reveal" }); const modifiedSheetTabId = openModifiedFileSheet(store); + const focusedBefore = focusedTabId(store); const tabId = store .getState() .openTab({ workspaceKey, target: otherTarget, intent: "reveal" }); - expect(sheetTabId(store)).toBe(modifiedSheetTabId); - expect(stripTab(store, tabId ?? "")).toMatchObject({ target: otherTarget }); - expect(focusedTabId(store)).toBe(tabId); + expect(tabId).toBe(otherTabId); + expect(sheetTargets(store)).toEqual([fileTarget, otherTarget]); + expect(store.getState().sheetsByWorkspace[workspaceKey]?.[0]?.tab.tabId).toBe( + modifiedSheetTabId, + ); + expect(stripTab(store, tabId ?? "")).toBeNull(); + expect(focusedTabId(store)).toBe(focusedBefore); }); it("opens a tab target under a modified sheet without closing it", () => { diff --git a/packages/app/src/stores/workspace-layout-store.ts b/packages/app/src/stores/workspace-layout-store.ts index 9d0a2a841e..ba186b655a 100644 --- a/packages/app/src/stores/workspace-layout-store.ts +++ b/packages/app/src/stores/workspace-layout-store.ts @@ -864,7 +864,7 @@ function sheetHoldsUnsavedWork(workspaceKey: string, sheet: WorkspaceSheet): boo /** * Where an open lands in the sheet stack, or null when it opens as a tab. A new target goes on * top. A reveal of a resource a sheet already shows keeps that sheet's tab and brings it back to - * the top, closing the sheets above it, unless one of those holds unsaved work. + * the top; the sheets above it close, except ones holding unsaved work, which stay beneath it. */ function resolveSheetPlacement( state: WorkspaceLayoutStore, @@ -878,11 +878,7 @@ function resolveSheetPlacement( const sheets = getSheets(state, workspaceKey); const resourceKey = panelResourceKey(target); const index = sheets.findIndex((sheet) => panelResourceKey(sheet.tab.target) === resourceKey); - if (index === -1) return { policy, keptIndex: null }; - const above = sheets.slice(index + 1); - return above.some((sheet) => sheetHoldsUnsavedWork(workspaceKey, sheet)) - ? null - : { policy, keptIndex: index }; + return { policy, keptIndex: index === -1 ? null : index }; } /** Closes the sheets because navigation moved on underneath them, except ones with unsaved work. */ @@ -1005,7 +1001,15 @@ export function createWorkspaceLayoutStore( parentTabId, state: input.state, }); - const below = keptIndex === null ? sheets : sheets.slice(0, keptIndex); + const below = + keptIndex === null + ? sheets + : [ + ...sheets.slice(0, keptIndex), + ...sheets + .slice(keptIndex + 1) + .filter((above) => sheetHoldsUnsavedWork(normalizedWorkspaceKey, above)), + ]; set((state) => withSheets(state, normalizedWorkspaceKey, [...below, sheet])); return sheet.tab.tabId; }