diff --git a/dev-dashboard/frontend/src/App.svelte b/dev-dashboard/frontend/src/App.svelte index edbc6fe918..d097ecdb3d 100644 --- a/dev-dashboard/frontend/src/App.svelte +++ b/dev-dashboard/frontend/src/App.svelte @@ -80,10 +80,52 @@ } } + function selectNeighborWorktree(direction: -1 | 1) { + const selectable = visibleWorktrees.filter( + (w) => w.path !== "(here)" && w.branch !== "main" && !removingBranches.has(w.branch) + ); + if (selectable.length === 0) return; + if (!selectedBranch) { + selectedBranch = selectable[direction === 1 ? 0 : selectable.length - 1].branch; + return; + } + const idx = selectable.findIndex((w) => w.branch === selectedBranch); + const next = idx + direction; + if (next >= 0 && next < selectable.length) { + selectedBranch = selectable[next].branch; + } + } + + function handleKeydown(e: KeyboardEvent) { + // Ignore shortcuts when a dialog is open (let dialog handle its own keys) + if (showCreateDialog || removeBranch) return; + + const mod = e.metaKey || e.ctrlKey; + if (!mod) return; + + if (e.key === "ArrowUp") { + e.preventDefault(); + selectNeighborWorktree(-1); + } else if (e.key === "ArrowDown") { + e.preventDefault(); + selectNeighborWorktree(1); + } else if (e.key === "k" || e.key === "K") { + e.preventDefault(); + if (!creating) showCreateDialog = true; + } else if (e.key === "d" || e.key === "D") { + e.preventDefault(); + if (selectedBranch && !isMain) removeBranch = selectedBranch; + } + } + onMount(() => { refresh(); const interval = setInterval(refresh, 5000); - return () => clearInterval(interval); + window.addEventListener("keydown", handleKeydown); + return () => { + clearInterval(interval); + window.removeEventListener("keydown", handleKeydown); + }; }); @@ -95,10 +137,15 @@ class="h-8 px-2 gap-1.5 rounded-md border border-edge bg-surface text-accent text-xs flex items-center justify-center cursor-pointer hover:bg-hover disabled:opacity-50 disabled:cursor-not-allowed" onclick={() => (showCreateDialog = true)} disabled={creating} - title="New Worktree" + title="New Worktree (Cmd+K)" >+ New (selectedBranch = b)} onremove={(b) => (removeBranch = b)} /> +
+
NavigateCmd+Up/Down
+
New worktreeCmd+K
+
RemoveCmd+D
+
diff --git a/dev-dashboard/frontend/src/lib/Terminal.svelte b/dev-dashboard/frontend/src/lib/Terminal.svelte index 6a975858f6..de2b458e3e 100644 --- a/dev-dashboard/frontend/src/lib/Terminal.svelte +++ b/dev-dashboard/frontend/src/lib/Terminal.svelte @@ -56,6 +56,17 @@ } }); + // Let app-level shortcuts (Cmd+Arrow, Cmd+N, Cmd+D) bubble up instead of + // being consumed by xterm. Return false → xterm ignores the event. + term.attachCustomKeyEventHandler((e: KeyboardEvent) => { + if (e.type !== "keydown") return true; + const mod = e.metaKey || e.ctrlKey; + if (mod && (e.key === "ArrowUp" || e.key === "ArrowDown")) return false; + if (mod && (e.key === "k" || e.key === "K")) return false; + if (mod && (e.key === "d" || e.key === "D")) return false; + return true; + }); + requestAnimationFrame(() => fitAddon.fit()); const protocol = location.protocol === "https:" ? "wss:" : "ws:";