From cf6b36edbb286f55979036c285dfd678d3327edb Mon Sep 17 00:00:00 2001 From: Sawyer Hood Date: Wed, 5 Aug 2026 23:35:36 +0000 Subject: [PATCH 1/2] Restore plugin pane chrome and the Docs plugin sidebar Four regressions landed together and left plugin panes with broken chrome. Three came from #991, one predates it. Docs sidebar. #991 restyled the sidebar to bg-sidebar and painted a fake background up into the app header. #1019 then gave every header a seam, which that background cut short, so the plugin's own panel read as the host's shared sidebar. The sidebar sits below the header again and the seam runs edge to edge. Docs header toggle. #991 also deleted the shared sidebar store when its mounts hit zero. The header and the sidebar live in separate React subtrees and find each other only through that map, so a deleted entry left each on its own store: two toggles, and the header one did nothing. Entries now live for the session; a reopened pane still starts clean because the view resets state on its first mount. Plugin pane controls. #991 stripped full screen and move-to-side from plugin panes, and dropped their reserve for the host's pinned panel toggle. That toggle then landed on Close pane whenever a plugin pane held the workspace corner. Position alone decides the reserve now, and plugin panes carry the same pane controls as any other pane. Empty right panel. #962 disabled the panel toggle for panes that publish no panel and deleted the placeholder; #991 removed the control outright. Both are back from #736: the toggle always works, and a pane without a panel shows "This pane has no right panel." The corner button stays visible while that empty state is open, since it has no chrome of its own to close from. Also: the reserve no longer trails an empty slot when the host hides its toggle (a full-screen pane), and the corner button moved to right-4 so it shares the pane header's action axis. Tests: 2364 pass in @bb/app; 59 in bb-plugin-simple-notes. Typecheck clean, lint reports no errors. Verified in the dev app: header seam, one working Docs toggle, plugin full screen and restore, no button collision, the empty-state panel, and even header spacing. Co-Authored-By: Claude Opus 5 (1M context) --- .../SecondaryPanelHostLayoutContext.ts | 8 + .../thread-detail/SplitThreadArea.test.tsx | 144 ++++++++++++++---- .../views/thread-detail/SplitThreadArea.tsx | 48 +++--- .../SplitWorkspaceSecondaryPanelHost.tsx | 133 ++++++++++++---- official-plugins/docs/app.test.tsx | 30 +--- official-plugins/docs/app.tsx | 52 ++----- 6 files changed, 263 insertions(+), 152 deletions(-) diff --git a/apps/app/src/components/secondary-panel/SecondaryPanelHostLayoutContext.ts b/apps/app/src/components/secondary-panel/SecondaryPanelHostLayoutContext.ts index 01e637b1f8..f923f1d80e 100644 --- a/apps/app/src/components/secondary-panel/SecondaryPanelHostLayoutContext.ts +++ b/apps/app/src/components/secondary-panel/SecondaryPanelHostLayoutContext.ts @@ -20,6 +20,14 @@ export interface SecondaryPanelHostLayout { isOpen: boolean; /** The panel remains logically open but is hidden while a thread is full screen. */ isSuppressed: boolean; + /** + * Whether the host paints its toggle over the workspace's top-right corner. + * An open panel hosts the toggle in its own chrome, and a full-screen pane + * hides it, so the right-edge pane headers reserve that corner only while + * this is true. The empty-state panel has no chrome of its own, so the + * corner toggle stays with it — otherwise no button could close it. + */ + pinsCornerToggle: boolean; } export const SecondaryPanelHostLayoutContext = diff --git a/apps/app/src/views/thread-detail/SplitThreadArea.test.tsx b/apps/app/src/views/thread-detail/SplitThreadArea.test.tsx index 4a0fdae993..f869967659 100644 --- a/apps/app/src/views/thread-detail/SplitThreadArea.test.tsx +++ b/apps/app/src/views/thread-detail/SplitThreadArea.test.tsx @@ -1161,7 +1161,8 @@ describe("SplitThreadArea", () => { expect(toggle.classList).toContain("absolute"); expect(toggle.classList).toContain("hidden"); expect(toggle.classList).not.toContain("relative"); - expect(toggle.classList).toContain("right-2.5"); + // The corner button shares the pane header's px-4 action axis. + expect(toggle.classList).toContain("right-4"); expect(toggle.classList).toContain("top-2.5"); const hint = screen.getByText("Ctrl Shift P"); expect(hint.classList).toContain("absolute"); @@ -1260,7 +1261,7 @@ describe("SplitThreadArea", () => { ).toBe("true"); }); - it("omits app panel and full-screen controls from plugin panes", async () => { + it("shows the empty panel state while a plugin pane is focused", async () => { const layout = pluginSplitLayout(); layout.focusedPaneId = "pane-1"; renderSplitArea({ @@ -1281,30 +1282,34 @@ describe("SplitThreadArea", () => { throw new Error("Expected plugin split pane"); } - // Focusing the plugin pane hides the app panel without layering disabled - // app controls over the plugin's own header and right panel. + // The plugin pane publishes no panel, so the window panel keeps its place + // and states that plainly. Its toggle and pane controls all stay live. fireEvent.pointerDown(pluginPane); - await waitFor(() => - expect(screen.queryByTestId("split-workspace-panel-toggle")).toBeNull(), + const emptyState = await screen.findByTestId( + "split-workspace-empty-panel-state", ); - expect( - document.getElementById("split-workspace-empty-secondary-panel"), - ).toBeNull(); - expect( - document.getElementById("split-workspace-empty-secondary-panel-handle"), - ).toBeNull(); + expect(emptyState.textContent).toContain("This pane has no right panel."); + const pluginToggle = screen + .getByTestId("split-workspace-panel-toggle") + .querySelector("button"); + expect(pluginToggle?.hasAttribute("disabled")).toBe(false); expect( pluginPane.querySelector('button[aria-label*="Full Screen"]'), - ).toBeNull(); - expect( - screen.queryByTestId("split-workspace-empty-panel-state"), - ).toBeNull(); + ).not.toBeNull(); + + // The toggle closes and reopens the empty state on its own. + fireEvent.click(pluginToggle!); + await waitFor(() => + expect( + screen + .getByTestId("split-workspace-panel-toggle") + .querySelector("button") + ?.getAttribute("aria-expanded"), + ).toBe("false"), + ); + // Refocusing the thread pane restores the remembered open panel. fireEvent.pointerDown(screen.getByTestId("pane-thr-a")); - const restoredOpenToggle = await screen.findByRole("button", { - name: "Hide right panel", - }); - expect(restoredOpenToggle.getAttribute("aria-expanded")).toBe("true"); expect(screen.getByTestId("hosted-panel-thr-a")).toBeTruthy(); expect( screen.queryByTestId("split-workspace-empty-panel-state"), @@ -1367,8 +1372,14 @@ describe("SplitThreadArea", () => { expect( screen.getByRole("button", { name: "Collapse notes sidebar" }), ).toBeTruthy(); - expect(screen.queryByTestId("split-workspace-panel-toggle")).toBeNull(); - expect(screen.queryByRole("button", { name: /Full Screen/ })).toBeNull(); + // Neither plugin pane publishes a panel, so the window offers its empty + // state instead of dropping the control. + expect(screen.getByTestId("split-workspace-panel-toggle")).toBeTruthy(); + // The app panel belongs to a publishing pane, but full screen is pane + // chrome: every pane in a split owns it, plugin panes included. + expect(screen.getAllByRole("button", { name: /Full Screen/ })).toHaveLength( + 2, + ); fireEvent.click(screen.getAllByRole("button", { name: "Close pane" })[0]!); @@ -1376,9 +1387,7 @@ describe("SplitThreadArea", () => { expect(screen.queryByText("Automations content")).toBeNull(), ); expect( - screen - .getByText("Docs content with notes sidebar") - .closest(".isolate"), + screen.getByText("Docs content with notes sidebar").closest(".isolate"), ).toBeNull(); expect( screen.getByRole("button", { name: "Collapse notes sidebar" }), @@ -1644,7 +1653,9 @@ describe("SplitThreadArea", () => { expect((await contentRow(path))?.className).not.toContain("pl-[104px]"); } - expect(screen.queryByRole("button", { name: /Full Screen/ })).toBeNull(); + expect(screen.getAllByRole("button", { name: /Full Screen/ })).toHaveLength( + 4, + ); }); it("assigns exactly one top-left owner through eight-pane structural changes", async () => { @@ -1729,6 +1740,87 @@ describe("SplitThreadArea", () => { ).not.toBe(0); }); + // The host pins its panel toggle over the workspace corner. A plugin pane in + // that corner used to skip the reserve, so the toggle covered Close pane. + it("reserves the window toggle corner for a plugin pane at the top right", async () => { + setPluginSlotRegistrations("docs", { + homepageSections: [], + settingsSections: [], + navPanels: [ + { + id: "docs", + title: "Docs", + icon: "FileText", + path: "docs", + component: () =>
Docs panel
, + }, + ], + threadPanelActions: [], + pendingInteractions: [], + sidebarFooterActions: [], + fileOpeners: [], + messageDirectives: [], + }); + + renderSplitArea({ + path: "/", + layout: { + root: { + type: "split", + dir: "col", + sizes: [0.5, 0.5], + children: [ + { type: "pane", paneId: "pane-docs", content: docsContent }, + { type: "pane", paneId: "pane-new", content: newThreadContent }, + ], + }, + focusedPaneId: "pane-new", + }, + routeContent: newThreadContent, + }); + + await screen.findByText("Docs panel"); + expect(screen.getByTestId("split-workspace-panel-toggle")).toBeTruthy(); + const [pluginClose] = screen.getAllByRole("button", { name: "Close pane" }); + const reserve = pluginClose?.nextElementSibling; + expect(reserve?.tagName).toBe("SPAN"); + expect(reserve?.getAttribute("aria-hidden")).toBe("true"); + + // Full screen hides the host toggle, so the reserved slot must go with it. + fireEvent.click(screen.getAllByRole("button", { name: /Full Screen/ })[0]!); + await waitFor(() => + expect( + screen.getAllByRole("button", { name: "Close pane" })[0] + ?.nextElementSibling, + ).toBeNull(), + ); + }); + + it("drops the corner reserve once an open panel hosts the toggle", async () => { + const layout = pluginSplitLayout(); + layout.focusedPaneId = "pane-1"; + renderSplitArea({ + path: threadPath("thr-a"), + layout, + routeAwareContent: true, + }); + + // pane-2 is the plugin pane at the right edge; the thread pane's panel + // starts open, so its own chrome carries the toggle. + const pluginClose = await screen.findByRole("button", { + name: "Close pane", + }); + expect(pluginClose.nextElementSibling).toBeNull(); + + fireEvent.click(screen.getByRole("button", { name: "Hide right panel" })); + await waitFor(() => + expect( + screen.getByRole("button", { name: "Close pane" }).nextElementSibling + ?.tagName, + ).toBe("SPAN"), + ); + }); + it("uses automation breadcrumbs in the split-owned plugin header", async () => { setPluginSlotRegistrations("automations", { homepageSections: [], diff --git a/apps/app/src/views/thread-detail/SplitThreadArea.tsx b/apps/app/src/views/thread-detail/SplitThreadArea.tsx index d12ddc3076..1e923e668d 100644 --- a/apps/app/src/views/thread-detail/SplitThreadArea.tsx +++ b/apps/app/src/views/thread-detail/SplitThreadArea.tsx @@ -274,8 +274,7 @@ function SplitThreadAreaContent({ routeContent }: SplitThreadAreaProps) { layout !== null && countPanes(layout.root) > 1 && maximizedPaneId !== null && - maximizedPane !== null && - maximizedPane.content.kind !== "plugin-panel" + maximizedPane !== null ? maximizedPaneId : null; const { @@ -333,8 +332,7 @@ function SplitThreadAreaContent({ routeContent }: SplitThreadAreaProps) { if ( layout === null || countPanes(layout.root) < 2 || - maximizedPane === null || - maximizedPane.content.kind === "plugin-panel" + maximizedPane === null ) { setMaximizedPaneId(null); return; @@ -404,12 +402,7 @@ function SplitThreadAreaContent({ routeContent }: SplitThreadAreaProps) { (paneId: string) => { const current = store.get(splitLayoutAtom); const pane = current === null ? null : findPane(current.root, paneId); - if ( - current === null || - countPanes(current.root) < 2 || - pane === null || - pane.content.kind === "plugin-panel" - ) { + if (current === null || countPanes(current.root) < 2 || pane === null) { return; } if (current.focusedPaneId !== paneId) { @@ -791,22 +784,14 @@ function SplitTree(props: SplitTreeProps) { isFocused={isFocused} isSplitPane secondaryPanelRegistry={props.secondaryPanelRegistry} - reservesWindowPanelToggle={ - node.content.kind !== "plugin-panel" && - (isMaximized || (isTopRow && isRightEdge)) - } + // Position alone decides this: the host pins its toggle over the + // workspace corner, so a plugin pane sitting there must reserve the + // same footprint or the toggle lands on its Close pane button. + reservesWindowPanelToggle={isMaximized || (isTopRow && isRightEdge)} onRequestClose={() => props.onClosePane(node.paneId)} isMaximized={isMaximized} - onToggleMaximize={ - node.content.kind === "plugin-panel" - ? null - : () => props.onToggleMaximizePane(node.paneId) - } - onMoveToSide={ - node.content.kind === "plugin-panel" - ? undefined - : (side) => props.onMovePaneToSide(node.paneId, side) - } + onToggleMaximize={() => props.onToggleMaximizePane(node.paneId)} + onMoveToSide={(side) => props.onMovePaneToSide(node.paneId, side)} isBoundedPane isTopRow={isMaximized || isTopRow} ownsWindowTopLeft={ @@ -1033,8 +1018,9 @@ function NonThreadPaneContent({ reservesWindowPanelToggle: false, isFocused: true, }; - const isWindowPanelOpen = - useContext(SecondaryPanelHostLayoutContext)?.isOpen === true; + const hostLayout = useContext(SecondaryPanelHostLayoutContext); + // The corner belongs to the pane unless the host paints its toggle there. + const showsWindowPanelToggle = hostLayout?.pinsCornerToggle === true; const [desktopInfo] = useState(getBbDesktopInfo); const usesDesktopChrome = shouldUseMacosDesktopChrome(desktopInfo); const panel = @@ -1070,7 +1056,7 @@ function NonThreadPaneContent({ subPath={content.kind === "plugin-panel" ? content.subPath : ""} /> ) : null} - {content.kind === "plugin-panel" ? null : } + {onRequestClose ? ( ) : null} - {reservesWindowPanelToggle && !isWindowPanelOpen ? ( + {reservesWindowPanelToggle && showsWindowPanelToggle ? ( // The host's shortcut hint drops below the chrome row; reserve only - // its stable 28px corner button beside these pane actions. With the - // window panel open, the toggle overlays the panel's own chrome - // instead, so the pane actions sit flush at the pane edge. + // its stable 28px corner button beside these pane actions. Whenever + // the host hides that toggle, the pane actions sit flush at the pane + // edge instead of trailing an empty slot. ) : null} diff --git a/apps/app/src/views/thread-detail/SplitWorkspaceSecondaryPanelHost.tsx b/apps/app/src/views/thread-detail/SplitWorkspaceSecondaryPanelHost.tsx index e3e75a7b7c..cedf7f0b22 100644 --- a/apps/app/src/views/thread-detail/SplitWorkspaceSecondaryPanelHost.tsx +++ b/apps/app/src/views/thread-detail/SplitWorkspaceSecondaryPanelHost.tsx @@ -6,13 +6,15 @@ import { useState, type ReactNode, } from "react"; -import { useAtomValue } from "jotai"; +import { useAtomValue, useSetAtom } from "jotai"; import { Panel, PanelGroup, + PanelResizeHandle, type ImperativePanelGroupHandle, } from "react-resizable-panels"; import { Button } from "@bb/shared-ui/button"; +import { EmptyStatePanel } from "@bb/shared-ui/empty-state"; import { Icon } from "@bb/shared-ui/icon"; import { cn } from "@bb/shared-ui/lib/utils"; import { HEADER_ICON_BUTTON_CLASS } from "@/components/layout/AppPageHeader"; @@ -22,12 +24,17 @@ import { useAppCommandShortcut, } from "@/components/commands/AppCommandProvider"; import { secondaryPanelWidthPercentAtom } from "@/components/secondary-panel/threadSecondaryPanelAtoms"; +import { + THREAD_SECONDARY_PANEL_MAX_SIZE_PERCENT, + THREAD_SECONDARY_PANEL_MIN_SIZE_PERCENT, +} from "@/components/secondary-panel/ThreadSecondaryPanel"; import { SecondaryPanelHostLayoutContext, type SecondaryPanelHostLayout, } from "@/components/secondary-panel/SecondaryPanelHostLayoutContext"; import { PANEL_COLLAPSE_TRANSITION_CLASS, + PANEL_RESIZE_HIT_AREA_MARGINS, } from "@/components/secondary-panel/panelTransitionTokens"; import { MACOS_APP_REGION_NO_DRAG_CLASS } from "@/lib/bb-desktop"; import { PluginComposerHostProvider } from "@/components/plugin/plugin-composer-host"; @@ -62,7 +69,7 @@ export function SplitWorkspaceSecondaryPanelHost({ // Null until the first pane publishes, so entering a split adopts the // focused content's persisted state. const [isPanelVisible, setIsPanelVisible] = useState(null); - const isOpen = model !== null && (isPanelVisible ?? model.isOpen); + const isOpen = isPanelVisible ?? model?.isOpen ?? false; const lastTargetRef = useRef<{ paneId: string; contentKey: string; @@ -141,36 +148,47 @@ export function SplitWorkspaceSecondaryPanelHost({ panelWidthPercent, ]); - // The remembered window visibility is intentionally left unchanged while a - // plugin pane is focused, so returning to a thread restores its app panel. + // A pane without a panel keeps the control working: the toggle drives the + // window visibility directly, so the empty state opens and closes like any + // panel. Refocusing a publishing pane re-aligns through the effect above. const toggleWindowPanel = () => { - model?.onToggle(); + if (model !== null) { + model.onToggle(); + return; + } + setIsPanelVisible((current) => !(current ?? false)); }; useAppCommandHandler("panel.toggle", () => { - return model === null; + if (model !== null) return false; + toggleWindowPanel(); + return true; }); - const toggleLabel = - model === null - ? "Right panel unavailable" - : isOpen - ? "Hide right panel" - : "Show right panel"; + // Resizing the empty-state panel mirrors the real panel's persistence: the + // shared width atom updates on drag end, so the size carries to whichever + // pane's panel shows next. Dragging it collapsed closes the window panel. + const setPanelWidthPercent = useSetAtom(secondaryPanelWidthPercentAtom); + const lastEmptyPanelSizeRef = useRef(0); + const handleEmptyPanelResize = (size: number) => { + if (size > 0) lastEmptyPanelSizeRef.current = size; + }; + const handleEmptyPanelDragging = (isDragging: boolean) => { + if (isDragging || lastEmptyPanelSizeRef.current <= 0) return; + setPanelWidthPercent(lastEmptyPanelSizeRef.current); + }; + const handleEmptyPanelCollapse = () => { + setIsPanelVisible(false); + }; + + const toggleLabel = isOpen ? "Hide right panel" : "Show right panel"; + // An open pane panel carries the toggle in its own chrome, and a full-screen + // pane hides it. The empty state has no chrome, so it keeps the corner. + const pinsCornerToggle = !isPaneMaximized && !(isOpen && model !== null); const hostLayout = useMemo( - () => ({ isOpen, isSuppressed: isPaneMaximized }), - [isOpen, isPaneMaximized], + () => ({ isOpen, isSuppressed: isPaneMaximized, pinsCornerToggle }), + [isOpen, isPaneMaximized, pinsCornerToggle], ); - if (model === null) { - return ( - -
- {children} -
-
- ); - } - return ( // The layout context serves two consumers: a freshly mounted pane panel // sizes its resizable Panel to the window visibility (its own persisted @@ -183,8 +201,11 @@ export function SplitWorkspaceSecondaryPanelHost({
- - {model.panel} - + {model === null ? ( + <> + {/* Working twin of the panel's gutter-style resize handle: the + seam keeps the split dividers' body, and the window panel + stays resizable even while it shows the empty state. */} + + +
+ + This pane has no right panel. + +
+
+ + ) : ( + + {model.panel} + + )}
diff --git a/official-plugins/docs/app.test.tsx b/official-plugins/docs/app.test.tsx index 3e7d7e69ac..0fba589b86 100644 --- a/official-plugins/docs/app.test.tsx +++ b/official-plugins/docs/app.test.tsx @@ -123,19 +123,16 @@ describe("Docs nav panel", () => { const HeaderContent = panel.headerContent!; const header = render(); const headerSegment = header.getByTestId("notes-sidebar-header"); - const headerBackground = header.getByTestId( - "notes-sidebar-header-background", - ); expect(headerSegment.classList.contains("w-8")).toBe(true); - expect(headerBackground.classList.contains("bg-sidebar")).toBe(true); - expect(headerBackground.style.width).toBe("288px"); - expect(headerBackground.style.right).toBe("-16px"); + // The sidebar is the plugin's own panel: it stays below the host header, + // so no plugin chrome may paint over the header's seam. + expect(header.queryByTestId("notes-sidebar-header-background")).toBeNull(); const toolbar = slot.getByRole("toolbar", { name: "Notes sidebar actions", }); expect(slot.getByRole("navigation", { name: "Notes" })).toBeTruthy(); expect( - slot.container.querySelector("aside")?.classList.contains("bg-sidebar"), + slot.container.querySelector("aside")?.classList.contains("bg-muted/20"), ).toBe(true); expect( within(toolbar).getByRole("button", { name: "Search notes" }), @@ -155,12 +152,10 @@ describe("Docs nav panel", () => { header.getByRole("button", { name: "Collapse notes sidebar" }), ); expect(slot.container.querySelector("aside")?.style.width).toBe("0px"); - expect(headerBackground.style.width).toBe("48px"); fireEvent.click( header.getByRole("button", { name: "Expand notes sidebar" }), ); expect(slot.container.querySelector("aside")?.style.width).toBe("288px"); - expect(headerBackground.style.width).toBe("288px"); header.unmount(); const fallbackToggle = await slot.findByRole("button", { @@ -175,7 +170,7 @@ describe("Docs nav panel", () => { ); }); - it("keeps the sidebar header background aligned behind split host controls", () => { + it("keeps the header toggle inside its own box in a split pane", () => { const HeaderContent = app.navPanels[0]!.headerContent!; const header = render(
@@ -184,11 +179,10 @@ describe("Docs nav panel", () => { ); const headerSegment = header.getByTestId("notes-sidebar-header"); - const background = header.getByTestId("notes-sidebar-header-background"); + // The toggle must not reach into the host's own split-pane controls. expect(headerSegment.classList.contains("-mr-4")).toBe(false); expect(headerSegment.classList.contains("w-8")).toBe(true); - expect(background.style.right).toBe("-48px"); - expect(background.style.width).toBe("288px"); + expect(header.queryByTestId("notes-sidebar-header-background")).toBeNull(); }); it("keeps the right sidebar pinned while a note loads", async () => { @@ -364,16 +358,6 @@ describe("Docs nav panel", () => { expect(firstAside?.style.width).toBe("400px"); expect(secondAside?.style.width).toBe("288px"); - expect( - within(firstHeader.container).getByTestId( - "notes-sidebar-header-background", - ).style.width, - ).toBe("400px"); - expect( - within(secondHeader.container).getByTestId( - "notes-sidebar-header-background", - ).style.width, - ).toBe("288px"); fireEvent.click( within(firstHeader.container).getByRole("button", { diff --git a/official-plugins/docs/app.tsx b/official-plugins/docs/app.tsx index c49dc7f288..b022387514 100644 --- a/official-plugins/docs/app.tsx +++ b/official-plugins/docs/app.tsx @@ -1241,11 +1241,6 @@ function orderEntries( } const SIDEBAR_AUTO_COLLAPSE_PANE_WIDTH = 640; -const HEADER_STANDALONE_EDGE_OFFSET = 16; -// Split host actions follow plugin header content: 4px action gap + 28px close -// button + the header's 16px edge padding. The visual sidebar background can -// extend behind that host-owned chrome, but its layout box must not overlap it. -const HEADER_SPLIT_EDGE_OFFSET = 48; interface NotesSidebarState { headerMounted: boolean; @@ -1261,6 +1256,11 @@ interface NotesSidebarStore { listeners: Set<() => void>; } +// The header toggle and the sidebar view live in separate React subtrees and +// find each other only through this map. Entries stay for the session: a +// deleted entry lets one subtree keep the old store while the other creates a +// new one, which silently breaks the header toggle. A reopened pane starts +// clean anyway, because the view resets the state on its first mount. const notesSidebarStores = new Map(); const STANDALONE_SIDEBAR_SCOPE = "standalone"; @@ -1270,7 +1270,6 @@ function notesSidebarVaultKey(subPath: string): string { } function useNotesSidebarScope(subPath: string): { - isSplitPane: boolean; scopeRef(element: HTMLElement | null): void; storeKey: string; } { @@ -1286,7 +1285,6 @@ function useNotesSidebarScope(subPath: string): { ); }, []); return { - isSplitPane: paneScope !== STANDALONE_SIDEBAR_SCOPE, scopeRef, storeKey: `${paneScope}:${notesSidebarVaultKey(subPath)}`, }; @@ -1327,19 +1325,6 @@ function updateNotesSidebarState( for (const listener of store.listeners) listener(); } -function deleteUnusedNotesSidebarStore( - key: string, - store: NotesSidebarStore, -): void { - if ( - store.headerMounts === 0 && - store.viewMounts === 0 && - notesSidebarStores.get(key) === store - ) { - notesSidebarStores.delete(key); - } -} - function useNotesSidebarState(key: string): { state: NotesSidebarState; store: NotesSidebarStore; @@ -1366,7 +1351,7 @@ function NotesSidebarToggle({ }) { return (