Skip to content

[PR3/5] feat(upstream): 空間認識型ペインナビゲーション + v2 Diff ビューア + タブタイトル解決API刷新#161

Merged
MocA-Love merged 4 commits intomainfrom
upstream-merge/pr3-spatial-pane-diff-viewer
Apr 14, 2026
Merged

[PR3/5] feat(upstream): 空間認識型ペインナビゲーション + v2 Diff ビューア + タブタイトル解決API刷新#161
MocA-Love merged 4 commits intomainfrom
upstream-merge/pr3-spatial-pane-diff-viewer

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Apr 14, 2026

Upstream Merge PR#3 - 空間認識ペインナビ + Diff ビューア

upstream から2つの大きな機能改善コミットを cherry-pick します。フォークにとって初の 大規模 API 変更 を含みます。

取り込むコミット

Commit Upstream PR 内容
`039edf21a` superset-sh#3403 Cmd+Alt+Arrow で v2 ペイン間を空間認識的にフォーカス移動
`3dd1de2e8` superset-sh#3420 v2 diff ビューアが独立タブで開く + タブ/ペインタイトル解決の刷新

改善内容の詳細

① 空間認識型ペインナビゲーション (superset-sh#3403)

何が問題だったか

従来の `PREV_PANE` / `NEXT_PANE` は 線形循環 でした:

  • ペインを登録順に1つずつ次へ/前へ移動
  • 2x2 グリッドのような複雑なレイアウトだと、視覚的に隣接していないペインへジャンプしてしまい直感に反する
  • Windows/Linux では `Ctrl+Alt+Arrow` が Intel HD Graphics ドライバに画面回転ショートカットとして奪われる問題もあった

どう直したか

4方向の空間認識型ナビゲーションに刷新:

```
FOCUS_PANE_LEFT → Cmd+Alt+Left (mac) / Ctrl+Shift+Alt+Left (win/linux)
FOCUS_PANE_RIGHT → Cmd+Alt+Right (mac) / Ctrl+Shift+Alt+Right (win/linux)
FOCUS_PANE_UP → Cmd+Alt+Up (mac) / Ctrl+Shift+Alt+Up (win/linux)
FOCUS_PANE_DOWN → Cmd+Alt+Down (mac) / Ctrl+Shift+Alt+Down (win/linux)
```

新規アルゴリズム `getSpatialNeighborPaneId()`:

  1. 現在のペインから LayoutNode ツリーを 逆向きに 登り、矢印方向に一致する軸(horizontal/vertical)を持つ split を見つける
  2. 見つかった split の反対側の subtree を descend し、エッジに最も近い leaf ペインを選ぶ
  3. descent 時に クロス軸の整合性を維持 (2x2 グリッドで「右上から下」を押したときに「右下」に正しく到達するため)

この整合性ロジックはバグ修正が重なってテストが追加されています:

  • 単一ペイン
  • 単純な horizontal/vertical split
  • エッジでの no-wrap(端では移動しない)
  • 2x2 グリッド(rows-first と cols-first 両方のレイアウト)

Windows/Linux での Intel ドライバ回避:

  • `Ctrl+Alt+Arrow` ではなく `Ctrl+Shift+Alt+Arrow` に割り当て
  • これで Intel ドライバの画面回転ショートカットと衝突しない

新規ファイル:

  • `packages/panes/src/core/store/utils/utils.ts` - `getSpatialNeighborPaneId()`, `FocusDirection` 型
  • `packages/panes/src/core/store/utils/utils.test.ts` - 上記のユニットテスト

② Diff ビューア独立タブ + タブタイトル解決API刷新 (superset-sh#3420)

何が問題だったか

Diff ビューアが既存タブに埋め込まれる:

  • Git の変更を見たいとき、現在開いているタブに diff ペインが追加されてしまい、編集中のファイルと混在していた
  • 「変更の確認」と「編集」が物理的に分離されず、ワークフローが混乱しがち

タブタイトルの責任が散らかっていた:

  • `tab.titleOverride` がユーザー rename とプリセット名とターミナルラベルを兼ねていた
  • タブ分割時に titleOverride が残ってしまい、意味不明なタイトルになることがあった
  • 各 pane 定義の `getTitle()` が `ReactNode` を返せるため、タブバーと context menu で異なる表示になる可能性があった

どう直したか

(a) Diff ビューアが独立タブで開く

  • Git changes から diff を開くと、新規タブを作って開く
  • 編集ワークフローを汚染しない

(b) `PaneDefinition.getTitle()` API の刷新

```typescript
// Before
interface PaneDefinition {
getTitle?(context: RendererContext): ReactNode;
}

// After
interface PaneDefinition {
getTitle?(pane: Pane): string | undefined; // 純粋な文字列のみ
renderTitle?(context: RendererContext): ReactNode; // JSX はこちらへ
}
```

これにより:

  • タイトル文字列はあらゆる場所(タブバー、"Move to Tab" コンテキストメニュー)で一貫
  • JSX の rich title は `renderTitle` に分離、必要な場所でだけ使う

(c) `titleOverride` をタブ→ペインレベルへ移動

```
Before:
tab.titleOverride ← ユーザー rename と preset 名と terminal label が混在

After:
tab.titleOverride ← ユーザー rename 専用(タブ分割時にクリア)
pane.titleOverride ← preset 名 / terminal label (pane 固有のラベル)
```

(d) 新規ヘルパー `resolveTabTitle()`

タブタイトル解決を一元化:

```typescript
// packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts
function resolveTabTitle(tab, tabs, registry) {
// 1. tab.titleOverride(ユーザー rename)が最優先
if (tab.titleOverride) return tab.titleOverride;

// 2. 単一ペインの場合:
// - pane.titleOverride
// - registry[pane.kind].getTitle(pane)
const onlyPane = single pane ? ... : undefined;
if (onlyPane) {
const fromPane = onlyPane.titleOverride ?? registry[...]?.getTitle?.(onlyPane);
if (fromPane) return fromPane;
}

// 3. 複数ペインの場合: "Tab N"
return `Tab ${tabs.indexOf(tab) + 1}`;
}
```

(e) ブラウザタブのタイトルが URL のポート番号を保持

  • `URL.hostname` → `URL.host` に変更
  • `localhost:3000` のようにポート付きで表示される

(f) `WorkspaceProps.getTabTitle` を削除、`openPane({ tabTitle })` を廃止

  • タブタイトル解決が `resolveTabTitle()` に一元化されたため、API を簡素化

コンフリクト解決

v1 workspace/$workspaceId/page.tsx

  • ✅ フォーク独自の deep-link navigation(`useSearch` / `WorkspaceSearchParams` / `useDeepLinkNavigationStore`)を保持
  • ✅ フォーク独自の `CLOSE_TERMINAL` / `CLOSE_TAB` ハンドラを保持
  • ✅ フォーク独自の tRPC-based PREV/NEXT_WORKSPACE ハンドラを保持
  • ❌ `PREV_PANE` / `NEXT_PANE` ハンドラは削除(upstreamに合わせる)

v2 workspace/$workspaceId/page.tsx

  • ✅ フォーク独自の追加 state(`rightSidebarOpenViewWidth`, `showPresetsBar` 等)を保持
  • ✅ upstream の `useDefaultContextMenuActions(paneRegistry)` シグネチャ変更を採用
  • ❌ `addTab({ titleOverride })` を削除(新API に合わせる)

registry.ts

  • 自動マージで `PREV_PANE` / `NEXT_PANE` 削除 + `FOCUS_PANE_*` 追加
  • フォーク独自の `BROWSER_RELOAD` / `BROWSER_HARD_RELOAD` / `SEARCH_IN_FILES` は維持

追加の型拡張(PR#2 と重複)

cherry-pick 後の typecheck パスのため、以下を追加:

  • `types.ts` の null-safe 化(PR#2 と同じ)
  • `registry.ts` に null-bound `PREV/NEXT_TAB`, `PREV/NEXT_WORKSPACE` を追加(PR#2 と同じ)
  • `useRecordHotkeys.ts` の null-guard(PR#2 と同じ)
  • `navigateToWorkspace` import を復活(フォーク固有)
  • v2 page.tsx の deprecated `tabTitle` 引数を削除

⚠️ PR#2 との重複について: `types.ts` と `registry.ts` の変更は PR#2 と同一内容です。git の3-way merge が identical な変更として自動解決するので、マージ順序は PR#2 → PR#3 のどちらが先でも問題なし。

フォーク固有機能の保持

  • v1 deep-link navigation: URL 経由でファイル/行/列を開く機能
  • v1 tRPC-based PREV/NEXT_WORKSPACE: フォーク独自の workspace 循環実装
  • v2 CLOSE_TERMINAL / CLOSE_TAB: フォーク独自のペイン・タブクローズホットキー
  • ブラウザホットキー: `BROWSER_RELOAD`, `BROWSER_HARD_RELOAD`, `SEARCH_IN_FILES`
  • HotkeyCategory "Browser"
  • v2 workspace 独自 state: `rightSidebarOpenViewWidth`, `showPresetsBar` 等

テスト計画

  • `bun run typecheck` パス
  • `bun run lint` パス
  • `Cmd+Alt+Arrow` で v2 ペイン間を空間認識で移動(水平2ペイン)
  • 2x2 グリッドで `Cmd+Alt+Arrow` が正しく動作(クロス軸整合性)
  • エッジで wrap しないことを確認
  • 単一ペインのタブ:ペインの getTitle() がタブタイトル
  • 複数ペインのタブ:"Tab N" 表示
  • ユーザーがタブ名を変更した場合:`tab.titleOverride` が効く
  • Terminal preset でカスタムラベル:`pane.titleOverride` が表示される
  • ブラウザタブのポート番号:`localhost:3000` のようにポート付きで表示
  • v1 workspace の既存ホットキーが動作:`PREV_TAB`/`NEXT_TAB`/`PREV_WORKSPACE`/`NEXT_WORKSPACE`
  • フォーク独自ホットキーが動作:ブラウザリロード、ハードリロード、ファイル内検索
  • Diff ビューアが新規タブで開く(既存タブに追加されない)

Summary by CodeRabbit

Release Notes

  • New Features

    • Directional pane focus navigation: Use arrow-based hotkeys (left, right, up, down) to navigate between panes with layout-aware positioning instead of linear cycling.
    • Updated hotkey bindings for improved consistency across platforms.
  • Refactor

    • Improved tab title resolution to better reflect pane and workspace context.
    • Simplified workspace state management by centralizing title handling.
  • Tests

    • Added comprehensive test coverage for spatial pane navigation logic.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

📝 Walkthrough

ウォークスルー

ペイン間のナビゲーションシステムを改新し、前後移動のホットキー(PREV_PANE/NEXT_PANE)を、方向性のある空間的フォーカス制御(FOCUS_PANE_LEFT/RIGHT/UP/DOWN)に置き換えました。タブタイトルの解決ロジックを一元化し、ペーン定義のタイトルコールバック署名を変更し、新しい空間的隣接ペーンID検索機能を追加しました。

変更

コホート / ファイル 概要
ホットキーレジストリ
apps/desktop/src/renderer/hotkeys/registry.ts
PREV_PANE/NEXT_PANEを削除、FOCUS_PANE_LEFT/RIGHT/UP/DOWNを追加(macはmeta+alt+left/right/up/down、windows/linuxはctrl+shift+alt+left/right/up/down)。
空間的ナビゲーション実装
packages/panes/src/core/store/utils/utils.ts, packages/panes/src/core/store/utils/utils.test.ts
新しいFocusDirection型、findPanePath()getSpatialNeighborPaneId()関数を追加。レイアウトツリーを走査して指定方向の隣接ペーンを計算します。
ホットキー処理の更新
apps/desktop/src/renderer/routes/.../useWorkspaceHotkeys/useWorkspaceHotkeys.ts
PREV_PANE/NEXT_PANEハンドラを削除、新しいFOCUS_PANE_*ハンドラとmoveFocusDirectional()ヘルパーを追加。titleOverrideパラメータをタブ作成から削除。
旧ナビゲーション関数の削除
apps/desktop/src/renderer/stores/tabs/utils.ts, apps/desktop/src/renderer/routes/.../workspace/.../page.tsx
getNextPaneId()getPreviousPaneId()を削除、インポートを削除。
タブタイトル解決の一元化
packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts, packages/panes/src/react/components/Workspace/Workspace.tsx, packages/panes/src/react/index.ts
新しいresolveTabTitle()ユーティリティ関数を追加。WorkspaceからgetTabTitlepropを削除し、レジストリとタブ状態に基づいた一元化された解決を使用。
ペーン定義署名の更新
packages/panes/src/react/types.ts, packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx
PaneDefinition.getTitle()の署名をRendererContextからPaneオブジェクトに変更。戻り値型をReactNodeからstring | undefinedに変更。
タイトルオーバーライド構造の変更
apps/desktop/src/renderer/routes/.../buildSetupPaneLayout.ts, apps/desktop/src/renderer/routes/.../useV2PresetExecution/useV2PresetExecution.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/.../page.tsx
titleOverrideをタブレベルからペーンレベルに移動。makeTerminalPane()titleOverrideパラメータを追加。
コンテキストメニュー処理の更新
apps/desktop/src/renderer/routes/.../useDefaultContextMenuActions/useDefaultContextMenuActions.tsx
paneRegistryパラメータを追加。resolveTabTitle()を使用してメニューラベルを計算。
ブラウザペーンタイトル関数の削除
apps/desktop/src/renderer/routes/.../usePaneRegistry/components/BrowserPane/BrowserPane.tsx, apps/desktop/src/renderer/routes/.../usePaneRegistry/components/BrowserPane/index.ts
getBrowserTabTitle()関数を削除。タイトル解決ロジックはレジストリのgetTitleコールバックに移行。
ペーンレジストリ実装の更新
apps/desktop/src/renderer/routes/.../usePaneRegistry/usePaneRegistry.tsx
ファイル・ブラウザペーンレジストリのgetTitle()署名を変更。fileレジストリにrenderTitleを追加。ファイル名解析とURL解析のロジックを改善。
ストア署名の簡潔化
packages/panes/src/core/store/store.ts, packages/panes/src/core/store/store.test.ts
WorkspaceStore.openPane()からtabTitleパラメータを削除。
パッケージエクスポートの拡張
packages/panes/src/core/store/utils/index.ts, packages/panes/src/index.ts
FocusDirectiongetSpatialNeighborPaneIdfindPanePathresolveTabTitleを新規エクスポート。

シーケンスダイアグラム

sequenceDiagram
    participant User
    participant HotkeyHandler as Hotkey Handler
    participant Store as Workspace Store
    participant LayoutUtils as Layout Utils
    participant UI as UI Components

    User->>HotkeyHandler: Press FOCUS_PANE_LEFT
    HotkeyHandler->>Store: Get active tab & pane
    HotkeyHandler->>LayoutUtils: getSpatialNeighborPaneId(layout, currentPaneId, "left")
    LayoutUtils->>LayoutUtils: findPanePath(layout, currentPaneId)
    LayoutUtils->>LayoutUtils: Traverse to matching-axis ancestor split
    LayoutUtils->>LayoutUtils: Select sibling branch & descend
    LayoutUtils-->>HotkeyHandler: Neighbor pane ID (or null)
    alt Neighbor Found
        HotkeyHandler->>Store: setActivePane(tabId, neighborPaneId)
        Store->>UI: Update focus state
        UI-->>User: Display focused pane
    else No Neighbor
        HotkeyHandler-->>User: No action (boundary reached)
    end
Loading

推定コードレビュー努力

🎯 4 (複雑) | ⏱️ ~60分

ポエム

🐰 ペーンは四方八方、空間に踊り
左右上下、新しい道
タイトルは一つに集約され
ナビゲーション、すっきりと
デスクトップの旅、もっと美しく

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% 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 PR タイトルが取り込むコミットの主な内容(空間認識型ペインナビゲーション、v2 Diff ビューア、タブタイトル解決 API)を正確に反映している。
Description check ✅ Passed PR 説明が必要なセクションを完全に含む。改善内容の詳細、コンフリクト解決、フォーク固有機能の保持、テスト計画がすべて記載されている。

✏️ 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 upstream-merge/pr3-spatial-pane-diff-viewer

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f460cd30c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@MocA-Love MocA-Love changed the title feat(upstream): spatial pane navigation + v2 diff viewer & tab title resolution feat(upstream): 空間認識型ペインナビゲーション + v2 Diff ビューア + タブタイトル解決API刷新 Apr 14, 2026
@MocA-Love MocA-Love changed the title feat(upstream): 空間認識型ペインナビゲーション + v2 Diff ビューア + タブタイトル解決API刷新 [PR3/5] feat(upstream): 空間認識型ペインナビゲーション + v2 Diff ビューア + タブタイトル解決API刷新 Apr 14, 2026
@MocA-Love
Copy link
Copy Markdown
Owner Author

Codex レビューへの対応

2件の P2 指摘を両方修正しました (a1cfba149)。

1. Windows パスセパレータの取り扱い (P2)

指摘: usePaneRegistry.tsxgetFileName/ でしか分割していないため、Windows 形式のパス C:\repo\foo.ts がフルパスのまま表示される。superset-sh#3420 以前は split(/[/\\]/) を使っていたクロスプラットフォーム対応だった。

修正:

function getFileName(filePath: string): string {
-	return filePath.split("/").pop() ?? filePath;
+	return filePath.split(/[/\\]/).pop() || filePath;
}

2. v2 useWorkspaceHotkeys の PREV_TAB/NEXT_TAB ハンドラ復元 (P2)

指摘: superset-sh#3403 が v2 useWorkspaceHotkeys から PREV_TAB/NEXT_TAB ハンドラを削除したが、PR#2 で cherry-pick される superset-sh#3422 で null-bound としてレジストリ ID は維持されている。ハンドラがないと、ユーザーが Settings でこれらを rebind してもコールバックが発火しない。

修正: upstream の c925f4d4a での復元に合わせて v2 useWorkspaceHotkeys に PREV_TAB/NEXT_TAB のハンドラを追加。これで override ベースのタブナビゲーションが v2 workspace でも動作する。

検証結果

  • bun run typecheck パス
  • ✅ Biome check パス

saddlepaddle and others added 4 commits April 15, 2026 02:33
…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.
…ne title resolution (superset-sh#3420)

* feat(desktop): v2 diff viewer opens in its own tab + pane-derived tab titles

openDiffPane now scans all tabs for an existing diff pane (focus + scroll)
and falls back to addTab, so clicking a file in the Changes sidebar never
hijacks the focused editor tab.

Collapses tab/pane title resolution onto a single canonical field:
PaneDefinition.getTitle is tightened to (pane) => string, file's rich JSX
moves into the existing renderTitle hook, and a new resolveTabTitle helper
powers both the tab bar and the "Move to Tab" context menu. tab.titleOverride
is reserved for user renames; every auto-default caller is stripped and
multi-pane tabs fall back to "Tab N" instead of "tab-<uuid>".

* feat(desktop): pane-derived tab titles reserve tab.titleOverride for user renames

Preset execution and workspace bootstrap were baking preset.name /
terminal.label onto tab.titleOverride, which meant those names persisted
misleadingly after a tab was split and couldn't be distinguished from a real
user rename. Move both to the pane's titleOverride instead, and teach
resolveTabTitle to read pane.titleOverride before falling through to
getTitle() for single-pane tabs. tab.titleOverride is now written only by
the tab-bar rename action; splitting a named tab flips the label to "Tab N"
while the pane keeps its name in its header, and user renames still win
over everything.

* fix(desktop): browser.getTitle falls back to "Browser" for about:blank

Unnavigated browser panes had their pane header fall through to pane.id
(a raw UUID) because getTitle returned undefined for about:blank and the
old titleOverride: "Browser" default was removed along with the other
auto-default titleOverride writes.

* fix(desktop): browser tab title uses URL.host to preserve port

URL.hostname drops the port, so localhost:3000 and localhost:4000 both
rendered as "localhost" in the tab bar. URL.host keeps the port when one
is explicitly set.
…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.
@MocA-Love MocA-Love force-pushed the upstream-merge/pr3-spatial-pane-diff-viewer branch from a1cfba1 to d26a7c9 Compare April 14, 2026 17:34
Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx (1)

123-137: ⚠️ Potential issue | 🟠 Major

displayName がタブタイトルに反映されていません

renderTitledisplayName を優先していますが、getTitle は常に filePath の basename を返しています。openFilePane(..., displayName) で開くメモ系タブはヘッダーとタブ名がずれるので、文字列版と ReactNode 版で同じ表示名解決を使った方がよいです。

💡 表示名の解決を 1 箇所に寄せる例
 function getFileName(filePath: string): string {
 	return filePath.split(/[/\\]/).pop() || filePath;
 }
+
+function getFileDisplayName(data: FilePaneData): string {
+	return data.displayName ?? getFileName(data.filePath);
+}
@@
-				getTitle: (pane) => getFileName((pane.data as FilePaneData).filePath),
+				getTitle: (pane) => getFileDisplayName(pane.data as FilePaneData),
 				renderTitle: (ctx: RendererContext<PaneViewerData>) => {
 					const data = ctx.pane.data as FilePaneData;
-					const name = data.displayName ?? getFileName(data.filePath);
+					const name = getFileDisplayName(data);
 					return (
🤖 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/usePaneRegistry/usePaneRegistry.tsx
around lines 123 - 137, getTitle currently always returns getFileName((pane.data
as FilePaneData).filePath) while renderTitle prefers data.displayName, causing
tab label/header mismatch for panes opened via openFilePane(..., displayName).
Fix by centralizing title resolution: add or use a helper (e.g.,
resolvePaneTitle or reuse getFileName logic) that returns data.displayName ??
getFileName(data.filePath) for FilePaneData, then have both getTitle and
renderTitle call that helper (reference: getTitle, renderTitle, FilePaneData,
getFileName, openFilePane, displayName).
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx (1)

288-300: ⚠️ Potential issue | 🟠 Major

旧レイアウトの diff pane まで再利用してしまいます

ここは kind === "diff" だけで再利用対象にしているので、以前の実装で保存された「他 pane と同居している diff」を持つワークスペースでも、そのタブに戻ってしまいます。今回の仕様は diff を独立タブで開くことなので、再利用するのは diff 専用タブに限定しないと移行後も旧挙動が残ります。

💡 例: 専用 diff タブだけ再利用する
 		(filePath: string) => {
 			const state = store.getState();
-			for (const tab of state.tabs) {
-				for (const pane of Object.values(tab.panes)) {
-					if (pane.kind !== "diff") continue;
-					const prev = pane.data as DiffPaneData;
-					state.setPaneData({
-						paneId: pane.id,
-						data: {
-							...prev,
-							path: filePath,
-						} as PaneViewerData,
-					});
-					state.setActiveTab(tab.id);
-					state.setActivePane({ tabId: tab.id, paneId: pane.id });
-					return;
-				}
+			for (const tab of state.tabs) {
+				const panes = Object.values(tab.panes);
+				if (panes.length !== 1 || panes[0]?.kind !== "diff") continue;
+				const pane = panes[0];
+				const prev = pane.data as DiffPaneData;
+				state.setPaneData({
+					paneId: pane.id,
+					data: {
+						...prev,
+						path: filePath,
+					} as PaneViewerData,
+				});
+				state.setActiveTab(tab.id);
+				state.setActivePane({ tabId: tab.id, paneId: pane.id });
+				return;
 			}
 			state.addTab({
🤖 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/page.tsx
around lines 288 - 300, 現在のループ再利用ロジックは pane.kind === "diff" のみで旧レイアウトの「他 pane
と同居する diff」まで再利用してしまうので、diff 専用タブに限定するチェックを追加してください:ループ内で tab(state.tabs
の要素)を調べ、当該 tab が専用 diff タブであること(例:Object.values(tab.panes).every(p => p.kind ===
"diff") またはタブに専用フラグがあればそれを使う)を確認してから DiffPaneData を prev として state.setPaneData /
state.setActiveTab / state.setActivePane を呼ぶように変更してください。
🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts (1)

90-188: preset.name || undefined は変数化して重複を減らしたいです。

同じ式が複数分岐で繰り返されているため、1 か所に寄せると可読性と変更耐性が上がります。

リファクタ案
 		try {
+			const paneTitleOverride = preset.name || undefined;
 			switch (plan) {
 				case "new-tab-single": {
 					const id = await createSessionWithCommand(
 						preset.commands[0] as string,
 					);
 					state.addTab({
-						panes: [makeTerminalPane(id, preset.name || undefined)],
+						panes: [makeTerminalPane(id, paneTitleOverride)],
 					});
 					break;
 				}
@@
-						const panes = ids.map((id) =>
-							makeTerminalPane(id, preset.name || undefined),
-						);
+						const panes = ids.map((id) => makeTerminalPane(id, paneTitleOverride));
🤖 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/useV2PresetExecution/useV2PresetExecution.ts
around lines 90 - 188, The repeated expression preset.name || undefined is
duplicated across multiple cases in the switch inside useV2PresetExecution;
extract it into a single const (e.g., paneTitle or titleFallback) declared
before the switch (or at top of the switch block) and replace every occurrence
passed to makeTerminalPane(...) with that variable (affecting cases
"new-tab-single", "new-tab-multi-pane", "new-tab-per-command",
"active-tab-single", and "active-tab-multi-pane"); ensure the variable is used
for all makeTerminalPane calls so the value is calculated once and duplication
is removed.
🤖 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/usePaneRegistry/usePaneRegistry.tsx:
- Around line 47-49: The onBeforeClose handler still uses a hardcoded split("/")
to derive basenames while getFileName(filePath: string) already handles both "/"
and "\\"; update onBeforeClose to call getFileName(...) wherever it currently
does filePath.split("/") (or similar) so the dirty-file confirmation dialog
shows the basename consistently on Windows and POSIX systems, and scan the same
file for any other remaining split("/") usages and replace them with
getFileName.

In `@packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts`:
- Line 17: The fallback title uses tabs.indexOf(tab) which can return -1 for
different object references, producing "Tab 0"; change the logic in
resolveTabTitle to compute the index by id (use tabs.findIndex(t => t.id ===
tab.id) or equivalent) and ensure you guard against -1 by falling back to 0
before adding 1 (e.g., index = Math.max(foundIndex, 0)); update the return that
currently uses tabs.indexOf(tab) to use this id-based index calculation so
titles are stable across reference mismatches.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx:
- Around line 123-137: getTitle currently always returns getFileName((pane.data
as FilePaneData).filePath) while renderTitle prefers data.displayName, causing
tab label/header mismatch for panes opened via openFilePane(..., displayName).
Fix by centralizing title resolution: add or use a helper (e.g.,
resolvePaneTitle or reuse getFileName logic) that returns data.displayName ??
getFileName(data.filePath) for FilePaneData, then have both getTitle and
renderTitle call that helper (reference: getTitle, renderTitle, FilePaneData,
getFileName, openFilePane, displayName).

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/page.tsx:
- Around line 288-300: 現在のループ再利用ロジックは pane.kind === "diff" のみで旧レイアウトの「他 pane
と同居する diff」まで再利用してしまうので、diff 専用タブに限定するチェックを追加してください:ループ内で tab(state.tabs
の要素)を調べ、当該 tab が専用 diff タブであること(例:Object.values(tab.panes).every(p => p.kind ===
"diff") またはタブに専用フラグがあればそれを使う)を確認してから DiffPaneData を prev として state.setPaneData /
state.setActiveTab / state.setActivePane を呼ぶように変更してください。

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.ts:
- Around line 90-188: The repeated expression preset.name || undefined is
duplicated across multiple cases in the switch inside useV2PresetExecution;
extract it into a single const (e.g., paneTitle or titleFallback) declared
before the switch (or at top of the switch block) and replace every occurrence
passed to makeTerminalPane(...) with that variable (affecting cases
"new-tab-single", "new-tab-multi-pane", "new-tab-per-command",
"active-tab-single", and "active-tab-multi-pane"); ensure the variable is used
for all makeTerminalPane calls so the value is calculated once and duplication
is removed.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4273840b-872d-4b71-bfac-f9f0d5e0aa3e

📥 Commits

Reviewing files that changed from the base of the PR and between 85f07f4 and d26a7c9.

📒 Files selected for processing (23)
  • apps/desktop/src/renderer/hotkeys/registry.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildSetupPaneLayout.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useDefaultContextMenuActions/useDefaultContextMenuActions.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2PresetExecution/useV2PresetExecution.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
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • packages/panes/src/core/store/store.test.ts
  • packages/panes/src/core/store/store.ts
  • packages/panes/src/core/store/utils/index.ts
  • packages/panes/src/core/store/utils/utils.test.ts
  • packages/panes/src/core/store/utils/utils.ts
  • packages/panes/src/index.ts
  • packages/panes/src/react/components/Workspace/Workspace.tsx
  • packages/panes/src/react/components/Workspace/components/Tab/components/Pane/Pane.tsx
  • packages/panes/src/react/components/Workspace/index.ts
  • packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts
  • packages/panes/src/react/index.ts
  • packages/panes/src/react/types.ts
💤 Files with no reviewable changes (4)
  • packages/panes/src/core/store/store.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/stores/tabs/utils.ts

Comment on lines 47 to 49
function getFileName(filePath: string): string {
return filePath.split("/").pop() ?? filePath;
return filePath.split(/[/\\]/).pop() || filePath;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

basename の解決をこの helper に寄せ切れていません

この helper 自体は直っていますが、同じファイルの onBeforeClose はまだ split("/") のままです。Windows では dirty-file の確認ダイアログだけフルパス表示が残るので、basename 解決は getFileName() に一本化した方が安全です。

💡 併せて置き換えておくと安全です
-					const name = data.filePath.split("/").pop();
+					const name = getFileName(data.filePath);
🤖 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/usePaneRegistry/usePaneRegistry.tsx
around lines 47 - 49, The onBeforeClose handler still uses a hardcoded
split("/") to derive basenames while getFileName(filePath: string) already
handles both "/" and "\\"; update onBeforeClose to call getFileName(...)
wherever it currently does filePath.split("/") (or similar) so the dirty-file
confirmation dialog shows the basename consistently on Windows and POSIX
systems, and scan the same file for any other remaining split("/") usages and
replace them with getFileName.

onlyPane.titleOverride ?? registry[onlyPane.kind]?.getTitle?.(onlyPane);
if (fromPane) return fromPane;
}
return `Tab ${tabs.indexOf(tab) + 1}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

フォールバックタイトルが Tab 0 になり得ます。

tabs.indexOf(tab) は参照一致前提なので、同一 id でも別参照の tab が渡ると -1 になり、Tab 0 が表示されます。id ベースで index を解決する方が安全です。

修正案
-	return `Tab ${tabs.indexOf(tab) + 1}`;
+	const index = tabs.findIndex((t) => t.id === tab.id);
+	return `Tab ${index >= 0 ? index + 1 : 1}`;
📝 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
return `Tab ${tabs.indexOf(tab) + 1}`;
const index = tabs.findIndex((t) => t.id === tab.id);
return `Tab ${index >= 0 ? index + 1 : 1}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/panes/src/react/components/Workspace/utils/resolveTabTitle.ts` at
line 17, The fallback title uses tabs.indexOf(tab) which can return -1 for
different object references, producing "Tab 0"; change the logic in
resolveTabTitle to compute the index by id (use tabs.findIndex(t => t.id ===
tab.id) or equivalent) and ensure you guard against -1 by falling back to 0
before adding 1 (e.g., index = Math.max(foundIndex, 0)); update the return that
currently uses tabs.indexOf(tab) to use this id-based index calculation so
titles are stable across reference mismatches.

@MocA-Love MocA-Love merged commit e302ab1 into main Apr 14, 2026
14 checks passed
MocA-Love pushed a commit that referenced this pull request Apr 14, 2026
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)
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.

3 participants