mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Cancel deferred sidebar navigation on newer intent
Subscribe the sidebar deferral to the shared navigation-intent signal so back, forward, deep links, and every other navigation source supersede stale channel work. Keep optimistic selection until the committed route catches up, and cover immediate competing intents plus traversal races. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ export function AppSidebarPrimaryMenu({
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
className="data-[active=true]:font-normal"
|
||||
data-testid="open-home-view"
|
||||
isActive={selectedView === "home"}
|
||||
onClick={onSelectHome}
|
||||
tooltip="Inbox"
|
||||
|
||||
@@ -2835,8 +2835,16 @@ test("superseded sidebar feedback cannot override newer navigation", async ({
|
||||
await page.getByTestId("channel-general").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
||||
|
||||
await page.getByTestId("channel-random").click();
|
||||
await page.getByRole("button", { name: "Inbox" }).click();
|
||||
await page.evaluate(() => {
|
||||
document
|
||||
.querySelector<HTMLElement>('[data-testid="channel-random"]')
|
||||
?.click();
|
||||
const inboxButton = document.querySelector<HTMLButtonElement>(
|
||||
'[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<HTMLElement>('[data-testid="channel-random"]')
|
||||
?.click();
|
||||
document
|
||||
.querySelector<HTMLButtonElement>('[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<HTMLElement>('[data-testid="channel-watercooler"]')
|
||||
?.click();
|
||||
document
|
||||
.querySelector<HTMLButtonElement>('[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,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user