abmux 0.0.8 → 0.0.9
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.md +100 -24
- package/dist/cli/index.js +233 -186
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
# abmux
|
|
2
2
|
|
|
3
|
-
AI Board on tmux — A
|
|
3
|
+
AI Board on tmux — A TUI for managing multiple Claude Code sessions from a single terminal screen.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- Manage tmux sessions grouped by project
|
|
8
|
-
- Detect Claude session status: waiting for input, thinking, running tools, idle
|
|
9
|
-
- Launch new Claude Code sessions with prompts via your `$EDITOR`
|
|
10
|
-
- Fuzzy search directories to add new sessions
|
|
11
|
-
- CLI commands for scripting: `new`, `open`, `kill`, `list`
|
|
5
|
+
Monitor, create, switch between, and delete Claude Code sessions running on tmux, all in one place.
|
|
12
6
|
|
|
13
7
|
## Requirements
|
|
14
8
|
|
|
@@ -19,34 +13,116 @@ AI Board on tmux — A terminal UI for managing Claude Code sessions on tmux.
|
|
|
19
13
|
## Install
|
|
20
14
|
|
|
21
15
|
```sh
|
|
22
|
-
npm install -g abmux
|
|
23
|
-
# or
|
|
24
|
-
yarn global add abmux
|
|
25
|
-
# or
|
|
26
16
|
pnpm add -g abmux
|
|
27
17
|
```
|
|
28
18
|
|
|
29
|
-
##
|
|
19
|
+
## Getting Started
|
|
30
20
|
|
|
31
21
|
```sh
|
|
32
|
-
abmux
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
abmux
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run without arguments to launch the TUI.
|
|
26
|
+
|
|
27
|
+
CLI commands are also available for scripting:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
abmux new <prompt> [--dir <path>] # Create a session
|
|
31
|
+
abmux open [session] # Attach to a session
|
|
32
|
+
abmux kill [session] # Kill a session
|
|
36
33
|
abmux list # List sessions
|
|
37
|
-
abmux --help # Show help
|
|
38
34
|
```
|
|
39
35
|
|
|
36
|
+
## Screen Layout
|
|
37
|
+
|
|
38
|
+
The main screen is split into three panels:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
42
|
+
│ abmux - v0.0.x │
|
|
43
|
+
├───────────────────────┬─────────────────────────────────────────┤
|
|
44
|
+
│ │ │
|
|
45
|
+
│ Session List │ Pane List │
|
|
46
|
+
│ (Left Panel) │ (Right Panel) │
|
|
47
|
+
│ │ │
|
|
48
|
+
│ > my-project (cwd) │ ⠋ [thinking] Refactoring... %5 │
|
|
49
|
+
│ other-project │ ✳ [running] Fixing tests %8 │
|
|
50
|
+
│ │ ○ [idle] Waiting %12 │
|
|
51
|
+
│ │ ● vim %3 │
|
|
52
|
+
│ │ │
|
|
53
|
+
├───────────────────────┴─────────────────────────────────────────┤
|
|
54
|
+
│ │
|
|
55
|
+
│ Session Overview (Bottom Panel) │
|
|
56
|
+
│ Summaries of what Claude is working on in each session │
|
|
57
|
+
│ │
|
|
58
|
+
├──────────────────────────────────────────────────────────────────┤
|
|
59
|
+
│ ↑/↓ move Enter select Tab next n add q quit ● 2 thinking │
|
|
60
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Press `Tab` to cycle focus: Left → Right → Bottom. The focused panel is highlighted with a green border.
|
|
64
|
+
|
|
65
|
+
## Panels
|
|
66
|
+
|
|
67
|
+
### Left Panel: Session List
|
|
68
|
+
|
|
69
|
+
Lists tmux sessions grouped by project directory. The session matching your current directory is marked with `(cwd)`.
|
|
70
|
+
|
|
71
|
+
| Key | Action |
|
|
72
|
+
| ------------- | ----------------------------------- |
|
|
73
|
+
| `↑` / `↓` | Move cursor |
|
|
74
|
+
| `Enter` / `→` | Select session, move to right panel |
|
|
75
|
+
| `n` | Add session via directory search |
|
|
76
|
+
| `d` | Delete session |
|
|
77
|
+
| `q` | Quit |
|
|
78
|
+
|
|
79
|
+
### Right Panel: Pane List
|
|
80
|
+
|
|
81
|
+
Shows all panes in the selected session. Claude Code panes display their status; other panes (editors, shells) are also listed.
|
|
82
|
+
|
|
83
|
+
| Key | Action |
|
|
84
|
+
| ----------- | ------------------------------- |
|
|
85
|
+
| `↑` / `↓` | Move cursor |
|
|
86
|
+
| `Enter` | Attach to pane (switch to tmux) |
|
|
87
|
+
| `n` | Create a new Claude session |
|
|
88
|
+
| `v` | Open session in `$EDITOR` |
|
|
89
|
+
| `d` | Kill pane |
|
|
90
|
+
| `Esc` / `←` | Back to left panel |
|
|
91
|
+
|
|
92
|
+
### Bottom Panel: Session Overview
|
|
93
|
+
|
|
94
|
+
Displays AI-generated summaries of what Claude is doing in each session. Auto-refreshes every 60 seconds.
|
|
95
|
+
|
|
96
|
+
| Key | Action |
|
|
97
|
+
| ----------- | ------------------ |
|
|
98
|
+
| `↑` / `↓` | Scroll |
|
|
99
|
+
| `Tab` | Next panel |
|
|
100
|
+
| `Esc` / `←` | Back to left panel |
|
|
101
|
+
|
|
102
|
+
## Status Icons
|
|
103
|
+
|
|
104
|
+
The right panel and the status bar show Claude session states with these icons:
|
|
105
|
+
|
|
106
|
+
| Icon | Status | Meaning |
|
|
107
|
+
| ------------- | -------- | ------------------------- |
|
|
108
|
+
| `⠋` (braille) | thinking | Claude is reasoning |
|
|
109
|
+
| `✳` | running | Executing a tool |
|
|
110
|
+
| `❓` | confirm | Waiting for user approval |
|
|
111
|
+
| `❯` | waiting | Ready for input |
|
|
112
|
+
| `○` | idle | Idle |
|
|
113
|
+
|
|
114
|
+
Non-Claude panes show `●` (busy) or `○` (available).
|
|
115
|
+
|
|
40
116
|
## Development
|
|
41
117
|
|
|
42
118
|
```sh
|
|
43
119
|
pnpm install
|
|
44
|
-
pnpm start # Run in
|
|
45
|
-
pnpm test # Run tests
|
|
46
|
-
pnpm typecheck # Type check
|
|
47
|
-
pnpm lint:check # Lint
|
|
48
|
-
pnpm format:check #
|
|
49
|
-
pnpm build # Bundle
|
|
120
|
+
pnpm start # Run in dev mode
|
|
121
|
+
pnpm test # Run tests
|
|
122
|
+
pnpm typecheck # Type check
|
|
123
|
+
pnpm lint:check # Lint
|
|
124
|
+
pnpm format:check # Format check
|
|
125
|
+
pnpm build # Bundle
|
|
50
126
|
```
|
|
51
127
|
|
|
52
128
|
## License
|
package/dist/cli/index.js
CHANGED
|
@@ -603,7 +603,7 @@ var createUsecases = (context) => ({
|
|
|
603
603
|
// package.json
|
|
604
604
|
var package_default = {
|
|
605
605
|
name: "abmux",
|
|
606
|
-
version: "0.0.
|
|
606
|
+
version: "0.0.9",
|
|
607
607
|
repository: {
|
|
608
608
|
type: "git",
|
|
609
609
|
url: "https://github.com/cut0/abmux.git"
|
|
@@ -673,8 +673,8 @@ import { createElement } from "react";
|
|
|
673
673
|
|
|
674
674
|
// src/components/ManagerView.tsx
|
|
675
675
|
import { basename as basename2 } from "node:path";
|
|
676
|
-
import { Box as
|
|
677
|
-
import { useCallback as useCallback5, useMemo as useMemo7, useRef as
|
|
676
|
+
import { Box as Box12, useInput as useInput8 } from "ink";
|
|
677
|
+
import { useCallback as useCallback5, useMemo as useMemo7, useRef as useRef4, useState as useState8 } from "react";
|
|
678
678
|
|
|
679
679
|
// src/components/shared/Header.tsx
|
|
680
680
|
import { Box, Text } from "ink";
|
|
@@ -776,11 +776,10 @@ var SessionListPanel = ({
|
|
|
776
776
|
onCursorChange,
|
|
777
777
|
onDeleteSession,
|
|
778
778
|
onAddSession,
|
|
779
|
-
initialCursor,
|
|
780
779
|
cursorRef
|
|
781
780
|
}) => {
|
|
782
781
|
const { exit } = useApp();
|
|
783
|
-
const [cursor, setCursor] = useState(
|
|
782
|
+
const [cursor, setCursor] = useState(cursorRef?.current ?? 0);
|
|
784
783
|
const sortedSessions = useMemo3(
|
|
785
784
|
() => sortSessions(sessions, currentSession),
|
|
786
785
|
[sessions, currentSession]
|
|
@@ -864,6 +863,7 @@ var SessionListPanel = ({
|
|
|
864
863
|
|
|
865
864
|
// src/components/PaneListPanel.tsx
|
|
866
865
|
import { Box as Box6, Text as Text6 } from "ink";
|
|
866
|
+
import { useRef as useRef2 } from "react";
|
|
867
867
|
|
|
868
868
|
// src/components/PaneListView.tsx
|
|
869
869
|
import { Box as Box5, Text as Text5, useApp as useApp2, useInput as useInput2 } from "ink";
|
|
@@ -911,12 +911,12 @@ var PaneListView = ({
|
|
|
911
911
|
onUnhighlight,
|
|
912
912
|
onBack,
|
|
913
913
|
onNewSession,
|
|
914
|
+
onOpenEditor,
|
|
914
915
|
onKillPane,
|
|
915
|
-
initialCursor,
|
|
916
916
|
cursorRef
|
|
917
917
|
}) => {
|
|
918
918
|
const { exit } = useApp2();
|
|
919
|
-
const [cursor, setCursor] = useState2(
|
|
919
|
+
const [cursor, setCursor] = useState2(cursorRef?.current ?? 0);
|
|
920
920
|
const highlightedRef = useRef(void 0);
|
|
921
921
|
const panes = useMemo4(() => group.tabs.flatMap((t) => t.panes), [group]);
|
|
922
922
|
const clampedCursor = cursor >= panes.length ? Math.max(0, panes.length - 1) : cursor;
|
|
@@ -992,6 +992,10 @@ var PaneListView = ({
|
|
|
992
992
|
onNewSession(selectedSession);
|
|
993
993
|
return;
|
|
994
994
|
}
|
|
995
|
+
if (input === "v") {
|
|
996
|
+
onOpenEditor(selectedSession);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
995
999
|
if (key.return) {
|
|
996
1000
|
const pane = panes[clampedCursor];
|
|
997
1001
|
if (pane) onNavigate(pane);
|
|
@@ -1012,7 +1016,7 @@ var PaneListView = ({
|
|
|
1012
1016
|
")"
|
|
1013
1017
|
] })
|
|
1014
1018
|
] }),
|
|
1015
|
-
/* @__PURE__ */ jsx5(Box5, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: panes.length === 0 ? /* @__PURE__ */ jsx5(Box5, { paddingLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No panes. Press n to create." }) }) : visiblePanes.map((up, i) => /* @__PURE__ */ jsx5(
|
|
1019
|
+
/* @__PURE__ */ jsx5(Box5, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: panes.length === 0 ? /* @__PURE__ */ jsx5(Box5, { paddingLeft: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No panes. Press n or v to create." }) }) : visiblePanes.map((up, i) => /* @__PURE__ */ jsx5(
|
|
1016
1020
|
PaneItem,
|
|
1017
1021
|
{
|
|
1018
1022
|
unifiedPane: up,
|
|
@@ -1035,10 +1039,15 @@ var PaneListPanel = ({
|
|
|
1035
1039
|
onUnhighlight,
|
|
1036
1040
|
onBack,
|
|
1037
1041
|
onNewSession,
|
|
1042
|
+
onOpenEditor,
|
|
1038
1043
|
onKillPane,
|
|
1039
|
-
initialCursor,
|
|
1040
1044
|
cursorRef
|
|
1041
1045
|
}) => {
|
|
1046
|
+
const prevSessionRef = useRef2(selectedSession);
|
|
1047
|
+
if (prevSessionRef.current !== selectedSession) {
|
|
1048
|
+
prevSessionRef.current = selectedSession;
|
|
1049
|
+
if (cursorRef) cursorRef.current = 0;
|
|
1050
|
+
}
|
|
1042
1051
|
if (!selectedSession) {
|
|
1043
1052
|
return /* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No session selected" }) });
|
|
1044
1053
|
}
|
|
@@ -1054,8 +1063,8 @@ var PaneListPanel = ({
|
|
|
1054
1063
|
onUnhighlight,
|
|
1055
1064
|
onBack,
|
|
1056
1065
|
onNewSession,
|
|
1066
|
+
onOpenEditor,
|
|
1057
1067
|
onKillPane,
|
|
1058
|
-
initialCursor,
|
|
1059
1068
|
cursorRef
|
|
1060
1069
|
},
|
|
1061
1070
|
selectedSession
|
|
@@ -1075,11 +1084,10 @@ var SessionOverviewPanel = ({
|
|
|
1075
1084
|
isFocused,
|
|
1076
1085
|
availableRows,
|
|
1077
1086
|
onBack,
|
|
1078
|
-
initialCursor,
|
|
1079
1087
|
cursorRef
|
|
1080
1088
|
}) => {
|
|
1081
1089
|
const { exit } = useApp3();
|
|
1082
|
-
const [cursor, setCursor] = useState3(
|
|
1090
|
+
const [cursor, setCursor] = useState3(cursorRef?.current ?? 0);
|
|
1083
1091
|
const lines = useMemo5(() => {
|
|
1084
1092
|
const summaryLines = overallSummary ? [
|
|
1085
1093
|
{ key: "summary", type: "summary", text: overallSummary },
|
|
@@ -1390,10 +1398,53 @@ var DirectorySearchView = ({ directories, onSelect, onCancel }) => {
|
|
|
1390
1398
|
] });
|
|
1391
1399
|
};
|
|
1392
1400
|
|
|
1401
|
+
// src/components/PromptInputView.tsx
|
|
1402
|
+
import { Box as Box11, Text as Text11, useInput as useInput7 } from "ink";
|
|
1403
|
+
import { useState as useState7 } from "react";
|
|
1404
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1405
|
+
var PromptInputView = ({ selectedDir, onSubmit, onCancel }) => {
|
|
1406
|
+
const { rows } = useTerminalSize();
|
|
1407
|
+
const [value, setValue] = useState7("");
|
|
1408
|
+
useInput7((input, key) => {
|
|
1409
|
+
if (key.escape) {
|
|
1410
|
+
onCancel();
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (key.return) {
|
|
1414
|
+
const trimmed = value.trim();
|
|
1415
|
+
if (trimmed) onSubmit(trimmed);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if (key.backspace || key.delete) {
|
|
1419
|
+
setValue((prev) => prev.slice(0, -1));
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
if (input && !key.ctrl && !key.meta) {
|
|
1423
|
+
setValue((prev) => prev + input);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: rows, children: [
|
|
1427
|
+
/* @__PURE__ */ jsx11(Header, { title: `${APP_TITLE} \u2014 ${selectedDir}` }),
|
|
1428
|
+
/* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { bold: true, children: "New prompt:" }) }),
|
|
1429
|
+
/* @__PURE__ */ jsxs9(Box11, { paddingLeft: 1, flexGrow: 1, children: [
|
|
1430
|
+
/* @__PURE__ */ jsxs9(Text11, { color: "green", children: [
|
|
1431
|
+
">",
|
|
1432
|
+
" "
|
|
1433
|
+
] }),
|
|
1434
|
+
/* @__PURE__ */ jsx11(Text11, { children: value }),
|
|
1435
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: value ? "" : "type your prompt..." })
|
|
1436
|
+
] }),
|
|
1437
|
+
/* @__PURE__ */ jsxs9(Box11, { gap: 2, children: [
|
|
1438
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Enter submit" }),
|
|
1439
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Esc cancel" })
|
|
1440
|
+
] })
|
|
1441
|
+
] });
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1393
1444
|
// src/hooks/use-interval.ts
|
|
1394
|
-
import { useEffect as useEffect2, useRef as
|
|
1445
|
+
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
1395
1446
|
var useInterval = (fn, intervalMs, enabled = true) => {
|
|
1396
|
-
const fnRef =
|
|
1447
|
+
const fnRef = useRef3(fn);
|
|
1397
1448
|
fnRef.current = fn;
|
|
1398
1449
|
useEffect2(() => {
|
|
1399
1450
|
if (!enabled) return;
|
|
@@ -1416,13 +1467,7 @@ var swallow = async (fn) => {
|
|
|
1416
1467
|
};
|
|
1417
1468
|
|
|
1418
1469
|
// src/components/ManagerView.tsx
|
|
1419
|
-
import { jsx as
|
|
1420
|
-
var MODE = {
|
|
1421
|
-
split: "split",
|
|
1422
|
-
confirm: "confirm",
|
|
1423
|
-
deleteSession: "deleteSession",
|
|
1424
|
-
addSession: "addSession"
|
|
1425
|
-
};
|
|
1470
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1426
1471
|
var FOCUS = {
|
|
1427
1472
|
left: "left",
|
|
1428
1473
|
right: "right",
|
|
@@ -1430,95 +1475,91 @@ var FOCUS = {
|
|
|
1430
1475
|
};
|
|
1431
1476
|
var POLL_INTERVAL = 3e3;
|
|
1432
1477
|
var OVERVIEW_POLL_INTERVAL = 6e4;
|
|
1478
|
+
var FIXED_ROWS = 3;
|
|
1479
|
+
var TOP_HEIGHT_RATIO = 1 / 2;
|
|
1480
|
+
var LEFT_WIDTH_RATIO = 1 / 3;
|
|
1481
|
+
var initState = (remountState) => {
|
|
1482
|
+
const snapshot = remountState?.snapshot;
|
|
1483
|
+
return {
|
|
1484
|
+
view: remountState?.prompt ? { mode: "confirm", prompt: remountState.prompt, cwd: remountState.cwd ?? "" } : { mode: "split" },
|
|
1485
|
+
focus: snapshot?.focus ?? FOCUS.left,
|
|
1486
|
+
selectedSession: remountState?.session ?? snapshot?.selectedSession,
|
|
1487
|
+
sessions: [],
|
|
1488
|
+
sessionsLoading: true,
|
|
1489
|
+
overviewResult: snapshot?.overviewResult
|
|
1490
|
+
};
|
|
1491
|
+
};
|
|
1433
1492
|
var ManagerView = ({
|
|
1434
1493
|
actions,
|
|
1435
1494
|
currentSession,
|
|
1436
1495
|
directories,
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
restoredCwd,
|
|
1440
|
-
snapshotRef,
|
|
1441
|
-
restoredState
|
|
1496
|
+
remountState,
|
|
1497
|
+
snapshotRef
|
|
1442
1498
|
}) => {
|
|
1443
1499
|
const { rows, columns } = useTerminalSize();
|
|
1444
|
-
const [
|
|
1445
|
-
|
|
1446
|
-
|
|
1500
|
+
const [state, setState] = useState8(() => initState(remountState));
|
|
1501
|
+
const overviewInFlightRef = useRef4(false);
|
|
1502
|
+
const snapshot = remountState?.snapshot;
|
|
1503
|
+
const cursorsRef = useRef4({
|
|
1504
|
+
session: { current: snapshot?.sessionListCursor ?? 0 },
|
|
1505
|
+
pane: { current: snapshot?.paneListCursor ?? 0 },
|
|
1506
|
+
overview: { current: snapshot?.overviewCursor ?? 0 }
|
|
1447
1507
|
});
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
restoredSession ?? restoredState?.selectedSession
|
|
1452
|
-
);
|
|
1453
|
-
const [pendingPrompt, setPendingPrompt] = useState7(restoredPrompt ?? "");
|
|
1454
|
-
const [pendingDeleteSession, setPendingDeleteSession] = useState7(void 0);
|
|
1455
|
-
const [overviewResult, setOverviewResult] = useState7(
|
|
1456
|
-
restoredState?.overviewResult ?? { overallSummary: "", sessions: [] }
|
|
1457
|
-
);
|
|
1458
|
-
const [overviewLoading, setOverviewLoading] = useState7(
|
|
1459
|
-
restoredState?.overviewResult ? false : true
|
|
1460
|
-
);
|
|
1461
|
-
const overviewInFlightRef = useRef3(false);
|
|
1462
|
-
const sessionCursorRef = useRef3(restoredState?.sessionListCursor ?? 0);
|
|
1463
|
-
const paneCursorRef = useRef3(restoredState?.paneListCursor ?? 0);
|
|
1464
|
-
const overviewCursorRef = useRef3(restoredState?.overviewCursor ?? 0);
|
|
1465
|
-
const paneRestoredRef = useRef3(false);
|
|
1508
|
+
const patch = useCallback5((partial) => {
|
|
1509
|
+
setState((prev) => ({ ...prev, ...partial }));
|
|
1510
|
+
}, []);
|
|
1466
1511
|
const refresh = useCallback5(async () => {
|
|
1467
1512
|
try {
|
|
1468
1513
|
const fetched = await actions.fetchSessions();
|
|
1469
|
-
|
|
1514
|
+
setState((prev) => {
|
|
1470
1515
|
const fetchedNames = new Set(fetched.map((s) => s.name));
|
|
1471
1516
|
const userOnly = prev.sessions.filter(
|
|
1472
1517
|
(s) => !fetchedNames.has(s.name) && s.groups.length === 0
|
|
1473
1518
|
);
|
|
1474
|
-
return { sessions: [...userOnly, ...fetched],
|
|
1519
|
+
return { ...prev, sessions: [...userOnly, ...fetched], sessionsLoading: false };
|
|
1475
1520
|
});
|
|
1476
1521
|
} catch {
|
|
1477
|
-
|
|
1522
|
+
patch({ sessionsLoading: false });
|
|
1478
1523
|
}
|
|
1479
|
-
}, [actions]);
|
|
1524
|
+
}, [actions, patch]);
|
|
1480
1525
|
useInterval(() => void refresh(), POLL_INTERVAL);
|
|
1481
1526
|
useInterval(
|
|
1482
1527
|
() => {
|
|
1483
1528
|
if (overviewInFlightRef.current) return;
|
|
1484
1529
|
overviewInFlightRef.current = true;
|
|
1485
|
-
void actions.fetchOverview(
|
|
1486
|
-
|
|
1530
|
+
void actions.fetchOverview(state.sessions).then((result) => {
|
|
1531
|
+
patch({ overviewResult: result });
|
|
1487
1532
|
}).catch(() => {
|
|
1488
1533
|
}).finally(() => {
|
|
1489
|
-
setOverviewLoading(false);
|
|
1490
1534
|
overviewInFlightRef.current = false;
|
|
1491
1535
|
});
|
|
1492
1536
|
},
|
|
1493
1537
|
OVERVIEW_POLL_INTERVAL,
|
|
1494
|
-
!
|
|
1538
|
+
!state.sessionsLoading
|
|
1495
1539
|
);
|
|
1496
|
-
|
|
1540
|
+
useInput8(
|
|
1497
1541
|
(_input, key) => {
|
|
1498
1542
|
if (key.tab) {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
return FOCUS.left;
|
|
1543
|
+
setState((prev) => {
|
|
1544
|
+
const next = prev.focus === FOCUS.left ? FOCUS.right : prev.focus === FOCUS.right ? FOCUS.bottom : FOCUS.left;
|
|
1545
|
+
return { ...prev, focus: next };
|
|
1503
1546
|
});
|
|
1504
1547
|
}
|
|
1505
1548
|
},
|
|
1506
|
-
{ isActive: mode ===
|
|
1549
|
+
{ isActive: state.view.mode === "split" }
|
|
1507
1550
|
);
|
|
1508
|
-
const resolvedSession = selectedSession ??
|
|
1509
|
-
const paneInitialCursor = !paneRestoredRef.current && restoredState?.selectedSession === resolvedSession ? restoredState?.paneListCursor : void 0;
|
|
1510
|
-
if (paneInitialCursor !== void 0) paneRestoredRef.current = true;
|
|
1551
|
+
const resolvedSession = state.selectedSession ?? state.sessions[0]?.name;
|
|
1511
1552
|
snapshotRef.current = {
|
|
1512
|
-
focus,
|
|
1553
|
+
focus: state.focus,
|
|
1513
1554
|
selectedSession: resolvedSession,
|
|
1514
|
-
sessionListCursor:
|
|
1515
|
-
paneListCursor:
|
|
1516
|
-
overviewCursor:
|
|
1517
|
-
overviewResult
|
|
1555
|
+
sessionListCursor: cursorsRef.current.session.current,
|
|
1556
|
+
paneListCursor: cursorsRef.current.pane.current,
|
|
1557
|
+
overviewCursor: cursorsRef.current.overview.current,
|
|
1558
|
+
overviewResult: state.overviewResult ?? { overallSummary: "", sessions: [] }
|
|
1518
1559
|
};
|
|
1519
1560
|
const selectedManagedSession = useMemo7(
|
|
1520
|
-
() =>
|
|
1521
|
-
[
|
|
1561
|
+
() => state.sessions.find((s) => s.name === resolvedSession),
|
|
1562
|
+
[state.sessions, resolvedSession]
|
|
1522
1563
|
);
|
|
1523
1564
|
const selectedGroup = useMemo7(() => {
|
|
1524
1565
|
if (!selectedManagedSession) return void 0;
|
|
@@ -1527,10 +1568,7 @@ var ManagerView = ({
|
|
|
1527
1568
|
tabs: selectedManagedSession.groups.flatMap((g) => g.tabs)
|
|
1528
1569
|
};
|
|
1529
1570
|
}, [selectedManagedSession]);
|
|
1530
|
-
const allGroups = useMemo7(
|
|
1531
|
-
() => sessionsState.sessions.flatMap((s) => s.groups),
|
|
1532
|
-
[sessionsState.sessions]
|
|
1533
|
-
);
|
|
1571
|
+
const allGroups = useMemo7(() => state.sessions.flatMap((s) => s.groups), [state.sessions]);
|
|
1534
1572
|
const statusCounts = useMemo7(
|
|
1535
1573
|
() => allGroups.flatMap((g) => g.tabs).flatMap((t) => t.panes).filter((p) => p.kind === "claude" && p.claudeStatus).reduce((acc, p) => {
|
|
1536
1574
|
const s = p.claudeStatus;
|
|
@@ -1541,52 +1579,53 @@ var ManagerView = ({
|
|
|
1541
1579
|
[allGroups]
|
|
1542
1580
|
);
|
|
1543
1581
|
const handleOpenAddSession = useCallback5(() => {
|
|
1544
|
-
|
|
1545
|
-
}, []);
|
|
1582
|
+
patch({ view: { mode: "addSession" } });
|
|
1583
|
+
}, [patch]);
|
|
1546
1584
|
const handleAddSessionSelect = useCallback5((path) => {
|
|
1547
1585
|
const name = basename2(path);
|
|
1548
|
-
|
|
1586
|
+
setState((prev) => {
|
|
1549
1587
|
const exists2 = prev.sessions.some((s) => s.name === name);
|
|
1550
|
-
if (exists2) return prev;
|
|
1551
1588
|
return {
|
|
1552
1589
|
...prev,
|
|
1553
|
-
|
|
1590
|
+
view: { mode: "split" },
|
|
1591
|
+
selectedSession: name,
|
|
1592
|
+
sessions: exists2 ? prev.sessions : [{ name, path, groups: [] }, ...prev.sessions]
|
|
1554
1593
|
};
|
|
1555
1594
|
});
|
|
1556
|
-
setSelectedSession(name);
|
|
1557
|
-
setMode(MODE.split);
|
|
1558
1595
|
}, []);
|
|
1559
1596
|
const handleCancelAddSession = useCallback5(() => {
|
|
1560
|
-
|
|
1561
|
-
}, []);
|
|
1562
|
-
const handleDeleteSession = useCallback5(
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1597
|
+
patch({ view: { mode: "split" } });
|
|
1598
|
+
}, [patch]);
|
|
1599
|
+
const handleDeleteSession = useCallback5(
|
|
1600
|
+
(name) => {
|
|
1601
|
+
patch({ view: { mode: "deleteSession", sessionName: name } });
|
|
1602
|
+
},
|
|
1603
|
+
[patch]
|
|
1604
|
+
);
|
|
1566
1605
|
const handleConfirmDelete = useCallback5(() => {
|
|
1567
|
-
if (
|
|
1568
|
-
const
|
|
1569
|
-
|
|
1606
|
+
if (state.view.mode !== "deleteSession") return;
|
|
1607
|
+
const { sessionName } = state.view;
|
|
1608
|
+
const session = state.sessions.find((s) => s.name === sessionName);
|
|
1609
|
+
setState((prev) => ({
|
|
1570
1610
|
...prev,
|
|
1571
|
-
|
|
1611
|
+
view: { mode: "split" },
|
|
1612
|
+
sessions: prev.sessions.filter((s) => s.name !== sessionName),
|
|
1613
|
+
selectedSession: prev.selectedSession === sessionName ? void 0 : prev.selectedSession
|
|
1572
1614
|
}));
|
|
1573
|
-
if (resolvedSession === pendingDeleteSession) {
|
|
1574
|
-
setSelectedSession(void 0);
|
|
1575
|
-
}
|
|
1576
1615
|
if (session) {
|
|
1577
1616
|
const killAll = Promise.all(
|
|
1578
1617
|
session.groups.map((g) => swallow(() => actions.killSession(g.sessionName)))
|
|
1579
1618
|
);
|
|
1580
1619
|
void killAll.then(() => void refresh());
|
|
1581
1620
|
}
|
|
1582
|
-
|
|
1583
|
-
setMode(MODE.split);
|
|
1584
|
-
}, [pendingDeleteSession, resolvedSession, sessionsState.sessions, actions, refresh]);
|
|
1621
|
+
}, [state.view, state.sessions, actions, refresh]);
|
|
1585
1622
|
const handleCancelDelete = useCallback5(() => {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1623
|
+
patch({ view: { mode: "split" } });
|
|
1624
|
+
}, [patch]);
|
|
1625
|
+
const handleNewSession = useCallback5(() => {
|
|
1626
|
+
patch({ view: { mode: "promptInput" } });
|
|
1627
|
+
}, [patch]);
|
|
1628
|
+
const handleOpenEditor = useCallback5(
|
|
1590
1629
|
(sessionName) => {
|
|
1591
1630
|
const cwd = selectedManagedSession?.path;
|
|
1592
1631
|
if (!cwd) return;
|
|
@@ -1594,28 +1633,41 @@ var ManagerView = ({
|
|
|
1594
1633
|
},
|
|
1595
1634
|
[actions, selectedManagedSession]
|
|
1596
1635
|
);
|
|
1636
|
+
const handlePromptInputSubmit = useCallback5(
|
|
1637
|
+
(prompt) => {
|
|
1638
|
+
const cwd = selectedManagedSession?.path;
|
|
1639
|
+
if (!cwd) return;
|
|
1640
|
+
patch({ view: { mode: "confirm", prompt, cwd } });
|
|
1641
|
+
},
|
|
1642
|
+
[patch, selectedManagedSession]
|
|
1643
|
+
);
|
|
1644
|
+
const handlePromptInputCancel = useCallback5(() => {
|
|
1645
|
+
patch({ view: { mode: "split" } });
|
|
1646
|
+
}, [patch]);
|
|
1597
1647
|
const handleConfirmNew = useCallback5(
|
|
1598
1648
|
({ worktree }) => {
|
|
1649
|
+
if (state.view.mode !== "confirm") return;
|
|
1599
1650
|
if (!resolvedSession) return;
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
void actions.createSession(resolvedSession, cwd, pendingPrompt, worktree).then(() => void refresh());
|
|
1603
|
-
setPendingPrompt("");
|
|
1604
|
-
setMode(MODE.split);
|
|
1651
|
+
void actions.createSession(resolvedSession, state.view.cwd, state.view.prompt, worktree).then(() => void refresh());
|
|
1652
|
+
patch({ view: { mode: "split" } });
|
|
1605
1653
|
},
|
|
1606
|
-
[
|
|
1654
|
+
[state.view, resolvedSession, actions, refresh, patch]
|
|
1607
1655
|
);
|
|
1608
1656
|
const handleCancelConfirm = useCallback5(() => {
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1657
|
+
patch({ view: { mode: "split" } });
|
|
1658
|
+
}, [patch]);
|
|
1659
|
+
const handleSessionSelect = useCallback5(
|
|
1660
|
+
(name) => {
|
|
1661
|
+
patch({ selectedSession: name, focus: FOCUS.right });
|
|
1662
|
+
},
|
|
1663
|
+
[patch]
|
|
1664
|
+
);
|
|
1665
|
+
const handleSessionCursorChange = useCallback5(
|
|
1666
|
+
(name) => {
|
|
1667
|
+
patch({ selectedSession: name });
|
|
1668
|
+
},
|
|
1669
|
+
[patch]
|
|
1670
|
+
);
|
|
1619
1671
|
const handleNavigate = useCallback5(
|
|
1620
1672
|
(up) => {
|
|
1621
1673
|
void actions.navigateToPane(up);
|
|
@@ -1623,8 +1675,8 @@ var ManagerView = ({
|
|
|
1623
1675
|
[actions]
|
|
1624
1676
|
);
|
|
1625
1677
|
const handleBack = useCallback5(() => {
|
|
1626
|
-
|
|
1627
|
-
}, []);
|
|
1678
|
+
patch({ focus: FOCUS.left });
|
|
1679
|
+
}, [patch]);
|
|
1628
1680
|
const handleKillPane = useCallback5(
|
|
1629
1681
|
async (paneId) => {
|
|
1630
1682
|
await swallow(() => actions.killPane(paneId));
|
|
@@ -1644,14 +1696,14 @@ var ManagerView = ({
|
|
|
1644
1696
|
},
|
|
1645
1697
|
[actions]
|
|
1646
1698
|
);
|
|
1647
|
-
if (
|
|
1648
|
-
return /* @__PURE__ */
|
|
1649
|
-
/* @__PURE__ */
|
|
1650
|
-
/* @__PURE__ */
|
|
1699
|
+
if (state.sessionsLoading) {
|
|
1700
|
+
return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", height: rows, children: [
|
|
1701
|
+
/* @__PURE__ */ jsx12(Header, { title: `${APP_TITLE} v${APP_VERSION}` }),
|
|
1702
|
+
/* @__PURE__ */ jsx12(StatusBar, { message: "Loading...", type: "info" })
|
|
1651
1703
|
] });
|
|
1652
1704
|
}
|
|
1653
|
-
if (mode ===
|
|
1654
|
-
return /* @__PURE__ */
|
|
1705
|
+
if (state.view.mode === "addSession") {
|
|
1706
|
+
return /* @__PURE__ */ jsx12(
|
|
1655
1707
|
DirectorySearchView,
|
|
1656
1708
|
{
|
|
1657
1709
|
directories,
|
|
@@ -1660,111 +1712,119 @@ var ManagerView = ({
|
|
|
1660
1712
|
}
|
|
1661
1713
|
);
|
|
1662
1714
|
}
|
|
1663
|
-
if (mode ===
|
|
1664
|
-
const
|
|
1715
|
+
if (state.view.mode === "deleteSession") {
|
|
1716
|
+
const { sessionName } = state.view;
|
|
1717
|
+
const deleteSession = state.sessions.find((s) => s.name === sessionName);
|
|
1665
1718
|
const paneCount = deleteSession?.groups.reduce(
|
|
1666
1719
|
(sum, g) => sum + g.tabs.reduce((s, t) => s + t.panes.length, 0),
|
|
1667
1720
|
0
|
|
1668
1721
|
) ?? 0;
|
|
1669
|
-
return /* @__PURE__ */
|
|
1722
|
+
return /* @__PURE__ */ jsx12(
|
|
1670
1723
|
DeleteSessionView,
|
|
1671
1724
|
{
|
|
1672
|
-
sessionName
|
|
1725
|
+
sessionName,
|
|
1673
1726
|
paneCount,
|
|
1674
1727
|
onConfirm: handleConfirmDelete,
|
|
1675
1728
|
onCancel: handleCancelDelete
|
|
1676
1729
|
}
|
|
1677
1730
|
);
|
|
1678
1731
|
}
|
|
1679
|
-
if (mode ===
|
|
1680
|
-
return /* @__PURE__ */
|
|
1732
|
+
if (state.view.mode === "promptInput") {
|
|
1733
|
+
return /* @__PURE__ */ jsx12(
|
|
1734
|
+
PromptInputView,
|
|
1735
|
+
{
|
|
1736
|
+
selectedDir: resolvedSession ?? "",
|
|
1737
|
+
onSubmit: handlePromptInputSubmit,
|
|
1738
|
+
onCancel: handlePromptInputCancel
|
|
1739
|
+
}
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
if (state.view.mode === "confirm") {
|
|
1743
|
+
return /* @__PURE__ */ jsx12(
|
|
1681
1744
|
ConfirmView,
|
|
1682
1745
|
{
|
|
1683
1746
|
selectedDir: resolvedSession ?? "",
|
|
1684
|
-
prompt:
|
|
1747
|
+
prompt: state.view.prompt,
|
|
1685
1748
|
onConfirm: handleConfirmNew,
|
|
1686
1749
|
onCancel: handleCancelConfirm
|
|
1687
1750
|
}
|
|
1688
1751
|
);
|
|
1689
1752
|
}
|
|
1690
|
-
const
|
|
1691
|
-
const
|
|
1692
|
-
const topHeight = Math.floor(contentHeight / 2);
|
|
1753
|
+
const contentHeight = rows - FIXED_ROWS;
|
|
1754
|
+
const topHeight = Math.floor(contentHeight * TOP_HEIGHT_RATIO);
|
|
1693
1755
|
const bottomHeight = contentHeight - topHeight;
|
|
1694
|
-
const leftWidth = Math.floor(columns
|
|
1756
|
+
const leftWidth = Math.floor(columns * LEFT_WIDTH_RATIO);
|
|
1695
1757
|
const rightWidth = columns - leftWidth;
|
|
1696
|
-
return /* @__PURE__ */
|
|
1697
|
-
/* @__PURE__ */
|
|
1698
|
-
/* @__PURE__ */
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
|
|
1758
|
+
return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", height: rows, children: [
|
|
1759
|
+
/* @__PURE__ */ jsx12(Header, { title: `${APP_TITLE} - v${APP_VERSION}` }),
|
|
1760
|
+
/* @__PURE__ */ jsxs10(Box12, { flexDirection: "row", height: topHeight, children: [
|
|
1761
|
+
/* @__PURE__ */ jsx12(
|
|
1762
|
+
Box12,
|
|
1701
1763
|
{
|
|
1702
1764
|
flexDirection: "column",
|
|
1703
1765
|
width: leftWidth,
|
|
1704
1766
|
borderStyle: "round",
|
|
1705
|
-
borderColor: focus === FOCUS.left ? "green" : "gray",
|
|
1706
|
-
children: /* @__PURE__ */
|
|
1767
|
+
borderColor: state.focus === FOCUS.left ? "green" : "gray",
|
|
1768
|
+
children: /* @__PURE__ */ jsx12(
|
|
1707
1769
|
SessionListPanel,
|
|
1708
1770
|
{
|
|
1709
|
-
sessions:
|
|
1771
|
+
sessions: state.sessions,
|
|
1710
1772
|
currentSession,
|
|
1711
|
-
isFocused: focus === FOCUS.left,
|
|
1773
|
+
isFocused: state.focus === FOCUS.left,
|
|
1712
1774
|
availableRows: topHeight - 2,
|
|
1713
1775
|
onSelect: handleSessionSelect,
|
|
1714
1776
|
onCursorChange: handleSessionCursorChange,
|
|
1715
1777
|
onDeleteSession: handleDeleteSession,
|
|
1716
1778
|
onAddSession: handleOpenAddSession,
|
|
1717
|
-
|
|
1718
|
-
cursorRef: sessionCursorRef
|
|
1779
|
+
cursorRef: cursorsRef.current.session
|
|
1719
1780
|
}
|
|
1720
1781
|
)
|
|
1721
1782
|
}
|
|
1722
1783
|
),
|
|
1723
|
-
/* @__PURE__ */
|
|
1724
|
-
|
|
1784
|
+
/* @__PURE__ */ jsx12(
|
|
1785
|
+
Box12,
|
|
1725
1786
|
{
|
|
1726
1787
|
flexDirection: "column",
|
|
1727
1788
|
width: rightWidth,
|
|
1728
1789
|
borderStyle: "round",
|
|
1729
|
-
borderColor: focus === FOCUS.right ? "green" : "gray",
|
|
1730
|
-
children: /* @__PURE__ */
|
|
1790
|
+
borderColor: state.focus === FOCUS.right ? "green" : "gray",
|
|
1791
|
+
children: /* @__PURE__ */ jsx12(
|
|
1731
1792
|
PaneListPanel,
|
|
1732
1793
|
{
|
|
1733
1794
|
selectedSession: resolvedSession,
|
|
1734
1795
|
group: selectedGroup,
|
|
1735
|
-
isFocused: focus === FOCUS.right,
|
|
1796
|
+
isFocused: state.focus === FOCUS.right,
|
|
1736
1797
|
availableRows: topHeight - 2,
|
|
1737
1798
|
onNavigate: handleNavigate,
|
|
1738
1799
|
onHighlight: handleHighlight,
|
|
1739
1800
|
onUnhighlight: handleUnhighlight,
|
|
1740
1801
|
onBack: handleBack,
|
|
1741
1802
|
onNewSession: handleNewSession,
|
|
1803
|
+
onOpenEditor: handleOpenEditor,
|
|
1742
1804
|
onKillPane: handleKillPane,
|
|
1743
|
-
|
|
1744
|
-
cursorRef: paneCursorRef
|
|
1805
|
+
cursorRef: cursorsRef.current.pane
|
|
1745
1806
|
}
|
|
1746
1807
|
)
|
|
1747
1808
|
}
|
|
1748
1809
|
)
|
|
1749
1810
|
] }),
|
|
1750
|
-
/* @__PURE__ */
|
|
1811
|
+
/* @__PURE__ */ jsx12(
|
|
1751
1812
|
SessionOverviewPanel,
|
|
1752
1813
|
{
|
|
1753
|
-
overallSummary: overviewResult
|
|
1754
|
-
items: overviewResult
|
|
1814
|
+
overallSummary: state.overviewResult?.overallSummary ?? "",
|
|
1815
|
+
items: state.overviewResult?.sessions ?? [],
|
|
1755
1816
|
groups: allGroups,
|
|
1756
|
-
isLoading:
|
|
1757
|
-
isFocused: focus === FOCUS.bottom,
|
|
1817
|
+
isLoading: !state.overviewResult,
|
|
1818
|
+
isFocused: state.focus === FOCUS.bottom,
|
|
1758
1819
|
availableRows: bottomHeight,
|
|
1759
1820
|
onBack: handleBack,
|
|
1760
|
-
|
|
1761
|
-
cursorRef: overviewCursorRef
|
|
1821
|
+
cursorRef: cursorsRef.current.overview
|
|
1762
1822
|
}
|
|
1763
1823
|
),
|
|
1764
|
-
/* @__PURE__ */
|
|
1824
|
+
/* @__PURE__ */ jsx12(
|
|
1765
1825
|
StatusBar,
|
|
1766
1826
|
{
|
|
1767
|
-
message: focus === FOCUS.left ? "\u2191/\u2193 move Enter/\u2192 select Tab next n add d delete q quit" : focus === FOCUS.right ? "\u2191/\u2193 move Enter focus Tab next n new d kill Esc/\u2190 back q quit" : "\u2191/\u2193 scroll Tab next Esc/\u2190 back q quit",
|
|
1827
|
+
message: state.focus === FOCUS.left ? "\u2191/\u2193 move Enter/\u2192 select Tab next n add d delete q quit" : state.focus === FOCUS.right ? "\u2191/\u2193 move Enter focus Tab next n new v vim d kill Esc/\u2190 back q quit" : "\u2191/\u2193 scroll Tab next Esc/\u2190 back q quit",
|
|
1768
1828
|
statusCounts
|
|
1769
1829
|
}
|
|
1770
1830
|
)
|
|
@@ -1775,11 +1835,8 @@ var ManagerView = ({
|
|
|
1775
1835
|
var createTuiCommand = ({ usecases, services, infra }) => async () => {
|
|
1776
1836
|
const directories = await services.directoryScan.scan();
|
|
1777
1837
|
let instance;
|
|
1778
|
-
let pendingPrompt;
|
|
1779
|
-
let pendingSession;
|
|
1780
|
-
let pendingCwd;
|
|
1781
1838
|
const snapshotRef = { current: void 0 };
|
|
1782
|
-
let
|
|
1839
|
+
let remountState;
|
|
1783
1840
|
const actions = {
|
|
1784
1841
|
fetchSessions: async () => {
|
|
1785
1842
|
const result = await usecases.manager.list();
|
|
@@ -1829,12 +1886,10 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
|
|
|
1829
1886
|
await usecases.manager.unhighlightWindow(up);
|
|
1830
1887
|
},
|
|
1831
1888
|
openEditor: (sessionName, cwd) => {
|
|
1832
|
-
|
|
1889
|
+
const snapshot = snapshotRef.current;
|
|
1833
1890
|
instance.unmount();
|
|
1834
1891
|
const prompt = infra.editor.open();
|
|
1835
|
-
|
|
1836
|
-
pendingSession = sessionName;
|
|
1837
|
-
pendingCwd = cwd;
|
|
1892
|
+
remountState = { prompt, session: sessionName, cwd, snapshot };
|
|
1838
1893
|
instance = renderApp();
|
|
1839
1894
|
return prompt;
|
|
1840
1895
|
},
|
|
@@ -1842,21 +1897,16 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
|
|
|
1842
1897
|
const target = `${up.pane.sessionName}:${String(up.pane.windowIndex)}`;
|
|
1843
1898
|
await infra.tmuxCli.selectWindow(target);
|
|
1844
1899
|
await infra.tmuxCli.selectPane(up.pane.paneId);
|
|
1845
|
-
|
|
1900
|
+
const snapshot = snapshotRef.current;
|
|
1846
1901
|
instance.unmount();
|
|
1847
1902
|
await infra.tmuxCli.attachSession(up.pane.sessionName);
|
|
1903
|
+
remountState = { snapshot };
|
|
1848
1904
|
instance = renderApp();
|
|
1849
1905
|
}
|
|
1850
1906
|
};
|
|
1851
1907
|
const renderApp = () => {
|
|
1852
|
-
const
|
|
1853
|
-
|
|
1854
|
-
const cwd = pendingCwd;
|
|
1855
|
-
const snapshot = restoredState;
|
|
1856
|
-
pendingPrompt = void 0;
|
|
1857
|
-
pendingSession = void 0;
|
|
1858
|
-
pendingCwd = void 0;
|
|
1859
|
-
restoredState = void 0;
|
|
1908
|
+
const state = remountState;
|
|
1909
|
+
remountState = void 0;
|
|
1860
1910
|
snapshotRef.current = void 0;
|
|
1861
1911
|
const rawCwd = process.cwd();
|
|
1862
1912
|
const currentSession = basename3(findMatchingDirectory(rawCwd, directories) ?? rawCwd);
|
|
@@ -1865,11 +1915,8 @@ var createTuiCommand = ({ usecases, services, infra }) => async () => {
|
|
|
1865
1915
|
actions,
|
|
1866
1916
|
currentSession,
|
|
1867
1917
|
directories,
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
restoredCwd: cwd,
|
|
1871
|
-
snapshotRef,
|
|
1872
|
-
restoredState: snapshot
|
|
1918
|
+
remountState: state,
|
|
1919
|
+
snapshotRef
|
|
1873
1920
|
}),
|
|
1874
1921
|
{ concurrent: true }
|
|
1875
1922
|
);
|