Fix notification clicks preserving workspace deck

This commit is contained in:
Mohamed Boudra
2026-04-21 21:57:15 +07:00
parent 8dad423635
commit 73b28a7f60
3 changed files with 71 additions and 6 deletions
@@ -139,6 +139,35 @@ test.describe("Workspace navigation regression", () => {
await expect(secondDeckEntry).toBeVisible({ timeout: 30_000 });
await expect(page.locator('[data-testid^="workspace-deck-entry-"]')).toHaveCount(2);
await page.evaluate(
({ agentId, serverId }) => {
globalThis.dispatchEvent(
new CustomEvent("paseo:web-notification-click", {
detail: {
data: {
serverId,
agentId,
reason: "finished",
},
},
cancelable: true,
}),
);
},
{ agentId: secondAgent.id, serverId },
);
await waitForWorkspaceTabsVisible(page);
await expect(page).toHaveURL(buildHostWorkspaceRoute(serverId, secondWorkspace.workspaceId), {
timeout: 30_000,
});
await expect(secondDeckEntry).toBeVisible({ timeout: 30_000 });
await expectWorkspaceTabVisible(page, secondAgent.id);
await expectWorkspaceTabHidden(page, firstAgent.id);
await expectOnlyWorkspaceAgentTabsVisible(page, [secondAgent.id]);
await expect(firstDeckEntry).toBeAttached();
await expect(firstDeckEntry).toBeHidden();
await expect(page.locator('[data-testid^="workspace-deck-entry-"]')).toHaveCount(2);
await switchWorkspaceViaSidebar({
page,
serverId,
+39 -5
View File
@@ -28,6 +28,7 @@ import { shouldUseDesktopDaemon } from "@/desktop/daemon/desktop-daemon";
import { loadSettingsFromStorage } from "@/hooks/use-settings";
import { useColorScheme } from "@/hooks/use-color-scheme";
import { useOpenProject } from "@/hooks/use-open-project";
import { useStableEvent } from "@/hooks/use-stable-event";
import { SessionProvider } from "@/contexts/session-context";
import type { HostProfile } from "@/types/host-connection";
import {
@@ -77,7 +78,7 @@ import {
import { listenToDesktopEvent } from "@/desktop/electron/events";
import { getDesktopHost } from "@/desktop/host";
import { updateDesktopWindowControls } from "@/desktop/electron/window";
import { buildNotificationRoute } from "@/utils/notification-routing";
import { buildNotificationRoute, resolveNotificationTarget } from "@/utils/notification-routing";
import {
buildHostRootRoute,
mapPathnameToServer,
@@ -86,6 +87,10 @@ import {
parseWorkspaceOpenIntent,
decodeWorkspaceIdFromPathSegment,
} from "@/utils/host-routes";
import { useSessionStore } from "@/stores/session-store";
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
import { navigateToWorkspace } from "@/hooks/use-workspace-navigation";
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
import {
addBrowserActiveWorkspaceLocationListener,
syncNavigationActiveWorkspace,
@@ -124,7 +129,36 @@ const HostRuntimeBootstrapContext = createContext<HostRuntimeBootstrapState>({
function PushNotificationRouter() {
const router = useRouter();
const pathname = usePathname();
const lastHandledIdRef = useRef<string | null>(null);
const openNotification = useStableEvent((data: Record<string, unknown> | undefined) => {
const target = resolveNotificationTarget(data);
const serverId = target.serverId;
const agentId = target.agentId;
if (serverId && agentId) {
const session = useSessionStore.getState().sessions[serverId];
const agent = session?.agents.get(agentId);
const workspaceId =
target.workspaceId ??
resolveWorkspaceIdByExecutionDirectory({
workspaces: session?.workspaces.values(),
workspaceDirectory: agent?.cwd,
});
if (workspaceId) {
prepareWorkspaceTab({
serverId,
workspaceId,
target: { kind: "agent", agentId },
pin: true,
});
navigateToWorkspace(serverId, workspaceId, { currentPathname: pathname });
return;
}
}
router.navigate(buildNotificationRoute(data));
});
useEffect(() => {
if (isWeb) {
@@ -145,7 +179,7 @@ function PushNotificationRouter() {
(payload as { data?: unknown }).data !== null
? (payload as { data: Record<string, unknown> }).data
: undefined;
router.push(buildNotificationRoute(data) as any);
openNotification(data);
},
);
@@ -165,7 +199,7 @@ function PushNotificationRouter() {
const openFromWebClick = (event: Event) => {
const customEvent = event as CustomEvent<WebNotificationClickDetail>;
event.preventDefault();
router.push(buildNotificationRoute(customEvent.detail?.data) as any);
openNotification(customEvent.detail?.data);
};
target.addEventListener(WEB_NOTIFICATION_CLICK_EVENT, openFromWebClick as EventListener);
@@ -198,7 +232,7 @@ function PushNotificationRouter() {
const data = response.notification.request.content.data as
| Record<string, unknown>
| undefined;
router.push(buildNotificationRoute(data) as any);
openNotification(data);
};
const subscription = Notifications.addNotificationResponseReceivedListener(openFromResponse);
@@ -212,7 +246,7 @@ function PushNotificationRouter() {
return () => {
subscription.remove();
};
}, [router]);
}, [openNotification]);
return null;
}
@@ -1,3 +1,4 @@
import type { Href } from "expo-router";
import {
buildHostAgentDetailRoute,
buildHostRootRoute,
@@ -5,6 +6,7 @@ import {
} from "@/utils/host-routes";
type NotificationData = Record<string, unknown> | null | undefined;
type NotificationRoute = Extract<Href, string>;
function readNonEmptyString(data: NotificationData, key: string): string | null {
const value = data?.[key];
@@ -27,7 +29,7 @@ export function resolveNotificationTarget(data: NotificationData): {
};
}
export function buildNotificationRoute(data: NotificationData) {
export function buildNotificationRoute(data: NotificationData): NotificationRoute {
const { serverId, agentId, workspaceId } = resolveNotificationTarget(data);
if (serverId && agentId) {
if (workspaceId) {