feat(workspace): split menus start AI tools; drop the Tools menu
A pane's split right/down menus offered only New terminal. They now list the same AI tools as the tab bar's + menu (newTabTools/newTabBody, shared from ConsoleTabs), started bare in the first free slot on that side of the pane. The tab bar's Tools menu is gone: its readme run hit 409 ERUNLIVE whenever slot 0 was live (always, in the session view) and depended on a personal skill; MCP sync and the dev-sync check served the dormant ship-feature-lane pipeline. The sidebar row menu's readme item goes too. The routes stay, reached by `agenthub lanes mcp sync` and `agenthub lanes sync-base`. Also: the home's Memory header and file tabs no longer shrink (a long file squeezed the tab row and clipped it).
This commit is contained in:
@@ -589,9 +589,8 @@ describe("AddLaneModal — folder browse", () => {
|
||||
});
|
||||
|
||||
describe("AddLaneModal — create/update readme", () => {
|
||||
it("no longer offers it here — it moved to the lane's Tools menu", async () => {
|
||||
// Creating a lane and writing its docs are separate decisions, and the
|
||||
// second one is worth re-running later; see LaneToolsMenu.
|
||||
it("is not offered here", async () => {
|
||||
// Creating a lane and writing its docs are separate decisions.
|
||||
renderModal();
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByRole("button", { name: "Repo" }));
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import LaneCard, { LaneToolsMenu } from "../LaneCard";
|
||||
import type { Lane, LaneRuntime } from "../../../lib/types";
|
||||
import LaneCard from "../LaneCard";
|
||||
import type { Lane } from "../../../lib/types";
|
||||
import { api } from "../../../lib/api";
|
||||
|
||||
vi.mock("../../../lib/api", () => ({
|
||||
@@ -24,7 +24,6 @@ vi.mock("../../../lib/api", () => ({
|
||||
down: vi.fn(),
|
||||
preflight: vi.fn().mockResolvedValue({ blocked: [], warnings: [] }),
|
||||
mcpSync: vi.fn(),
|
||||
syncBaseCheck: vi.fn(),
|
||||
},
|
||||
locks: {
|
||||
list: vi.fn(),
|
||||
@@ -294,82 +293,3 @@ describe("LaneCard — named locks", () => {
|
||||
expect(screen.queryByTestId("lane-locks-1")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("LaneToolsMenu", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(api.lanes.runtime).mockResolvedValue({
|
||||
available: true as const,
|
||||
provisioned: true as const,
|
||||
up: false,
|
||||
slot: 1,
|
||||
profileDir: "/work/demo/.agenthub/profile",
|
||||
ports: {},
|
||||
} as unknown as LaneRuntime);
|
||||
});
|
||||
|
||||
it("clicking Sync MCP calls the API and shows the result", async () => {
|
||||
vi.mocked(api.lanes.mcpSync).mockResolvedValue({
|
||||
servers: ["playwright"],
|
||||
profilesSeeded: [],
|
||||
});
|
||||
render(<LaneToolsMenu lane={makeLane()} />);
|
||||
await userEvent.click(await screen.findByTestId("lane-tools-menu"));
|
||||
await userEvent.click(await screen.findByTestId("lane-mcp-sync"));
|
||||
await waitFor(() => expect(api.lanes.mcpSync).toHaveBeenCalledWith(1));
|
||||
expect(await screen.findByTestId("lane-action-result")).toHaveTextContent("playwright");
|
||||
});
|
||||
|
||||
it("clicking Check dev sync reports a clean result", async () => {
|
||||
vi.mocked(api.lanes.syncBaseCheck).mockResolvedValue({
|
||||
code: 0,
|
||||
devDelta: ["a.txt", "b.txt"],
|
||||
overlap: [],
|
||||
});
|
||||
render(<LaneToolsMenu lane={makeLane()} />);
|
||||
await userEvent.click(await screen.findByTestId("lane-tools-menu"));
|
||||
await userEvent.click(await screen.findByTestId("lane-sync-check"));
|
||||
await waitFor(() => expect(api.lanes.syncBaseCheck).toHaveBeenCalledWith(1));
|
||||
expect(await screen.findByTestId("lane-action-result")).toHaveTextContent("2");
|
||||
});
|
||||
|
||||
it("clicking Check dev sync reports a migration collision", async () => {
|
||||
vi.mocked(api.lanes.syncBaseCheck).mockResolvedValue({
|
||||
code: 5,
|
||||
collisions: [{ file: "002_a.sql", collidesWith: "002_b.sql", suggestion: "003_a.sql" }],
|
||||
});
|
||||
render(<LaneToolsMenu lane={makeLane()} />);
|
||||
await userEvent.click(await screen.findByTestId("lane-tools-menu"));
|
||||
await userEvent.click(await screen.findByTestId("lane-sync-check"));
|
||||
expect(await screen.findByTestId("lane-action-result")).toHaveTextContent("003_a.sql");
|
||||
});
|
||||
|
||||
it("clicking Create/update readme opens the lane's terminal on the docs skill", async () => {
|
||||
// Moved here from the Add-lane modal: a skill only runs inside a CLI
|
||||
// session, and writing the docs is worth re-running long after creation.
|
||||
vi.mocked(api.run.start).mockResolvedValue({} as never);
|
||||
render(<LaneToolsMenu lane={makeLane({ cwd: "/work/demo" })} />);
|
||||
await userEvent.click(await screen.findByTestId("lane-tools-menu"));
|
||||
await userEvent.click(await screen.findByTestId("lane-write-docs"));
|
||||
await waitFor(() =>
|
||||
expect(api.run.start).toHaveBeenCalledWith({
|
||||
laneId: 1,
|
||||
cwd: "/work/demo",
|
||||
initialPrompt: "/sg-all:sg-md-writing",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("hides Create/update readme on a codex lane, which has no slash commands", async () => {
|
||||
render(<LaneToolsMenu lane={makeLane({ tool: "codex" })} />);
|
||||
await userEvent.click(await screen.findByTestId("lane-tools-menu"));
|
||||
expect(await screen.findByTestId("lane-mcp-sync")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("lane-write-docs")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders nothing when the lane has no profile", async () => {
|
||||
vi.mocked(api.lanes.runtime).mockResolvedValue({ available: false });
|
||||
const { container } = render(<LaneToolsMenu lane={makeLane()} />);
|
||||
await waitFor(() => expect(api.lanes.runtime).toHaveBeenCalled());
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user