Fix notification clicks preserving workspace deck
This commit is contained in:
@@ -139,6 +139,35 @@ test.describe("Workspace navigation regression", () => {
|
|||||||
await expect(secondDeckEntry).toBeVisible({ timeout: 30_000 });
|
await expect(secondDeckEntry).toBeVisible({ timeout: 30_000 });
|
||||||
await expect(page.locator('[data-testid^="workspace-deck-entry-"]')).toHaveCount(2);
|
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({
|
await switchWorkspaceViaSidebar({
|
||||||
page,
|
page,
|
||||||
serverId,
|
serverId,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { shouldUseDesktopDaemon } from "@/desktop/daemon/desktop-daemon";
|
|||||||
import { loadSettingsFromStorage } from "@/hooks/use-settings";
|
import { loadSettingsFromStorage } from "@/hooks/use-settings";
|
||||||
import { useColorScheme } from "@/hooks/use-color-scheme";
|
import { useColorScheme } from "@/hooks/use-color-scheme";
|
||||||
import { useOpenProject } from "@/hooks/use-open-project";
|
import { useOpenProject } from "@/hooks/use-open-project";
|
||||||
|
import { useStableEvent } from "@/hooks/use-stable-event";
|
||||||
import { SessionProvider } from "@/contexts/session-context";
|
import { SessionProvider } from "@/contexts/session-context";
|
||||||
import type { HostProfile } from "@/types/host-connection";
|
import type { HostProfile } from "@/types/host-connection";
|
||||||
import {
|
import {
|
||||||
@@ -77,7 +78,7 @@ import {
|
|||||||
import { listenToDesktopEvent } from "@/desktop/electron/events";
|
import { listenToDesktopEvent } from "@/desktop/electron/events";
|
||||||
import { getDesktopHost } from "@/desktop/host";
|
import { getDesktopHost } from "@/desktop/host";
|
||||||
import { updateDesktopWindowControls } from "@/desktop/electron/window";
|
import { updateDesktopWindowControls } from "@/desktop/electron/window";
|
||||||
import { buildNotificationRoute } from "@/utils/notification-routing";
|
import { buildNotificationRoute, resolveNotificationTarget } from "@/utils/notification-routing";
|
||||||
import {
|
import {
|
||||||
buildHostRootRoute,
|
buildHostRootRoute,
|
||||||
mapPathnameToServer,
|
mapPathnameToServer,
|
||||||
@@ -86,6 +87,10 @@ import {
|
|||||||
parseWorkspaceOpenIntent,
|
parseWorkspaceOpenIntent,
|
||||||
decodeWorkspaceIdFromPathSegment,
|
decodeWorkspaceIdFromPathSegment,
|
||||||
} from "@/utils/host-routes";
|
} 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 {
|
import {
|
||||||
addBrowserActiveWorkspaceLocationListener,
|
addBrowserActiveWorkspaceLocationListener,
|
||||||
syncNavigationActiveWorkspace,
|
syncNavigationActiveWorkspace,
|
||||||
@@ -124,7 +129,36 @@ const HostRuntimeBootstrapContext = createContext<HostRuntimeBootstrapState>({
|
|||||||
|
|
||||||
function PushNotificationRouter() {
|
function PushNotificationRouter() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
const lastHandledIdRef = useRef<string | null>(null);
|
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(() => {
|
useEffect(() => {
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
@@ -145,7 +179,7 @@ function PushNotificationRouter() {
|
|||||||
(payload as { data?: unknown }).data !== null
|
(payload as { data?: unknown }).data !== null
|
||||||
? (payload as { data: Record<string, unknown> }).data
|
? (payload as { data: Record<string, unknown> }).data
|
||||||
: undefined;
|
: undefined;
|
||||||
router.push(buildNotificationRoute(data) as any);
|
openNotification(data);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -165,7 +199,7 @@ function PushNotificationRouter() {
|
|||||||
const openFromWebClick = (event: Event) => {
|
const openFromWebClick = (event: Event) => {
|
||||||
const customEvent = event as CustomEvent<WebNotificationClickDetail>;
|
const customEvent = event as CustomEvent<WebNotificationClickDetail>;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
router.push(buildNotificationRoute(customEvent.detail?.data) as any);
|
openNotification(customEvent.detail?.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
target.addEventListener(WEB_NOTIFICATION_CLICK_EVENT, openFromWebClick as EventListener);
|
target.addEventListener(WEB_NOTIFICATION_CLICK_EVENT, openFromWebClick as EventListener);
|
||||||
@@ -198,7 +232,7 @@ function PushNotificationRouter() {
|
|||||||
const data = response.notification.request.content.data as
|
const data = response.notification.request.content.data as
|
||||||
| Record<string, unknown>
|
| Record<string, unknown>
|
||||||
| undefined;
|
| undefined;
|
||||||
router.push(buildNotificationRoute(data) as any);
|
openNotification(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscription = Notifications.addNotificationResponseReceivedListener(openFromResponse);
|
const subscription = Notifications.addNotificationResponseReceivedListener(openFromResponse);
|
||||||
@@ -212,7 +246,7 @@ function PushNotificationRouter() {
|
|||||||
return () => {
|
return () => {
|
||||||
subscription.remove();
|
subscription.remove();
|
||||||
};
|
};
|
||||||
}, [router]);
|
}, [openNotification]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { Href } from "expo-router";
|
||||||
import {
|
import {
|
||||||
buildHostAgentDetailRoute,
|
buildHostAgentDetailRoute,
|
||||||
buildHostRootRoute,
|
buildHostRootRoute,
|
||||||
@@ -5,6 +6,7 @@ import {
|
|||||||
} from "@/utils/host-routes";
|
} from "@/utils/host-routes";
|
||||||
|
|
||||||
type NotificationData = Record<string, unknown> | null | undefined;
|
type NotificationData = Record<string, unknown> | null | undefined;
|
||||||
|
type NotificationRoute = Extract<Href, string>;
|
||||||
|
|
||||||
function readNonEmptyString(data: NotificationData, key: string): string | null {
|
function readNonEmptyString(data: NotificationData, key: string): string | null {
|
||||||
const value = data?.[key];
|
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);
|
const { serverId, agentId, workspaceId } = resolveNotificationTarget(data);
|
||||||
if (serverId && agentId) {
|
if (serverId && agentId) {
|
||||||
if (workspaceId) {
|
if (workspaceId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user