feat: Stop archives a session, Sleep keeps it; SSH keys per type (#82); update Auto fix
- Stop (a tab's or pane's X, Close tab) now archives the session it held (DELETE /api/run/:id?archive=1); the new Sleep (moon left of the X, and in the tab, pane and session right-click menus) stops the pane but keeps the session listed to resume later. Shared endRuns in client/src/lib/stopSession.ts. - Git identity (#82): an account keeps an ED25519 AND an RSA key (AWS CodeCommit takes only RSA), every ssh git offers both, and a member's ~/.gitconfig gets core.sshCommand so a git typed in a terminal uses them. - Update dialog: Auto fix starts a Claude Code session on the install folder with the failed update's error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ vi.mock("../../../lib/api", () => ({
|
||||
get: vi.fn(),
|
||||
generateKey: vi.fn(),
|
||||
removeKey: vi.fn(),
|
||||
setTerminalSsh: vi.fn(),
|
||||
saveHttps: vi.fn(),
|
||||
saveAuthor: vi.fn(),
|
||||
removeHttps: vi.fn(),
|
||||
@@ -51,7 +52,7 @@ describe("GitIdentitySettings", () => {
|
||||
await userEvent.click(await screen.findByRole("button", { name: "Generate SSH key" }));
|
||||
expect(confirmDialog).not.toHaveBeenCalled();
|
||||
expect(api.gitIdentity.generateKey).toHaveBeenCalledWith(false, "ed25519");
|
||||
expect(await screen.findByLabelText("Public key")).toHaveValue(KEY.publicKey);
|
||||
expect(await screen.findByLabelText(/^Public key/)).toHaveValue(KEY.publicKey);
|
||||
});
|
||||
|
||||
it("copies the public key and asks before replacing it", async () => {
|
||||
@@ -97,6 +98,31 @@ describe("GitIdentitySettings", () => {
|
||||
expect(await screen.findByText(/^RSA 4096 · /)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("adds an RSA key beside the ED25519 one and deletes each on its own", async () => {
|
||||
const RSA = {
|
||||
type: "rsa" as const,
|
||||
publicKey: "ssh-rsa AAAAB3 an@agenthub",
|
||||
fingerprint: "SHA256:r",
|
||||
};
|
||||
const ED = { ...KEY, type: "ed25519" as const };
|
||||
vi.mocked(api.gitIdentity.get).mockResolvedValue(view({ ssh: ED, sshKeys: [ED] }));
|
||||
vi.mocked(api.gitIdentity.generateKey).mockResolvedValue(
|
||||
view({ ssh: ED, sshKeys: [ED, RSA], sshTerminal: "on" })
|
||||
);
|
||||
vi.mocked(api.gitIdentity.removeKey).mockResolvedValue(view({ ssh: ED, sshKeys: [ED] }));
|
||||
render(<GitIdentitySettings />);
|
||||
await userEvent.selectOptions(await screen.findByLabelText("Key type"), "rsa");
|
||||
// No RSA key yet: a plain Generate, no replace prompt, the ED25519 key stays.
|
||||
await userEvent.click(screen.getByRole("button", { name: "Generate SSH key" }));
|
||||
expect(confirmDialog).not.toHaveBeenCalled();
|
||||
expect(api.gitIdentity.generateKey).toHaveBeenCalledWith(false, "rsa");
|
||||
expect(await screen.findByLabelText("Public key RSA")).toHaveValue(RSA.publicKey);
|
||||
expect(screen.getByLabelText("Public key ED25519")).toHaveValue(ED.publicKey);
|
||||
expect(screen.getByRole("switch", { name: /terminal/ })).toBeChecked();
|
||||
await userEvent.click(screen.getAllByRole("button", { name: "Delete" })[1]!);
|
||||
await waitFor(() => expect(api.gitIdentity.removeKey).toHaveBeenCalledWith("rsa"));
|
||||
});
|
||||
|
||||
it("warns while the commit author is unset and saves it", async () => {
|
||||
vi.mocked(api.gitIdentity.get).mockResolvedValue(view({ author: { name: null, email: null } }));
|
||||
vi.mocked(api.gitIdentity.saveAuthor).mockResolvedValue(view());
|
||||
@@ -118,7 +144,7 @@ describe("GitIdentitySettings", () => {
|
||||
view({ account: "host", writable: false, ssh: KEY })
|
||||
);
|
||||
render(<GitIdentitySettings />);
|
||||
expect(await screen.findByLabelText("Public key")).toBeInTheDocument();
|
||||
expect(await screen.findByLabelText(/^Public key/)).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Replace key" })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Add login" })).toBeNull();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user