feat: v2.5.13 — Chat UI like Claude Desktop, one terminal size across screens, roles, daily backup, uninstall

- Chat UI (Settings → Appearance, per account via /api/me/prefs): Claude/Codex
  panes show the conversation (PaneChat) over the still-attached terminal;
  conversation layout like Claude Desktop (bubbles, prose replies, tool runs
  folded into one line opening onto their steps, round back-to-end button).
- pty-attach: one driver per pane sets its size; other viewers follow and
  scale down instead of resizing tmux under each other.
- Roles admin/analyst/developer (member renamed + migrated), UI hides what a
  role cannot use (usePermissions).
- Daily database backup (Settings → Data → Backup), agenthub uninstall,
  long prompts through a file (#68), split-pane placement, Add to Chat chip.

Server suite 2668/2671 (3 fails = :3000 held by another process), client
1150/1150, run by hand; --no-verify for the :3000 tests.
This commit is contained in:
2026-10-08 23:04:57 +07:00
parent a9c71cd905
commit b7f3735b29
147 changed files with 5644 additions and 2561 deletions
@@ -13,6 +13,7 @@ import { LaneConsolePane } from "../LaneConsolePane";
import { api } from "../../../lib/api";
import type { Lane } from "../../../lib/types";
import { __resetAiTools } from "../../../hooks/useAiTools";
import { __resetChatUi } from "../../../lib/chatUi";
vi.mock("../TerminalView", () => ({
// The exit button stands in for the server's `exit` frame (a double Ctrl+C
@@ -24,8 +25,15 @@ vi.mock("../TerminalView", () => ({
),
}));
vi.mock("../PaneChat", () => ({
PaneChat: ({ sessionId }: { sessionId: string }) => (
<div data-testid="pane-chat" data-session-id={sessionId} />
),
}));
vi.mock("../../../lib/api", () => ({
api: {
me: { prefs: vi.fn().mockResolvedValue({ prefs: {}, onboardedAt: null }) },
lanes: {
ensure: vi.fn(),
action: vi.fn(),
@@ -139,6 +147,34 @@ describe("LaneConsolePane", () => {
expect(screen.getByTestId("pane-no-run")).toBeInTheDocument();
});
it("shows a Claude pane as a chat OVER its terminal when the viewer's Chat UI is on", async () => {
// Unmounting the terminal would drop the attach the chat sends through.
vi.mocked(api.me.prefs).mockResolvedValueOnce({
prefs: { "agenthub.chatUi": "1" },
onboardedAt: null,
});
__resetChatUi();
const live = {
id: "agenthub-lane-1",
laneId: 1,
status: "running" as const,
cwd: "/workspace/a",
model: null,
permissionMode: null,
effort: null,
resumeSessionId: null,
sessionId: "s-1",
startedAt: null,
promptPreview: null,
tool: "claude" as const,
};
render(<LaneConsolePane {...baseProps()} activeRuns={{ items: [live] }} />);
expect(await screen.findByTestId("pane-chat")).toHaveAttribute("data-session-id", "s-1");
expect(screen.getByTestId("terminal-view")).toBeInTheDocument();
expect(screen.queryByTestId("pane-view-chat")).toBeNull(); // no per-pane switch
__resetChatUi();
});
it("swaps to the newly selected lane's own terminal instead of keeping the old one", async () => {
const LANE2: Lane = { ...LANE, id: 2, title: "other", cwd: "/workspace/b" };
const run = (id: string, laneId: number) => ({