feat(ui): call lanes "workspaces" in the UI, CLI output and docs
Copy-only rename (workspace redesign, phase 0). The word a user reads changes — "Lane" → "Workspace", "Add lane" → "Add workspace" — in both locales, in agenthub CLI prose and help, in the server's error messages and in the docs. Every identifier stays: /api/lanes, `agenthub lanes`, --lane, AGENTHUB_LANE_ID, error codes, tmux names, DB tables, file, component and i18n key names. docs/LANES.md keeps its name, retitled "Workspaces (lanes)".
This commit is contained in:
@@ -243,7 +243,7 @@ describe("AddLaneModal — picking an existing branch (issue #34)", () => {
|
||||
await user.click(screen.getByRole("button", { name: "Existing branch" }));
|
||||
const select = await screen.findByLabelText("Branch to check out");
|
||||
await user.selectOptions(select, "feat/other");
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(api.lanes.worktree).toHaveBeenCalledWith(
|
||||
@@ -287,7 +287,7 @@ describe("AddLaneModal — picking an existing branch (issue #34)", () => {
|
||||
"Automatic — named from the title"
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
await waitFor(() =>
|
||||
expect(api.lanes.worktree).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ branch: undefined, base: "main" })
|
||||
@@ -301,7 +301,7 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
// the server name it" (issue #34).
|
||||
it("disables confirm until a repo, a title and a resolved branch list are all present", () => {
|
||||
renderModal();
|
||||
expect(screen.getByRole("button", { name: "Add lane" })).toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Add workspace" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("looks up branches once the repo path settles, and shows them as a picker", async () => {
|
||||
@@ -337,7 +337,7 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
await waitFor(() => expect(api.lanes.branches).toHaveBeenCalled());
|
||||
expect(await screen.findByText(/Not a git repository/)).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("Branch to fork from")).toBeNull();
|
||||
expect(screen.getByRole("button", { name: "Add lane" })).toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Add workspace" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("refuses a worktree of a repo with no commits and steers to Repo mode", async () => {
|
||||
@@ -355,11 +355,11 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
expect(await screen.findByText(/commit first, then publish/i)).toBeInTheDocument();
|
||||
await user.type(screen.getByLabelText("Title"), "demo");
|
||||
// There is nothing to branch from, so the worktree cannot be created.
|
||||
expect(screen.getByRole("button", { name: "Add lane" })).toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Add workspace" })).toBeDisabled();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Use as a Repo lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Use as a Repo workspace" }));
|
||||
expect(screen.getByRole("button", { name: "Repo" })).toHaveAttribute("aria-pressed", "true");
|
||||
expect(screen.getByRole("button", { name: "Add lane" })).toBeEnabled();
|
||||
expect(screen.getByRole("button", { name: "Add workspace" })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("submits through the worktree provisioning endpoint, with the typed branch name", async () => {
|
||||
@@ -375,7 +375,7 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
await fillWorktreeForm(user, { title: "New feature", branch: "feat/new-feature" });
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(api.lanes.worktree).toHaveBeenCalledWith({
|
||||
@@ -407,7 +407,7 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
await fillWorktreeForm(user, { title: "New feature" });
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
expect(await screen.findByText("EWORKTREEDIRCOLLISION")).toBeInTheDocument();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
@@ -432,7 +432,7 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
await fillWorktreeForm(user);
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() => expect(api.lanes.worktree).toHaveBeenCalled());
|
||||
await waitFor(() => expect(api.lanes.profileInit).toHaveBeenCalledWith(42));
|
||||
@@ -457,7 +457,7 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
await fillWorktreeForm(user, { title: "demo2" });
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() => expect(onAdded).toHaveBeenCalled());
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
@@ -478,14 +478,14 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
await fillWorktreeForm(user, { title: "demo3" });
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
// A failure is the one case worth reading before the dialog goes away.
|
||||
expect(await screen.findByText("Setup")).toBeInTheDocument();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
// And then the primary button must close, not sit there disabled: the lane
|
||||
// already exists, so "Add lane" has nothing left to do.
|
||||
expect(screen.queryByRole("button", { name: "Add lane" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Add workspace" })).toBeNull();
|
||||
await user.click(screen.getByRole("button", { name: "Close" }));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
@@ -510,7 +510,7 @@ describe("AddLaneModal — worktree mode (default)", () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
await fillWorktreeForm(user, { title: "demo4" });
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() => expect(onAdded).toHaveBeenCalled());
|
||||
expect(await screen.findByText(/auto-setup was skipped/)).toBeInTheDocument();
|
||||
@@ -525,12 +525,12 @@ describe("AddLaneModal — repo mode (adopt)", () => {
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByRole("button", { name: "Repo" }));
|
||||
|
||||
expect(screen.getByRole("button", { name: "Add lane" })).toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Add workspace" })).toBeDisabled();
|
||||
const dirField = screen.getByLabelText("Directory");
|
||||
await focusField(user, dirField);
|
||||
await user.type(dirField, "/Users/tester/projects/repo");
|
||||
|
||||
expect(screen.getByRole("button", { name: "Add lane" })).toBeEnabled();
|
||||
expect(screen.getByRole("button", { name: "Add workspace" })).toBeEnabled();
|
||||
expect(screen.queryByLabelText("Branch to fork from")).toBeNull();
|
||||
expect(screen.queryByLabelText("New branch name")).toBeNull();
|
||||
expect(api.lanes.branches).not.toHaveBeenCalled();
|
||||
@@ -551,7 +551,7 @@ describe("AddLaneModal — repo mode (adopt)", () => {
|
||||
await focusField(user, dirField);
|
||||
await user.type(dirField, "/Users/tester/projects/repo");
|
||||
await user.type(screen.getByLabelText("Title"), "main repo");
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(api.lanes.ensure).toHaveBeenCalledWith({
|
||||
@@ -582,9 +582,11 @@ describe("AddLaneModal — repo mode (adopt)", () => {
|
||||
const dirField = screen.getByLabelText("Directory");
|
||||
await focusField(user, dirField);
|
||||
await user.type(dirField, "/Users/tester/projects/repo");
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
expect(await screen.findByText(/already has a lane.*#3.*SG - Services/)).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText(/already has a workspace.*#3.*SG - Services/)
|
||||
).toBeInTheDocument();
|
||||
// The existing lane is selected so the user sees it, but nothing claims a new one.
|
||||
expect(onAdded).toHaveBeenCalledWith(expect.objectContaining({ id: 3 }));
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
@@ -625,7 +627,7 @@ describe("AddLaneModal — folder browse", () => {
|
||||
|
||||
expect(screen.queryByRole("dialog", { name: "Browse for a folder" })).toBeNull();
|
||||
expect(
|
||||
screen.getByRole("dialog", { name: "Create a lane from a working directory" })
|
||||
screen.getByRole("dialog", { name: "Create a workspace from a working directory" })
|
||||
).toBeInTheDocument();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -655,7 +657,7 @@ describe("AddLaneModal — AI Router", () => {
|
||||
const repoField = screen.getByLabelText("Directory");
|
||||
await focusField(user, repoField);
|
||||
await user.type(repoField, "/Users/tester/projects/repo");
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() => expect(api.lanes.routerApply).toHaveBeenCalledWith(9, "native"));
|
||||
});
|
||||
@@ -674,7 +676,7 @@ describe("AddLaneModal — AI Router", () => {
|
||||
await focusField(user, repoField);
|
||||
await user.type(repoField, "/Users/tester/projects/repo");
|
||||
await user.click(await screen.findByRole("radio", { name: "AI Router" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() => expect(api.lanes.routerApply).toHaveBeenCalledWith(9, "9router"));
|
||||
});
|
||||
@@ -703,7 +705,7 @@ describe("AddLaneModal — AI Router", () => {
|
||||
const repoField = screen.getByLabelText("Directory");
|
||||
await focusField(user, repoField);
|
||||
await user.type(repoField, "/Users/tester/projects/repo");
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() => expect(api.lanes.ensure).toHaveBeenCalled());
|
||||
});
|
||||
@@ -726,7 +728,7 @@ describe("AddLaneModal — a created lane is never lost to a setup failure", ()
|
||||
const repoField = screen.getByLabelText("Directory");
|
||||
await focusField(user, repoField);
|
||||
await user.type(repoField, "/Users/tester/projects/repo");
|
||||
await user.click(screen.getByRole("button", { name: "Add lane" }));
|
||||
await user.click(screen.getByRole("button", { name: "Add workspace" }));
|
||||
|
||||
await waitFor(() => expect(onAdded).toHaveBeenCalled());
|
||||
// …and the failure is still surfaced, on a modal left open.
|
||||
@@ -792,7 +794,7 @@ describe("AddLaneModal — tool selection", () => {
|
||||
const picker = await screen.findByRole("button", { name: /Claude Code.*Default/ });
|
||||
expect(picker).toHaveAttribute("aria-expanded", "false");
|
||||
await user.click(picker);
|
||||
const list = screen.getByRole("group", { name: /Which CLI drives this lane/ });
|
||||
const list = screen.getByRole("group", { name: /Which CLI drives this workspace/ });
|
||||
expect(
|
||||
within(list)
|
||||
.getAllByRole("button")
|
||||
@@ -802,7 +804,7 @@ describe("AddLaneModal — tool selection", () => {
|
||||
// The picked tool replaces the default on the button, and the list closes.
|
||||
expect(screen.getByRole("button", { name: "Codex", expanded: false })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Gemini" })).toBeNull();
|
||||
await user.click(screen.getByRole("button", { name: /add lane|create/i }));
|
||||
await user.click(screen.getByRole("button", { name: /add workspace|create/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(api.lanes.ensure).toHaveBeenCalledWith(expect.objectContaining({ tool: "codex" }))
|
||||
@@ -842,7 +844,7 @@ describe("AddLaneModal — tool selection", () => {
|
||||
const dirField = screen.getByLabelText("Directory");
|
||||
await focusField(user, dirField);
|
||||
await user.type(dirField, "/tmp/demo");
|
||||
await user.click(screen.getByRole("button", { name: /add lane|create/i }));
|
||||
await user.click(screen.getByRole("button", { name: /add workspace|create/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(api.lanes.ensure).toHaveBeenCalledWith(expect.objectContaining({ tool: "claude" }))
|
||||
|
||||
Reference in New Issue
Block a user