feat: v2.7.8 — Artifacts hub, clone related repos as extra folders, quieter resume prompt
- Artifacts (/artifacts): claude.ai pages sessions publish (captured from the Artifact tool's hook), pinned workspace files, skills and plugins, each kept as a snapshot; shared ones appear in a Hub and a skill or plugin installs into the viewer's own account (server/lib/artifacts.js, /api/artifacts, docs/plans/artifacts/). - Workspace extra folders: clone one or more related repos beside the workspace and add them in one step (POST /api/lanes/:id/sources/clone). - A pane that ended by itself while tmux kept running is no longer offered for resume (tmux.serverStartedAt in reconcileOrphans). - Changes panel: a folder that is not a git repo opens on Files, and its files open from there. - Plus the parallel sessions' work in this tree: File Explorer button in the panel header, user delete, RSA SSH keys, chat/terminal and pet fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,12 @@ const { HOME, LANE_A, LANE_B } = vi.hoisted(() => {
|
||||
|
||||
// The workspace's home (its own suite) fetches sources and memory; these
|
||||
// tests are about the session view it hides, so it is a stub here.
|
||||
// Local mode unless a test says otherwise.
|
||||
const perms = vi.hoisted(() => ({ local: true }));
|
||||
vi.mock("../../components/AuthGate", async (importOriginal) => {
|
||||
const m = await importOriginal<typeof import("../../components/AuthGate")>();
|
||||
return { ...m, usePermissions: () => ({ ...m.usePermissions(), local: perms.local }) };
|
||||
});
|
||||
vi.mock("../../components/lanes/WorkspaceHome", () => ({
|
||||
WorkspaceHome: () => <div data-testid="workspace-home" />,
|
||||
paneRunId: (id: number, slot: number) =>
|
||||
@@ -108,6 +114,12 @@ vi.mock("../../lib/api", async (importOriginal) => {
|
||||
features: { list: r({ features: [] }), show: r({ feature: null }) },
|
||||
proof: { list: r({ features: [] }), imageUrl: () => "" },
|
||||
},
|
||||
// The File Explorer opened from the panel header.
|
||||
files: {
|
||||
homes: r({ homes: [] }),
|
||||
list: r({ path: "", parent: null, entries: [], truncated: false }),
|
||||
trash: r({ items: [] }),
|
||||
},
|
||||
aiProxies: { list: r({ items: [] }) },
|
||||
sessions: { list: r({ sessions: [], total: 0, limit: 50, offset: 0 }) },
|
||||
},
|
||||
@@ -147,6 +159,7 @@ async function settle() {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
perms.local = true;
|
||||
i18n.changeLanguage("en");
|
||||
});
|
||||
|
||||
@@ -203,6 +216,22 @@ describe("Workspace — the Changes panel's width", () => {
|
||||
expect(card().className).toContain("w-[25%]");
|
||||
});
|
||||
|
||||
it("opens the File Explorer from the button beside Full screen", async () => {
|
||||
perms.local = false;
|
||||
await openPanel();
|
||||
const btn = screen.getByTestId("panel-file-explorer");
|
||||
// Right before the window controls, in the same header strip.
|
||||
expect(btn.nextElementSibling).toBe(screen.getByTestId("lane-changes-resize"));
|
||||
fireEvent.click(btn);
|
||||
expect(await screen.findByTestId("file-explorer")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("has no File Explorer button in local mode", async () => {
|
||||
await openPanel();
|
||||
expect(screen.queryByTestId("panel-file-explorer")).toBeNull();
|
||||
expect(screen.getByTestId("lane-changes-resize")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("remembers the width across a reload, and never asks the server", async () => {
|
||||
await openPanel();
|
||||
fireEvent.click(screen.getByTestId("lane-changes-resize"));
|
||||
|
||||
Reference in New Issue
Block a user