From 321d051c9f04fe729762b71c7b0758530f94d4e8 Mon Sep 17 00:00:00 2001 From: fullstack-engineer Date: Sat, 23 May 2026 01:40:49 -0700 Subject: [PATCH 1/2] Add display control state to Display tab --- canvas/src/components/tabs/DisplayTab.tsx | 113 ++++++++++++++-- .../tabs/__tests__/DisplayTab.test.tsx | 125 +++++++++++++++++- 2 files changed, 223 insertions(+), 15 deletions(-) diff --git a/canvas/src/components/tabs/DisplayTab.tsx b/canvas/src/components/tabs/DisplayTab.tsx index e9fa002ad..74b9b073c 100644 --- a/canvas/src/components/tabs/DisplayTab.tsx +++ b/canvas/src/components/tabs/DisplayTab.tsx @@ -13,31 +13,76 @@ interface DisplayStatus { height?: number; } +interface DisplayControlStatus { + controller: "none" | "user" | "agent"; + controlled_by?: string; + expires_at?: string; +} + interface Props { workspaceId: string; } export function DisplayTab({ workspaceId }: Props) { const [status, setStatus] = useState(null); + const [control, setControl] = useState(null); const [error, setError] = useState(null); + const [controlError, setControlError] = useState(null); + const [controlBusy, setControlBusy] = useState(false); useEffect(() => { let cancelled = false; setStatus(null); + setControl(null); setError(null); - api - .get(`/workspaces/${workspaceId}/display`) - .then((data) => { - if (!cancelled) setStatus(data); - }) - .catch((err) => { + setControlError(null); + async function load() { + try { + const displayStatus = await api.get(`/workspaces/${workspaceId}/display`); + if (cancelled) return; + setStatus(displayStatus); + if (displayStatus.reason === "display_not_enabled") return; + try { + const displayControl = await api.get(`/workspaces/${workspaceId}/display/control`); + if (!cancelled) setControl(displayControl); + } catch (err) { + if (!cancelled) { + setControl(null); + setControlError("Display control unavailable"); + } + } + } catch (err) { if (!cancelled) setError(err instanceof Error ? err.message : "Display status unavailable"); - }); + } + } + load(); return () => { cancelled = true; }; }, [workspaceId]); + const acquireControl = async () => { + setControlBusy(true); + setControlError(null); + try { + const next = await api.post(`/workspaces/${workspaceId}/display/control/acquire`, { + controller: "user", + ttl_seconds: 300, + }); + setControl(next); + } catch (err) { + setControlError("Failed to take control"); + try { + const latest = await api.get(`/workspaces/${workspaceId}/display/control`); + setControl(latest); + } catch { + setControl(null); + } + } finally { + setControlBusy(false); + } + }; + if (error) { return (
@@ -81,12 +126,48 @@ export function DisplayTab({ workspaceId }: Props) { : "This workspace has display configuration, but the desktop session infrastructure is not configured yet."}

{!isNotEnabled && ( -
-
Mode
-
{status.mode || "unknown"}
-
Status
-
{status.status || "unknown"}
-
+ <> +
+
Mode
+
{status.mode || "unknown"}
+
Status
+
{status.status || "unknown"}
+
+
+ {control ? ( +
+
+

+ {control.controller === "none" + ? "No active controller" + : `Controlled by ${displayControlActorLabel(control)}`} +

+ {control.expires_at && ( +

+ Until {new Date(control.expires_at).toLocaleTimeString()} +

+ )} + {controlError &&

{controlError}

} +
+ {control.controller === "none" && ( + + )} +
+ ) : ( +
+
+ {controlError &&

{controlError}

} +
+ )} +
+ )}
); @@ -94,3 +175,9 @@ export function DisplayTab({ workspaceId }: Props) { return null; } + +function displayControlActorLabel(control: DisplayControlStatus): string { + if (control.controller === "agent") return "Agent"; + if (control.controlled_by === "admin-token") return "Admin"; + return "User"; +} diff --git a/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx b/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx index ad34b421c..391a2cafc 100644 --- a/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx +++ b/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx @@ -1,12 +1,13 @@ // @vitest-environment jsdom import { describe, it, expect, vi, beforeEach } from "vitest"; -import { render, screen, waitFor } from "@testing-library/react"; +import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; -const { mockGet } = vi.hoisted(() => ({ mockGet: vi.fn() })); +const { mockGet, mockPost } = vi.hoisted(() => ({ mockGet: vi.fn(), mockPost: vi.fn() })); vi.mock("@/lib/api", () => ({ api: { get: mockGet, + post: mockPost, }, })); @@ -14,7 +15,9 @@ import { DisplayTab } from "../DisplayTab"; describe("DisplayTab", () => { beforeEach(() => { + cleanup(); mockGet.mockReset(); + mockPost.mockReset(); }); it("renders unavailable state for non-display workspaces", async () => { @@ -29,5 +32,123 @@ describe("DisplayTab", () => { expect(screen.getByText("Display is not enabled for this workspace.")).toBeTruthy(); }); expect(mockGet).toHaveBeenCalledWith("/workspaces/ws-no-display/display"); + expect(mockGet).not.toHaveBeenCalledWith("/workspaces/ws-no-display/display/control"); + }); + + it("renders control acquisition for display-configured workspaces", async () => { + mockGet + .mockResolvedValueOnce({ + available: false, + reason: "display_session_unavailable", + mode: "desktop-control", + status: "not_configured", + }) + .mockResolvedValueOnce({ + controller: "none", + }); + mockPost.mockResolvedValueOnce({ + controller: "user", + controlled_by: "admin-token", + expires_at: "2026-05-23T08:48:27Z", + }); + + render(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Take control" })).toBeTruthy(); + }); + expect(mockGet).toHaveBeenCalledWith("/workspaces/ws-display/display"); + expect(mockGet).toHaveBeenCalledWith("/workspaces/ws-display/display/control"); + + fireEvent.click(screen.getByRole("button", { name: "Take control" })); + + await waitFor(() => { + expect(screen.getByText("Controlled by Admin")).toBeTruthy(); + }); + expect(mockPost).toHaveBeenCalledWith("/workspaces/ws-display/display/control/acquire", { + controller: "user", + ttl_seconds: 300, + }); + }); + + it("renders active display control locks as observe-only", async () => { + mockGet + .mockResolvedValueOnce({ + available: false, + reason: "display_session_unavailable", + mode: "desktop-control", + status: "not_configured", + }) + .mockResolvedValueOnce({ + controller: "agent", + controlled_by: "sidecar", + expires_at: "2026-05-23T08:48:27Z", + }); + + render(); + + await waitFor(() => { + expect(screen.getByText("Controlled by Agent")).toBeTruthy(); + }); + expect(screen.queryByRole("button", { name: "Release" })).toBeNull(); + expect(screen.queryByRole("button", { name: "Take control" })).toBeNull(); + expect(mockPost).not.toHaveBeenCalled(); + }); + + it("refreshes display control state after failed acquisition", async () => { + mockGet + .mockResolvedValueOnce({ + available: false, + reason: "display_session_unavailable", + mode: "desktop-control", + status: "not_configured", + }) + .mockResolvedValueOnce({ + controller: "none", + }) + .mockResolvedValueOnce({ + controller: "agent", + controlled_by: "sidecar", + expires_at: "2026-05-23T08:48:27Z", + }); + mockPost.mockRejectedValueOnce(new Error("API POST /workspaces/ws-display/display/control/acquire: 409 conflict")); + + render(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Take control" })).toBeTruthy(); + }); + + fireEvent.click(screen.getByRole("button", { name: "Take control" })); + + await waitFor(() => { + expect(screen.getByText("Controlled by Agent")).toBeTruthy(); + }); + expect(screen.getByText("Failed to take control")).toBeTruthy(); + expect(mockGet).toHaveBeenCalledWith("/workspaces/ws-display/display/control"); + expect(mockGet).toHaveBeenCalledTimes(3); + expect(mockPost).toHaveBeenCalledWith("/workspaces/ws-display/display/control/acquire", { + controller: "user", + ttl_seconds: 300, + }); + }); + + it("keeps display status visible without takeover actions when control status fails", async () => { + mockGet + .mockResolvedValueOnce({ + available: false, + reason: "display_session_unavailable", + mode: "desktop-control", + status: "not_configured", + }) + .mockRejectedValueOnce(new Error("API GET /workspaces/ws-display/display/control: 401 unauthorized")); + + render(); + + await waitFor(() => { + expect(screen.getByText("Display session is not ready.")).toBeTruthy(); + }); + expect(screen.queryByRole("button", { name: "Take control" })).toBeNull(); + expect(screen.getByText("Display control unavailable")).toBeTruthy(); }); }); -- 2.52.0 From af3d98e4782adc93506fff5b1a317b9ade7bbcb3 Mon Sep 17 00:00:00 2001 From: fullstack-engineer Date: Sat, 23 May 2026 01:46:46 -0700 Subject: [PATCH 2/2] Harden display control tab state handling --- canvas/src/components/tabs/DisplayTab.tsx | 31 ++++-- .../tabs/__tests__/DisplayTab.test.tsx | 96 +++++++++++++++++++ 2 files changed, 118 insertions(+), 9 deletions(-) diff --git a/canvas/src/components/tabs/DisplayTab.tsx b/canvas/src/components/tabs/DisplayTab.tsx index 74b9b073c..61df1b4d8 100644 --- a/canvas/src/components/tabs/DisplayTab.tsx +++ b/canvas/src/components/tabs/DisplayTab.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { api } from "@/lib/api"; interface DisplayStatus { @@ -29,30 +29,34 @@ export function DisplayTab({ workspaceId }: Props) { const [error, setError] = useState(null); const [controlError, setControlError] = useState(null); const [controlBusy, setControlBusy] = useState(false); + const requestGeneration = useRef(0); useEffect(() => { + const generation = requestGeneration.current + 1; + requestGeneration.current = generation; let cancelled = false; setStatus(null); setControl(null); setError(null); setControlError(null); + setControlBusy(false); async function load() { try { const displayStatus = await api.get(`/workspaces/${workspaceId}/display`); - if (cancelled) return; + if (cancelled || requestGeneration.current !== generation) return; setStatus(displayStatus); if (displayStatus.reason === "display_not_enabled") return; try { const displayControl = await api.get(`/workspaces/${workspaceId}/display/control`); - if (!cancelled) setControl(displayControl); + if (!cancelled && requestGeneration.current === generation) setControl(displayControl); } catch (err) { - if (!cancelled) { + if (!cancelled && requestGeneration.current === generation) { setControl(null); setControlError("Display control unavailable"); } } } catch (err) { - if (!cancelled) setError(err instanceof Error ? err.message : "Display status unavailable"); + if (!cancelled && requestGeneration.current === generation) setError("The display status could not be loaded."); } } load(); @@ -62,24 +66,30 @@ export function DisplayTab({ workspaceId }: Props) { }, [workspaceId]); const acquireControl = async () => { + const generation = requestGeneration.current; + const controlPath = `/workspaces/${workspaceId}/display/control`; setControlBusy(true); setControlError(null); try { - const next = await api.post(`/workspaces/${workspaceId}/display/control/acquire`, { + const next = await api.post(`${controlPath}/acquire`, { controller: "user", ttl_seconds: 300, }); + if (requestGeneration.current !== generation) return; setControl(next); } catch (err) { + if (requestGeneration.current !== generation) return; setControlError("Failed to take control"); try { - const latest = await api.get(`/workspaces/${workspaceId}/display/control`); + const latest = await api.get(controlPath); + if (requestGeneration.current !== generation) return; setControl(latest); } catch { + if (requestGeneration.current !== generation) return; setControl(null); } } finally { - setControlBusy(false); + if (requestGeneration.current === generation) setControlBusy(false); } }; @@ -162,7 +172,9 @@ export function DisplayTab({ workspaceId }: Props) {
) : (
-
+ {!controlError && ( +
+ )} {controlError &&

{controlError}

}
)} @@ -179,5 +191,6 @@ export function DisplayTab({ workspaceId }: Props) { function displayControlActorLabel(control: DisplayControlStatus): string { if (control.controller === "agent") return "Agent"; if (control.controlled_by === "admin-token") return "Admin"; + if (control.controlled_by?.startsWith("org-token:")) return "Automation"; return "User"; } diff --git a/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx b/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx index 391a2cafc..51e88e4ad 100644 --- a/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx +++ b/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx @@ -95,6 +95,29 @@ describe("DisplayTab", () => { expect(mockPost).not.toHaveBeenCalled(); }); + it("labels org-token display control locks as automation", async () => { + mockGet + .mockResolvedValueOnce({ + available: false, + reason: "display_session_unavailable", + mode: "desktop-control", + status: "not_configured", + }) + .mockResolvedValueOnce({ + controller: "user", + controlled_by: "org-token:abc123", + expires_at: "2026-05-23T08:48:27Z", + }); + + render(); + + await waitFor(() => { + expect(screen.getByText("Controlled by Automation")).toBeTruthy(); + }); + expect(screen.queryByText("org-token:abc123")).toBeNull(); + expect(screen.queryByRole("button", { name: "Take control" })).toBeNull(); + }); + it("refreshes display control state after failed acquisition", async () => { mockGet .mockResolvedValueOnce({ @@ -151,4 +174,77 @@ describe("DisplayTab", () => { expect(screen.queryByRole("button", { name: "Take control" })).toBeNull(); expect(screen.getByText("Display control unavailable")).toBeTruthy(); }); + + it("does not render raw display status errors", async () => { + mockGet.mockRejectedValueOnce(new Error("API GET /workspaces/ws-display/display: 500 secret backend details")); + + render(); + + await waitFor(() => { + expect(screen.getByText("Display status unavailable")).toBeTruthy(); + }); + expect(screen.queryByText(/secret backend details/)).toBeNull(); + }); + + it("ignores stale acquire responses after workspace changes", async () => { + const acquire = deferred<{ controller: "user"; controlled_by: string; expires_at: string }>(); + mockGet + .mockResolvedValueOnce({ + available: false, + reason: "display_session_unavailable", + mode: "desktop-control", + status: "not_configured", + }) + .mockResolvedValueOnce({ + controller: "none", + }) + .mockResolvedValueOnce({ + available: false, + reason: "display_session_unavailable", + mode: "desktop-control", + status: "not_configured", + }) + .mockResolvedValueOnce({ + controller: "none", + }); + mockPost.mockReturnValueOnce(acquire.promise); + + const { rerender } = render(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Take control" })).toBeTruthy(); + }); + fireEvent.click(screen.getByRole("button", { name: "Take control" })); + + rerender(); + + await waitFor(() => { + expect(mockGet).toHaveBeenCalledWith("/workspaces/ws-b/display/control"); + }); + await waitFor(() => { + expect(screen.getByRole("button", { name: "Take control" })).toBeTruthy(); + }); + + acquire.resolve({ + controller: "user", + controlled_by: "admin-token", + expires_at: "2026-05-23T08:48:27Z", + }); + await acquire.promise; + + await waitFor(() => { + expect(screen.queryByText("Controlled by Admin")).toBeNull(); + }); + expect(screen.getByRole("button", { name: "Take control" })).toBeTruthy(); + }); }); + +function deferred() { + let resolve!: (value: T) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} -- 2.52.0