hjworktree-cli 2.0.0 → 2.2.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.
Files changed (36) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.context-snapshots/context-snapshot-20260106-180000.md +108 -0
  3. package/.context-snapshots/context-snapshot-20260106-235500.md +108 -0
  4. package/README.md +134 -0
  5. package/dist/server/routes/api.d.ts.map +1 -1
  6. package/dist/server/routes/api.js +45 -1
  7. package/dist/server/routes/api.js.map +1 -1
  8. package/dist/server/services/worktreeService.d.ts +23 -0
  9. package/dist/server/services/worktreeService.d.ts.map +1 -1
  10. package/dist/server/services/worktreeService.js +156 -14
  11. package/dist/server/services/worktreeService.js.map +1 -1
  12. package/dist/server/socketHandlers.d.ts.map +1 -1
  13. package/dist/server/socketHandlers.js +74 -1
  14. package/dist/server/socketHandlers.js.map +1 -1
  15. package/dist/shared/types/index.d.ts +36 -0
  16. package/dist/shared/types/index.d.ts.map +1 -1
  17. package/dist/web/assets/{index-C61yAbey.css → index-CsixHL-D.css} +1 -1
  18. package/dist/web/assets/index-D8dr9mJa.js +53 -0
  19. package/dist/web/assets/index-D8dr9mJa.js.map +1 -0
  20. package/dist/web/index.html +2 -2
  21. package/package.json +1 -1
  22. package/server/routes/api.ts +51 -2
  23. package/server/services/worktreeService.ts +181 -13
  24. package/server/socketHandlers.ts +91 -2
  25. package/shared/types/index.ts +45 -0
  26. package/web/src/App.tsx +3 -0
  27. package/web/src/components/Layout/LeftNavBar.tsx +290 -26
  28. package/web/src/components/Layout/MainLayout.tsx +2 -6
  29. package/web/src/components/Modals/AddWorktreeModal.tsx +87 -0
  30. package/web/src/components/Modals/ConfirmDeleteModal.tsx +114 -0
  31. package/web/src/components/Modals/ModalContainer.tsx +21 -0
  32. package/web/src/components/Terminal/TerminalPanel.tsx +32 -37
  33. package/web/src/stores/useAppStore.ts +200 -3
  34. package/web/src/styles/global.css +522 -0
  35. package/dist/web/assets/index-WEdVUKxb.js +0 -53
  36. package/dist/web/assets/index-WEdVUKxb.js.map +0 -1
@@ -1,6 +1,14 @@
1
1
  import React from 'react';
2
2
  import { useAppStore } from '../../stores/useAppStore.js';
3
- import type { NavigationStep, StepStatus } from '../../../../shared/types/index.js';
3
+ import type { NavigationStep, StepStatus, AgentStatus } from '../../../../shared/types/index.js';
4
+ import { AI_AGENTS } from '../../../../shared/constants.js';
5
+
6
+ // Step configuration
7
+ const STEP_CONFIG = [
8
+ { id: 'branch' as const, number: 1, label: 'Branch' },
9
+ { id: 'agent' as const, number: 2, label: 'Agent' },
10
+ { id: 'worktree' as const, number: 3, label: 'Count' },
11
+ ];
4
12
 
5
13
  interface StepItemProps {
6
14
  step: NavigationStep;
@@ -11,13 +19,6 @@ interface StepItemProps {
11
19
  disabled: boolean;
12
20
  }
13
21
 
14
- const STEP_CONFIG = [
15
- { id: 'branch' as const, number: 1, label: 'Branch Selection' },
16
- { id: 'agent' as const, number: 2, label: 'Agent Selection' },
17
- { id: 'worktree' as const, number: 3, label: 'Worktree Count' },
18
- { id: 'running' as const, number: 4, label: 'Running' },
19
- ];
20
-
21
22
  function StepItem({ number, label, status, onClick, disabled }: StepItemProps) {
22
23
  return (
23
24
  <button
@@ -34,32 +35,295 @@ function StepItem({ number, label, status, onClick, disabled }: StepItemProps) {
34
35
  );
35
36
  }
36
37
 
37
- function LeftNavBar() {
38
- const { step, goToStep, canNavigateTo, getStepStatus } = useAppStore();
38
+ // Setup Section Component
39
+ function SetupSection() {
40
+ const {
41
+ step,
42
+ goToStep,
43
+ canNavigateTo,
44
+ getStepStatus,
45
+ isSetupCollapsed,
46
+ toggleSetupCollapse,
47
+ terminals,
48
+ } = useAppStore();
49
+
50
+ const hasRunningTerminals = terminals.length > 0;
51
+
52
+ return (
53
+ <div className="lnb-section setup-section">
54
+ <div
55
+ className="lnb-section-header"
56
+ onClick={toggleSetupCollapse}
57
+ role="button"
58
+ tabIndex={0}
59
+ >
60
+ <span className="section-title">SETUP</span>
61
+ <span className="collapse-icon">{isSetupCollapsed ? '+' : '−'}</span>
62
+ </div>
63
+ {!isSetupCollapsed && (
64
+ <div className="lnb-steps">
65
+ {STEP_CONFIG.map((config) => (
66
+ <StepItem
67
+ key={config.id}
68
+ step={config.id}
69
+ number={config.number}
70
+ label={config.label}
71
+ status={getStepStatus(config.id)}
72
+ onClick={() => goToStep(config.id)}
73
+ disabled={!canNavigateTo(config.id)}
74
+ />
75
+ ))}
76
+ {hasRunningTerminals && (
77
+ <button
78
+ className={`lnb-step ${step === 'running' ? 'current' : 'completed'}`}
79
+ onClick={() => goToStep('running')}
80
+ type="button"
81
+ >
82
+ <span className="step-number">4</span>
83
+ <span className="step-label">Running</span>
84
+ </button>
85
+ )}
86
+ </div>
87
+ )}
88
+ </div>
89
+ );
90
+ }
91
+
92
+ // Status indicator component
93
+ function StatusIndicator({ status }: { status: AgentStatus }) {
94
+ const statusColors: Record<AgentStatus, string> = {
95
+ initializing: '#f59e0b',
96
+ installing: '#f59e0b',
97
+ running: '#22c55e',
98
+ stopped: '#6b7280',
99
+ error: '#ef4444',
100
+ };
101
+
102
+ return (
103
+ <span
104
+ className="status-indicator"
105
+ style={{ backgroundColor: statusColors[status] }}
106
+ title={status}
107
+ />
108
+ );
109
+ }
110
+
111
+ // Session Item Component
112
+ interface SessionItemProps {
113
+ sessionId: string;
114
+ worktreeName: string;
115
+ agentType: string;
116
+ status: AgentStatus;
117
+ isActive: boolean;
118
+ isSelected: boolean;
119
+ onSelect: () => void;
120
+ onToggleSelect: (e: React.MouseEvent) => void;
121
+ onClose: (e: React.MouseEvent) => void;
122
+ }
123
+
124
+ function SessionItem({
125
+ sessionId,
126
+ worktreeName,
127
+ agentType,
128
+ status,
129
+ isActive,
130
+ isSelected,
131
+ onSelect,
132
+ onToggleSelect,
133
+ onClose,
134
+ }: SessionItemProps) {
135
+ const agent = AI_AGENTS.find(a => a.id === agentType);
136
+
137
+ return (
138
+ <div
139
+ className={`session-item ${isActive ? 'active' : ''} ${isSelected ? 'selected' : ''}`}
140
+ onClick={onSelect}
141
+ role="button"
142
+ tabIndex={0}
143
+ >
144
+ <input
145
+ type="checkbox"
146
+ className="session-checkbox"
147
+ checked={isSelected}
148
+ onClick={onToggleSelect}
149
+ onChange={() => {}}
150
+ />
151
+ <StatusIndicator status={status} />
152
+ <span className="session-name" title={worktreeName}>
153
+ {worktreeName}
154
+ </span>
155
+ <span className="session-agent" title={agent?.name}>
156
+ {agentType.charAt(0).toUpperCase()}
157
+ </span>
158
+ <button
159
+ className="session-close"
160
+ onClick={onClose}
161
+ title="Close session"
162
+ type="button"
163
+ >
164
+ ×
165
+ </button>
166
+ </div>
167
+ );
168
+ }
39
169
 
40
- // Don't show LNB on running step
41
- if (step === 'running') {
170
+ // Session Group Component
171
+ interface SessionGroupProps {
172
+ branchName: string;
173
+ sessionCount: number;
174
+ isCollapsed: boolean;
175
+ onToggleCollapse: () => void;
176
+ children: React.ReactNode;
177
+ }
178
+
179
+ function SessionGroup({
180
+ branchName,
181
+ sessionCount,
182
+ isCollapsed,
183
+ onToggleCollapse,
184
+ children,
185
+ }: SessionGroupProps) {
186
+ return (
187
+ <div className="session-group">
188
+ <div
189
+ className="session-group-header"
190
+ onClick={onToggleCollapse}
191
+ role="button"
192
+ tabIndex={0}
193
+ >
194
+ <span className="group-collapse-icon">{isCollapsed ? '▶' : '▼'}</span>
195
+ <span className="group-name">{branchName}</span>
196
+ <span className="group-count">({sessionCount})</span>
197
+ </div>
198
+ {!isCollapsed && <div className="session-group-items">{children}</div>}
199
+ </div>
200
+ );
201
+ }
202
+
203
+ // Sessions Section Component
204
+ function SessionsSection() {
205
+ const {
206
+ terminals,
207
+ activeSessionId,
208
+ selectedSessionIds,
209
+ getSessionGroups,
210
+ setActiveSession,
211
+ toggleSessionSelection,
212
+ toggleBranchCollapse,
213
+ removeTerminal,
214
+ openModal,
215
+ } = useAppStore();
216
+
217
+ const sessionGroups = getSessionGroups();
218
+
219
+ const handleCloseSession = async (e: React.MouseEvent, sessionId: string, worktreeName: string) => {
220
+ e.stopPropagation();
221
+
222
+ try {
223
+ const response = await fetch(`/api/worktrees/${encodeURIComponent(worktreeName)}`, {
224
+ method: 'DELETE',
225
+ });
226
+
227
+ if (response.ok) {
228
+ removeTerminal(sessionId);
229
+ }
230
+ } catch (error) {
231
+ console.error('Failed to close session:', error);
232
+ }
233
+ };
234
+
235
+ if (terminals.length === 0) {
42
236
  return null;
43
237
  }
44
238
 
45
239
  return (
46
- <nav className="left-nav-bar">
47
- <div className="lnb-header">
48
- <span>Setup Steps</span>
240
+ <div className="lnb-section sessions-section">
241
+ <div className="lnb-section-header">
242
+ <span className="section-title">SESSIONS</span>
243
+ <span className="session-count">{terminals.length}</span>
49
244
  </div>
50
- <div className="lnb-steps">
51
- {STEP_CONFIG.map((config) => (
52
- <StepItem
53
- key={config.id}
54
- step={config.id}
55
- number={config.number}
56
- label={config.label}
57
- status={getStepStatus(config.id)}
58
- onClick={() => goToStep(config.id)}
59
- disabled={!canNavigateTo(config.id)}
60
- />
245
+ <div className="session-list">
246
+ {sessionGroups.map((group) => (
247
+ <SessionGroup
248
+ key={group.branchName}
249
+ branchName={group.branchName}
250
+ sessionCount={group.sessions.length}
251
+ isCollapsed={group.isCollapsed}
252
+ onToggleCollapse={() => toggleBranchCollapse(group.branchName)}
253
+ >
254
+ {group.sessions.map((session) => (
255
+ <SessionItem
256
+ key={session.sessionId}
257
+ sessionId={session.sessionId}
258
+ worktreeName={session.worktreeName}
259
+ agentType={session.agentType}
260
+ status={session.status}
261
+ isActive={session.sessionId === activeSessionId}
262
+ isSelected={selectedSessionIds.includes(session.sessionId)}
263
+ onSelect={() => setActiveSession(session.sessionId)}
264
+ onToggleSelect={(e) => {
265
+ e.stopPropagation();
266
+ toggleSessionSelection(session.sessionId);
267
+ }}
268
+ onClose={(e) => handleCloseSession(e, session.sessionId, session.worktreeName)}
269
+ />
270
+ ))}
271
+ </SessionGroup>
61
272
  ))}
62
273
  </div>
274
+ <button
275
+ className="add-session-btn"
276
+ onClick={() => openModal('addWorktree')}
277
+ type="button"
278
+ >
279
+ + Add Session
280
+ </button>
281
+ </div>
282
+ );
283
+ }
284
+
285
+ // Bulk Actions Bar Component
286
+ function BulkActionsBar() {
287
+ const {
288
+ selectedSessionIds,
289
+ clearSessionSelection,
290
+ openModal,
291
+ } = useAppStore();
292
+
293
+ if (selectedSessionIds.length === 0) {
294
+ return null;
295
+ }
296
+
297
+ return (
298
+ <div className="bulk-actions-bar">
299
+ <span className="selected-count">
300
+ {selectedSessionIds.length} selected
301
+ </span>
302
+ <button
303
+ className="bulk-delete-btn"
304
+ onClick={() => openModal('confirmDelete', { sessionIds: selectedSessionIds })}
305
+ type="button"
306
+ >
307
+ Delete
308
+ </button>
309
+ <button
310
+ className="bulk-cancel-btn"
311
+ onClick={clearSessionSelection}
312
+ type="button"
313
+ >
314
+ Cancel
315
+ </button>
316
+ </div>
317
+ );
318
+ }
319
+
320
+ // Main LeftNavBar Component
321
+ function LeftNavBar() {
322
+ return (
323
+ <nav className="left-nav-bar">
324
+ <SetupSection />
325
+ <SessionsSection />
326
+ <BulkActionsBar />
63
327
  </nav>
64
328
  );
65
329
  }
@@ -1,19 +1,15 @@
1
1
  import React from 'react';
2
2
  import LeftNavBar from './LeftNavBar.js';
3
- import { useAppStore } from '../../stores/useAppStore.js';
4
3
 
5
4
  interface MainLayoutProps {
6
5
  children: React.ReactNode;
7
6
  }
8
7
 
9
8
  function MainLayout({ children }: MainLayoutProps) {
10
- const step = useAppStore((state) => state.step);
11
- const showLNB = step !== 'running';
12
-
13
9
  return (
14
10
  <div className="main-layout">
15
- {showLNB && <LeftNavBar />}
16
- <div className={`main-content-area ${showLNB ? 'with-lnb' : 'full-width'}`}>
11
+ <LeftNavBar />
12
+ <div className="main-content-area with-lnb">
17
13
  {children}
18
14
  </div>
19
15
  </div>
@@ -0,0 +1,87 @@
1
+ import React, { useState } from 'react';
2
+ import { useAppStore } from '../../stores/useAppStore.js';
3
+ import { AI_AGENTS } from '../../../../shared/constants.js';
4
+ import type { AgentId } from '../../../../shared/types/index.js';
5
+
6
+ function AddWorktreeModal() {
7
+ const { branches, closeModal, createSingleWorktree, isLoading } = useAppStore();
8
+ const [selectedBranch, setSelectedBranch] = useState<string>('');
9
+ const [selectedAgent, setSelectedAgent] = useState<AgentId>('claude');
10
+
11
+ const handleCreate = async () => {
12
+ if (!selectedBranch) return;
13
+ await createSingleWorktree(selectedBranch, selectedAgent);
14
+ };
15
+
16
+ return (
17
+ <div className="modal-overlay" onClick={closeModal}>
18
+ <div className="modal-content" onClick={(e) => e.stopPropagation()}>
19
+ <div className="modal-header">
20
+ <h2>Add New Session</h2>
21
+ <button className="modal-close" onClick={closeModal} type="button">
22
+ &times;
23
+ </button>
24
+ </div>
25
+
26
+ <div className="modal-body">
27
+ <div className="form-group">
28
+ <label htmlFor="branch-select">Branch</label>
29
+ <select
30
+ id="branch-select"
31
+ value={selectedBranch}
32
+ onChange={(e) => setSelectedBranch(e.target.value)}
33
+ disabled={isLoading}
34
+ >
35
+ <option value="">Select a branch...</option>
36
+ {branches.map((branch) => (
37
+ <option key={branch.name} value={branch.name}>
38
+ {branch.name}
39
+ {branch.isCurrent ? ' (current)' : ''}
40
+ {branch.isRemote ? ' (remote)' : ''}
41
+ </option>
42
+ ))}
43
+ </select>
44
+ </div>
45
+
46
+ <div className="form-group">
47
+ <label>Agent</label>
48
+ <div className="agent-options">
49
+ {AI_AGENTS.map((agent) => (
50
+ <button
51
+ key={agent.id}
52
+ type="button"
53
+ className={`agent-option ${selectedAgent === agent.id ? 'selected' : ''}`}
54
+ onClick={() => setSelectedAgent(agent.id)}
55
+ disabled={isLoading}
56
+ >
57
+ <span className="agent-name">{agent.name}</span>
58
+ </button>
59
+ ))}
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ <div className="modal-footer">
65
+ <button
66
+ type="button"
67
+ className="btn-secondary"
68
+ onClick={closeModal}
69
+ disabled={isLoading}
70
+ >
71
+ Cancel
72
+ </button>
73
+ <button
74
+ type="button"
75
+ className="btn-primary"
76
+ onClick={handleCreate}
77
+ disabled={!selectedBranch || isLoading}
78
+ >
79
+ {isLoading ? 'Creating...' : 'Create Session'}
80
+ </button>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ );
85
+ }
86
+
87
+ export default AddWorktreeModal;
@@ -0,0 +1,114 @@
1
+ import React, { useState } from 'react';
2
+ import { useAppStore } from '../../stores/useAppStore.js';
3
+ import { useSocket } from '../../hooks/useSocket.js';
4
+
5
+ function ConfirmDeleteModal() {
6
+ const {
7
+ modalData,
8
+ terminals,
9
+ closeModal,
10
+ removeTerminal,
11
+ clearAllTerminals,
12
+ clearSessionSelection,
13
+ } = useAppStore();
14
+ const { emit } = useSocket();
15
+ const [isDeleting, setIsDeleting] = useState(false);
16
+
17
+ const sessionIds = modalData?.sessionIds || [];
18
+ const sessionsToDelete = terminals.filter(t => sessionIds.includes(t.sessionId));
19
+ const isDeleteAll = sessionIds.length === terminals.length;
20
+
21
+ const handleDelete = async () => {
22
+ setIsDeleting(true);
23
+
24
+ try {
25
+ // Kill all terminals via socket
26
+ for (const sessionId of sessionIds) {
27
+ emit('terminal:kill', { sessionId });
28
+ }
29
+
30
+ if (isDeleteAll) {
31
+ // Delete all worktrees
32
+ await fetch('/api/worktrees', { method: 'DELETE' });
33
+ clearAllTerminals();
34
+ } else {
35
+ // Batch delete specific worktrees
36
+ const names = sessionsToDelete.map(t => t.worktreeName);
37
+ await fetch('/api/worktrees/batch', {
38
+ method: 'DELETE',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify({ names }),
41
+ });
42
+
43
+ // Remove terminals from state
44
+ for (const sessionId of sessionIds) {
45
+ removeTerminal(sessionId);
46
+ }
47
+ }
48
+
49
+ clearSessionSelection();
50
+ closeModal();
51
+ } catch (error) {
52
+ console.error('Failed to delete sessions:', error);
53
+ } finally {
54
+ setIsDeleting(false);
55
+ }
56
+ };
57
+
58
+ return (
59
+ <div className="modal-overlay" onClick={closeModal}>
60
+ <div className="modal-content modal-confirm" onClick={(e) => e.stopPropagation()}>
61
+ <div className="modal-header">
62
+ <h2>Confirm Delete</h2>
63
+ <button className="modal-close" onClick={closeModal} type="button">
64
+ &times;
65
+ </button>
66
+ </div>
67
+
68
+ <div className="modal-body">
69
+ <p className="confirm-message">
70
+ {isDeleteAll
71
+ ? 'Are you sure you want to delete ALL sessions?'
72
+ : `Are you sure you want to delete ${sessionIds.length} session${sessionIds.length > 1 ? 's' : ''}?`}
73
+ </p>
74
+
75
+ {sessionsToDelete.length > 0 && (
76
+ <ul className="delete-list">
77
+ {sessionsToDelete.map((session) => (
78
+ <li key={session.sessionId}>
79
+ <span className={`status ${session.status}`} />
80
+ {session.worktreeName}
81
+ </li>
82
+ ))}
83
+ </ul>
84
+ )}
85
+
86
+ <p className="warning-text">
87
+ This action will terminate running agents and delete the worktrees. This cannot be undone.
88
+ </p>
89
+ </div>
90
+
91
+ <div className="modal-footer">
92
+ <button
93
+ type="button"
94
+ className="btn-secondary"
95
+ onClick={closeModal}
96
+ disabled={isDeleting}
97
+ >
98
+ Cancel
99
+ </button>
100
+ <button
101
+ type="button"
102
+ className="btn-danger"
103
+ onClick={handleDelete}
104
+ disabled={isDeleting}
105
+ >
106
+ {isDeleting ? 'Deleting...' : 'Delete'}
107
+ </button>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ );
112
+ }
113
+
114
+ export default ConfirmDeleteModal;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { useAppStore } from '../../stores/useAppStore.js';
3
+ import AddWorktreeModal from './AddWorktreeModal.js';
4
+ import ConfirmDeleteModal from './ConfirmDeleteModal.js';
5
+
6
+ function ModalContainer() {
7
+ const modalType = useAppStore((state) => state.modalType);
8
+
9
+ if (!modalType) return null;
10
+
11
+ switch (modalType) {
12
+ case 'addWorktree':
13
+ return <AddWorktreeModal />;
14
+ case 'confirmDelete':
15
+ return <ConfirmDeleteModal />;
16
+ default:
17
+ return null;
18
+ }
19
+ }
20
+
21
+ export default ModalContainer;
@@ -4,7 +4,15 @@ import { useSocket } from '../../hooks/useSocket.js';
4
4
  import XTerminal from './XTerminal.js';
5
5
 
6
6
  function TerminalPanel() {
7
- const { terminals, activeTerminalIndex, setActiveTerminal, removeTerminal, clearAllTerminals, reset } = useAppStore();
7
+ const {
8
+ terminals,
9
+ activeTerminalIndex,
10
+ activeSessionId,
11
+ setActiveSession,
12
+ removeTerminal,
13
+ clearAllTerminals,
14
+ openModal,
15
+ } = useAppStore();
8
16
  const { emit } = useSocket();
9
17
 
10
18
  const handleCloseTerminal = async (sessionId: string, e: React.MouseEvent) => {
@@ -28,37 +36,27 @@ function TerminalPanel() {
28
36
  removeTerminal(sessionId);
29
37
  };
30
38
 
31
- const handleCloseAll = async () => {
32
- // Kill all terminals via socket
33
- for (const terminal of terminals) {
34
- emit('terminal:kill', { sessionId: terminal.sessionId });
35
- }
36
-
37
- // Delete all worktrees
38
- try {
39
- await fetch('/api/worktrees', {
40
- method: 'DELETE',
41
- });
42
- } catch (error) {
43
- console.error('Failed to delete worktrees:', error);
44
- }
45
-
46
- clearAllTerminals();
47
- reset();
39
+ const handleCloseAll = () => {
40
+ const sessionIds = terminals.map(t => t.sessionId);
41
+ openModal('confirmDelete', { sessionIds });
48
42
  };
49
43
 
50
- const handleBackToSetup = () => {
51
- if (terminals.length > 0) {
52
- const confirmed = window.confirm(
53
- 'This will terminate all running agents and delete all worktrees. Are you sure you want to go back to setup?'
54
- );
55
- if (confirmed) {
56
- handleCloseAll();
57
- }
58
- } else {
59
- reset();
60
- }
61
- };
44
+ // Find active index based on sessionId
45
+ const currentActiveIndex = activeSessionId
46
+ ? terminals.findIndex(t => t.sessionId === activeSessionId)
47
+ : activeTerminalIndex;
48
+ const effectiveActiveIndex = currentActiveIndex >= 0 ? currentActiveIndex : 0;
49
+
50
+ if (terminals.length === 0) {
51
+ return (
52
+ <div className="terminal-panel empty">
53
+ <div className="empty-message">
54
+ <p>No active sessions</p>
55
+ <p>Use the Setup wizard or click "+ Add Session" in the sidebar</p>
56
+ </div>
57
+ </div>
58
+ );
59
+ }
62
60
 
63
61
  return (
64
62
  <div className="terminal-panel">
@@ -66,8 +64,8 @@ function TerminalPanel() {
66
64
  {terminals.map((terminal, index) => (
67
65
  <button
68
66
  key={terminal.sessionId}
69
- className={`terminal-tab ${index === activeTerminalIndex ? 'active' : ''}`}
70
- onClick={() => setActiveTerminal(index)}
67
+ className={`terminal-tab ${terminal.sessionId === activeSessionId || index === effectiveActiveIndex ? 'active' : ''}`}
68
+ onClick={() => setActiveSession(terminal.sessionId)}
71
69
  >
72
70
  <span className={`status ${terminal.status}`} />
73
71
  <span>{terminal.worktreeName}</span>
@@ -82,9 +80,6 @@ function TerminalPanel() {
82
80
  ))}
83
81
 
84
82
  <div className="terminal-actions">
85
- <button onClick={handleBackToSetup}>
86
- Back to Setup
87
- </button>
88
83
  <button className="danger" onClick={handleCloseAll}>
89
84
  Close All
90
85
  </button>
@@ -95,13 +90,13 @@ function TerminalPanel() {
95
90
  {terminals.map((terminal, index) => (
96
91
  <div
97
92
  key={terminal.sessionId}
98
- className={`terminal-wrapper ${index !== activeTerminalIndex ? 'hidden' : ''}`}
93
+ className={`terminal-wrapper ${index !== effectiveActiveIndex ? 'hidden' : ''}`}
99
94
  >
100
95
  <XTerminal
101
96
  sessionId={terminal.sessionId}
102
97
  worktreePath={terminal.worktreePath}
103
98
  agentType={terminal.agentType}
104
- isActive={index === activeTerminalIndex}
99
+ isActive={index === effectiveActiveIndex}
105
100
  />
106
101
  </div>
107
102
  ))}