mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Recover saved Computer tabs after task environment changes
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
4e3cf6b07e
commit
85746677ff
2 files changed
+54
-3
No files matched your search
@@ -9,6 +9,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import {
|
||||
taskPanelAgentTasksTab,
|
||||
taskPanelArtifactsTab,
|
||||
taskPanelComputerTab,
|
||||
taskPanelDocumentTab,
|
||||
taskPanelPropertiesTab,
|
||||
writeTaskSidePanelState,
|
||||
@@ -26,8 +27,12 @@ class ResizeObserverStub {
|
||||
(globalThis as { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserverStub as unknown as typeof ResizeObserver;
|
||||
|
||||
const browserFixture = vi.hoisted(() => ({ data: [] as import("@paperclipai/shared").TaskBrowser[], viewer: vi.fn(async () => ({ url: "https://live.browser-use.com/test-viewer" })), control: vi.fn(async () => ({})) }));
|
||||
const computerFixture = vi.hoisted(() => ({ get: vi.fn<() => Promise<import("@paperclipai/shared").TaskComputer | null>>() }));
|
||||
vi.mock("@/api/computers", () => ({ computersApi: { get: computerFixture.get } }));
|
||||
const computerFixture = vi.hoisted(() => ({
|
||||
get: vi.fn<() => Promise<import("@paperclipai/shared").TaskComputer | null>>(),
|
||||
connect: vi.fn(),
|
||||
disconnect: vi.fn(async () => undefined),
|
||||
}));
|
||||
vi.mock("@/api/computers", () => ({ computersApi: computerFixture }));
|
||||
vi.mock("@/hooks/useTaskBrowsers", () => ({ useTaskBrowsers: () => ({ data: browserFixture.data, isError: false }) }));
|
||||
vi.mock("@/api/browser-use", () => ({ browserUseApi: { viewer: browserFixture.viewer, control: browserFixture.control, presence: vi.fn(async () => ({ accepted: true })) } }));
|
||||
const fixture = vi.hoisted(() => ({
|
||||
@@ -135,6 +140,8 @@ describe("TaskSidePanel", () => {
|
||||
browserFixture.data = [];
|
||||
browserFixture.control.mockClear();
|
||||
computerFixture.get.mockReset().mockResolvedValue(null);
|
||||
computerFixture.connect.mockReset();
|
||||
computerFixture.disconnect.mockClear();
|
||||
fixture.documents = [];
|
||||
fixture.plan = null;
|
||||
routeFixture.location.search = "";
|
||||
@@ -212,6 +219,36 @@ describe("TaskSidePanel", () => {
|
||||
.some((item) => item.textContent === "Retry")).toBe(false);
|
||||
});
|
||||
|
||||
it("releases a saved computer viewer after reassignment and opens the current computer", async () => {
|
||||
const owner = { computerId: "computer-a", ownerId: "viewer-a", generation: 1 };
|
||||
computerFixture.get.mockResolvedValue({ environmentId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", name: "First Boat" });
|
||||
computerFixture.connect.mockResolvedValue({
|
||||
viewerUrl: "https://desktop.example.test/viewer",
|
||||
expiresAt: new Date(Date.now() + 60_000).toISOString(), owner,
|
||||
});
|
||||
writeTaskSidePanelState("user-1", "company-1", "task-1", {
|
||||
state: { tabs: [taskPanelComputerTab("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa")], activeTabId: "computer:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" },
|
||||
launcherOpen: false, userInteracted: true, autoPlanHandled: false, updatedAt: 1,
|
||||
});
|
||||
await render(panel());
|
||||
await act(async () => Array.from(container.querySelectorAll<HTMLButtonElement>("button"))
|
||||
.find(button => button.textContent === "Connect")?.click());
|
||||
expect(container.querySelector("iframe")).not.toBeNull();
|
||||
await act(async () => queryClient.setQueryData(["task-computer", "task-1"], {
|
||||
environmentId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", name: "Second Boat",
|
||||
}));
|
||||
await vi.waitFor(() => expect(container.textContent).toContain("This task now uses a different computer."));
|
||||
expect(container.querySelector("iframe")).toBeNull();
|
||||
expect(computerFixture.disconnect).toHaveBeenCalledWith("task-1", "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", owner);
|
||||
await act(async () => Array.from(container.querySelectorAll<HTMLButtonElement>("button"))
|
||||
.find(button => button.textContent === "Open current computer")?.click());
|
||||
expect(container.querySelector('[data-side-panel-tab-target="computer:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"]')).toBeNull();
|
||||
expect(container.querySelector('[data-side-panel-tab-target="computer:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"][aria-selected="true"]')).not.toBeNull();
|
||||
await act(async () => Array.from(container.querySelectorAll<HTMLButtonElement>("button"))
|
||||
.find(button => button.textContent === "Connect")?.click());
|
||||
expect(computerFixture.connect).toHaveBeenLastCalledWith("task-1", "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb");
|
||||
});
|
||||
|
||||
it("lets an attachment request take focus from a workspace-file route", async () => {
|
||||
routeFixture.location.search = "?file=README.md&workspace=project";
|
||||
window.history.replaceState(null, "", `${routeFixture.location.pathname}${routeFixture.location.search}`);
|
||||
|
||||
@@ -868,7 +868,21 @@ export function TaskSidePanel({
|
||||
>
|
||||
{controller.tabs.map(tab => tab.payload.kind === "computer" ? (
|
||||
<div key={tab.id} hidden={tab.id !== controller.activeTabId} className={cn("h-full min-h-0", tab.id !== controller.activeTabId && "hidden")}>
|
||||
<TaskComputerPanel active={tab.id === controller.activeTabId} issueId={issue.id} environmentId={tab.payload.environmentId} />
|
||||
{computerQuery.data !== undefined && computerQuery.data?.environmentId !== tab.payload.environmentId ? (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-4 p-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{computerQuery.data ? "This task now uses a different computer." : "This task no longer has a computer."}
|
||||
</p>
|
||||
{computerQuery.data && (
|
||||
<Button onClick={() => {
|
||||
controller.closeTab(tab.id);
|
||||
controller.openTab(taskPanelComputerTab(computerQuery.data!.environmentId));
|
||||
}}>Open current computer</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<TaskComputerPanel active={tab.id === controller.activeTabId} issueId={issue.id} environmentId={tab.payload.environmentId} />
|
||||
)}
|
||||
</div>
|
||||
) : null)}
|
||||
{controller.tabs.map(tab => tab.payload.kind === "browser" ? (
|
||||
|
||||
Reference in new issue
Block a user