dmux 5.6.3 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +93 -0
- package/README.md +7 -1
- package/dist/DmuxApp.d.ts.map +1 -1
- package/dist/DmuxApp.js +273 -35
- package/dist/DmuxApp.js.map +1 -1
- package/dist/actions/implementations/closeAction.d.ts.map +1 -1
- package/dist/actions/implementations/closeAction.js +85 -70
- package/dist/actions/implementations/closeAction.js.map +1 -1
- package/dist/actions/implementations/createPullRequestAction.d.ts +4 -0
- package/dist/actions/implementations/createPullRequestAction.d.ts.map +1 -0
- package/dist/actions/implementations/createPullRequestAction.js +218 -0
- package/dist/actions/implementations/createPullRequestAction.js.map +1 -0
- package/dist/actions/implementations/index.d.ts +1 -0
- package/dist/actions/implementations/index.d.ts.map +1 -1
- package/dist/actions/implementations/index.js +1 -0
- package/dist/actions/implementations/index.js.map +1 -1
- package/dist/actions/implementations/mergeAction.js +8 -3
- package/dist/actions/implementations/mergeAction.js.map +1 -1
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +2 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/paneActions.d.ts +1 -1
- package/dist/actions/paneActions.d.ts.map +1 -1
- package/dist/actions/paneActions.js +1 -1
- package/dist/actions/paneActions.js.map +1 -1
- package/dist/actions/types.d.ts +11 -2
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js +9 -0
- package/dist/actions/types.js.map +1 -1
- package/dist/components/inputs/CleanTextInput.d.ts +1 -0
- package/dist/components/inputs/CleanTextInput.d.ts.map +1 -1
- package/dist/components/inputs/CleanTextInput.js +40 -42
- package/dist/components/inputs/CleanTextInput.js.map +1 -1
- package/dist/components/panes/PaneCard.d.ts +3 -1
- package/dist/components/panes/PaneCard.d.ts.map +1 -1
- package/dist/components/panes/PaneCard.js +14 -4
- package/dist/components/panes/PaneCard.js.map +1 -1
- package/dist/components/panes/PanesGrid.d.ts +4 -1
- package/dist/components/panes/PanesGrid.d.ts.map +1 -1
- package/dist/components/panes/PanesGrid.js +27 -15
- package/dist/components/panes/PanesGrid.js.map +1 -1
- package/dist/components/popups/config.d.ts +4 -4
- package/dist/components/popups/config.js +6 -6
- package/dist/components/popups/config.js.map +1 -1
- package/dist/components/popups/inputPopup.js +3 -3
- package/dist/components/popups/inputPopup.js.map +1 -1
- package/dist/components/popups/prReviewPopup.d.ts +12 -0
- package/dist/components/popups/prReviewPopup.d.ts.map +1 -0
- package/dist/components/popups/prReviewPopup.js +278 -0
- package/dist/components/popups/prReviewPopup.js.map +1 -0
- package/dist/components/popups/reopenWorktreePopup.js +1 -1
- package/dist/components/popups/reopenWorktreePopup.js.map +1 -1
- package/dist/components/popups/settingsPopup.js +72 -6
- package/dist/components/popups/settingsPopup.js.map +1 -1
- package/dist/components/ui/FooterHelp.d.ts.map +1 -1
- package/dist/components/ui/FooterHelp.js +5 -4
- package/dist/components/ui/FooterHelp.js.map +1 -1
- package/dist/hooks/useActionSystem.d.ts +12 -2
- package/dist/hooks/useActionSystem.d.ts.map +1 -1
- package/dist/hooks/useActionSystem.js +19 -1
- package/dist/hooks/useActionSystem.js.map +1 -1
- package/dist/hooks/useInputHandling.d.ts +2 -1
- package/dist/hooks/useInputHandling.d.ts.map +1 -1
- package/dist/hooks/useInputHandling.js +84 -18
- package/dist/hooks/useInputHandling.js.map +1 -1
- package/dist/hooks/usePaneLoading.d.ts.map +1 -1
- package/dist/hooks/usePaneLoading.js +28 -3
- package/dist/hooks/usePaneLoading.js.map +1 -1
- package/dist/hooks/usePaneSync.d.ts.map +1 -1
- package/dist/hooks/usePaneSync.js +8 -5
- package/dist/hooks/usePaneSync.js.map +1 -1
- package/dist/hooks/usePanes.d.ts.map +1 -1
- package/dist/hooks/usePanes.js +8 -1
- package/dist/hooks/usePanes.js.map +1 -1
- package/dist/hooks/useShellDetection.d.ts.map +1 -1
- package/dist/hooks/useShellDetection.js +7 -1
- package/dist/hooks/useShellDetection.js.map +1 -1
- package/dist/index.js +35 -9
- package/dist/index.js.map +1 -1
- package/dist/panes/decorative-pane.js +57 -27
- package/dist/panes/decorative-pane.js.map +1 -1
- package/dist/services/DmuxFocusService.d.ts.map +1 -1
- package/dist/services/DmuxFocusService.js +19 -14
- package/dist/services/DmuxFocusService.js.map +1 -1
- package/dist/services/PaneEventService.d.ts +11 -0
- package/dist/services/PaneEventService.d.ts.map +1 -1
- package/dist/services/PaneEventService.js +31 -3
- package/dist/services/PaneEventService.js.map +1 -1
- package/dist/services/PaneWorkerManager.d.ts.map +1 -1
- package/dist/services/PaneWorkerManager.js +1 -0
- package/dist/services/PaneWorkerManager.js.map +1 -1
- package/dist/services/PopupManager.d.ts +17 -5
- package/dist/services/PopupManager.d.ts.map +1 -1
- package/dist/services/PopupManager.js +140 -30
- package/dist/services/PopupManager.js.map +1 -1
- package/dist/services/StatusDetector.d.ts +1 -0
- package/dist/services/StatusDetector.d.ts.map +1 -1
- package/dist/services/StatusDetector.js +44 -0
- package/dist/services/StatusDetector.js.map +1 -1
- package/dist/services/TmuxHookManager.d.ts.map +1 -1
- package/dist/services/TmuxHookManager.js +3 -2
- package/dist/services/TmuxHookManager.js.map +1 -1
- package/dist/services/TmuxService.d.ts +7 -0
- package/dist/services/TmuxService.d.ts.map +1 -1
- package/dist/services/TmuxService.js +15 -1
- package/dist/services/TmuxService.js.map +1 -1
- package/dist/theme/colors.d.ts +24 -10
- package/dist/theme/colors.d.ts.map +1 -1
- package/dist/theme/colors.js +143 -19
- package/dist/theme/colors.js.map +1 -1
- package/dist/theme/themePalette.d.ts +6 -0
- package/dist/theme/themePalette.d.ts.map +1 -0
- package/dist/theme/themePalette.js +18 -0
- package/dist/theme/themePalette.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agentLaunch.d.ts +2 -0
- package/dist/utils/agentLaunch.d.ts.map +1 -1
- package/dist/utils/agentLaunch.js +10 -9
- package/dist/utils/agentLaunch.js.map +1 -1
- package/dist/utils/aiMerge.d.ts +4 -0
- package/dist/utils/aiMerge.d.ts.map +1 -1
- package/dist/utils/aiMerge.js +7 -4
- package/dist/utils/aiMerge.js.map +1 -1
- package/dist/utils/asciiArt.d.ts.map +1 -1
- package/dist/utils/asciiArt.js +2 -1
- package/dist/utils/asciiArt.js.map +1 -1
- package/dist/utils/attachAgent.d.ts.map +1 -1
- package/dist/utils/attachAgent.js +20 -1
- package/dist/utils/attachAgent.js.map +1 -1
- package/dist/utils/codexHooks.d.ts +25 -0
- package/dist/utils/codexHooks.d.ts.map +1 -0
- package/dist/utils/codexHooks.js +131 -0
- package/dist/utils/codexHooks.js.map +1 -0
- package/dist/utils/conflictResolutionPane.d.ts.map +1 -1
- package/dist/utils/conflictResolutionPane.js +4 -0
- package/dist/utils/conflictResolutionPane.js.map +1 -1
- package/dist/utils/generated-agents-doc.d.ts +1 -1
- package/dist/utils/generated-agents-doc.js +1 -1
- package/dist/utils/githubPullRequest.d.ts +18 -0
- package/dist/utils/githubPullRequest.d.ts.map +1 -0
- package/dist/utils/githubPullRequest.js +196 -0
- package/dist/utils/githubPullRequest.js.map +1 -0
- package/dist/utils/input.d.ts +7 -6
- package/dist/utils/input.d.ts.map +1 -1
- package/dist/utils/input.js +47 -24
- package/dist/utils/input.js.map +1 -1
- package/dist/utils/paneColors.d.ts +7 -0
- package/dist/utils/paneColors.d.ts.map +1 -0
- package/dist/utils/paneColors.js +42 -0
- package/dist/utils/paneColors.js.map +1 -0
- package/dist/utils/paneCreation.d.ts.map +1 -1
- package/dist/utils/paneCreation.js +103 -34
- package/dist/utils/paneCreation.js.map +1 -1
- package/dist/utils/paneTitle.d.ts +1 -0
- package/dist/utils/paneTitle.d.ts.map +1 -1
- package/dist/utils/paneTitle.js +5 -1
- package/dist/utils/paneTitle.js.map +1 -1
- package/dist/utils/paneTitlePrefix.d.ts +8 -0
- package/dist/utils/paneTitlePrefix.d.ts.map +1 -0
- package/dist/utils/paneTitlePrefix.js +20 -0
- package/dist/utils/paneTitlePrefix.js.map +1 -0
- package/dist/utils/popup.d.ts +1 -0
- package/dist/utils/popup.d.ts.map +1 -1
- package/dist/utils/popup.js +9 -2
- package/dist/utils/popup.js.map +1 -1
- package/dist/utils/prSummary.d.ts +34 -0
- package/dist/utils/prSummary.d.ts.map +1 -0
- package/dist/utils/prSummary.js +130 -0
- package/dist/utils/prSummary.js.map +1 -0
- package/dist/utils/projectActions.d.ts +6 -0
- package/dist/utils/projectActions.d.ts.map +1 -1
- package/dist/utils/projectActions.js +46 -0
- package/dist/utils/projectActions.js.map +1 -1
- package/dist/utils/reopenWorktree.d.ts.map +1 -1
- package/dist/utils/reopenWorktree.js +37 -5
- package/dist/utils/reopenWorktree.js.map +1 -1
- package/dist/utils/resumeBranches.d.ts.map +1 -1
- package/dist/utils/resumeBranches.js +9 -3
- package/dist/utils/resumeBranches.js.map +1 -1
- package/dist/utils/settingsManager.d.ts +11 -3
- package/dist/utils/settingsManager.d.ts.map +1 -1
- package/dist/utils/settingsManager.js +96 -21
- package/dist/utils/settingsManager.js.map +1 -1
- package/dist/utils/sidebarProjects.d.ts +10 -3
- package/dist/utils/sidebarProjects.d.ts.map +1 -1
- package/dist/utils/sidebarProjects.js +125 -10
- package/dist/utils/sidebarProjects.js.map +1 -1
- package/dist/utils/tmuxHookCommands.d.ts +6 -0
- package/dist/utils/tmuxHookCommands.d.ts.map +1 -1
- package/dist/utils/tmuxHookCommands.js +12 -0
- package/dist/utils/tmuxHookCommands.js.map +1 -1
- package/dist/utils/tmuxSendKeys.d.ts +2 -0
- package/dist/utils/tmuxSendKeys.d.ts.map +1 -0
- package/dist/utils/tmuxSendKeys.js +8 -0
- package/dist/utils/tmuxSendKeys.js.map +1 -0
- package/dist/utils/toastLayout.d.ts +10 -0
- package/dist/utils/toastLayout.d.ts.map +1 -0
- package/dist/utils/toastLayout.js +24 -0
- package/dist/utils/toastLayout.js.map +1 -0
- package/dist/utils/welcomePane.d.ts +5 -1
- package/dist/utils/welcomePane.d.ts.map +1 -1
- package/dist/utils/welcomePane.js +33 -3
- package/dist/utils/welcomePane.js.map +1 -1
- package/dist/utils/welcomePaneManager.d.ts +3 -1
- package/dist/utils/welcomePaneManager.d.ts.map +1 -1
- package/dist/utils/welcomePaneManager.js +40 -2
- package/dist/utils/welcomePaneManager.js.map +1 -1
- package/dist/workers/PaneWorker.js +77 -0
- package/dist/workers/PaneWorker.js.map +1 -1
- package/dist/workers/WorkerMessages.d.ts +9 -1
- package/dist/workers/WorkerMessages.d.ts.map +1 -1
- package/dist/workers/WorkerMessages.js.map +1 -1
- package/dist/workers/panePollingWorker.js +33 -2
- package/dist/workers/panePollingWorker.js.map +1 -1
- package/package.json +1 -1
package/README.ja.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./dmux.png" alt="dmux logo" width="400" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h3 align="center">tmuxとworktreeを使った並列エージェント</h3>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
分離されたgit worktreeで複数のAIコーディングエージェントを管理。<br/>
|
|
9
|
+
ブランチ作成、開発、マージを — すべて並列で実行。
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://dmux.ai"><strong>ドキュメント</strong></a> ·
|
|
14
|
+
<a href="https://dmux.ai#getting-started"><strong>クイックスタート</strong></a> ·
|
|
15
|
+
<a href="https://github.com/formkit/dmux/issues"><strong>イシュー</strong></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<strong>言語:</strong>
|
|
20
|
+
<a href="./README.md">English</a> |
|
|
21
|
+
<a href="./README.ja.md">日本語</a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<img src="./dmux.webp" alt="dmux demo" width="100%" />
|
|
27
|
+
|
|
28
|
+
## インストール
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g dmux
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## クイックスタート
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd /path/to/your/project
|
|
38
|
+
dmux
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`n`キーを押して新しいペインを作成し、プロンプトを入力、1つ以上のエージェントを選択(またはプレーンターミナルの場合は選択なし)すると、dmuxが残りの処理(worktree、ブランチ、エージェント起動)を自動的に処理します。
|
|
42
|
+
|
|
43
|
+
## dmuxとは
|
|
44
|
+
|
|
45
|
+
dmuxは各タスクに対してtmuxペインを作成します。各ペインには独自のgit worktreeとブランチが割り当てられるため、エージェントは完全に分離されて作業できます。タスクが完了したら、ペインメニューで`m`を押してマージを選択すると、メインブランチに変更を取り込めます。
|
|
46
|
+
|
|
47
|
+
- **Worktree分離** — 各ペインは完全な作業コピーで、エージェント間の競合はありません
|
|
48
|
+
- **エージェントサポート** — Claude Code、Codex、OpenCode、Cline CLI、Gemini CLI、Qwen CLI、Amp CLI、pi CLI、Cursor CLI、Copilot CLI、Crush CLI
|
|
49
|
+
- **複数選択起動** — プロンプトごとに有効なエージェントを任意の組み合わせで選択可能
|
|
50
|
+
- **AI命名** — ブランチとコミットメッセージを自動生成
|
|
51
|
+
- **スマートマージ** — 自動コミット、マージ、クリーンアップを1ステップで実行
|
|
52
|
+
- **macOS通知** — バックグラウンドペインが処理完了時にネイティブのアラートを送信
|
|
53
|
+
- **内蔵ファイルブラウザ** — dmuxを離れずにペインのworktreeを閲覧、ファイル検索、コードや差分のプレビュー
|
|
54
|
+
- **ペイン表示制御** — 個別ペインの非表示、プロジェクトの分離、後から全表示の復元
|
|
55
|
+
- **マルチプロジェクト** — 同じセッションに複数のリポジトリを追加
|
|
56
|
+
- **ライフサイクルフック** — worktree作成、プレマージ、ポストマージ時のスクリプト実行
|
|
57
|
+
|
|
58
|
+
## キーボードショートカット
|
|
59
|
+
|
|
60
|
+
| キー | アクション |
|
|
61
|
+
|-----|--------|
|
|
62
|
+
| `n` | 新しいペイン(worktree + エージェント) |
|
|
63
|
+
| `t` | 新しいターミナルペイン |
|
|
64
|
+
| `j` / `Enter` | ペインにジャンプ |
|
|
65
|
+
| `m` | ペインメニューを開く |
|
|
66
|
+
| `f` | 選択したペインのworktreeを閲覧 |
|
|
67
|
+
| `x` | ペインを閉じる |
|
|
68
|
+
| `h` | 選択したペインを表示/非表示 |
|
|
69
|
+
| `H` | 他のすべてのペインを表示/非表示 |
|
|
70
|
+
| `p` | 別のプロジェクトに新しいペインを作成 |
|
|
71
|
+
| `P` | 選択したプロジェクトのペインのみ表示、その後すべて表示 |
|
|
72
|
+
| `s` | 設定 |
|
|
73
|
+
| `q` | 終了 |
|
|
74
|
+
|
|
75
|
+
## 必要要件
|
|
76
|
+
|
|
77
|
+
- tmux 3.0+
|
|
78
|
+
- Node.js 18+
|
|
79
|
+
- Git 2.20+
|
|
80
|
+
- 少なくとも1つのサポート対象エージェントCLI(例:[Claude Code](https://docs.anthropic.com/en/docs/claude-code)、[Codex](https://github.com/openai/codex)、[OpenCode](https://github.com/opencode-ai/opencode)、[Cline CLI](https://docs.cline.bot/cline-cli/getting-started)、[Gemini CLI](https://github.com/google-gemini/gemini-cli)、[Qwen CLI](https://github.com/QwenLM/qwen-code)、[Amp CLI](https://ampcode.com/manual)、[pi CLI](https://www.npmjs.com/package/@mariozechner/pi-coding-agent)、[Cursor CLI](https://docs.cursor.com/en/cli/overview)、[Copilot CLI](https://github.com/github/copilot-cli)、[Crush CLI](https://github.com/charmbracelet/crush))
|
|
81
|
+
- [OpenRouter APIキー](https://openrouter.ai/)(オプション、AIブランチ名とコミットメッセージ用)
|
|
82
|
+
|
|
83
|
+
## ドキュメント
|
|
84
|
+
|
|
85
|
+
完全なドキュメントは **[dmux.ai](https://dmux.ai)** でご覧いただけます。セットアップガイド、設定、フックの情報が含まれています。
|
|
86
|
+
|
|
87
|
+
## コントリビュート
|
|
88
|
+
|
|
89
|
+
推奨されるローカル「dmux-on-dmux」開発ループ、フックセットアップ、PRワークフローについては、**[CONTRIBUTING.md](./CONTRIBUTING.md)** をご覧ください。
|
|
90
|
+
|
|
91
|
+
## ライセンス
|
|
92
|
+
|
|
93
|
+
MIT
|
package/README.md
CHANGED
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
<a href="https://github.com/formkit/dmux/issues"><strong>Issues</strong></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
+
<p align="center">
|
|
19
|
+
<strong>Language:</strong>
|
|
20
|
+
<a href="./README.md">English</a> |
|
|
21
|
+
<a href="./README.ja.md">日本語</a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
18
24
|
---
|
|
19
25
|
|
|
20
26
|
<img src="./dmux.webp" alt="dmux demo" width="100%" />
|
|
@@ -36,7 +42,7 @@ Press `n` to create a new pane, type a prompt, pick one or more agents (or none
|
|
|
36
42
|
|
|
37
43
|
## What it does
|
|
38
44
|
|
|
39
|
-
dmux creates a tmux pane for each task. Every pane gets its own git worktree and branch so agents work in complete isolation. When a task is done, open the pane menu with `m` and choose Merge to bring it back into your main branch.
|
|
45
|
+
dmux creates a tmux pane for each task. Every pane gets its own git worktree and branch so agents work in complete isolation. When a task is done, open the pane menu with `m` and choose Merge to bring it back into your main branch, or Create GitHub PR to push the branch and file a pull request.
|
|
40
46
|
|
|
41
47
|
- **Worktree isolation** — each pane is a full working copy, no conflicts between agents
|
|
42
48
|
- **Agent support** — Claude Code, Codex, OpenCode, Cline CLI, Gemini CLI, Qwen CLI, Amp CLI, pi CLI, Cursor CLI, Copilot CLI, and Crush CLI
|
package/dist/DmuxApp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DmuxApp.d.ts","sourceRoot":"","sources":["../src/DmuxApp.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"DmuxApp.d.ts","sourceRoot":"","sources":["../src/DmuxApp.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAA;AAwEnE,OAAO,KAAK,EAEV,YAAY,EAGb,MAAM,YAAY,CAAA;AAmCnB,QAAA,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CA2jDnC,CAAA;AAED,eAAe,OAAO,CAAA"}
|
package/dist/DmuxApp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useState, useEffect, useMemo } from "react";
|
|
1
|
+
import React, { useState, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import { Box, Text, useApp, useStdout, useInput } from "ink";
|
|
3
|
+
import stringWidth from "string-width";
|
|
3
4
|
import { TmuxService } from "./services/TmuxService.js";
|
|
4
5
|
// Hooks
|
|
5
6
|
import usePanes from "./hooks/usePanes.js";
|
|
@@ -43,14 +44,21 @@ import { getPaneDisplayName } from "./utils/paneTitle.js";
|
|
|
43
44
|
import { FOOTER_TIP_ROTATION_INTERVAL, getFooterTips, getNextFooterTipIndex, getRandomFooterTipIndex, } from "./utils/footerTips.js";
|
|
44
45
|
const __filename = fileURLToPath(import.meta.url);
|
|
45
46
|
const __dirname = dirname(__filename);
|
|
47
|
+
const ACTIVE_PANE_SYNC_INTERVAL_MS = 125;
|
|
46
48
|
import PanesGrid from "./components/panes/PanesGrid.js";
|
|
47
49
|
import CommandPromptDialog from "./components/dialogs/CommandPromptDialog.js";
|
|
48
50
|
import FileCopyPrompt from "./components/ui/FileCopyPrompt.js";
|
|
49
51
|
import FooterHelp from "./components/ui/FooterHelp.js";
|
|
50
52
|
import TmuxHooksPromptDialog from "./components/dialogs/TmuxHooksPromptDialog.js";
|
|
51
53
|
import { PaneEventService } from "./services/PaneEventService.js";
|
|
52
|
-
import { buildProjectActionLayout, buildVisualNavigationRows, buildGroupStartRows, getProjectActionByIndex, } from "./utils/projectActions.js";
|
|
54
|
+
import { buildProjectActionLayout, buildVisualNavigationRows, buildGroupStartRows, getProjectActionByIndex, resolveSelectionAfterPaneClose, } from "./utils/projectActions.js";
|
|
53
55
|
import { getPaneProjectRoot } from "./utils/paneProject.js";
|
|
56
|
+
import { applyDmuxTheme, getDmuxThemePalette, } from "./theme/colors.js";
|
|
57
|
+
import { applyTmuxThemeToSession, refreshWelcomePaneTheme, } from "./utils/welcomePane.js";
|
|
58
|
+
import { syncWelcomePaneVisibility } from "./utils/welcomePaneManager.js";
|
|
59
|
+
import { getPaneColorTheme, resolveProjectColorTheme, } from "./utils/paneColors.js";
|
|
60
|
+
import { getPaneTitlePrefixValue, paneNeedsAnimatedTitlePrefix, PANE_TITLE_BUSY_FRAMES, } from "./utils/paneTitlePrefix.js";
|
|
61
|
+
import { getPaneTmuxDisplayTitle } from "./utils/paneTitle.js";
|
|
54
62
|
const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoot, autoUpdater, controlPaneId, }) => {
|
|
55
63
|
const { stdout } = useStdout();
|
|
56
64
|
const terminalHeight = stdout?.rows || 40;
|
|
@@ -58,13 +66,19 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
58
66
|
const sessionProjectRoot = projectRoot || process.cwd();
|
|
59
67
|
/* panes state moved to usePanes */
|
|
60
68
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
69
|
+
const [focusedPaneId, setFocusedPaneId] = useState(null);
|
|
61
70
|
const { statusMessage, setStatusMessage } = useStatusMessages();
|
|
62
71
|
const [isCreatingPane, setIsCreatingPane] = useState(false);
|
|
63
72
|
const { trackProjectActivity, isProjectBusy: isTrackedProjectBusy, } = useProjectActivity(sessionProjectRoot);
|
|
64
73
|
// Settings state
|
|
65
74
|
const [settingsManager] = useState(() => new SettingsManager(projectRoot));
|
|
66
75
|
const { projectSettings, saveSettings } = useProjectSettings(settingsFile);
|
|
67
|
-
const
|
|
76
|
+
const [themeRefreshNonce, setThemeRefreshNonce] = useState(0);
|
|
77
|
+
const [settings, setSettings] = useState(() => new SettingsManager(sessionProjectRoot).getSettings());
|
|
78
|
+
const paneTitlePrefixCacheRef = useRef(new Map());
|
|
79
|
+
const paneTitleLabelCacheRef = useRef(new Map());
|
|
80
|
+
const paneActiveBorderStyleCacheRef = useRef(new Map());
|
|
81
|
+
const paneTitleSpinnerFrameRef = useRef(0);
|
|
68
82
|
// Dialog state management
|
|
69
83
|
const dialogState = useDialogState();
|
|
70
84
|
const { showCommandPrompt, setShowCommandPrompt, commandInput, setCommandInput, showFileCopyPrompt, setShowFileCopyPrompt, currentCommandType, setCurrentCommandType, runningCommand, setRunningCommand, quitConfirmMode, setQuitConfirmMode, } = dialogState;
|
|
@@ -78,6 +92,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
78
92
|
// Agent selection is settings-driven.
|
|
79
93
|
// Installation checks are performed lazily in the Enabled Agents settings popup.
|
|
80
94
|
const availableAgents = resolveEnabledAgentsSelection(settings.enabledAgents);
|
|
95
|
+
const getAvailableAgentsForProject = (targetProjectRoot = selectedProjectRoot) => resolveEnabledAgentsSelection(new SettingsManager(targetProjectRoot).getSettings().enabledAgents);
|
|
81
96
|
const footerTips = useMemo(() => getFooterTips(isDevMode), [isDevMode]);
|
|
82
97
|
const showFooterTips = settings.showFooterTips !== false && footerTips.length > 0;
|
|
83
98
|
const [footerTipIndex, setFooterTipIndex] = useState(() => getRandomFooterTipIndex(footerTips.length));
|
|
@@ -133,12 +148,12 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
133
148
|
};
|
|
134
149
|
}, []);
|
|
135
150
|
// Panes state and persistence (skipLoading will be updated after actionSystem is initialized)
|
|
136
|
-
const { panes, setPanes, sidebarProjects, isLoading, loadPanes, savePanes, saveSidebarProjects, } = usePanes(panesFile, false, sessionName, controlPaneId, useHooks);
|
|
151
|
+
const { panes, setPanes, sidebarProjects, isLoading, loadPanes, savePanes, saveSidebarProjects, eventMode, } = usePanes(panesFile, false, sessionName, controlPaneId, useHooks);
|
|
137
152
|
// Check for tmux hooks preference on startup
|
|
138
153
|
useEffect(() => {
|
|
139
154
|
const checkHooksPreference = async () => {
|
|
140
155
|
// Check if user already has a preference
|
|
141
|
-
const settings =
|
|
156
|
+
const settings = new SettingsManager(sessionProjectRoot).getSettings();
|
|
142
157
|
if (settings.useTmuxHooks !== undefined) {
|
|
143
158
|
// User has already decided
|
|
144
159
|
setUseHooks(settings.useTmuxHooks);
|
|
@@ -153,6 +168,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
153
168
|
setUseHooks(true);
|
|
154
169
|
// Save the preference
|
|
155
170
|
settingsManager.updateSetting('useTmuxHooks', true, 'global');
|
|
171
|
+
refreshDmuxSettings();
|
|
156
172
|
}
|
|
157
173
|
else {
|
|
158
174
|
// Need to ask user - show prompt
|
|
@@ -347,18 +363,163 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
347
363
|
// getPanePositions moved to utils/tmux
|
|
348
364
|
const activeDevSourcePath = isDevMode ? process.cwd() : undefined;
|
|
349
365
|
const projectActionLayout = useMemo(() => buildProjectActionLayout(panes, sidebarProjects, sessionProjectRoot, projectName), [panes, sidebarProjects, sessionProjectRoot, projectName]);
|
|
366
|
+
const selectedPane = useMemo(() => {
|
|
367
|
+
for (const group of projectActionLayout.groups) {
|
|
368
|
+
const entry = group.panes.find((candidate) => candidate.index === selectedIndex);
|
|
369
|
+
if (entry) {
|
|
370
|
+
return entry.pane;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return undefined;
|
|
374
|
+
}, [projectActionLayout.groups, selectedIndex]);
|
|
350
375
|
const selectedProjectRoot = useMemo(() => {
|
|
351
|
-
const selectedPane = selectedIndex < panes.length ? panes[selectedIndex] : undefined;
|
|
352
376
|
if (selectedPane) {
|
|
353
377
|
return getPaneProjectRoot(selectedPane, sessionProjectRoot);
|
|
354
378
|
}
|
|
355
379
|
return (getProjectActionByIndex(projectActionLayout.actionItems, selectedIndex)?.projectRoot
|
|
356
380
|
|| sessionProjectRoot);
|
|
357
|
-
}, [
|
|
381
|
+
}, [selectedPane, selectedIndex, projectActionLayout.actionItems, sessionProjectRoot]);
|
|
382
|
+
const focusedPane = useMemo(() => focusedPaneId
|
|
383
|
+
? panes.find((pane) => pane.paneId === focusedPaneId)
|
|
384
|
+
: undefined, [focusedPaneId, panes]);
|
|
385
|
+
const activeProjectRoot = selectedProjectRoot;
|
|
386
|
+
const resolveProjectThemeName = React.useCallback((activeProjectRoot) => {
|
|
387
|
+
return resolveProjectColorTheme(activeProjectRoot, sidebarProjects);
|
|
388
|
+
}, [sidebarProjects]);
|
|
389
|
+
const activeBorderPane = focusedPane || selectedPane;
|
|
390
|
+
const activeBorderPaneId = activeBorderPane?.paneId;
|
|
391
|
+
const selectedThemeName = useMemo(() => resolveProjectThemeName(activeProjectRoot), [
|
|
392
|
+
resolveProjectThemeName,
|
|
393
|
+
activeProjectRoot,
|
|
394
|
+
themeRefreshNonce,
|
|
395
|
+
]);
|
|
396
|
+
const visiblePaneCount = useMemo(() => panes.filter((pane) => !pane.hidden).length, [panes]);
|
|
397
|
+
const controlPaneActiveBorderStyle = useMemo(() => `fg=colour${getDmuxThemePalette(selectedThemeName).activeBorder}`, [selectedThemeName]);
|
|
398
|
+
const projectThemeByRoot = useMemo(() => {
|
|
399
|
+
const themeMap = new Map();
|
|
400
|
+
for (const group of projectActionLayout.groups) {
|
|
401
|
+
const paneTheme = group.panes.find((entry) => entry.pane.colorTheme)?.pane.colorTheme;
|
|
402
|
+
themeMap.set(group.projectRoot, paneTheme || resolveProjectThemeName(group.projectRoot));
|
|
403
|
+
}
|
|
404
|
+
return themeMap;
|
|
405
|
+
}, [projectActionLayout.groups, resolveProjectThemeName, themeRefreshNonce]);
|
|
406
|
+
applyDmuxTheme(selectedThemeName);
|
|
407
|
+
const refreshDmuxSettings = (_activeProjectRoot = selectedProjectRoot) => {
|
|
408
|
+
setSettings(new SettingsManager(sessionProjectRoot).getSettings());
|
|
409
|
+
setThemeRefreshNonce((current) => current + 1);
|
|
410
|
+
};
|
|
358
411
|
const navigationRows = useMemo(() => isLoading
|
|
359
412
|
? projectActionLayout.groups.flatMap((group) => group.panes.map((entry) => [entry.index]))
|
|
360
413
|
: buildVisualNavigationRows(projectActionLayout), [isLoading, projectActionLayout]);
|
|
361
414
|
const groupStartRows = useMemo(() => isLoading ? [] : buildGroupStartRows(projectActionLayout), [isLoading, projectActionLayout]);
|
|
415
|
+
useEffect(() => {
|
|
416
|
+
try {
|
|
417
|
+
applyTmuxThemeToSession(sessionName, activeProjectRoot, selectedThemeName);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// Theme updates are best-effort at runtime.
|
|
421
|
+
}
|
|
422
|
+
void refreshWelcomePaneTheme(panesFile, activeProjectRoot, selectedThemeName);
|
|
423
|
+
}, [panesFile, activeProjectRoot, selectedThemeName, sessionName]);
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
if (isLoading) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
void syncWelcomePaneVisibility(sessionProjectRoot, controlPaneId, visiblePaneCount === 0, selectedThemeName);
|
|
429
|
+
}, [
|
|
430
|
+
controlPaneId,
|
|
431
|
+
isLoading,
|
|
432
|
+
selectedThemeName,
|
|
433
|
+
sessionProjectRoot,
|
|
434
|
+
visiblePaneCount,
|
|
435
|
+
]);
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
if (!process.env.TMUX) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const tmuxService = TmuxService.getInstance();
|
|
441
|
+
const syncPaneTitlePrefixes = () => {
|
|
442
|
+
const cachedPrefixes = paneTitlePrefixCacheRef.current;
|
|
443
|
+
const cachedLabels = paneTitleLabelCacheRef.current;
|
|
444
|
+
const cachedActiveBorderStyles = paneActiveBorderStyleCacheRef.current;
|
|
445
|
+
const activePaneIds = new Set(panes.map((pane) => pane.paneId));
|
|
446
|
+
const activeBorderStylePaneIds = new Set(activePaneIds);
|
|
447
|
+
if (controlPaneId) {
|
|
448
|
+
activeBorderStylePaneIds.add(controlPaneId);
|
|
449
|
+
}
|
|
450
|
+
for (const paneId of Array.from(cachedPrefixes.keys())) {
|
|
451
|
+
if (!activePaneIds.has(paneId)) {
|
|
452
|
+
tmuxService.unsetPaneOptionSync(paneId, '@dmux_title_prefix');
|
|
453
|
+
cachedPrefixes.delete(paneId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
for (const paneId of Array.from(cachedLabels.keys())) {
|
|
457
|
+
if (!activePaneIds.has(paneId)) {
|
|
458
|
+
tmuxService.unsetPaneOptionSync(paneId, '@dmux_title_label');
|
|
459
|
+
cachedLabels.delete(paneId);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
for (const paneId of Array.from(cachedActiveBorderStyles.keys())) {
|
|
463
|
+
if (!activeBorderStylePaneIds.has(paneId)) {
|
|
464
|
+
tmuxService.unsetPaneOptionSync(paneId, '@dmux_active_border_style');
|
|
465
|
+
cachedActiveBorderStyles.delete(paneId);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
for (const pane of panes) {
|
|
469
|
+
const paneThemeName = getPaneColorTheme(pane, sidebarProjects, sessionProjectRoot);
|
|
470
|
+
const prefixValue = getPaneTitlePrefixValue(pane, sidebarProjects, sessionProjectRoot, paneTitleSpinnerFrameRef.current);
|
|
471
|
+
const labelValue = getPaneTmuxDisplayTitle(pane, sessionProjectRoot, projectName);
|
|
472
|
+
const activeBorderStyle = `fg=colour${getDmuxThemePalette(paneThemeName).activeBorder}`;
|
|
473
|
+
if (cachedPrefixes.get(pane.paneId) !== prefixValue) {
|
|
474
|
+
tmuxService.setPaneOptionSync(pane.paneId, '@dmux_title_prefix', prefixValue);
|
|
475
|
+
cachedPrefixes.set(pane.paneId, prefixValue);
|
|
476
|
+
}
|
|
477
|
+
if (cachedLabels.get(pane.paneId) !== labelValue) {
|
|
478
|
+
tmuxService.setPaneOptionSync(pane.paneId, '@dmux_title_label', labelValue);
|
|
479
|
+
cachedLabels.set(pane.paneId, labelValue);
|
|
480
|
+
}
|
|
481
|
+
if (cachedActiveBorderStyles.get(pane.paneId) !== activeBorderStyle) {
|
|
482
|
+
tmuxService.setPaneOptionSync(pane.paneId, '@dmux_active_border_style', activeBorderStyle);
|
|
483
|
+
cachedActiveBorderStyles.set(pane.paneId, activeBorderStyle);
|
|
484
|
+
}
|
|
485
|
+
if (pane.paneId === activeBorderPaneId) {
|
|
486
|
+
tmuxService.setSessionOptionSync(sessionName, 'pane-active-border-style', activeBorderStyle);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (controlPaneId && cachedActiveBorderStyles.get(controlPaneId) !== controlPaneActiveBorderStyle) {
|
|
490
|
+
tmuxService.setPaneOptionSync(controlPaneId, '@dmux_active_border_style', controlPaneActiveBorderStyle);
|
|
491
|
+
cachedActiveBorderStyles.set(controlPaneId, controlPaneActiveBorderStyle);
|
|
492
|
+
}
|
|
493
|
+
if (!focusedPane) {
|
|
494
|
+
tmuxService.setSessionOptionSync(sessionName, 'pane-active-border-style', controlPaneActiveBorderStyle);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
const hasAnimatedPrefix = panes.some(paneNeedsAnimatedTitlePrefix);
|
|
498
|
+
if (!hasAnimatedPrefix) {
|
|
499
|
+
paneTitleSpinnerFrameRef.current = 0;
|
|
500
|
+
}
|
|
501
|
+
syncPaneTitlePrefixes();
|
|
502
|
+
if (!hasAnimatedPrefix) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const interval = setInterval(() => {
|
|
506
|
+
paneTitleSpinnerFrameRef.current = (paneTitleSpinnerFrameRef.current + 1) % PANE_TITLE_BUSY_FRAMES.length;
|
|
507
|
+
syncPaneTitlePrefixes();
|
|
508
|
+
}, 90);
|
|
509
|
+
return () => {
|
|
510
|
+
clearInterval(interval);
|
|
511
|
+
};
|
|
512
|
+
}, [
|
|
513
|
+
panes,
|
|
514
|
+
sidebarProjects,
|
|
515
|
+
sessionProjectRoot,
|
|
516
|
+
projectName,
|
|
517
|
+
activeBorderPaneId,
|
|
518
|
+
sessionName,
|
|
519
|
+
controlPaneId,
|
|
520
|
+
controlPaneActiveBorderStyle,
|
|
521
|
+
focusedPane,
|
|
522
|
+
]);
|
|
362
523
|
useEffect(() => {
|
|
363
524
|
const maxIndex = Math.max(0, projectActionLayout.totalItems - 1);
|
|
364
525
|
if (selectedIndex > maxIndex) {
|
|
@@ -368,14 +529,59 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
368
529
|
// Navigation logic moved to hook
|
|
369
530
|
const { getCardGridPosition, findCardInDirection } = useNavigation(navigationRows, groupStartRows);
|
|
370
531
|
// findCardInDirection provided by useNavigation
|
|
532
|
+
const syncSelectedIndexToFocusedPane = React.useCallback(async (activePaneId) => {
|
|
533
|
+
try {
|
|
534
|
+
const focusedPaneId = activePaneId ?? await TmuxService.getInstance().getActivePaneId();
|
|
535
|
+
if (!focusedPaneId || focusedPaneId === controlPaneId) {
|
|
536
|
+
setFocusedPaneId(null);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
setFocusedPaneId((currentPaneId) => currentPaneId === focusedPaneId ? currentPaneId : focusedPaneId);
|
|
540
|
+
const focusedIndex = panes.findIndex((pane) => pane.paneId === focusedPaneId);
|
|
541
|
+
if (focusedIndex === -1) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
setSelectedIndex((currentIndex) => currentIndex === focusedIndex ? currentIndex : focusedIndex);
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
// Focus sync is best-effort; pane lifecycle handling will correct stale IDs.
|
|
548
|
+
}
|
|
549
|
+
}, [controlPaneId, panes]);
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
const paneEventService = PaneEventService.getInstance();
|
|
552
|
+
return paneEventService.onPaneFocusChanged((event) => {
|
|
553
|
+
void syncSelectedIndexToFocusedPane(event.activePaneId);
|
|
554
|
+
});
|
|
555
|
+
}, [syncSelectedIndexToFocusedPane]);
|
|
556
|
+
useEffect(() => {
|
|
557
|
+
if (!process.env.TMUX || panes.length === 0) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
let syncInFlight = false;
|
|
561
|
+
const syncActivePane = () => {
|
|
562
|
+
if (syncInFlight) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
syncInFlight = true;
|
|
566
|
+
void syncSelectedIndexToFocusedPane().finally(() => {
|
|
567
|
+
syncInFlight = false;
|
|
568
|
+
});
|
|
569
|
+
};
|
|
570
|
+
syncActivePane();
|
|
571
|
+
const interval = setInterval(syncActivePane, ACTIVE_PANE_SYNC_INTERVAL_MS);
|
|
572
|
+
return () => {
|
|
573
|
+
clearInterval(interval);
|
|
574
|
+
};
|
|
575
|
+
}, [eventMode, panes.length, syncSelectedIndexToFocusedPane]);
|
|
371
576
|
// savePanes moved to usePanes
|
|
372
577
|
// applySmartLayout moved to utils/tmux
|
|
373
578
|
// Helper function to handle agent choice and pane creation
|
|
374
579
|
const selectAgentsForPaneCreation = async (targetProjectRoot) => {
|
|
375
|
-
|
|
580
|
+
const targetRoot = targetProjectRoot || selectedProjectRoot;
|
|
581
|
+
if (getAvailableAgentsForProject(targetRoot).length === 0) {
|
|
376
582
|
return [];
|
|
377
583
|
}
|
|
378
|
-
const selectedAgents = await popupManager.launchAgentChoicePopup(
|
|
584
|
+
const selectedAgents = await popupManager.launchAgentChoicePopup(targetRoot);
|
|
379
585
|
if (selectedAgents === null) {
|
|
380
586
|
return null;
|
|
381
587
|
}
|
|
@@ -514,7 +720,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
514
720
|
const reopenProjectRoot = targetProjectRoot || projectRoot || process.cwd();
|
|
515
721
|
let selectedAgent;
|
|
516
722
|
if (!candidate.path) {
|
|
517
|
-
if (
|
|
723
|
+
if (getAvailableAgentsForProject(reopenProjectRoot).length === 0) {
|
|
518
724
|
setStatusMessage("No enabled agents available for opening this branch");
|
|
519
725
|
setTimeout(() => setStatusMessage(""), STATUS_MESSAGE_DURATION_SHORT);
|
|
520
726
|
return;
|
|
@@ -632,7 +838,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
632
838
|
else if (result.type === "input") {
|
|
633
839
|
if (!result.onSubmit)
|
|
634
840
|
return;
|
|
635
|
-
const inputValue = await popupManager.launchInputPopup(result.title || "Input", result.message, result.placeholder, result.defaultValue, selectedProjectRoot);
|
|
841
|
+
const inputValue = await popupManager.launchInputPopup(result.title || "Input", result.message, result.placeholder, result.defaultValue, selectedProjectRoot, result.inputMaxVisibleLines);
|
|
636
842
|
if (inputValue !== null) {
|
|
637
843
|
const nextResult = await trackProjectActivity(() => result.onSubmit(inputValue), selectedProjectRoot);
|
|
638
844
|
// Recursively handle nested results
|
|
@@ -641,6 +847,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
641
847
|
}
|
|
642
848
|
}
|
|
643
849
|
}
|
|
850
|
+
else if (result.type === "pr_review") {
|
|
851
|
+
if (!result.onSubmit || !result.reviewData)
|
|
852
|
+
return;
|
|
853
|
+
const inputValue = await popupManager.launchPRReviewPopup({
|
|
854
|
+
title: result.title || "Pull Request",
|
|
855
|
+
message: result.message || "",
|
|
856
|
+
defaultValue: result.defaultValue || "",
|
|
857
|
+
repoPath: result.reviewData.repoPath,
|
|
858
|
+
sourceBranch: result.reviewData.sourceBranch,
|
|
859
|
+
targetBranch: result.reviewData.targetBranch,
|
|
860
|
+
files: result.reviewData.files,
|
|
861
|
+
aiFailed: result.reviewData.aiFailed,
|
|
862
|
+
}, selectedProjectRoot);
|
|
863
|
+
if (inputValue !== null) {
|
|
864
|
+
const nextResult = await trackProjectActivity(() => result.onSubmit(inputValue), selectedProjectRoot);
|
|
865
|
+
if (nextResult) {
|
|
866
|
+
await handleActionResult(nextResult);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
644
870
|
else if (result.type === "navigation") {
|
|
645
871
|
// Navigate to target pane if specified
|
|
646
872
|
if (result.targetPaneId) {
|
|
@@ -673,25 +899,25 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
673
899
|
projectName,
|
|
674
900
|
defaultProjectRoot: sessionProjectRoot,
|
|
675
901
|
onPaneRemove: async (paneId) => {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if (
|
|
902
|
+
const nextSelection = resolveSelectionAfterPaneClose(panes, paneId, sidebarProjects, sessionProjectRoot, projectName);
|
|
903
|
+
if (nextSelection) {
|
|
904
|
+
setSelectedIndex(nextSelection.selectedIndex);
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
const maxIndex = Math.max(0, projectActionLayout.totalItems - 2);
|
|
908
|
+
if (selectedIndex > maxIndex) {
|
|
909
|
+
setSelectedIndex(maxIndex);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const targetPaneId = nextSelection?.pane && !nextSelection.pane.hidden
|
|
913
|
+
? nextSelection.pane.paneId
|
|
914
|
+
: controlPaneId;
|
|
915
|
+
if (targetPaneId) {
|
|
690
916
|
try {
|
|
691
|
-
await TmuxService.getInstance().selectPane(
|
|
917
|
+
await TmuxService.getInstance().selectPane(targetPaneId);
|
|
692
918
|
}
|
|
693
919
|
catch {
|
|
694
|
-
// Ignore -
|
|
920
|
+
// Ignore - the target pane might have closed during cleanup
|
|
695
921
|
}
|
|
696
922
|
}
|
|
697
923
|
},
|
|
@@ -702,6 +928,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
702
928
|
launchConfirmPopup: popupManager.launchConfirmPopup.bind(popupManager),
|
|
703
929
|
launchChoicePopup: popupManager.launchChoicePopup.bind(popupManager),
|
|
704
930
|
launchInputPopup: popupManager.launchInputPopup.bind(popupManager),
|
|
931
|
+
launchPRReviewPopup: popupManager.launchPRReviewPopup.bind(popupManager),
|
|
705
932
|
launchProgressPopup: popupManager.launchProgressPopup.bind(popupManager),
|
|
706
933
|
}
|
|
707
934
|
: undefined,
|
|
@@ -816,12 +1043,14 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
816
1043
|
setShowHooksPrompt(false);
|
|
817
1044
|
setUseHooks(true);
|
|
818
1045
|
settingsManager.updateSetting('useTmuxHooks', true, 'global');
|
|
1046
|
+
refreshDmuxSettings();
|
|
819
1047
|
}
|
|
820
1048
|
else if (input === 'n') {
|
|
821
1049
|
// No - use polling
|
|
822
1050
|
setShowHooksPrompt(false);
|
|
823
1051
|
setUseHooks(false);
|
|
824
1052
|
settingsManager.updateSetting('useTmuxHooks', false, 'global');
|
|
1053
|
+
refreshDmuxSettings();
|
|
825
1054
|
}
|
|
826
1055
|
else if (key.return) {
|
|
827
1056
|
// Select current option
|
|
@@ -829,6 +1058,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
829
1058
|
const selected = hooksPromptIndex === 0;
|
|
830
1059
|
setUseHooks(selected);
|
|
831
1060
|
settingsManager.updateSetting('useTmuxHooks', selected, 'global');
|
|
1061
|
+
refreshDmuxSettings();
|
|
832
1062
|
}
|
|
833
1063
|
}, { isActive: showHooksPrompt });
|
|
834
1064
|
// Input handling - extracted to dedicated hook
|
|
@@ -856,6 +1086,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
856
1086
|
projectSettings,
|
|
857
1087
|
saveSettings,
|
|
858
1088
|
settingsManager,
|
|
1089
|
+
refreshDmuxSettings,
|
|
859
1090
|
popupManager,
|
|
860
1091
|
actionSystem,
|
|
861
1092
|
controlPaneId,
|
|
@@ -872,7 +1103,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
872
1103
|
saveSidebarProjects,
|
|
873
1104
|
loadPanes,
|
|
874
1105
|
cleanExit,
|
|
875
|
-
|
|
1106
|
+
getAvailableAgentsForProject,
|
|
876
1107
|
panesFile,
|
|
877
1108
|
projectRoot: sessionProjectRoot,
|
|
878
1109
|
projectActionItems: projectActionLayout.actionItems,
|
|
@@ -884,7 +1115,8 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
884
1115
|
// - Normal mode calculation:
|
|
885
1116
|
// - Base footer: 4 lines (marginTop + logs divider + logs line + keyboard shortcuts)
|
|
886
1117
|
// - Footer tip: +1 line when footer tips are enabled
|
|
887
|
-
// - Toast:
|
|
1118
|
+
// - Toast (active): wrapped lines + header + marginBottom
|
|
1119
|
+
// - Toast (queued, transitioning): header + marginBottom (2 lines)
|
|
888
1120
|
// - Debug info: +1 line if DEBUG_DMUX
|
|
889
1121
|
// - Status line: +1 line if updateAvailable/currentBranch/debugMessage
|
|
890
1122
|
// - Status messages: +1 line per active message
|
|
@@ -896,20 +1128,26 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
896
1128
|
else {
|
|
897
1129
|
footerLines = 0;
|
|
898
1130
|
if (showFooterHelp) {
|
|
899
|
-
footerLines =
|
|
1131
|
+
footerLines = 3; // logs divider + logs + shortcuts
|
|
900
1132
|
if (currentFooterTip) {
|
|
901
1133
|
footerLines += 1;
|
|
902
1134
|
}
|
|
903
1135
|
// Add toast notification (calculate wrapped lines + header)
|
|
904
1136
|
if (currentToast) {
|
|
905
1137
|
// Toast format: "✓ message" - icon (1) + space (1) + message
|
|
906
|
-
|
|
907
|
-
const
|
|
1138
|
+
// Use stringWidth for CJK-aware display width calculation
|
|
1139
|
+
const iconAndSpaceWidth = 2;
|
|
1140
|
+
const toastDisplayWidth = iconAndSpaceWidth + stringWidth(currentToast.message);
|
|
908
1141
|
// Available width is sidebar width (40) minus padding/margins (~2)
|
|
909
1142
|
const availableWidth = SIDEBAR_WIDTH - 2;
|
|
910
|
-
const wrappedLines = Math.ceil(
|
|
1143
|
+
const wrappedLines = Math.ceil(toastDisplayWidth / availableWidth);
|
|
911
1144
|
footerLines += wrappedLines + 1 + 1; // wrapped lines + header line + marginBottom
|
|
912
1145
|
}
|
|
1146
|
+
else if (toastQueueLength > 0) {
|
|
1147
|
+
// When there are queued toasts but no current toast (transition state),
|
|
1148
|
+
// FooterHelp still renders the notification header + marginBottom
|
|
1149
|
+
footerLines += 1 + 1; // header line + marginBottom
|
|
1150
|
+
}
|
|
913
1151
|
// Add debug info
|
|
914
1152
|
if (process.env.DEBUG_DMUX) {
|
|
915
1153
|
footerLines += 1;
|
|
@@ -928,9 +1166,9 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, projectRoo
|
|
|
928
1166
|
}
|
|
929
1167
|
}
|
|
930
1168
|
const contentHeight = Math.max(terminalHeight - footerLines, 10);
|
|
931
|
-
return (React.createElement(Box, { flexDirection: "column", height: terminalHeight },
|
|
1169
|
+
return (React.createElement(Box, { key: `theme-${selectedThemeName}-${themeRefreshNonce}`, flexDirection: "column", height: terminalHeight },
|
|
932
1170
|
React.createElement(Box, { flexDirection: "column", height: contentHeight, overflow: "hidden" },
|
|
933
|
-
React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, isLoading: isLoading, agentStatuses: agentStatuses, activeDevSourcePath: activeDevSourcePath, sidebarProjects: sidebarProjects, fallbackProjectRoot: projectRoot || process.cwd(), fallbackProjectName: projectName, isProjectBusy: isProjectHeaderBusy }),
|
|
1171
|
+
React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, activeProjectRoot: activeProjectRoot, isLoading: isLoading, themeName: selectedThemeName, projectThemeByRoot: projectThemeByRoot, agentStatuses: agentStatuses, activeDevSourcePath: activeDevSourcePath, sidebarProjects: sidebarProjects, fallbackProjectRoot: projectRoot || process.cwd(), fallbackProjectName: projectName, isProjectBusy: isProjectHeaderBusy }),
|
|
934
1172
|
showCommandPrompt && (React.createElement(CommandPromptDialog, { type: showCommandPrompt, value: commandInput, onChange: setCommandInput })),
|
|
935
1173
|
showFileCopyPrompt && React.createElement(FileCopyPrompt, null),
|
|
936
1174
|
showHooksPrompt && (React.createElement(TmuxHooksPromptDialog, { selectedIndex: hooksPromptIndex }))),
|