Phase 3: drag pane past window edge to detach

Extends the existing header-drag gesture (which swaps panes inside
the window) with an "outside the window" case: release the drag more
than 60px past any viewport edge and the pane detaches into a new
window via the same moveToNewWindow path the right-click menu uses.

The 60px slop avoids triggering on accidental release over the OS
titlebar / window chrome — without it any drag that ended above
clientY=0 would fire as a detach, which is wrong because that area is
still inside the user's window.

No backend changes — Phase 2's transfer mechanism already handles
everything; this just wires a second entry point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-28 18:59:48 +01:00
parent 8ad51787fc
commit 6faf7e5e19
3 changed files with 32 additions and 6 deletions

View file

@ -52,6 +52,7 @@ A Windows desktop app for running and arranging many WSL terminals at once. Buil
| Key | Action |
|---|---|
| `Right-click pane toolbar → Move to new window` | Pop the active pane into a fresh tiletopia window (PTY survives the move; scrollback ring replays) |
| `Drag pane toolbar past the window edge` | Same as the right-click action — release the drag well outside the window to detach into a new window |
**Navigation**
@ -92,7 +93,7 @@ A Windows desktop app for running and arranging many WSL terminals at once. Buil
- **SSH host manager** — Titlebar 🔑 SSH hosts opens the manager. Add hostname / user / port / identity file / jump host / extra ssh args. Saved hosts appear in every pane's dropdown.
- **Saved passwords** — Optionally save a host's password — stored in Windows Credential Manager (DPAPI-encrypted), never written to hosts.json. When ssh prompts on connect it's typed automatically. Hosts with a saved password show 🔒 in the list.
- **Clickable links** — http and https URLs in terminal output get underlined and open in your default browser on click.
- **Drag pane headers to swap** — Grab a pane's title bar and drag it onto another pane to swap their tree positions. Useful for reorganizing without keyboard.
- **Drag pane headers to swap or detach** — Grab a pane's title bar and drag onto another pane to swap their tree positions. Drag well outside the window edge (more than ~60px past) and release to detach the pane into a new window — same mechanism as the right-click 'Move to new window' action, PTY stays alive.
- **Workspace persistence** — Layout, labels, distro choices, and SSH hosts auto-save to %APPDATA%/com.megaproxy.tiletopia (debounced 500ms). Closed panes don't come back — only the structure is restored, shells spawn fresh on next launch.
- **Tabs (workspaces)** — Each tab is an independent tile layout — useful for keeping one tab per project. PTYs in non-active tabs keep running (a Claude session in tab A keeps going while you work in tab B). New tab starts with one default-shell pane; close confirms when the tab has live panes. Tabs auto-save to the same workspace.json.
- **MCP server (let Claude drive the workspace)** — Titlebar 🤖 opens the MCP control panel. Start the server, then for Claude Desktop click 'Download .mcpb' and drag the file into Settings → Extensions — zero-config because the bundle reads your bearer token from %APPDATA% at launch (no copy-paste, survives token rotation). For Claude Code (terminal CLI) use the fallback snippet in the panel: it wires npx mcp-remote as a stdio shim because Claude Code's HTTP-MCP client ignores static bearer auth and tries OAuth instead. URL + token persist across restarts; Regenerate the token in the panel if it leaks. Default-deny per pane: toggle 🤖 on each pane's toolbar to expose it to MCP.

View file

@ -268,6 +268,12 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
[orch.beginHeaderDrag, orch.setHeaderDragOver, leaf.id],
);
/** How far past a viewport edge the cursor must travel before a release
* is treated as "drag pane out of window" instead of "drop on empty
* space inside this window". Picked so an accidental release on the OS
* titlebar (~30px tall) stays inside the threshold. */
const PANE_DRAG_OUT_MARGIN = 60;
const onToolbarPointerUp = useCallback(
(e: ReactPointerEvent<HTMLDivElement>) => {
const st = dragStartRef.current;
@ -275,12 +281,26 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
const wasDragging = st.dragging;
dragStartRef.current = null;
if (wasDragging) {
document.body.style.cursor = "";
if (!wasDragging) return;
document.body.style.cursor = "";
const releasedFarOutside =
e.clientX < -PANE_DRAG_OUT_MARGIN ||
e.clientX > window.innerWidth + PANE_DRAG_OUT_MARGIN ||
e.clientY < -PANE_DRAG_OUT_MARGIN ||
e.clientY > window.innerHeight + PANE_DRAG_OUT_MARGIN;
if (releasedFarOutside) {
// Cancel any in-flight swap state without committing, then pop
// this pane into a fresh window. moveToNewWindow handles the
// PTY-handoff + closeLeaf in the source.
orch.endHeaderDrag(false);
orch.moveToNewWindow(leaf.id);
} else {
orch.endHeaderDrag(true);
}
},
[orch.endHeaderDrag],
[orch.endHeaderDrag, orch.moveToNewWindow, leaf.id],
);
const onToolbarPointerCancel = useCallback(

View file

@ -53,6 +53,11 @@ export const SHORTCUT_SECTIONS: ShortcutSection[] = [
description:
"Pop the active pane into a fresh tiletopia window (PTY survives the move; scrollback ring replays)",
},
{
keys: "Drag pane toolbar past the window edge",
description:
"Same as the right-click action — release the drag well outside the window to detach into a new window",
},
],
},
{
@ -126,8 +131,8 @@ export const TIPS: TipSpec[] = [
body: "http and https URLs in terminal output get underlined and open in your default browser on click.",
},
{
title: "Drag pane headers to swap",
body: "Grab a pane's title bar and drag it onto another pane to swap their tree positions. Useful for reorganizing without keyboard.",
title: "Drag pane headers to swap or detach",
body: "Grab a pane's title bar and drag onto another pane to swap their tree positions. Drag well outside the window edge (more than ~60px past) and release to detach the pane into a new window — same mechanism as the right-click 'Move to new window' action, PTY stays alive.",
},
{
title: "Workspace persistence",