diff --git a/desktop/frontend-next/src/ui/panels/Extensions.progress.interaction.test.tsx b/desktop/frontend-next/src/ui/panels/Extensions.progress.interaction.test.tsx
new file mode 100644
index 0000000000..5e40ebcda9
--- /dev/null
+++ b/desktop/frontend-next/src/ui/panels/Extensions.progress.interaction.test.tsx
@@ -0,0 +1,81 @@
+// @vitest-environment jsdom
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { cleanup, render } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import type { ExtensionSurface } from "../../port/wire";
+import { Extensions } from "./Extensions";
+
+afterEach(cleanup);
+
+const panel = (title: string | undefined, progress: number | undefined): ExtensionSurface => ({
+ pluginId: "sync-watch", surfaceId: "watch", sessionId: "sess-panel", generation: 7, kind: "panel",
+ panel: {
+ title, text: "Watching workspace", progress,
+ fields: [{ key: "branch", value: "studio" }],
+ actions: [{ actionId: "stop", label: "Stop sync" }],
+ },
+});
+
+describe("standing extension panel progress", () => {
+ it.each([0, 0.4, 1])("names titled progress at %s from the visible panel identity", (progress) => {
+ const ui = render();
+ const bar = ui.getByRole("progressbar", { name: "Workspace sync" });
+ expect(bar.getAttribute("aria-valuenow")).toBe(String(Math.round(progress * 100)));
+ expect((bar.firstElementChild as HTMLElement).style.width).toBe(`${progress * 100}%`);
+ expect(ui.getByText("Workspace sync")).toBeTruthy();
+ });
+
+ it.each([0, 0.4, 1])("names untitled progress at %s from the visible plugin identity", (progress) => {
+ const ui = render();
+ const bar = ui.getByRole("progressbar", { name: "sync-watch" });
+ expect(bar.getAttribute("aria-valuenow")).toBe(String(Math.round(progress * 100)));
+ expect(ui.getByText("sync-watch")).toBeTruthy();
+ });
+
+ it("updates the accessible identity and value when the same surface is republished", () => {
+ const invoke = vi.fn();
+ const ui = render();
+ ui.rerender();
+ expect(ui.queryByRole("progressbar", { name: "Workspace sync" })).toBeNull();
+ expect(ui.getByRole("progressbar", { name: "Index rebuild" }).getAttribute("aria-valuenow")).toBe("70");
+ ui.rerender();
+ expect(ui.getByRole("progressbar", { name: "sync-watch" }).getAttribute("aria-valuenow")).toBe("100");
+ });
+
+ it("keeps fields and the canonical action available by keyboard", async () => {
+ const invoke = vi.fn();
+ const ui = render();
+ expect(ui.getByText("Watching workspace")).toBeTruthy();
+ expect(ui.getByText("branch").tagName).toBe("DT");
+ expect(ui.getByText("studio").tagName).toBe("DD");
+ const user = userEvent.setup();
+ await user.tab();
+ expect(document.activeElement).toBe(ui.getByRole("button", { name: "Stop sync" }));
+ await user.keyboard("{Enter}");
+ expect(invoke.mock.calls).toEqual([["/sync-watch:stop"]]);
+ });
+
+ it("does not invent progress when the published panel has none", () => {
+ const ui = render();
+ expect(ui.queryByRole("progressbar")).toBeNull();
+ expect(ui.getByRole("button", { name: "Stop sync" })).toBeTruthy();
+ });
+
+ it("keeps composed views and their action invocation beside standing panels", async () => {
+ const invoke = vi.fn();
+ const view: ExtensionSurface = {
+ pluginId: "task-watch", surfaceId: "summary", kind: "view",
+ view: { body: [{ kind: "text", value: "Task summary" }, { kind: "button", label: "Inspect task", actionId: "/task-watch:inspect" }] },
+ };
+ const ui = render();
+ expect(ui.getByText("Task summary")).toBeTruthy();
+ expect(ui.getAllByRole("progressbar")).toHaveLength(1);
+ await userEvent.setup().click(ui.getByRole("button", { name: "Inspect task" }));
+ expect(invoke.mock.calls).toEqual([["/task-watch:inspect"]]);
+ });
+
+ it("renders no extension block when there are no surfaces", () => {
+ const ui = render();
+ expect(ui.container.firstChild).toBeNull();
+ });
+});
diff --git a/desktop/frontend-next/src/ui/panels/Extensions.tsx b/desktop/frontend-next/src/ui/panels/Extensions.tsx
index ecf5cbec47..f2e5a3354d 100644
--- a/desktop/frontend-next/src/ui/panels/Extensions.tsx
+++ b/desktop/frontend-next/src/ui/panels/Extensions.tsx
@@ -45,7 +45,7 @@ export function Extensions({
{panel.text &&
{panel.text}
}
{panel.progress !== undefined && (
-
+
)}