feat: workspace aliases, tab groups of split panes, native path picker (v2.1.0)
- /api/workspaces and `agenthub workspace(s)` / `--workspace` as aliases of /api/lanes and `agenthub lanes` / `--lane`; old names print one stderr note. REPL `help lanes|workspace|workspaces` resolves through the aliases. - A workspace holds up to ten sessions (slots 0-9); every console tab is a group of 1-4 split panes (PaneGroups, agenthub.workspace.paneLayout); parseGroups keeps the stored active tab when an earlier one is dropped. - Browse button beside path fields: POST /api/lanes/pick-path opens the OS picker on the server's desktop — loopback only and no forwarding header (EREMOTE), a display failure is ENODIALOG not a Cancel, the dialog is killed when the request closes, a folder field falls back to the in-app browser on any failure. - Changes rail counts chip, image viewer (/git/raw), status bar session counts and per-day cost (`date=` on /api/pricing/cost). - /agenthub-open and /agenthub-doctor run from the INSTALLED build (supersededBy), doctor offers fixes behind two confirmations; plugin-open treats a locked bootstrap as non-fatal. - New-terminal shell waits for the first attach (issue #55). - repo-onboarding / debug-live-issue skills removed. - OpenAPI: pick-path, lane/date on /pricing/cost, tool on /run/history, /api/workspaces mount.
This commit is contained in:
@@ -26,6 +26,7 @@ vi.mock("../../../lib/api", () => ({
|
||||
worktree: vi.fn(),
|
||||
ensure: vi.fn(),
|
||||
browse: vi.fn(),
|
||||
pickPath: vi.fn(),
|
||||
get: vi.fn(),
|
||||
profileInit: vi.fn(),
|
||||
mcpSync: vi.fn(),
|
||||
@@ -147,6 +148,7 @@ beforeEach(() => {
|
||||
vi.mocked(api.lanes.worktree).mockReset();
|
||||
vi.mocked(api.lanes.ensure).mockReset();
|
||||
vi.mocked(api.lanes.browse).mockReset();
|
||||
vi.mocked(api.lanes.pickPath).mockReset();
|
||||
// Provisioning finishes instantly by default - tests that care about the
|
||||
// provisioning-in-progress race override this per-test.
|
||||
vi.mocked(api.lanes.get)
|
||||
@@ -549,7 +551,45 @@ describe("AddLaneModal — repo mode (adopt)", () => {
|
||||
});
|
||||
|
||||
describe("AddLaneModal — folder browse", () => {
|
||||
const noNativePicker = () =>
|
||||
vi
|
||||
.mocked(api.lanes.pickPath)
|
||||
.mockRejectedValue(Object.assign(new Error("no picker"), { code: "ENODIALOG" }));
|
||||
|
||||
it("fills the path field from the OS's own picker", async () => {
|
||||
vi.mocked(api.lanes.pickPath).mockResolvedValue({ path: "/Users/tester/app" });
|
||||
renderModal();
|
||||
const user = userEvent.setup();
|
||||
|
||||
await user.click(screen.getByTitle("Browse for a folder"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(document.getElementById("add-lane-repo")).toHaveValue("/Users/tester/app")
|
||||
);
|
||||
expect(api.lanes.browse).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to the in-app browser when no native picker is available", async () => {
|
||||
noNativePicker();
|
||||
vi.mocked(api.lanes.browse).mockResolvedValue({
|
||||
path: "/Users/tester",
|
||||
parent: null,
|
||||
entries: [],
|
||||
});
|
||||
const onClose = vi.fn();
|
||||
renderModal({ onClose });
|
||||
const user = userEvent.setup();
|
||||
|
||||
await user.click(screen.getByTitle("Browse for a folder"));
|
||||
const browser = await screen.findByRole("dialog", { name: "Browse for a folder" });
|
||||
// Its backdrop closes only the browser, never the modal behind it.
|
||||
await user.click(browser.parentElement!);
|
||||
expect(screen.queryByRole("dialog", { name: "Browse for a folder" })).toBeNull();
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("opens the browser, lists subdirectories, and selecting one fills the path field", async () => {
|
||||
noNativePicker();
|
||||
vi.mocked(api.lanes.browse).mockResolvedValue({
|
||||
path: "/Users/tester",
|
||||
parent: "/Users",
|
||||
@@ -566,6 +606,7 @@ describe("AddLaneModal — folder browse", () => {
|
||||
});
|
||||
|
||||
it("Escape closes only the folder browser, not the whole modal", async () => {
|
||||
noNativePicker();
|
||||
vi.mocked(api.lanes.browse).mockResolvedValue({
|
||||
path: "/Users/tester",
|
||||
parent: null,
|
||||
|
||||
Reference in New Issue
Block a user