Skip to content

feat(desktop): wire up v2 workspace hotkeys#3190

Merged
saddlepaddle merged 1 commit into
mainfrom
satya/v2-hotkeys
Apr 5, 2026
Merged

feat(desktop): wire up v2 workspace hotkeys#3190
saddlepaddle merged 1 commit into
mainfrom
satya/v2-hotkeys

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 5, 2026

Summary

  • Add useV2WorkspaceHotkeys hook with tab management (close, prev/next, jump 1-9), pane management (prev/next, split auto/right/down/chat/browser, equalize), and tab creation (new terminal/chat/browser)
  • Add prev/next workspace navigation (⌘⌥↑/↓) to useDashboardSidebarShortcuts with cycling (wraps around)
  • OPEN_IN_APP already handled by shared OpenInMenuButton in TopBar

Not ported (v2 features not built yet):

  • REOPEN_TAB — needs tab history tracking
  • RUN_WORKSPACE_COMMAND — not built for v2
  • COPY_PATH / OPEN_PR — need host service workspace data
  • TOGGLE_EXPAND_SIDEBAR — v2 sidebar doesn't have expand/collapse modes
  • OPEN_PRESET_1..9 — presets use v1 tab system

Test plan

  • ⌘T / ⌘⇧T / ⌘⇧B — new terminal/chat/browser tabs
  • ⌘W — close pane, ⌘⇧W — close tab
  • ⌘⌥←/→ — prev/next tab, ctrl+tab/ctrl+shift+tab — alt tab cycling
  • ⌘⌥1-9 — jump to tab by index
  • ⌘⇧←/→ — prev/next pane
  • ⌘D / ⌘⇧D / ⌘E — split right/down/auto
  • ⌘⇧E / ⌘⇧S — split with chat/browser
  • ⌘⇧0 — equalize pane splits
  • ⌘⌥↑/↓ — prev/next workspace (cycles)

Summary by cubic

Wired up all v2 workspace hotkeys for full keyboard control of tabs, panes, sidebar, and workspace navigation. Integrated useWorkspaceHotkeys in the v2 workspace and added wrap-around prev/next workspace switching via useDashboardSidebarShortcuts.

  • New Features

    • Hook useWorkspaceHotkeys for v2 workspaces; adds right sidebar toggle.
    • Tabs: create terminal/chat/browser; close tab/pane; prev/next (incl. alt-tab); jump to 1–9.
    • Panes: prev/next; split auto/right/down or with chat/browser; equalize.
    • Sidebar: prev/next workspace navigation with wrap-around.
  • Refactors

    • Centralized hotkey wiring into useWorkspaceHotkeys and useDashboardSidebarShortcuts; removed inline handlers from the v2 workspace page.

Written for commit f80eb46. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added workspace navigation hotkeys to jump to previous/next workspace.
    • Added workspace-scoped hotkeys for tab and pane management: create new tabs (terminal/chat/browser), close tabs/panes, switch between tabs, jump to numbered tabs, cycle pane focus, split panes in various orientations, and equalize split layouts.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

Centralizes workspace-scoped keyboard shortcuts into a new useWorkspaceHotkeys hook and adds dashboard-level hotkeys to cycle to the previous/next workspace by matching the current route and navigating accordingly.

Changes

Cohort / File(s) Summary
Dashboard Sidebar Navigation
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts
Added route-matching via useMatchRoute to resolve currentWorkspaceId and implemented PREV_WORKSPACE / NEXT_WORKSPACE hotkeys that compute the previous/next index in flattenedWorkspaces (with wrap-around) and call navigateToV2Workspace.
Workspace Hotkey Consolidation
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/index.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts
Removed local hotkey registrations from page.tsx and introduced useWorkspaceHotkeys (re-exported via index) that registers workspace-scoped shortcuts: sidebar toggle, tab creation (terminal/chat/browser), tab navigation/close, pane focus cycling, splits, and equalize split operations using store.getState() and workspace local state.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Hotkey as HotkeyHandler
    participant Router as Router/useMatchRoute
    participant Nav as navigateToV2Workspace
    participant Store as WorkspaceList

    rect rgba(200,200,255,0.5)
    User->>Hotkey: Press NEXT_WORKSPACE / PREV_WORKSPACE
    Hotkey->>Router: useMatchRoute -> resolve currentWorkspaceId
    Hotkey->>Store: read flattenedWorkspaces
    Hotkey->>Hotkey: compute next/prev index (wrap)
    Hotkey->>Nav: navigateToV2Workspace(nextWorkspaceId)
    Nav-->>Router: update route
    end
Loading
sequenceDiagram
    participant User as User
    participant Hotkey as useWorkspaceHotkeys
    participant Store as WorkspaceStore
    participant Pane as PaneManager

    rect rgba(200,255,200,0.5)
    User->>Hotkey: Press workspace hotkey (e.g., NEW_TERMINAL, SPLIT_RIGHT)
    Hotkey->>Store: store.getState() (tabs, activeTabId, panes)
    Hotkey->>Pane: addTab / splitPane / removeActiveTab / focusPane / equalizeSplit
    Pane-->>Store: update state
    Hotkey-->>User: UI updates (new tab/pane/focus)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through keys, a merry spree,
Hotkeys stitched where they ought to be,
Workspaces spin with a nimble twirl,
Tabs split and jump — a fluffy whirl!
Binky stamps two paws: "Looks tidy to me!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the primary change: wiring up v2 workspace hotkeys, matching the changeset's focus on keyboard shortcut implementation.
Description check ✅ Passed The PR description is comprehensive, covering summary, rationale, test plan, and implementation details aligned with the template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch satya/v2-hotkeys

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@saddlepaddle saddlepaddle force-pushed the satya/v2-hotkeys branch 2 times, most recently from c345f3a to a75b368 Compare April 5, 2026 20:28
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 5, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx:227">
P1: The new workspace hotkey hook enables `CLOSE_TAB` via direct `removeTab`, which bypasses the unsaved-changes confirmation flow (`onBeforeCloseTab`) and can drop dirty edits.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts:152">
P2: `SPLIT_AUTO` is wired as a fixed right split, so the auto-split shortcut does not match its intended behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

});
}, [collections, workspaceId]);

useV2WorkspaceHotkeys({ store });
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The new workspace hotkey hook enables CLOSE_TAB via direct removeTab, which bypasses the unsaved-changes confirmation flow (onBeforeCloseTab) and can drop dirty edits.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx, line 227:

<comment>The new workspace hotkey hook enables `CLOSE_TAB` via direct `removeTab`, which bypasses the unsaved-changes confirmation flow (`onBeforeCloseTab`) and can drop dirty edits.</comment>

<file context>
@@ -223,10 +224,8 @@ function WorkspaceContent({
 		});
 	}, [collections, workspaceId]);
 
+	useV2WorkspaceHotkeys({ store });
 	useHotkey("TOGGLE_SIDEBAR", toggleSidebar);
-	useHotkey("NEW_GROUP", addTerminalTab);
</file context>
Fix with Cubic

state.splitPane({
tabId: active.tabId,
paneId: active.pane.id,
position: "right",
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: SPLIT_AUTO is wired as a fixed right split, so the auto-split shortcut does not match its intended behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts, line 152:

<comment>`SPLIT_AUTO` is wired as a fixed right split, so the auto-split shortcut does not match its intended behavior.</comment>

<file context>
@@ -0,0 +1,231 @@
+		state.splitPane({
+			tabId: active.tabId,
+			paneId: active.pane.id,
+			position: "right",
+			newPane: {
+				kind: "terminal",
</file context>
Fix with Cubic

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 5, 2026

Greptile Summary

This PR wires up the full set of v2 workspace hotkeys — tab creation (⌘T/⌘⇧T/⌘⇧B), tab management (⌘W/⌘⇧W, cycling, jump-to-index), pane management (split, equalize, prev/next), and workspace cycling (⌘⌥↑/↓). The majority of handlers are correctly implemented and consistent with the hotkey registry.

One functional bug stands out: SPLIT_AUTO (⌘E) is a verbatim copy of SPLIT_RIGHT (⌘D) — both hardcode position: 'right' — despite the registry describing SPLIT_AUTO as "Split the current pane along its longer side". getPaneDimensions() already exists in the pane-refs infrastructure for exactly this purpose but is not called. Two smaller issues round out the review: PREV_WORKSPACE does not guard index === -1 consistently with NEXT_WORKSPACE, and the browser default URL http://localhost:3000 is hardcoded in two handlers rather than extracted to a constant.

  • useV2WorkspaceHotkeys wires 20+ hotkeys spanning tab creation, tab navigation, pane cycling, and splitting
  • useDashboardSidebarShortcuts gains PREV_WORKSPACE / NEXT_WORKSPACE cycling with wrap-around logic
  • Bug: SPLIT_AUTO (⌘E) is functionally identical to SPLIT_RIGHT (⌘D) — dimension-aware direction selection is entirely missing
  • Minor: PREV_WORKSPACE edge case when the current workspace is absent from flattenedWorkspaces is asymmetric with NEXT_WORKSPACE
  • Minor: "http://localhost:3000" is hardcoded in two handlers; a shared constant is preferable

Confidence Score: 4/5

Safe to merge after fixing SPLIT_AUTO to use dimension-aware direction detection.

The vast majority of hotkeys (19 out of ~21) are correctly implemented and consistent with the registry. The SPLIT_AUTO bug makes ⌘E a redundant alias for ⌘D rather than causing data loss or security risk. The PREV_WORKSPACE -1 edge case is unlikely in practice due to the outer guard. Score is 4 rather than 5 because one advertised feature (auto-split direction) demonstrably does not work.

useV2WorkspaceHotkeys.ts lines 145-158 (SPLIT_AUTO handler) needs dimension-aware position logic before the feature is complete.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts Core v2 workspace hotkey hook — 20+ hotkeys wired correctly, but SPLIT_AUTO duplicates SPLIT_RIGHT instead of detecting pane dimensions, and two browser handlers hardcode http://localhost:3000
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts Adds PREV/NEXT_WORKSPACE cycling with wrap-around; PREV_WORKSPACE lacks explicit index===-1 guard present in NEXT_WORKSPACE, causing asymmetric edge-case behavior
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/index.ts Simple barrel export for useV2WorkspaceHotkeys; no issues
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx Correctly mounts useV2WorkspaceHotkeys hook alongside TOGGLE_SIDEBAR and QUICK_OPEN; no issues introduced

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Key press] --> B{Hotkey}
    B -->|⌘T| C[addTab terminal]
    B -->|⌘⇧T| D[addTab chat]
    B -->|⌘⇧B| E[addTab browser]
    B -->|⌘W| F[closePane active pane]
    B -->|⌘⇧W| G[removeTab activeTabId]
    B -->|⌘⌥← / ⌘⌥→| H[setActiveTab prev/next with wrap]
    B -->|⌘⌥1-9| I[setActiveTab by index]
    B -->|⌘⇧← / ⌘⇧→| J[setActivePane prev/next with wrap]
    B -->|⌘E SPLIT_AUTO| K[splitPane - position right - BUG]
    B -->|⌘D SPLIT_RIGHT| L[splitPane - position right]
    K -.same as.- L
    B -->|⌘⇧D SPLIT_DOWN| M[splitPane - position down]
    B -->|⌘⇧E / ⌘⇧S| N[splitPane - right - chat or browser]
    B -->|⌘⇧0| O[equalizeSplit top-level layout]
    B -->|⌘⌥↑ PREV_WORKSPACE| P[navigate prevIndex - no -1 guard]
    B -->|⌘⌥↓ NEXT_WORKSPACE| Q[navigate nextIndex - has -1 guard]
Loading

Comments Outside Diff (2)

  1. apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts, line 145-158 (link)

    P1 SPLIT_AUTO is identical to SPLIT_RIGHT

    Both handlers hardcode position: "right", but the hotkey registry (registry.ts) describes SPLIT_AUTO as "Split the current pane along its longer side" — meaning it should detect the pane's dimensions and choose "right" when width ≥ height or "down" when height > width. That logic is entirely absent here.

    The helper getPaneDimensions(paneId) already exists in the pane-refs infrastructure and was purpose-built for exactly this use case. Without using it, ⌘E becomes a duplicate of ⌘D and the "auto" behavior promised to users is never delivered.

    useHotkey("SPLIT_AUTO", () => {
    	const state = store.getState();
    	const active = state.getActivePane();
    	if (!active) return;
    	const dims = getPaneDimensions(active.pane.id);
    	const position = dims && dims.height > dims.width ? "down" : "right";
    	state.splitPane({
    		tabId: active.tabId,
    		paneId: active.pane.id,
    		position,
    		newPane: {
    			kind: "terminal",
    			data: { terminalId: crypto.randomUUID() } as TerminalPaneData,
    		},
    	});
    });
  2. apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts, line 44-50 (link)

    P2 Hardcoded "http://localhost:3000" in two places

    The same URL literal appears again at line ~215 inside the SPLIT_WITH_BROWSER handler. Extracting it to a named constant (e.g. const DEFAULT_BROWSER_URL = "http://localhost:3000") would make the intent explicit, prevent accidental drift between the two occurrences, and allow a single-point update.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "feat(desktop): wire up missing hotkeys f..." | Re-trigger Greptile

Comment on lines +60 to +67
useHotkey("PREV_WORKSPACE", () => {
if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
const index = flattenedWorkspaces.findIndex(
(w) => w.id === currentWorkspaceId,
);
const prevIndex = index <= 0 ? flattenedWorkspaces.length - 1 : index - 1;
navigateToV2Workspace(flattenedWorkspaces[prevIndex].id, navigate);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 PREV_WORKSPACE doesn't guard index === -1

When currentWorkspaceId is set but the workspace is absent from flattenedWorkspaces (e.g. it has creationStatus and is filtered out), findIndex returns -1. Since -1 <= 0 is true, prevIndex silently wraps to flattenedWorkspaces.length - 1 (the last workspace).

NEXT_WORKSPACE (line 75) explicitly handles this with || index === -1, routing to index 0. The inconsistency means PREV navigates to the last workspace while NEXT navigates to the first on the same edge case — asymmetric and potentially surprising.

Consider a no-op guard for consistency:

Suggested change
useHotkey("PREV_WORKSPACE", () => {
if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
const index = flattenedWorkspaces.findIndex(
(w) => w.id === currentWorkspaceId,
);
const prevIndex = index <= 0 ? flattenedWorkspaces.length - 1 : index - 1;
navigateToV2Workspace(flattenedWorkspaces[prevIndex].id, navigate);
});
useHotkey("PREV_WORKSPACE", () => {
if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
const index = flattenedWorkspaces.findIndex(
(w) => w.id === currentWorkspaceId,
);
if (index === -1) return;
const prevIndex = index <= 0 ? flattenedWorkspaces.length - 1 : index - 1;
navigateToV2Workspace(flattenedWorkspaces[prevIndex].id, navigate);
});

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts (1)

70-102: Deduplicate tab-cycling handlers to reduce drift risk.

Line 70-102 repeats the same index/wrap logic four times (PREV_TAB, NEXT_TAB, PREV_TAB_ALT, NEXT_TAB_ALT). A shared helper will keep behavior aligned across all variants.

Refactor sketch
+const cycleTab = useCallback(
+	(step: -1 | 1) => {
+		const state = store.getState();
+		if (!state.activeTabId || state.tabs.length === 0) return;
+		const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
+		if (index === -1) return;
+		const next = (index + step + state.tabs.length) % state.tabs.length;
+		state.setActiveTab(state.tabs[next].id);
+	},
+	[store],
+);
+
-useHotkey("PREV_TAB", () => {
-	const state = store.getState();
-	if (!state.activeTabId || state.tabs.length === 0) return;
-	const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
-	const prevIndex = index <= 0 ? state.tabs.length - 1 : index - 1;
-	state.setActiveTab(state.tabs[prevIndex].id);
-});
+useHotkey("PREV_TAB", () => cycleTab(-1));

-useHotkey("NEXT_TAB", () => {
-	const state = store.getState();
-	if (!state.activeTabId || state.tabs.length === 0) return;
-	const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
-	const nextIndex =
-		index >= state.tabs.length - 1 || index === -1 ? 0 : index + 1;
-	state.setActiveTab(state.tabs[nextIndex].id);
-});
+useHotkey("NEXT_TAB", () => cycleTab(1));

-useHotkey("PREV_TAB_ALT", () => {
-	const state = store.getState();
-	if (!state.activeTabId || state.tabs.length === 0) return;
-	const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
-	const prevIndex = index <= 0 ? state.tabs.length - 1 : index - 1;
-	state.setActiveTab(state.tabs[prevIndex].id);
-});
+useHotkey("PREV_TAB_ALT", () => cycleTab(-1));

-useHotkey("NEXT_TAB_ALT", () => {
-	const state = store.getState();
-	if (!state.activeTabId || state.tabs.length === 0) return;
-	const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
-	const nextIndex =
-		index >= state.tabs.length - 1 || index === -1 ? 0 : index + 1;
-	state.setActiveTab(state.tabs[nextIndex].id);
-});
+useHotkey("NEXT_TAB_ALT", () => cycleTab(1));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts
around lines 70 - 102, The tab-cycling hotkey handlers duplicate the same
index/find/wrap logic across useHotkey calls (PREV_TAB, NEXT_TAB, PREV_TAB_ALT,
NEXT_TAB_ALT); extract a small helper (e.g., computeWrappedTabId or
changeActiveTab(direction)) that calls store.getState(), finds the current index
via tabs.findIndex, computes the wrapped prev/next index, and calls
state.setActiveTab(...) so each useHotkey simply calls that helper with a
direction enum or ±1; update all four handlers to reuse the helper to keep
behavior consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts`:
- Around line 60-76: The hotkey handlers in useDashboardSidebarShortcuts (the
PREV_WORKSPACE and NEXT_WORKSPACE callbacks) call findIndex on
flattenedWorkspaces which can return -1; add an explicit check for index === -1
and return early (no-op) before computing prevIndex/nextIndex and calling
navigateToV2Workspace so we don't unexpectedly jump when the currentWorkspaceId
is not present in flattenedWorkspaces; update both handlers (the callbacks using
currentWorkspaceId, flattenedWorkspaces, findIndex, and navigateToV2Workspace)
to perform this guard.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts:
- Around line 145-157: The SPLIT_AUTO hotkey currently always passes position:
"right", so it mirrors SPLIT_RIGHT; change the handler in useV2WorkspaceHotkeys
(the useHotkey("SPLIT_AUTO", ...) callback) to compute the split position the
same way the pane action logic does in page.tsx (look at the auto-split decision
around the pane action handling) and pass that computed position to
state.splitPane rather than the hard-coded "right"; keep the newPane payload and
terminal creation the same and reuse the same decision function/logic (or inline
the same checks) so SPLIT_AUTO truly auto-selects left/right based on active
pane layout.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts:
- Around line 70-102: The tab-cycling hotkey handlers duplicate the same
index/find/wrap logic across useHotkey calls (PREV_TAB, NEXT_TAB, PREV_TAB_ALT,
NEXT_TAB_ALT); extract a small helper (e.g., computeWrappedTabId or
changeActiveTab(direction)) that calls store.getState(), finds the current index
via tabs.findIndex, computes the wrapped prev/next index, and calls
state.setActiveTab(...) so each useHotkey simply calls that helper with a
direction enum or ±1; update all four handlers to reuse the helper to keep
behavior consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a1082389-0fde-4fc4-b54e-bf6935c6ddc3

📥 Commits

Reviewing files that changed from the base of the PR and between 1219200 and a75b368.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx

Comment on lines +60 to +76
useHotkey("PREV_WORKSPACE", () => {
if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
const index = flattenedWorkspaces.findIndex(
(w) => w.id === currentWorkspaceId,
);
const prevIndex = index <= 0 ? flattenedWorkspaces.length - 1 : index - 1;
navigateToV2Workspace(flattenedWorkspaces[prevIndex].id, navigate);
});

useHotkey("NEXT_WORKSPACE", () => {
if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
const index = flattenedWorkspaces.findIndex(
(w) => w.id === currentWorkspaceId,
);
const nextIndex =
index >= flattenedWorkspaces.length - 1 || index === -1 ? 0 : index + 1;
navigateToV2Workspace(flattenedWorkspaces[nextIndex].id, navigate);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle missing workspace index explicitly before cycling.

On Line 62 / Line 71, findIndex can return -1 (e.g., current route workspace is filtered out of flattenedWorkspaces). Current logic then navigates to last/first workspace (Line 65 / Line 75), which is an unexpected jump. Prefer a no-op when index === -1.

Suggested patch
 useHotkey("PREV_WORKSPACE", () => {
 	if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
 	const index = flattenedWorkspaces.findIndex(
 		(w) => w.id === currentWorkspaceId,
 	);
+	if (index === -1) return;
 	const prevIndex = index <= 0 ? flattenedWorkspaces.length - 1 : index - 1;
-	navigateToV2Workspace(flattenedWorkspaces[prevIndex].id, navigate);
+	void navigateToV2Workspace(flattenedWorkspaces[prevIndex].id, navigate);
 });

 useHotkey("NEXT_WORKSPACE", () => {
 	if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
 	const index = flattenedWorkspaces.findIndex(
 		(w) => w.id === currentWorkspaceId,
 	);
+	if (index === -1) return;
 	const nextIndex =
-		index >= flattenedWorkspaces.length - 1 || index === -1 ? 0 : index + 1;
-	navigateToV2Workspace(flattenedWorkspaces[nextIndex].id, navigate);
+		index >= flattenedWorkspaces.length - 1 ? 0 : index + 1;
+	void navigateToV2Workspace(flattenedWorkspaces[nextIndex].id, navigate);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts`
around lines 60 - 76, The hotkey handlers in useDashboardSidebarShortcuts (the
PREV_WORKSPACE and NEXT_WORKSPACE callbacks) call findIndex on
flattenedWorkspaces which can return -1; add an explicit check for index === -1
and return early (no-op) before computing prevIndex/nextIndex and calling
navigateToV2Workspace so we don't unexpectedly jump when the currentWorkspaceId
is not present in flattenedWorkspaces; update both handlers (the callbacks using
currentWorkspaceId, flattenedWorkspaces, findIndex, and navigateToV2Workspace)
to perform this guard.

Comment on lines +145 to +157
useHotkey("SPLIT_AUTO", () => {
const state = store.getState();
const active = state.getActivePane();
if (!active) return;
state.splitPane({
tabId: active.tabId,
paneId: active.pane.id,
position: "right",
newPane: {
kind: "terminal",
data: { terminalId: crypto.randomUUID() } as TerminalPaneData,
},
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

SPLIT_AUTO is not auto-splitting right now.

Line 152 hard-codes "right", so SPLIT_AUTO behaves the same as SPLIT_RIGHT. This diverges from the expected auto behavior (and from the existing pane action logic in apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx, Line 198-200).

Suggested patch
 useHotkey("SPLIT_AUTO", () => {
 	const state = store.getState();
 	const active = state.getActivePane();
 	if (!active) return;
+	const position =
+		active.pane.parentDirection === "horizontal" ? "down" : "right";
 	state.splitPane({
 		tabId: active.tabId,
 		paneId: active.pane.id,
-		position: "right",
+		position,
 		newPane: {
 			kind: "terminal",
 			data: { terminalId: crypto.randomUUID() } as TerminalPaneData,
 		},
 	});
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useHotkey("SPLIT_AUTO", () => {
const state = store.getState();
const active = state.getActivePane();
if (!active) return;
state.splitPane({
tabId: active.tabId,
paneId: active.pane.id,
position: "right",
newPane: {
kind: "terminal",
data: { terminalId: crypto.randomUUID() } as TerminalPaneData,
},
});
useHotkey("SPLIT_AUTO", () => {
const state = store.getState();
const active = state.getActivePane();
if (!active) return;
const position =
active.pane.parentDirection === "horizontal" ? "down" : "right";
state.splitPane({
tabId: active.tabId,
paneId: active.pane.id,
position,
newPane: {
kind: "terminal",
data: { terminalId: crypto.randomUUID() } as TerminalPaneData,
},
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useV2WorkspaceHotkeys/useV2WorkspaceHotkeys.ts
around lines 145 - 157, The SPLIT_AUTO hotkey currently always passes position:
"right", so it mirrors SPLIT_RIGHT; change the handler in useV2WorkspaceHotkeys
(the useHotkey("SPLIT_AUTO", ...) callback) to compute the split position the
same way the pane action logic does in page.tsx (look at the auto-split decision
around the pane action handling) and pass that computed position to
state.splitPane rather than the hard-coded "right"; keep the newPane payload and
terminal creation the same and reuse the same decision function/logic (or inline
the same checks) so SPLIT_AUTO truly auto-selects left/right based on active
pane layout.

Add useV2WorkspaceHotkeys hook with:
- Tab management: close tab/pane, prev/next tab, jump to tab 1-9
- Pane management: prev/next pane, split auto/right/down/chat/browser, equalize splits
- Workspace navigation: prev/next workspace
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts (3)

50-62: Hardcoded localhost URL for browser panes.

The URL http://localhost:3000 is hardcoded for new browser tabs and split-with-browser operations. If this is intentional for local development preview, consider extracting it to a constant or configuration to make the intent clearer and allow easier updates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts
around lines 50 - 62, Replace the hardcoded "http://localhost:3000" used when
creating browser panes with a single source of truth: introduce a constant or
config entry (e.g., BROWSER_DEFAULT_URL) and use it wherever browser panes are
created; update the useHotkey("NEW_BROWSER") handler and any split-with-browser
code paths to reference that constant (respecting the BrowserPaneData shape) so
the default URL is configurable and not baked into useWorkspaceHotkeys or addTab
calls.

67-73: Hotkey name doesn't match behavior.

CLOSE_TERMINAL closes any active pane (terminal, chat, or browser), not specifically terminals. If this is intentional reuse of an existing hotkey binding, the behavior is fine. Otherwise, consider renaming to CLOSE_PANE or adding a pane type check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts
around lines 67 - 73, The hotkey binding registered via
useHotkey("CLOSE_TERMINAL", ...) closes any active pane regardless of type;
either rename the hotkey to "CLOSE_PANE" to reflect current behavior or add a
pane type check before calling state.closePane (use
store.getState().getActivePane() and inspect active.pane.type or similar) so
only terminal panes are closed; update the hotkey string and any
documentation/usages if renaming, or implement the conditional guard around
state.closePane to restrict closure to terminal panes.

82-114: Duplicate logic for tab navigation hotkeys.

PREV_TAB/PREV_TAB_ALT and NEXT_TAB/NEXT_TAB_ALT have identical implementations. Extract the navigation logic into helper functions to reduce duplication.

♻️ Proposed refactor
+	const navigateToPrevTab = useCallback(() => {
+		const state = store.getState();
+		if (!state.activeTabId || state.tabs.length === 0) return;
+		const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
+		const prevIndex = index <= 0 ? state.tabs.length - 1 : index - 1;
+		state.setActiveTab(state.tabs[prevIndex].id);
+	}, [store]);
+
+	const navigateToNextTab = useCallback(() => {
+		const state = store.getState();
+		if (!state.activeTabId || state.tabs.length === 0) return;
+		const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
+		const nextIndex =
+			index >= state.tabs.length - 1 || index === -1 ? 0 : index + 1;
+		state.setActiveTab(state.tabs[nextIndex].id);
+	}, [store]);
+
-	useHotkey("PREV_TAB", () => {
-		const state = store.getState();
-		if (!state.activeTabId || state.tabs.length === 0) return;
-		const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
-		const prevIndex = index <= 0 ? state.tabs.length - 1 : index - 1;
-		state.setActiveTab(state.tabs[prevIndex].id);
-	});
-
-	useHotkey("NEXT_TAB", () => {
-		const state = store.getState();
-		if (!state.activeTabId || state.tabs.length === 0) return;
-		const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
-		const nextIndex =
-			index >= state.tabs.length - 1 || index === -1 ? 0 : index + 1;
-		state.setActiveTab(state.tabs[nextIndex].id);
-	});
-
-	useHotkey("PREV_TAB_ALT", () => {
-		const state = store.getState();
-		if (!state.activeTabId || state.tabs.length === 0) return;
-		const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
-		const prevIndex = index <= 0 ? state.tabs.length - 1 : index - 1;
-		state.setActiveTab(state.tabs[prevIndex].id);
-	});
-
-	useHotkey("NEXT_TAB_ALT", () => {
-		const state = store.getState();
-		if (!state.activeTabId || state.tabs.length === 0) return;
-		const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
-		const nextIndex =
-			index >= state.tabs.length - 1 || index === -1 ? 0 : index + 1;
-		state.setActiveTab(state.tabs[nextIndex].id);
-	});
+	useHotkey("PREV_TAB", navigateToPrevTab);
+	useHotkey("NEXT_TAB", navigateToNextTab);
+	useHotkey("PREV_TAB_ALT", navigateToPrevTab);
+	useHotkey("NEXT_TAB_ALT", navigateToNextTab);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts
around lines 82 - 114, The four hotkey handlers (useHotkey("PREV_TAB"),
"PREV_TAB_ALT", "NEXT_TAB", "NEXT_TAB_ALT") duplicate the same tab navigation
logic; extract the logic into two small helpers (e.g., getPrevTabId(state) and
getNextTabId(state) or navigateToPrevTab(store)/navigateToNextTab(store)) that
compute the target tab id (handling index === -1 and wrap-around) and then call
state.setActiveTab(id); replace each handler body with a call to the appropriate
helper to remove duplication while keeping state.setActiveTab as the single
setter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts:
- Around line 157-185: The SPLIT_AUTO handler currently duplicates SPLIT_RIGHT;
update the useHotkey("SPLIT_AUTO", ...) callback so it computes the split
direction dynamically instead of hardcoding "right": obtain the active pane via
store.getState().getActivePane(), read its dimensions (e.g., active.pane.width
and active.pane.height or comparable layout metrics), decide direction (e.g.,
choose "right" if width > height else "bottom"), then call state.splitPane with
that computed position; leave useHotkey("SPLIT_RIGHT", ...) as-is or
remove/rename one of the handlers if auto-behavior is not desired. Ensure you
reference the existing functions/objects state.splitPane, store.getState(),
getActivePane, and the "SPLIT_AUTO"/"SPLIT_RIGHT" hotkey identifiers when making
the change.
- Around line 137-155: The PREV_PANE and NEXT_PANE hotkey handlers use
Object.keys(tab.panes) (in useHotkey callbacks) which follows insertion order
instead of visual layout order; change each handler to call the existing
utilities getPreviousPaneId(tab.layout, tab.activePaneId) and
getNextPaneId(tab.layout, tab.activePaneId) (respectively) obtained from the
utils module, check the returned paneId exists, and then call
state.setActivePane({ tabId: tab.id, paneId: returnedId }) — remove the manual
index/wrapping logic and use tab.layout and those util functions to determine
the next/previous pane.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts:
- Around line 50-62: Replace the hardcoded "http://localhost:3000" used when
creating browser panes with a single source of truth: introduce a constant or
config entry (e.g., BROWSER_DEFAULT_URL) and use it wherever browser panes are
created; update the useHotkey("NEW_BROWSER") handler and any split-with-browser
code paths to reference that constant (respecting the BrowserPaneData shape) so
the default URL is configurable and not baked into useWorkspaceHotkeys or addTab
calls.
- Around line 67-73: The hotkey binding registered via
useHotkey("CLOSE_TERMINAL", ...) closes any active pane regardless of type;
either rename the hotkey to "CLOSE_PANE" to reflect current behavior or add a
pane type check before calling state.closePane (use
store.getState().getActivePane() and inspect active.pane.type or similar) so
only terminal panes are closed; update the hotkey string and any
documentation/usages if renaming, or implement the conditional guard around
state.closePane to restrict closure to terminal panes.
- Around line 82-114: The four hotkey handlers (useHotkey("PREV_TAB"),
"PREV_TAB_ALT", "NEXT_TAB", "NEXT_TAB_ALT") duplicate the same tab navigation
logic; extract the logic into two small helpers (e.g., getPrevTabId(state) and
getNextTabId(state) or navigateToPrevTab(store)/navigateToNextTab(store)) that
compute the target tab id (handling index === -1 and wrap-around) and then call
state.setActiveTab(id); replace each handler body with a call to the appropriate
helper to remove duplication while keeping state.setActiveTab as the single
setter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 379aa61f-6429-4aba-8b2b-d3c62771e41f

📥 Commits

Reviewing files that changed from the base of the PR and between a75b368 and 91011e9.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts

Comment on lines +137 to +155
useHotkey("PREV_PANE", () => {
const state = store.getState();
const tab = state.getActiveTab();
if (!tab || !tab.activePaneId) return;
const paneIds = Object.keys(tab.panes);
const index = paneIds.indexOf(tab.activePaneId);
const prevIndex = index <= 0 ? paneIds.length - 1 : index - 1;
state.setActivePane({ tabId: tab.id, paneId: paneIds[prevIndex] });
});

useHotkey("NEXT_PANE", () => {
const state = store.getState();
const tab = state.getActiveTab();
if (!tab || !tab.activePaneId) return;
const paneIds = Object.keys(tab.panes);
const index = paneIds.indexOf(tab.activePaneId);
const nextIndex = index >= paneIds.length - 1 ? 0 : index + 1;
state.setActivePane({ tabId: tab.id, paneId: paneIds[nextIndex] });
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if WorkspaceStore has methods for ordered pane traversal
ast-grep --pattern 'getPanesInOrder($$$)'
ast-grep --pattern 'getOrderedPanes($$$)'
rg -n "pane.*order|ordered.*pane" --type ts -i

Repository: superset-sh/superset

Length of output: 2716


🏁 Script executed:

cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts | sed -n '130,160p'

Repository: superset-sh/superset

Length of output: 1398


🏁 Script executed:

# Check the structure of tab.panes and whether utilities are imported
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts | head -50

Repository: superset-sh/superset

Length of output: 1667


🏁 Script executed:

# Check how getNextPaneIdInVisualOrder and getPreviousPaneIdInVisualOrder are used elsewhere
rg -n "getNextPaneIdInVisualOrder|getPreviousPaneIdInVisualOrder" --type ts

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Check the implementation of these utility functions
cat -n apps/desktop/src/renderer/stores/tabs/utils.ts | sed -n '510,560p'

Repository: superset-sh/superset

Length of output: 2052


🏁 Script executed:

# Check the WorkspaceStore type and tab structure
rg -n "type.*Tab|interface.*Tab" apps/desktop/src/renderer/stores/tabs --type ts -A 5 | head -40

Repository: superset-sh/superset

Length of output: 3047


🏁 Script executed:

# Check where getNextPaneId and getPreviousPaneId are actually used
rg -n "getNextPaneId|getPreviousPaneId" apps/desktop/src --type ts

Repository: superset-sh/superset

Length of output: 787


🏁 Script executed:

# Check the full WorkspaceStore definition
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts | sed -n '1,20p'

Repository: superset-sh/superset

Length of output: 764


🏁 Script executed:

# Check if there's a layout field in the tab or state
rg -n "\.layout" apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/useWorkspaceHotkeys --type ts

Repository: superset-sh/superset

Length of output: 443


Use visual layout order for pane navigation instead of insertion order.

The code uses Object.keys(tab.panes) which returns insertion order, but tab.layout (a MosaicNode<string>) is available and required for visual order traversal (left-to-right, top-to-bottom). Replace with getNextPaneId() and getPreviousPaneId() from the utils module, which are already used elsewhere in the codebase (workspace/page.tsx). These utilities also handle wrapping correctly with modulo arithmetic, eliminating the need for manual index boundary checks.

Current code
useHotkey("PREV_PANE", () => {
	const state = store.getState();
	const tab = state.getActiveTab();
	if (!tab || !tab.activePaneId) return;
	const paneIds = Object.keys(tab.panes);
	const index = paneIds.indexOf(tab.activePaneId);
	const prevIndex = index <= 0 ? paneIds.length - 1 : index - 1;
	state.setActivePane({ tabId: tab.id, paneId: paneIds[prevIndex] });
});

useHotkey("NEXT_PANE", () => {
	const state = store.getState();
	const tab = state.getActiveTab();
	if (!tab || !tab.activePaneId) return;
	const paneIds = Object.keys(tab.panes);
	const index = paneIds.indexOf(tab.activePaneId);
	const nextIndex = index >= paneIds.length - 1 ? 0 : index + 1;
	state.setActivePane({ tabId: tab.id, paneId: paneIds[nextIndex] });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts
around lines 137 - 155, The PREV_PANE and NEXT_PANE hotkey handlers use
Object.keys(tab.panes) (in useHotkey callbacks) which follows insertion order
instead of visual layout order; change each handler to call the existing
utilities getPreviousPaneId(tab.layout, tab.activePaneId) and
getNextPaneId(tab.layout, tab.activePaneId) (respectively) obtained from the
utils module, check the returned paneId exists, and then call
state.setActivePane({ tabId: tab.id, paneId: returnedId }) — remove the manual
index/wrapping logic and use tab.layout and those util functions to determine
the next/previous pane.

Comment on lines +157 to +185
useHotkey("SPLIT_AUTO", () => {
const state = store.getState();
const active = state.getActivePane();
if (!active) return;
state.splitPane({
tabId: active.tabId,
paneId: active.pane.id,
position: "right",
newPane: {
kind: "terminal",
data: { terminalId: crypto.randomUUID() } as TerminalPaneData,
},
});
});

useHotkey("SPLIT_RIGHT", () => {
const state = store.getState();
const active = state.getActivePane();
if (!active) return;
state.splitPane({
tabId: active.tabId,
paneId: active.pane.id,
position: "right",
newPane: {
kind: "terminal",
data: { terminalId: crypto.randomUUID() } as TerminalPaneData,
},
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

SPLIT_AUTO and SPLIT_RIGHT have identical behavior.

Both hotkeys perform a right split with a terminal. The name "SPLIT_AUTO" suggests it should automatically determine the split direction (e.g., based on pane dimensions), but it's hardcoded to "right".

If auto-direction detection is intended, implement the logic. Otherwise, consider removing the duplication or renaming for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.ts
around lines 157 - 185, The SPLIT_AUTO handler currently duplicates SPLIT_RIGHT;
update the useHotkey("SPLIT_AUTO", ...) callback so it computes the split
direction dynamically instead of hardcoding "right": obtain the active pane via
store.getState().getActivePane(), read its dimensions (e.g., active.pane.width
and active.pane.height or comparable layout metrics), decide direction (e.g.,
choose "right" if width > height else "bottom"), then call state.splitPane with
that computed position; leave useHotkey("SPLIT_RIGHT", ...) as-is or
remove/rename one of the handlers if auto-behavior is not desired. Ensure you
reference the existing functions/objects state.splitPane, store.getState(),
getActivePane, and the "SPLIT_AUTO"/"SPLIT_RIGHT" hotkey identifiers when making
the change.

@saddlepaddle saddlepaddle merged commit e728ebd into main Apr 5, 2026
14 checks passed
@saddlepaddle saddlepaddle deleted the satya/v2-hotkeys branch April 5, 2026 20:49
MocA-Love pushed a commit to MocA-Love/superset that referenced this pull request Apr 7, 2026
…3190)

Add useV2WorkspaceHotkeys hook with:
- Tab management: close tab/pane, prev/next tab, jump to tab 1-9
- Pane management: prev/next pane, split auto/right/down/chat/browser, equalize splits
- Workspace navigation: prev/next workspace
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 7, 2026
cherry-pick済み:
- e728ebd feat(desktop): wire up missing hotkeys for v2 workspace (superset-sh#3190)
- 1eddeb3 feat(desktop): git changes sidebar with resource-oriented API (superset-sh#3177)
- 11ed4f8 V2 terminal env (superset-sh#3184)
- 0c52ecc feat(desktop): pane context menus + binary tree layout (superset-sh#3196)
- 5578746 fix(desktop): resolve file icons from origin instead of href (superset-sh#3199)
- 5a1e5d1 feat(panes): prefer sibling pane when closing active pane (superset-sh#3198)
- d670c4a V2 top bar: right sidebar toggle, org dropdown in sidebar, unified open-in button (superset-sh#3197)
- 2573fa2 fix(desktop): remove macOS background-to-tray quit interception (superset-sh#3205)
- 4a29342 feat: Superset CLI + CLI framework + Better Auth 1.5.6 (superset-sh#3194)
- 700cd65 fix(desktop): revert broken file icon origin fix + bundle all icon sources (superset-sh#3218)

フォーク独自対応:
- cleanupMainWindowResources()をexit pathに移動維持 (#3205対応)
- BROWSER_HARD_RELOAD/SEARCH_IN_FILESをv2 workspaceに配線
- BROWSER_RELOAD/HARD_RELOADのuseHotkey配線修正(リマップ対応)
- ansi_up依存維持
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant