feat(ui): finish the workspace-sessions plan — tool filter, session search, explicit Resume
- Sidebar: a tool filter (shown once workspaces of two tools exist, kept in agenthub.sidebar.projects → tool; reordering under it keeps the hidden workspaces' places), and search that also finds a workspace by one of its session names, showing only the matching sessions. - Workspace home: the repository it belongs to (root, main branch, worktree count) under the title, and each recent's cost. - Transcript page: an explicit Resume for a Claude session started in a workspace's own folder (SessionResumeButton). It warns that a resume re-reads the whole transcript, re-reads the live panes, then starts in the first free pane and opens it; a live session offers Open pane instead.
This commit is contained in:
@@ -190,6 +190,64 @@ describe("WorkspaceTree", () => {
|
||||
expect(rowIds()).toEqual(["workspace-row-3"]);
|
||||
});
|
||||
|
||||
it("filters by tool, remembered, and offers the filter only with two tools", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { unmount } = renderTree();
|
||||
await screen.findByTestId("workspace-row-1");
|
||||
await user.selectOptions(screen.getByLabelText("Filter by tool"), "codex");
|
||||
expect(rowIds()).toEqual(["workspace-row-3"]);
|
||||
expect(JSON.parse(localStorage.getItem("agenthub.sidebar.projects")!).tool).toBe("codex");
|
||||
await user.selectOptions(screen.getByLabelText("Filter by tool"), "");
|
||||
expect(rowIds()).toEqual(["workspace-row-1", "workspace-row-2", "workspace-row-3"]);
|
||||
unmount();
|
||||
|
||||
// One tool only: no filter, and a stale stored one is ignored.
|
||||
localStorage.setItem("agenthub.sidebar.projects", JSON.stringify({ tool: "codex" }));
|
||||
lanes = lanes.filter((l) => l.tool === "claude");
|
||||
renderTree();
|
||||
await screen.findByTestId("workspace-row-1");
|
||||
expect(screen.queryByLabelText("Filter by tool")).toBeNull();
|
||||
expect(rowIds()).toEqual(["workspace-row-1", "workspace-row-2"]);
|
||||
});
|
||||
|
||||
it("reorders within a tool filter without losing the hidden workspaces' places", async () => {
|
||||
localStorage.setItem("agenthub.workspace.laneOrder", JSON.stringify([1, 3, 2]));
|
||||
localStorage.setItem("agenthub.sidebar.projects", JSON.stringify({ tool: "claude" }));
|
||||
renderTree();
|
||||
fireEvent.keyDown(await screen.findByTestId("workspace-open-2"), {
|
||||
key: "ArrowUp",
|
||||
altKey: true,
|
||||
});
|
||||
expect(JSON.parse(localStorage.getItem("agenthub.workspace.laneOrder")!)).toEqual([2, 3, 1]);
|
||||
expect(rowIds()).toEqual(["workspace-row-2", "workspace-row-1"]);
|
||||
});
|
||||
|
||||
it("finds a workspace by one of its session names, showing only the matches", async () => {
|
||||
vi.mocked(api.sessions.list).mockImplementation(async (params) => ({
|
||||
sessions:
|
||||
(params as { cwd?: string }).cwd === "/r/sub"
|
||||
? [session("s-x", "Fix login bug"), session("s-y", "Docs")]
|
||||
: [session("s-live", "Live one"), session("s-old", "Old one")],
|
||||
total: 2,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
}));
|
||||
const user = userEvent.setup();
|
||||
renderTree();
|
||||
await screen.findByTestId("workspace-row-1");
|
||||
await user.click(screen.getByRole("button", { name: "Search workspaces and sessions" }));
|
||||
await user.type(screen.getByRole("searchbox"), "login");
|
||||
await waitFor(() => expect(rowIds()).toEqual(["workspace-row-2"]));
|
||||
expect(screen.getByTestId("workspace-session-s-x")).toBeVisible();
|
||||
expect(screen.queryByTestId("workspace-session-s-y")).toBeNull();
|
||||
|
||||
// A workspace whose NAME matches keeps all of its sessions.
|
||||
await user.clear(screen.getByRole("searchbox"));
|
||||
await user.type(screen.getByRole("searchbox"), "sub");
|
||||
await waitFor(() => expect(rowIds()).toEqual(["workspace-row-2"]));
|
||||
expect(screen.getByTestId("workspace-session-s-y")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("offers Reset only on a managed workspace and the readme run only on claude", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderTree();
|
||||
|
||||
Reference in New Issue
Block a user