mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
fix(desktop): preserve activity window navigation context
Co-authored-by: Rizz <302abe414ca6e3134763d2539bfcf145aea2a63fe5f8455204ed602fd40cf381@buzz.block.builderlab.xyz> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
@@ -51,6 +51,7 @@ export default defineConfig({
|
||||
"**/observer-feed-screenshots.spec.ts",
|
||||
"**/core-memory-screenshots.spec.ts",
|
||||
"**/activity-scope-label-screenshots.spec.ts",
|
||||
"**/agent-activity-window.spec.ts",
|
||||
"**/welcome-agent-modal-screenshots.spec.ts",
|
||||
"**/local-archive-screenshots.spec.ts",
|
||||
"**/voice-settings.spec.ts",
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@tanstack/react-router";
|
||||
|
||||
import { openSearchHitWithNavigation } from "@/app/navigation/searchHitNavigation";
|
||||
import { currentCompanionWindowKind } from "@/app/companionWindow";
|
||||
import type { SearchHit } from "@/shared/api/types";
|
||||
|
||||
type NavigationBehavior = {
|
||||
@@ -20,6 +21,16 @@ export function useAppNavigation() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const canGoBack = useCanGoBack();
|
||||
const companionSearch = location.search as Record<string, unknown>;
|
||||
const companionCoordinates =
|
||||
currentCompanionWindowKind() === "agent-activity" &&
|
||||
typeof companionSearch.community === "string" &&
|
||||
typeof companionSearch.agentSession === "string"
|
||||
? {
|
||||
community: companionSearch.community,
|
||||
agentSession: companionSearch.agentSession,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const commitNavigation = React.useCallback(
|
||||
async (
|
||||
@@ -31,20 +42,26 @@ export function useAppNavigation() {
|
||||
},
|
||||
behavior: NavigationBehavior = {},
|
||||
) => {
|
||||
const nextLocation = router.buildLocation(next as never);
|
||||
const destination = companionCoordinates
|
||||
? {
|
||||
...next,
|
||||
search: { ...next.search, ...companionCoordinates },
|
||||
}
|
||||
: next;
|
||||
const nextLocation = router.buildLocation(destination as never);
|
||||
|
||||
if (location.href === nextLocation.href && !behavior.force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await navigate({
|
||||
...next,
|
||||
...destination,
|
||||
replace: behavior.replace,
|
||||
resetScroll: behavior.resetScroll,
|
||||
} as never);
|
||||
return true;
|
||||
},
|
||||
[location.href, navigate, router],
|
||||
[companionCoordinates, location.href, navigate, router],
|
||||
);
|
||||
|
||||
const goHome = React.useCallback(
|
||||
|
||||
@@ -13,6 +13,8 @@ import { ViewLoadingFallback } from "@/shared/ui/ViewLoadingFallback";
|
||||
|
||||
type ChannelRouteSearch = {
|
||||
agentSession?: string;
|
||||
/** Immutable origin community carried by dedicated activity windows. */
|
||||
community?: string;
|
||||
/**
|
||||
* When set, the composer on mount will auto-submit its loaded draft once,
|
||||
* then clear this param. Value is the draft key that was loaded so the
|
||||
@@ -36,6 +38,7 @@ function validateChannelSearch(
|
||||
): ChannelRouteSearch {
|
||||
return {
|
||||
agentSession: nonEmptyString(search.agentSession),
|
||||
community: nonEmptyString(search.community),
|
||||
autoSend: nonEmptyString(search.autoSend),
|
||||
messageId: nonEmptyString(search.messageId),
|
||||
profile: nonEmptyString(search.profile),
|
||||
|
||||
75
desktop/tests/e2e/agent-activity-window.spec.ts
Normal file
75
desktop/tests/e2e/agent-activity-window.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
||||
|
||||
const AGENT_PUBKEY = TEST_IDENTITIES.tyler.pubkey;
|
||||
const AGENTS_CHANNEL_ID = "94a444a4-c0a3-5966-ab05-530c6ddc2301";
|
||||
const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
|
||||
const COMMUNITY_ID = "e2e-default-community";
|
||||
|
||||
test("keeps dedicated activity windows connected when opening a channel reference", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, {
|
||||
windowLabel: `agent-activity-${AGENT_PUBKEY}-${AGENTS_CHANNEL_ID}`,
|
||||
managedAgents: [
|
||||
{
|
||||
pubkey: AGENT_PUBKEY,
|
||||
name: "Observer Agent",
|
||||
status: "running",
|
||||
channelNames: ["agents"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await page.goto(
|
||||
`/#/channels/${AGENTS_CHANNEL_ID}?community=${COMMUNITY_ID}&agentSession=${AGENT_PUBKEY}`,
|
||||
);
|
||||
const panel = page.getByTestId("agent-session-thread-panel");
|
||||
await expect(panel).toBeVisible({ timeout: 10_000 });
|
||||
await page.waitForFunction(
|
||||
() => typeof window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__ === "function",
|
||||
);
|
||||
await page.evaluate(
|
||||
({ agentPubkey, channelId }) => {
|
||||
window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__?.({
|
||||
agentPubkey,
|
||||
events: [
|
||||
{
|
||||
seq: 1,
|
||||
timestamp: new Date().toISOString(),
|
||||
kind: "acp_read",
|
||||
agentIndex: 0,
|
||||
channelId,
|
||||
sessionId: "session-activity-window",
|
||||
turnId: "turn-activity-window",
|
||||
payload: {
|
||||
method: "session/update",
|
||||
params: {
|
||||
update: {
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: "Continue in #general" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
{ agentPubkey: AGENT_PUBKEY, channelId: AGENTS_CHANNEL_ID },
|
||||
);
|
||||
|
||||
const channelReference = panel.getByRole("button", {
|
||||
name: "Open channel general",
|
||||
});
|
||||
await expect(channelReference).toBeVisible();
|
||||
await channelReference.click();
|
||||
|
||||
await expect(page).toHaveURL(
|
||||
new RegExp(
|
||||
`#/channels/${GENERAL_CHANNEL_ID}\\?.*community=${COMMUNITY_ID}`,
|
||||
),
|
||||
);
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(page.locator("body")).not.toBeEmpty();
|
||||
});
|
||||
Reference in New Issue
Block a user