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:
@@ -59,11 +59,36 @@ describe("openInLayout", () => {
|
||||
expect(out.focus).toBe(3);
|
||||
});
|
||||
|
||||
it("splits the focused pane to the right on a split open", () => {
|
||||
const l: PaneLayout = { root: split("row", pane(0), pane(1)), focus: 0 };
|
||||
const out = openInLayout(l, 2, "split");
|
||||
expect(out.root).toEqual(split("row", split("row", pane(0), pane(2)), pane(1)));
|
||||
expect(out.focus).toBe(2);
|
||||
it("fills a split open into the 2×2 grid: right, under the right, under the left", () => {
|
||||
let l: PaneLayout = one;
|
||||
l = openInLayout(l, 1, "split");
|
||||
expect(l.root).toEqual(split("row", pane(0), pane(1)));
|
||||
// The focus is on the left pane: the grid order wins, not the focus.
|
||||
l = openInLayout({ ...l, focus: 0 }, 2, "split");
|
||||
expect(l.root).toEqual(split("row", pane(0), split("col", pane(1), pane(2))));
|
||||
l = openInLayout(l, 3, "split");
|
||||
expect(l.root).toEqual(
|
||||
split("row", split("col", pane(0), pane(3)), split("col", pane(1), pane(2)))
|
||||
);
|
||||
expect(l.focus).toBe(3);
|
||||
});
|
||||
|
||||
it("a split open fills the one gap of a three-pane layout, whatever its shape", () => {
|
||||
const left: PaneLayout = {
|
||||
root: split("row", split("col", pane(0), pane(1)), pane(2)),
|
||||
focus: 0,
|
||||
};
|
||||
expect(openInLayout(left, 3, "split").root).toEqual(
|
||||
split("row", split("col", pane(0), pane(1)), split("col", pane(2), pane(3)))
|
||||
);
|
||||
// A dragged divider does not move the gap.
|
||||
const wide: PaneLayout = {
|
||||
root: split("row", pane(0), split("col", pane(1), pane(2)), 0.3),
|
||||
focus: 1,
|
||||
};
|
||||
expect(openInLayout(wide, 3, "split").root).toEqual(
|
||||
split("row", split("col", pane(0), pane(3)), split("col", pane(1), pane(2)), 0.3)
|
||||
);
|
||||
});
|
||||
|
||||
it("replaces instead of splitting once MAX_PANES are shown", () => {
|
||||
@@ -226,7 +251,7 @@ describe("pane groups (tabs of up to four panes)", () => {
|
||||
for (const s of [1, 2, 3]) g = openInGroups(g, s, true);
|
||||
expect(leaves(g.groups[0]!.root)).toHaveLength(MAX_PANES);
|
||||
const out = openInGroups(g, 4, true);
|
||||
expect(out.groups.map((x) => leaves(x.root))).toEqual([[0, 1, 2, 3], [4]]);
|
||||
expect(out.groups.map((x) => leaves(x.root))).toEqual([[0, 3, 1, 2], [4]]);
|
||||
expect(out.active).toBe(1);
|
||||
});
|
||||
|
||||
@@ -384,6 +409,18 @@ describe("joinGroup", () => {
|
||||
expect(g.groups[0]?.focus).toBe(3);
|
||||
});
|
||||
|
||||
it("fills the gap under the right pane when the left one is already split", () => {
|
||||
const combo: PaneLayout = {
|
||||
root: split("row", split("col", pane(0), pane(1)), pane(2)),
|
||||
focus: 0,
|
||||
};
|
||||
const g = joinGroup({ groups: [combo, solo(3)], active: 1 }, [3], 0);
|
||||
expect(g.groups).toHaveLength(1);
|
||||
expect(g.groups[0]?.root).toEqual(
|
||||
split("row", split("col", pane(0), pane(1)), split("col", pane(2), pane(3)))
|
||||
);
|
||||
});
|
||||
|
||||
it("activates the target tab and takes a pane out of a combo", () => {
|
||||
const combo: PaneLayout = { root: split("row", pane(1), pane(2)), focus: 1 };
|
||||
const g = joinGroup({ groups: [solo(0), combo], active: 0 }, [2], 1);
|
||||
|
||||
Reference in New Issue
Block a user