feat(desktop): Cmd+Alt+Arrow moves focus between v2 panes#3403
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
💤 Files with no reviewable changes (1)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughReplaced workspace/tab-cycling hotkeys with directional pane-focus hotkeys and added spatial neighbor utilities to compute and focus adjacent panes based on layout geometry. Route-derived prev/next workspace and linear tab/pane cycling handlers were removed. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant HotkeyHandler as Hotkey Handler
participant PaneUtils as Pane Utils
participant WorkspaceUI as Workspace UI
User->>HotkeyHandler: Press FOCUS_PANE_LEFT
HotkeyHandler->>PaneUtils: getSpatialNeighborPaneId(root, activePaneId, "left")
PaneUtils->>PaneUtils: findPanePath(activePaneId)
PaneUtils->>PaneUtils: ascend to axis-aligned split, pick sibling, descend to edge pane
PaneUtils-->>HotkeyHandler: neighborPaneId or null
alt neighbor found
HotkeyHandler->>WorkspaceUI: setActivePane(neighborPaneId)
WorkspaceUI-->>User: focus moves to neighbor pane
else none
HotkeyHandler-->>User: no neighbor (no-op)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
320f968 to
aeac84e
Compare
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 56b73d57-7315-445e-9c08-5adc4773f525
📒 Files selected for processing (7)
apps/desktop/src/renderer/hotkeys/registry.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspaceHotkeys/useWorkspaceHotkeys.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxpackages/panes/src/core/store/utils/index.tspackages/panes/src/core/store/utils/utils.tspackages/panes/src/index.ts
There was a problem hiding this comment.
1 issue 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="packages/panes/src/core/store/utils/utils.ts">
<violation number="1" location="packages/panes/src/core/store/utils/utils.ts:200">
P1: Perpendicular split descent always chooses `first`, which can route focus to a non-adjacent pane (e.g., bottom-left → top-right in a 2×2 grid).</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Greptile SummaryThis PR introduces directional pane navigation (Cmd+Alt+Arrow on Mac, Ctrl+Alt+Arrow on Windows/Linux) for v2 workspaces, reclaiming those key bindings from the retired Key changes:
Issues found:
Confidence Score: 3/5Not safe to merge as-is — the spatial navigation algorithm produces wrong results for common 2×2 grid layouts (horizontal navigation skips rows), which is the primary user-facing feature being shipped. The hotkey plumbing, shortcut retirement, and overall architecture are solid. However the core algorithm (
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
K["Hotkey pressed FOCUS_PANE"]
MFD["moveFocusDirectional(dir)"]
GAT["store.getActiveTab()"]
NoTab{"tab or activePaneId?"}
FPP["findPanePath(root, activePaneId)"]
NotFound{"path found?"}
GSNP["getSpatialNeighborPaneId"]
NoMatch{"matching ancestor?"}
FEP["findEdgePaneId(sibling, dir)"]
PerpendicularSplit{"split direction == axis?"}
SameAxis["pick nearEdge branch"]
Perpendicular["default to node.first (bug)"]
NoOp["no-op (edge, no wrap)"]
SetActive["state.setActivePane"]
K --> MFD --> GAT --> NoTab
NoTab -- missing --> NoOp
NoTab -- present --> FPP --> NotFound
NotFound -- null --> NoOp
NotFound -- path --> GSNP --> NoMatch
NoMatch -- null --> NoOp
NoMatch -- found --> FEP --> PerpendicularSplit
PerpendicularSplit -- same axis --> SameAxis --> SetActive
PerpendicularSplit -- perpendicular --> Perpendicular --> SetActive
Reviews (1): Last reviewed commit: "feat(desktop): directional pane focus vi..." | Re-trigger Greptile |
| const nearEdge: SplitBranch = | ||
| dir === "right" || dir === "down" ? "first" : "second"; | ||
| return findEdgePaneId(node[nearEdge], dir); | ||
| } | ||
| return findEdgePaneId(node.first, dir); |
There was a problem hiding this comment.
Incorrect perpendicular-split descent in
findEdgePaneId
When descending into the sibling subtree and encountering a perpendicular split, the function unconditionally falls back to node.first. This breaks horizontal navigation in a 2×2 grid.
Consider this layout (built with "split right" then "split down" in each column):
A | B
C | D
split(horizontal):
first: split(vertical): { first: A, second: C }
second: split(vertical): { first: B, second: D }
Tracing D → LEFT:
- D's path is
["second", "second"] - The horizontal ancestor is
root; sibling branch isroot.first=split(vertical, A, C) findEdgePaneId(split(vertical, A, C), "left")—dir="left"wants axis=horizontal, butnode.direction="vertical"→ perpendicular fallback → picksnode.first= A
Expected result: C (same row as D).
Actual result: A (top of left column).
The same defect affects C → RIGHT → gives B instead of D. Vertical navigation (up/down) is unaffected.
A fix requires tracking the source pane's perpendicular-axis position when descending. One approach: pass the source path through findEdgePaneId and, when hitting a perpendicular split, pick the branch whose subtree contains a pane at the same depth index. Alternatively, precompute spatial coordinates for all panes and find the nearest neighbor by distance — the approach used by tmux internally.
| export function findPanePath( | ||
| node: LayoutNode, | ||
| paneId: string, | ||
| currentPath: SplitBranch[] = [], | ||
| ): SplitPath | null { | ||
| if (node.type === "pane") { | ||
| return node.paneId === paneId ? currentPath : null; | ||
| } | ||
| const firstPath = findPanePath(node.first, paneId, [...currentPath, "first"]); | ||
| if (firstPath) return firstPath; | ||
| return findPanePath(node.second, paneId, [...currentPath, "second"]); | ||
| } | ||
|
|
||
| function findEdgePaneId(node: LayoutNode, dir: FocusDirection): string | null { | ||
| if (node.type === "pane") return node.paneId; | ||
| const axis: SplitDirection = | ||
| dir === "left" || dir === "right" ? "horizontal" : "vertical"; | ||
| // If the descent hits a split on the matching axis, pick the near edge: | ||
| // for "right"/"down" the near edge is `first`, for "left"/"up" it's `second`. | ||
| // For perpendicular splits there is no spatial preference, default to `first`. | ||
| if (node.direction === axis) { | ||
| const nearEdge: SplitBranch = | ||
| dir === "right" || dir === "down" ? "first" : "second"; | ||
| return findEdgePaneId(node[nearEdge], dir); | ||
| } | ||
| return findEdgePaneId(node.first, dir); | ||
| } | ||
|
|
||
| export function getSpatialNeighborPaneId( | ||
| root: LayoutNode, | ||
| paneId: string, | ||
| dir: FocusDirection, | ||
| ): string | null { | ||
| const path = findPanePath(root, paneId); | ||
| if (!path) return null; | ||
|
|
||
| const axis: SplitDirection = | ||
| dir === "left" || dir === "right" ? "horizontal" : "vertical"; | ||
| const wantSecond = dir === "right" || dir === "down"; | ||
|
|
||
| for (let i = path.length - 1; i >= 0; i--) { | ||
| const ancestor = getNodeAtPath(root, path.slice(0, i)); | ||
| if (!ancestor || ancestor.type !== "split") continue; | ||
| if (ancestor.direction !== axis) continue; | ||
| const cameFrom = path[i]; | ||
| if (wantSecond && cameFrom !== "first") continue; | ||
| if (!wantSecond && cameFrom !== "second") continue; | ||
| const siblingBranch: SplitBranch = wantSecond ? "second" : "first"; | ||
| return findEdgePaneId(ancestor[siblingBranch], dir); | ||
| } | ||
| return null; | ||
| } |
There was a problem hiding this comment.
No unit tests for
getSpatialNeighborPaneId or findPanePath
These are non-trivial tree-traversal functions exported from @superset/panes, yet utils.test.ts has zero tests covering them. The PR description targets 71/71 existing tests passing — but none of those 71 cover the new logic.
Given that the algorithm has at least one identified edge-case defect (perpendicular-split descent in a 2×2 grid), test coverage is particularly important here. Tests for the following cases would be valuable:
- Single-pane root → all directions return
null - Two-pane horizontal split → left/right navigation; up/down return
null - Two-pane vertical split → up/down navigation; left/right return
null - 2×2 grid (horizontal + two vertical children) → all four directions from all four corners
- Non-matching direction at edge returns
null(no wrap) - Layout with deeply nested mixed splits
| FOCUS_PANE_LEFT: { | ||
| key: { | ||
| mac: "meta+alt+left", | ||
| windows: "ctrl+alt+left", | ||
| linux: "ctrl+alt+left", | ||
| }, | ||
| label: "Focus Pane Left", | ||
| category: "Terminal", | ||
| description: "Focus the pane to the left of the active pane", | ||
| }, | ||
| FOCUS_PANE_RIGHT: { | ||
| key: { | ||
| mac: "meta+alt+right", | ||
| windows: "ctrl+alt+right", | ||
| linux: "ctrl+alt+right", | ||
| }, | ||
| label: "Focus Pane Right", | ||
| category: "Terminal", | ||
| description: "Focus the pane to the right of the active pane", | ||
| }, | ||
| FOCUS_PANE_UP: { | ||
| key: { | ||
| mac: "meta+alt+up", | ||
| windows: "ctrl+alt+up", | ||
| linux: "ctrl+alt+up", | ||
| }, | ||
| label: "Focus Pane Up", | ||
| category: "Terminal", | ||
| description: "Focus the pane above the active pane", | ||
| }, | ||
| FOCUS_PANE_DOWN: { | ||
| key: { | ||
| mac: "meta+alt+down", | ||
| windows: "ctrl+alt+down", | ||
| linux: "ctrl+alt+down", | ||
| }, | ||
| label: "Focus Pane Down", | ||
| category: "Terminal", | ||
| description: "Focus the pane below the active pane", | ||
| }, | ||
| JUMP_TO_TAB_1: { |
There was a problem hiding this comment.
ctrl+alt+arrow conflicts with Intel HD Graphics shortcuts on Windows
On Windows, Ctrl+Alt+Arrow (Left/Right/Up/Down) is the default hotkey for Intel HD / UHD Graphics screen-rotation shortcuts and is registered at the driver level. While users can disable this in the Intel Graphics Control Panel, it's a very common configuration that would silently swallow these keystrokes before the Electron app sees them, making the feature completely non-functional for a significant portion of Windows users.
The old PREV_TAB/NEXT_TAB used ctrl+shift+alt+left/right (three-modifier), which avoided this collision. Consider restoring the three-modifier combo for Windows/Linux:
| FOCUS_PANE_LEFT: { | |
| key: { | |
| mac: "meta+alt+left", | |
| windows: "ctrl+alt+left", | |
| linux: "ctrl+alt+left", | |
| }, | |
| label: "Focus Pane Left", | |
| category: "Terminal", | |
| description: "Focus the pane to the left of the active pane", | |
| }, | |
| FOCUS_PANE_RIGHT: { | |
| key: { | |
| mac: "meta+alt+right", | |
| windows: "ctrl+alt+right", | |
| linux: "ctrl+alt+right", | |
| }, | |
| label: "Focus Pane Right", | |
| category: "Terminal", | |
| description: "Focus the pane to the right of the active pane", | |
| }, | |
| FOCUS_PANE_UP: { | |
| key: { | |
| mac: "meta+alt+up", | |
| windows: "ctrl+alt+up", | |
| linux: "ctrl+alt+up", | |
| }, | |
| label: "Focus Pane Up", | |
| category: "Terminal", | |
| description: "Focus the pane above the active pane", | |
| }, | |
| FOCUS_PANE_DOWN: { | |
| key: { | |
| mac: "meta+alt+down", | |
| windows: "ctrl+alt+down", | |
| linux: "ctrl+alt+down", | |
| }, | |
| label: "Focus Pane Down", | |
| category: "Terminal", | |
| description: "Focus the pane below the active pane", | |
| }, | |
| JUMP_TO_TAB_1: { | |
| FOCUS_PANE_LEFT: { | |
| key: { | |
| mac: "meta+alt+left", | |
| windows: "ctrl+shift+alt+left", | |
| linux: "ctrl+shift+alt+left", | |
| }, | |
| label: "Focus Pane Left", | |
| category: "Terminal", | |
| description: "Focus the pane to the left of the active pane", | |
| }, | |
| FOCUS_PANE_RIGHT: { | |
| key: { | |
| mac: "meta+alt+right", | |
| windows: "ctrl+shift+alt+right", | |
| linux: "ctrl+shift+alt+right", | |
| }, | |
| label: "Focus Pane Right", | |
| category: "Terminal", | |
| description: "Focus the pane to the right of the active pane", | |
| }, | |
| FOCUS_PANE_UP: { | |
| key: { | |
| mac: "meta+alt+up", | |
| windows: "ctrl+shift+alt+up", | |
| linux: "ctrl+shift+alt+up", | |
| }, | |
| label: "Focus Pane Up", | |
| category: "Terminal", | |
| description: "Focus the pane above the active pane", | |
| }, | |
| FOCUS_PANE_DOWN: { | |
| key: { | |
| mac: "meta+alt+down", | |
| windows: "ctrl+shift+alt+down", | |
| linux: "ctrl+shift+alt+down", | |
| }, | |
| label: "Focus Pane Down", | |
| category: "Terminal", | |
| description: "Focus the pane below the active pane", | |
| }, |
Note: the old PREV_PANE/NEXT_PANE already use ctrl+shift+alt+left/right on Windows — you'd need to choose distinct combos (e.g., ctrl+shift+alt+up/down for vertical, or a different modifier set entirely).
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/panes/src/core/store/utils/utils.test.ts (1)
329-335: Consider adding a test for non-existent pane IDs.The single-pane test validates behavior for an existing pane but doesn't explicitly verify that passing a non-existent
paneIdreturnsnull. While the implementation handles this correctly (viafindPanePathreturningnull), an explicit test would document this edge case.🧪 Optional: Add test for invalid pane ID
it("returns null when there is only one pane", () => { const layout: LayoutNode = { type: "pane", paneId: "a" }; expect(getSpatialNeighborPaneId(layout, "a", "left")).toBeNull(); expect(getSpatialNeighborPaneId(layout, "a", "right")).toBeNull(); expect(getSpatialNeighborPaneId(layout, "a", "up")).toBeNull(); expect(getSpatialNeighborPaneId(layout, "a", "down")).toBeNull(); }); + + it("returns null when pane does not exist in layout", () => { + const layout: LayoutNode = { type: "pane", paneId: "a" }; + expect(getSpatialNeighborPaneId(layout, "nonexistent", "right")).toBeNull(); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/panes/src/core/store/utils/utils.test.ts` around lines 329 - 335, Add a unit test to assert getSpatialNeighborPaneId returns null when given a non-existent paneId: create a layout (e.g., single-pane LayoutNode like { type: "pane", paneId: "a" }) and call getSpatialNeighborPaneId with a paneId that doesn't exist (e.g., "missing") for each direction ("left", "right", "up", "down"), expecting null; this documents the edge case and verifies the function (which relies on findPanePath) handles invalid pane IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/panes/src/core/store/utils/utils.test.ts`:
- Around line 329-335: Add a unit test to assert getSpatialNeighborPaneId
returns null when given a non-existent paneId: create a layout (e.g.,
single-pane LayoutNode like { type: "pane", paneId: "a" }) and call
getSpatialNeighborPaneId with a paneId that doesn't exist (e.g., "missing") for
each direction ("left", "right", "up", "down"), expecting null; this documents
the edge case and verifies the function (which relies on findPanePath) handles
invalid pane IDs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c9a0b0b4-a17f-45da-9ab8-eb5a5dbbbdeb
📒 Files selected for processing (2)
packages/panes/src/core/store/utils/utils.test.tspackages/panes/src/core/store/utils/utils.ts
9917627 to
4025c59
Compare
…workspace Widen PlatformKey and HotkeyDefinition so hotkey entries can register with a null chord per platform. Downstream consumers were already null-safe from #3391 (useBinding, buildRegisteredAppChords, formatHotkeyDisplay, sanitizeOverride, HotkeyMenuShortcut), so the schema widening is the only structural change needed. Re-introduce PREV_TAB, NEXT_TAB, PREV_WORKSPACE, NEXT_WORKSPACE as registered-but-unbound entries so users who want tab/workspace neighbor navigation can rebind them in Settings → Keyboard. PR #3403 removed these to free Cmd+Alt+Arrow for directional pane focus; this restores the hotkey IDs (and their v1/v2 handlers) without claiming any default chord. Users with pre-#3403 overrides for these IDs will transparently get their bindings back since the override is preserved in localStorage. - Null-guard canonicalizeChord(defaultKey) in useRecordHotkeys so recording a new chord for an unbound hotkey no longer throws. - Replace the force-cast in resolveHotkeyFromEvent.test.ts sample picker with a type predicate so sampleDef.key narrows to string honestly instead of lying about the widened schema.
…kspace (#3422) * feat(desktop/hotkeys): allow unbound defaults; restore PREV/NEXT tab+workspace Widen PlatformKey and HotkeyDefinition so hotkey entries can register with a null chord per platform. Downstream consumers were already null-safe from #3391 (useBinding, buildRegisteredAppChords, formatHotkeyDisplay, sanitizeOverride, HotkeyMenuShortcut), so the schema widening is the only structural change needed. Re-introduce PREV_TAB, NEXT_TAB, PREV_WORKSPACE, NEXT_WORKSPACE as registered-but-unbound entries so users who want tab/workspace neighbor navigation can rebind them in Settings → Keyboard. PR #3403 removed these to free Cmd+Alt+Arrow for directional pane focus; this restores the hotkey IDs (and their v1/v2 handlers) without claiming any default chord. Users with pre-#3403 overrides for these IDs will transparently get their bindings back since the override is preserved in localStorage. - Null-guard canonicalizeChord(defaultKey) in useRecordHotkeys so recording a new chord for an unbound hotkey no longer throws. - Replace the force-cast in resolveHotkeyFromEvent.test.ts sample picker with a type predicate so sampleDef.key narrows to string honestly instead of lying about the widened schema. * fix(desktop/hotkeys): restore prevIndex in v2 PREV_WORKSPACE handler
…kspace (superset-sh#3422) * feat(desktop/hotkeys): allow unbound defaults; restore PREV/NEXT tab+workspace Widen PlatformKey and HotkeyDefinition so hotkey entries can register with a null chord per platform. Downstream consumers were already null-safe from superset-sh#3391 (useBinding, buildRegisteredAppChords, formatHotkeyDisplay, sanitizeOverride, HotkeyMenuShortcut), so the schema widening is the only structural change needed. Re-introduce PREV_TAB, NEXT_TAB, PREV_WORKSPACE, NEXT_WORKSPACE as registered-but-unbound entries so users who want tab/workspace neighbor navigation can rebind them in Settings → Keyboard. PR superset-sh#3403 removed these to free Cmd+Alt+Arrow for directional pane focus; this restores the hotkey IDs (and their v1/v2 handlers) without claiming any default chord. Users with pre-superset-sh#3403 overrides for these IDs will transparently get their bindings back since the override is preserved in localStorage. - Null-guard canonicalizeChord(defaultKey) in useRecordHotkeys so recording a new chord for an unbound hotkey no longer throws. - Replace the force-cast in resolveHotkeyFromEvent.test.ts sample picker with a type predicate so sampleDef.key narrows to string honestly instead of lying about the widened schema. * fix(desktop/hotkeys): restore prevIndex in v2 PREV_WORKSPACE handler
…h#3403) * feat(desktop): directional pane focus via Cmd+Alt+Arrow (v2) Adds spatial pane navigation to v2 workspaces: Cmd+Alt+Arrow jumps focus to the visually adjacent pane in that direction (no wrap at edges). Reclaims Cmd+Alt+Arrow from the retired PREV/NEXT_TAB and PREV/NEXT_WORKSPACE shortcuts — tabs still cycle via Ctrl+Tab and both tabs and workspaces keep Cmd+Alt+1..9 jump-to-N. The spatial neighbor util walks up the LayoutNode path to find the deepest ancestor split whose axis matches the arrow, then descends into the sibling subtree picking the near-edge leaf. * fix(panes): preserve cross-axis alignment during spatial neighbor descent findEdgePaneId previously fell through to node.first on any perpendicular split encountered while descending into the sibling subtree, losing the source pane's row/column position. In a 2x2 grid this caused the directional focus move to land on the wrong pane depending on how the grid was grouped in the layout tree (e.g. in a rows-first 2x2, down from top-right landed on bottom-left). Track the source pane's path below the pivot ancestor as an alignment path and consume one entry per perpendicular-split descent, so the descent mirrors the source's cross-axis choices. Adds unit tests for getSpatialNeighborPaneId covering single pane, simple horizontal/vertical splits, edge no-wrap, and both groupings of the 2x2 grid. * refactor(desktop): drop linear PREV/NEXT_PANE now that directional nav exists The 4-way FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} shortcuts supersede linear pane cycling. Removing PREV/NEXT_PANE also frees ctrl+shift+alt+Arrow on Windows/Linux, which dodges the Intel HD Graphics screen-rotation driver shortcut that steals ctrl+alt+Arrow at the OS level. - Remove PREV_PANE/NEXT_PANE from the hotkey registry and both v1/v2 handler sites. - Remap FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} on Windows/Linux from ctrl+alt+Arrow to ctrl+shift+alt+Arrow. - Delete now-unused getNextPaneId/getPreviousPaneId helpers from renderer/stores/tabs/utils.ts. Users who relied on linear cycling can re-add it via settings once the unbound-default hotkey support lands as a follow-up.
…ff viewer Follow-up fixes for superset-sh#3403 and superset-sh#3420 cherry-picks: hotkeys/types.ts: - Widen PlatformKey and HotkeyDefinition.key to allow null per platform (mirrors upstream superset-sh#3422; needed here because v1 workspace uses fork's tRPC-based PREV/NEXT_WORKSPACE handlers that reference null-bound keys) hotkeys/registry.ts: - Re-add PREV_TAB, NEXT_TAB, PREV_WORKSPACE, NEXT_WORKSPACE as null-bound (matches upstream superset-sh#3422 final state; deleted by superset-sh#3403's auto-merge but fork still needs the IDs for v1 tRPC-based workspace nav handlers) hotkeys/useRecordHotkeys.ts: - Null-guard canonicalizeChord(defaultKey) so recording a new chord for an unbound hotkey doesn't throw routes/workspace/$workspaceId/page.tsx: - Restore navigateToWorkspace import (removed by cherry-pick auto-merge); fork's tRPC-based PREV/NEXT_WORKSPACE handlers still need it routes/v2-workspace/$workspaceId/page.tsx: - Remove tabTitle argument from openPane call (deprecated by superset-sh#3420; pane-level titleOverride handles tab titles now via resolveTabTitle) FORK NOTE: the registry entries and types.ts widening overlap with PR#2 (superset-sh#3422 cherry-pick). When both PRs merge, git detects identical changes and auto-resolves.
1. Parse file tab titles with Windows path separators (Codex P2)
- getFileName in usePaneRegistry.tsx split on / only, regressing Windows paths
like C:\repo\foo.ts which would render as the full path.
- Use split(/[/\\]/) to handle both separators cross-platform.
2. Restore PREV_TAB/NEXT_TAB handlers in v2 useWorkspaceHotkeys (Codex P2)
- superset-sh#3403 (cherry-picked here) removed these handlers, but superset-sh#3422 (PR#2)
restored them as null-bound in the registry. Without callbacks, users
rebinding these hotkeys in Settings would press them to no effect.
- Add handlers matching upstream c925f4d's restoration so override-based
tab navigation works in v2 workspaces.
…h#3403) * feat(desktop): directional pane focus via Cmd+Alt+Arrow (v2) Adds spatial pane navigation to v2 workspaces: Cmd+Alt+Arrow jumps focus to the visually adjacent pane in that direction (no wrap at edges). Reclaims Cmd+Alt+Arrow from the retired PREV/NEXT_TAB and PREV/NEXT_WORKSPACE shortcuts — tabs still cycle via Ctrl+Tab and both tabs and workspaces keep Cmd+Alt+1..9 jump-to-N. The spatial neighbor util walks up the LayoutNode path to find the deepest ancestor split whose axis matches the arrow, then descends into the sibling subtree picking the near-edge leaf. * fix(panes): preserve cross-axis alignment during spatial neighbor descent findEdgePaneId previously fell through to node.first on any perpendicular split encountered while descending into the sibling subtree, losing the source pane's row/column position. In a 2x2 grid this caused the directional focus move to land on the wrong pane depending on how the grid was grouped in the layout tree (e.g. in a rows-first 2x2, down from top-right landed on bottom-left). Track the source pane's path below the pivot ancestor as an alignment path and consume one entry per perpendicular-split descent, so the descent mirrors the source's cross-axis choices. Adds unit tests for getSpatialNeighborPaneId covering single pane, simple horizontal/vertical splits, edge no-wrap, and both groupings of the 2x2 grid. * refactor(desktop): drop linear PREV/NEXT_PANE now that directional nav exists The 4-way FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} shortcuts supersede linear pane cycling. Removing PREV/NEXT_PANE also frees ctrl+shift+alt+Arrow on Windows/Linux, which dodges the Intel HD Graphics screen-rotation driver shortcut that steals ctrl+alt+Arrow at the OS level. - Remove PREV_PANE/NEXT_PANE from the hotkey registry and both v1/v2 handler sites. - Remap FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} on Windows/Linux from ctrl+alt+Arrow to ctrl+shift+alt+Arrow. - Delete now-unused getNextPaneId/getPreviousPaneId helpers from renderer/stores/tabs/utils.ts. Users who relied on linear cycling can re-add it via settings once the unbound-default hotkey support lands as a follow-up.
…ff viewer Follow-up fixes for superset-sh#3403 and superset-sh#3420 cherry-picks: hotkeys/types.ts: - Widen PlatformKey and HotkeyDefinition.key to allow null per platform (mirrors upstream superset-sh#3422; needed here because v1 workspace uses fork's tRPC-based PREV/NEXT_WORKSPACE handlers that reference null-bound keys) hotkeys/registry.ts: - Re-add PREV_TAB, NEXT_TAB, PREV_WORKSPACE, NEXT_WORKSPACE as null-bound (matches upstream superset-sh#3422 final state; deleted by superset-sh#3403's auto-merge but fork still needs the IDs for v1 tRPC-based workspace nav handlers) hotkeys/useRecordHotkeys.ts: - Null-guard canonicalizeChord(defaultKey) so recording a new chord for an unbound hotkey doesn't throw routes/workspace/$workspaceId/page.tsx: - Restore navigateToWorkspace import (removed by cherry-pick auto-merge); fork's tRPC-based PREV/NEXT_WORKSPACE handlers still need it routes/v2-workspace/$workspaceId/page.tsx: - Remove tabTitle argument from openPane call (deprecated by superset-sh#3420; pane-level titleOverride handles tab titles now via resolveTabTitle) FORK NOTE: the registry entries and types.ts widening overlap with PR#2 (superset-sh#3422 cherry-pick). When both PRs merge, git detects identical changes and auto-resolves.
1. Parse file tab titles with Windows path separators (Codex P2)
- getFileName in usePaneRegistry.tsx split on / only, regressing Windows paths
like C:\repo\foo.ts which would render as the full path.
- Use split(/[/\\]/) to handle both separators cross-platform.
2. Restore PREV_TAB/NEXT_TAB handlers in v2 useWorkspaceHotkeys (Codex P2)
- superset-sh#3403 (cherry-picked here) removed these handlers, but superset-sh#3422 (PR#2)
restored them as null-bound in the registry. Without callbacks, users
rebinding these hotkeys in Settings would press them to no effect.
- Add handlers matching upstream c925f4d's restoration so override-based
tab navigation works in v2 workspaces.
All 9 upstream commits have been individually cherry-picked via PR#159~#163: | Upstream | Our PR | Description | |---|---|---| | d656b7e (superset-sh#3415) | #159 (PR#1) | terminal clipboard handling | | 31fcf19 (superset-sh#3416) | #162 (PR#4) | v1 split pane startup sizing fix | | 039edf2 (superset-sh#3403) | #161 (PR#3) | Cmd+Alt+Arrow spatial pane focus | | b18a00c (superset-sh#3421) | #159 (PR#1) | v2 right sidebar toggle reactive | | 3dd1de2 (superset-sh#3420) | #161 (PR#3) | v2 diff viewer + tab title resolution | | b42a114 (superset-sh#3418) | #159 (PR#1) | CodeMirror hotkey enablement | | c925f4d (superset-sh#3422) | #160 (PR#2) | unbound defaults + restore prev/next tab/workspace | | bb12c09 (superset-sh#3419) | #163 (PR#5) | version bump 1.5.3 | | 47efa73 (superset-sh#3432) | #159 (PR#1) | pending/update-required error selectable | Fork-specific features preserved: - auto-updater (IS_FORK, GitHub Releases API) - QuitMode/cleanupMainWindowResources lifecycle - GitHubSyncService, SpreadsheetViewer - BROWSER_RELOAD / BROWSER_HARD_RELOAD / SEARCH_IN_FILES hotkeys - HotkeyCategory "Browser" - v1 deep-link navigation (useSearch/WorkspaceSearchParams) - v1 tRPC-based PREV/NEXT_WORKSPACE handlers - v1 CLOSE_TERMINAL/CLOSE_TAB hotkey handlers - v2 extra state (rightSidebarOpenViewWidth, showPresetsBar)
Flip Cmd+Alt+Arrow (mac) / Ctrl+Shift+Alt+Arrow (win/linux) back to the pre-#3403 behavior: - Cmd+Alt+Left / Right → Previous / Next Tab - Cmd+Alt+Up / Down → Previous / Next Workspace FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} remain registered but are now unbound by default — users who want directional pane focus can rebind them in Settings → Keyboard. PREV_TAB_ALT / NEXT_TAB_ALT (Ctrl+Tab / Ctrl+Shift+Tab) are unchanged. Also move SCROLL_TO_BOTTOM on Windows/Linux from ctrl+shift+alt+down to ctrl+end to avoid the silent collision with NEXT_WORKSPACE that this restoration would otherwise introduce. buildRegisteredAppChords uses a Map keyed by canonical chord, so duplicate chords silently clobber each other in the reverse lookup. mac SCROLL_TO_BOTTOM (meta+shift+down) is unchanged. Registered users who previously overrode any of these IDs keep their overrides — defaults are just fallbacks.
…3472) Flip Cmd+Alt+Arrow (mac) / Ctrl+Shift+Alt+Arrow (win/linux) back to the pre-#3403 behavior: - Cmd+Alt+Left / Right → Previous / Next Tab - Cmd+Alt+Up / Down → Previous / Next Workspace FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} remain registered but are now unbound by default — users who want directional pane focus can rebind them in Settings → Keyboard. PREV_TAB_ALT / NEXT_TAB_ALT (Ctrl+Tab / Ctrl+Shift+Tab) are unchanged. Also move SCROLL_TO_BOTTOM on Windows/Linux from ctrl+shift+alt+down to ctrl+end to avoid the silent collision with NEXT_WORKSPACE that this restoration would otherwise introduce. buildRegisteredAppChords uses a Map keyed by canonical chord, so duplicate chords silently clobber each other in the reverse lookup. mac SCROLL_TO_BOTTOM (meta+shift+down) is unchanged. Registered users who previously overrode any of these IDs keep their overrides — defaults are just fallbacks.
…uperset-sh#3472) Flip Cmd+Alt+Arrow (mac) / Ctrl+Shift+Alt+Arrow (win/linux) back to the pre-superset-sh#3403 behavior: - Cmd+Alt+Left / Right → Previous / Next Tab - Cmd+Alt+Up / Down → Previous / Next Workspace FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} remain registered but are now unbound by default — users who want directional pane focus can rebind them in Settings → Keyboard. PREV_TAB_ALT / NEXT_TAB_ALT (Ctrl+Tab / Ctrl+Shift+Tab) are unchanged. Also move SCROLL_TO_BOTTOM on Windows/Linux from ctrl+shift+alt+down to ctrl+end to avoid the silent collision with NEXT_WORKSPACE that this restoration would otherwise introduce. buildRegisteredAppChords uses a Map keyed by canonical chord, so duplicate chords silently clobber each other in the reverse lookup. mac SCROLL_TO_BOTTOM (meta+shift+down) is unchanged. Registered users who previously overrode any of these IDs keep their overrides — defaults are just fallbacks.
Summary
getSpatialNeighborPaneIdto@superset/panes— walks up theLayoutNodepath to find the deepest ancestor split whose axis matches the arrow, then descends into the sibling subtree picking the near-edge leafTest plan
bun run typecheckcleanbun run lintcleanpackages/panestests pass (71/71)Summary by cubic
Cmd+Alt+Arrow now moves focus to the adjacent pane in v2 workspaces with no wrap at edges. We removed linear pane cycling and reclaimed these keys from tab/workspace switching; Ctrl+Tab and Cmd+Alt+1..9 stay the same.
New Features
@superset/panes: addgetSpatialNeighborPaneId,findPanePath, andFocusDirection; registerFOCUS_PANE_{LEFT,RIGHT,UP,DOWN}; removePREV/NEXT_TAB,PREV/NEXT_WORKSPACE, and linearPREV/NEXT_PANE.Bug Fixes
Written for commit 4025c59. Summary will update on new commits.
Summary by CodeRabbit
New Features
Chores
Tests