diff --git a/webui/src/components/ChatList.tsx b/webui/src/components/ChatList.tsx
index e8d4735ae..a91a074e3 100644
--- a/webui/src/components/ChatList.tsx
+++ b/webui/src/components/ChatList.tsx
@@ -865,7 +865,12 @@ export const ChatList = memo(function ChatList({
: updated.has(s.chatId) && !topicActive
? "updated"
: null;
- const canDragSession = !topicActive && !deleteSelectionMode;
+ const hasPaneMoveTarget = Boolean(onAttachPane)
+ && paneGroupTargets.some((target) => (
+ target.key !== paneGroup?.tabKey && !target.atCapacity
+ ));
+ const canDragSession = !deleteSelectionMode
+ && (!topicActive || hasPaneMoveTarget);
const actionMenuId = `session:${s.key}`;
return (
!target.atCapacity);
+ const canDragSession = !deleteSelectionMode
+ && (!active || Boolean(onDetachPane) || hasPaneMoveTarget);
const actionMenuId = `pane:${pane.key}`;
return (
diff --git a/webui/src/tests/chat-list.test.tsx b/webui/src/tests/chat-list.test.tsx
index 54f561eef..19cf74140 100644
--- a/webui/src/tests/chat-list.test.tsx
+++ b/webui/src/tests/chat-list.test.tsx
@@ -92,6 +92,8 @@ describe("ChatList", () => {
expect(screen.getByRole("button", { name: "Group: Root topic" }))
.toHaveAttribute("draggable", "false");
+ expect(screen.getByRole("button", { name: "Root topic" }))
+ .toHaveAttribute("draggable", "false");
const pane = screen.getByRole("button", { name: "Research pane" });
expect(pane).toHaveAttribute("draggable", "true");
const dataTransfer = {
@@ -145,7 +147,7 @@ describe("ChatList", () => {
fireEvent.click(await screen.findByRole("menuitem", { name: "Rename" }));
expect(onRequestRename).toHaveBeenCalledWith("websocket:child", "Research pane");
- const tabRow = screen.getByRole("button", { name: "Tab: Root topic" })
+ const tabRow = screen.getByRole("button", { name: "Group: Root topic" })
.closest("[data-workbench-tab]")!;
fireEvent.contextMenu(tabRow);
fireEvent.click(await screen.findByRole("menuitem", { name: "Dissolve group" }));
@@ -247,7 +249,7 @@ describe("ChatList", () => {
session({ chatId: "target", title: "Target group" }),
session({ chatId: "full", title: "Full group" }),
]}
- activeKey="websocket:target"
+ activeKey="websocket:solo"
paneGroups={{
"websocket:solo": {
tabKey: "tab:solo",
@@ -293,6 +295,7 @@ describe("ChatList", () => {
types: [SESSION_DRAG_TYPE],
} as unknown as DataTransfer;
const source = screen.getByRole("button", { name: "Solo topic" });
+ expect(source).toHaveAttribute("draggable", "true");
const targetGroup = screen.getByRole("button", { name: "Group: Target group" })
.closest("[data-sidebar-tab-group]")!;
const targetSurface = targetGroup.querySelector("[data-workbench-tab-surface]")!;
@@ -334,7 +337,7 @@ describe("ChatList", () => {
"websocket:root": {
tabKey: "tab:root",
title: "Root group",
- activePaneKey: "websocket:root",
+ activePaneKey: "websocket:child",
visible: true,
panes: [
{ key: "websocket:root", chatId: "root", title: "Root topic" },
@@ -367,6 +370,8 @@ describe("ChatList", () => {
types: [SESSION_DRAG_TYPE],
} as unknown as DataTransfer;
const source = screen.getByRole("button", { name: "Research pane" });
+ expect(source).toHaveAttribute("aria-current", "true");
+ expect(source).toHaveAttribute("draggable", "true");
const sourceSurface = screen.getByRole("button", { name: "Group: Root topic" })
.closest("[data-workbench-tab-surface]")!;
const standaloneList = document.querySelector("[data-chat-list-content]")!;
@@ -509,7 +514,7 @@ describe("ChatList", () => {
.toBeInTheDocument();
fireEvent.keyDown(document, { key: "Escape" });
- expect(child).toHaveAttribute("draggable", "false");
+ expect(child).toHaveAttribute("draggable", "true");
expect(screen.getByRole("button", { name: "Group: Target tab" }))
.toHaveAttribute("draggable", "false");
});
diff --git a/webui/src/tests/thread-composer.test.tsx b/webui/src/tests/thread-composer.test.tsx
index 8812301d3..c454fec50 100644
--- a/webui/src/tests/thread-composer.test.tsx
+++ b/webui/src/tests/thread-composer.test.tsx
@@ -1807,7 +1807,7 @@ describe("ThreadComposer", () => {
});
});
- it("rejects session drops that are unavailable to the composer", () => {
+ it("rejects self-session drops that are unavailable to the composer", () => {
render(