feat(ui): sidebar project tree, status bar and a tabbed workbench for workspaces
The Workspace page's lane strip moves into the sidebar as a project tree: workspaces grouped by git repository (new GET /api/projects, cached in memory, POST /api/projects/refresh), untracked worktrees with Adopt, inline rename, drag/Alt+arrow ordering and a right-click row menu. The selected workspace is the URL's ?lane=, one shared lane list feeds the tree, the new bottom status bar and the page. The Changes panel gains an activity bar (Files / Changes / Sessions, with resume), and split panes get a header.
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, act, screen, waitFor } from "@testing-library/react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { MemoryRouter, useSearchParams } from "react-router-dom";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
type LaneFixture = {
|
||||
@@ -179,9 +179,24 @@ beforeEach(() => {
|
||||
h.lanes = lanesToReturn as unknown as typeof h.lanes;
|
||||
});
|
||||
|
||||
/** Stands in for the sidebar's project tree: picking a workspace sets `?lane=`. */
|
||||
function Picker() {
|
||||
const [, setParams] = useSearchParams();
|
||||
return (
|
||||
<>
|
||||
{lanesToReturn.map((l) => (
|
||||
<button key={l.id} onClick={() => setParams({ lane: String(l.id) })}>
|
||||
{`pick ${l.title}`}
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function renderWorkspace() {
|
||||
const utils = render(
|
||||
<MemoryRouter initialEntries={["/run"]}>
|
||||
<Picker />
|
||||
<Workspace />
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -191,11 +206,9 @@ async function renderWorkspace() {
|
||||
return utils;
|
||||
}
|
||||
|
||||
/** Click the strip tile for a lane, which is how a lane is selected. */
|
||||
/** Pick a lane the way the sidebar does: by naming it in the URL. */
|
||||
async function selectLane(user: ReturnType<typeof userEvent.setup>, title: string) {
|
||||
const tile = screen.getAllByText(title)[0];
|
||||
if (!tile) throw new Error(`no strip tile for ${title}`);
|
||||
await user.click(tile);
|
||||
await user.click(screen.getByRole("button", { name: `pick ${title}` }));
|
||||
await settle();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user