From fdc98242a1e5d08ee491abc0bfdf3b7dce91d9a5 Mon Sep 17 00:00:00 2001 From: Momo <35096042+momothemage@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:25:42 +0800 Subject: [PATCH] fix(ui): keep Workboard detail drawer actions in parity (#101658) * Fix Workboard detail action parity * test(ui): cover Workboard detail drawer actions * fix(ui): satisfy Workboard action lint --- ui/src/pages/workboard/view.test.ts | 73 +++++ ui/src/pages/workboard/view.ts | 320 ++++++++++++------- ui/src/pages/workboard/workboard.e2e.test.ts | 20 +- ui/src/styles/workboard.css | 17 + 4 files changed, 303 insertions(+), 127 deletions(-) diff --git a/ui/src/pages/workboard/view.test.ts b/ui/src/pages/workboard/view.test.ts index a464d193cf1..1d052d93d4d 100644 --- a/ui/src/pages/workboard/view.test.ts +++ b/ui/src/pages/workboard/view.test.ts @@ -149,6 +149,7 @@ describe("renderWorkboard", () => { const request = vi.fn(async () => ({ card: state.cards[0] })); state.loaded = true; state.dispatching = true; + state.detailCardId = "card-1"; state.cards = [ { id: "card-1", @@ -184,6 +185,15 @@ describe("renderWorkboard", () => { expect( container.querySelector(".workboard-card__move-select")?.disabled, ).toBe(true); + const detailActions = container.querySelector(".workboard-detail__actions"); + expect(detailActions).not.toBeNull(); + expect(buttonByLabel(detailActions!, "Edit card")?.disabled).toBe(true); + expect(buttonByLabel(detailActions!, "Archive card")?.disabled).toBe(true); + expect(buttonByLabel(detailActions!, "Stop session")?.disabled).toBe(true); + expect(buttonByLabel(detailActions!, "Delete card")?.disabled).toBe(true); + expect( + detailActions!.querySelector(".workboard-card__move-select")?.disabled, + ).toBe(true); expect(container.querySelector(".workboard-card")?.getAttribute("draggable")).toBe( "false", ); @@ -1243,6 +1253,69 @@ describe("renderWorkboard", () => { expect(onOpenSession).not.toHaveBeenCalled(); }); + it("mirrors compact card actions in the detail drawer", () => { + const host = {}; + const state = getWorkboardState(host); + const sessionKey = "agent:main:detail-parity"; + state.loaded = true; + state.detailCardId = "card-1"; + state.cards = [ + { + id: "card-1", + title: "Detail parity", + status: "running", + priority: "normal", + labels: [], + position: 1000, + createdAt: 1, + updatedAt: 1, + sessionKey, + }, + ]; + const container = document.createElement("div"); + const onOpenSession = vi.fn(); + + render( + renderWorkboard({ + host, + client: { request: vi.fn() } as unknown as GatewayBrowserClient, + connected: true, + pluginEnabled: true, + agentsList: null, + sessions: [ + { + key: sessionKey, + kind: "direct", + displayName: "Detail parity session", + updatedAt: 2, + hasActiveRun: true, + status: "running", + }, + ], + onOpenSession, + }), + container, + ); + + const actions = container.querySelector(".workboard-detail__actions"); + expect(actions).not.toBeNull(); + expect(buttonByLabel(actions!, "Edit card")).not.toBeNull(); + expect(buttonByLabel(actions!, "Archive card")).not.toBeNull(); + expect(buttonByLabel(actions!, "Stop session")).not.toBeNull(); + expect(buttonByLabel(actions!, "Open session")).not.toBeNull(); + expect(buttonByLabel(actions!, "Delete card")).not.toBeNull(); + + const moveSelect = actions!.querySelector(".workboard-card__move-select"); + expect(moveSelect?.getAttribute("aria-label")).toBe("Status: Detail parity"); + expect([...moveSelect!.options].map((option) => option.textContent?.trim())).toContain( + "Review", + ); + + buttonByLabel(actions!, "Edit card")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + expect(state.draftOpen).toBe(true); + expect(state.editingCardId).toBe("card-1"); + }); + it("keeps focus inside the card modal and restores focus on Escape", async () => { const host = {}; const state = getWorkboardState(host); diff --git a/ui/src/pages/workboard/view.ts b/ui/src/pages/workboard/view.ts index b98ea61cfed..397325de9a3 100644 --- a/ui/src/pages/workboard/view.ts +++ b/ui/src/pages/workboard/view.ts @@ -1135,7 +1135,12 @@ function moveCardToStatus( }); } -function renderCardMoveControl(props: WorkboardProps, card: WorkboardCard, busy: boolean) { +function renderCardMoveControl( + props: WorkboardProps, + card: WorkboardCard, + busy: boolean, + options: { wide?: boolean } = {}, +) { const state = getWorkboardState(props.host); const statuses = state.statuses.includes(card.status) ? state.statuses @@ -1144,7 +1149,10 @@ function renderCardMoveControl(props: WorkboardProps, card: WorkboardCard, busy: return nothing; } return html` -