diff --git a/desktop/src/app/AppShell.tsx b/desktop/src/app/AppShell.tsx index 3fc65f504..91f17cf18 100644 --- a/desktop/src/app/AppShell.tsx +++ b/desktop/src/app/AppShell.tsx @@ -155,7 +155,6 @@ export function AppShell() { [location.pathname], ); const { - cancel: cancelPendingSidebarNavigation, pendingChannelId: pendingSidebarChannelId, selectDeferred: handleSidebarChannelSelect, } = useDeferredSidebarNavigation({ @@ -858,10 +857,7 @@ export function AppShell() { }); await goChannel(directMessage.id); }} - onSelectAgents={() => { - cancelPendingSidebarNavigation(); - void goAgents(); - }} + onSelectAgents={() => void goAgents()} onSelectChannel={handleSidebarChannelSelect} onOpenSearchResult={handleOpenSearchResult} searchChannels={channels} @@ -869,18 +865,9 @@ export function AppShell() { searchFocusRequest, scopeSearchFocusRequest, ]} - onSelectHome={() => { - cancelPendingSidebarNavigation(); - void goHome(); - }} - onSelectProjects={() => { - cancelPendingSidebarNavigation(); - void goProjects(); - }} - onSelectPulse={() => { - cancelPendingSidebarNavigation(); - void goPulse(); - }} + onSelectHome={() => void goHome()} + onSelectProjects={() => void goProjects()} + onSelectPulse={() => void goPulse()} onSelectSettings={handleOpenSettings} onSelectWorkflows={() => void goWorkflows()} onSetPresenceStatus={(status) => @@ -904,7 +891,9 @@ export function AppShell() { : undefined } selectedChannelId={sidebarSelectedChannelId} - selectedView={selectedView} + selectedView={ + pendingSidebarChannelId ? "channel" : selectedView + } unreadChannelIds={unreadChannelIds} previewActivityChannelIds={unreadThreadChannelIds} unreadChannelCounts={unreadChannelCounts} diff --git a/desktop/src/app/navigation/useDeferredSidebarNavigation.ts b/desktop/src/app/navigation/useDeferredSidebarNavigation.ts index 28110d210..b2800280d 100644 --- a/desktop/src/app/navigation/useDeferredSidebarNavigation.ts +++ b/desktop/src/app/navigation/useDeferredSidebarNavigation.ts @@ -22,7 +22,7 @@ export function useDeferredSidebarNavigation({ const pathnameRef = React.useRef(pathname); pathnameRef.current = pathname; - const cancel = React.useCallback(() => { + const cancelDeferred = React.useCallback(() => { generationRef.current += 1; if (frameRef.current !== null) { window.cancelAnimationFrame(frameRef.current); @@ -32,10 +32,23 @@ export function useDeferredSidebarNavigation({ window.clearTimeout(timerRef.current); timerRef.current = null; } - setPendingChannelId(null); }, []); + const cancel = React.useCallback(() => { + cancelDeferred(); + setPendingChannelId(null); + }, [cancelDeferred]); + React.useEffect(() => cancel, [cancel]); + React.useEffect(() => { + const handleNavigationIntent = () => cancelDeferred(); + window.addEventListener("buzz:navigation-intent", handleNavigationIntent); + return () => + window.removeEventListener( + "buzz:navigation-intent", + handleNavigationIntent, + ); + }, [cancelDeferred]); React.useEffect(() => { // A committed route change supersedes any deferred sidebar intent. void pathname; @@ -84,5 +97,5 @@ export function useDeferredSidebarNavigation({ [cancel, selectChannel], ); - return { cancel, pendingChannelId, selectDeferred }; + return { pendingChannelId, selectDeferred }; } diff --git a/desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx b/desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx index 4a618fcf0..775c7387d 100644 --- a/desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx +++ b/desktop/src/features/sidebar/ui/AppSidebarPinnedHeader.tsx @@ -105,6 +105,7 @@ export function AppSidebarPrimaryMenu({ { + document + .querySelector('[data-testid="channel-random"]') + ?.click(); + const inboxButton = document.querySelector( + '[data-testid="open-home-view"]', + ); + if (!inboxButton) throw new Error("Expected Inbox button"); + inboxButton.click(); + }); await expect(page.getByTestId("home-inbox")).toBeVisible(); await page.waitForTimeout(100); @@ -2847,6 +2855,58 @@ test("superseded sidebar feedback cannot override newer navigation", async ({ ); }); +test("back navigation supersedes pending sidebar navigation", async ({ + page, +}) => { + await page.goto("/"); + await page.getByTestId("channel-general").click(); + await expect(page.getByTestId("chat-title")).toHaveText("general"); + + const historyLength = await page.evaluate(() => window.history.length); + await page.evaluate(() => { + document + .querySelector('[data-testid="channel-random"]') + ?.click(); + document + .querySelector('[data-testid="global-back"]') + ?.click(); + }); + + await expect(page).toHaveURL(/\/$/); + await expect(page.getByTestId("home-inbox")).toBeVisible(); + await page.waitForTimeout(100); + await expect(page.getByTestId("home-inbox")).toBeVisible(); + expect(await page.evaluate(() => window.history.length)).toBe(historyLength); +}); + +test("forward navigation supersedes pending sidebar navigation", async ({ + page, +}) => { + await page.goto("/"); + await page.getByTestId("channel-general").click(); + await expect(page.getByTestId("chat-title")).toHaveText("general"); + await page.getByTestId("channel-random").click(); + await expect(page.getByTestId("chat-title")).toHaveText("random"); + await page.getByTestId("global-back").click(); + await expect(page.getByTestId("chat-title")).toHaveText("general"); + + const historyLength = await page.evaluate(() => window.history.length); + await page.evaluate(() => { + document + .querySelector('[data-testid="channel-watercooler"]') + ?.click(); + document + .querySelector('[data-testid="global-forward"]') + ?.click(); + }); + + await expect(page.getByTestId("chat-title")).toHaveText("random"); + await expect(page).toHaveURL(/#\/channels\/[^?]+$/); + await page.waitForTimeout(100); + await expect(page.getByTestId("chat-title")).toHaveText("random"); + expect(await page.evaluate(() => window.history.length)).toBe(historyLength); +}); + test("thread open paints immediate feedback before deferred panel work", async ({ page, }) => {