@brimveyn/aimux 1.12.6 → 1.13.2
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 +3 -0
- package/package.json +4 -3
- package/src/app-runtime/auto-commit-driver.ts +8 -4
- package/src/app-runtime/auto-commit-ref.ts +1 -1
- package/src/app-runtime/backend-attach-runtime.ts +29 -13
- package/src/app-runtime/backend-runtime-events.ts +1 -1
- package/src/app-runtime/selection-scroll.ts +2 -2
- package/src/app-runtime/session-actions.ts +16 -4
- package/src/app-runtime/side-effects.ts +563 -85
- package/src/app-runtime/snippet-actions.ts +3 -3
- package/src/app-runtime/use-auto-commit-driver.ts +7 -4
- package/src/app-runtime/use-backend-runtime.ts +3 -3
- package/src/app-runtime/use-directory-search.ts +7 -5
- package/src/app-runtime/use-mouse-handlers.ts +11 -6
- package/src/app-runtime/use-pane-size-report.ts +1 -1
- package/src/app-runtime/use-renderer-bindings.ts +6 -5
- package/src/app.tsx +12 -7
- package/src/auto-commit/headless-commands.ts +4 -6
- package/src/daemon/daemon.ts +21 -11
- package/src/daemon/runtime-paths.ts +1 -1
- package/src/daemon/session-manager.ts +1 -1
- package/src/daemon/session-registry.ts +10 -3
- package/src/diff-parser/parse-patch-files.ts +18 -18
- package/src/git/command-queue.ts +19 -3
- package/src/git/divergence.ts +46 -0
- package/src/git/git-diff.ts +12 -5
- package/src/git/git-poller.ts +33 -11
- package/src/git/git-status.ts +14 -3
- package/src/git/move-worktree.ts +96 -0
- package/src/git/use-repo-discovery.ts +2 -1
- package/src/git/worktree-divergence-poller.ts +59 -0
- package/src/git/worktree.ts +99 -0
- package/src/index.tsx +1 -1
- package/src/input/keymap/describe-bindings.ts +27 -9
- package/src/input/keymap/key-chord.ts +4 -4
- package/src/input/keymap/key-format.ts +2 -2
- package/src/input/keymap/sequence-resolver.ts +1 -1
- package/src/input/keymap/trie.ts +1 -1
- package/src/input/modes/bridge.ts +7 -0
- package/src/input/modes/transitions.ts +2 -1
- package/src/input/modes/types.ts +11 -1
- package/src/input/raw-input-handler.ts +1 -1
- package/src/input/terminal-text-extraction.ts +9 -5
- package/src/integrations/claude-syntax-overlay.ts +8 -8
- package/src/integrations/claude-theme-sync.ts +12 -12
- package/src/ipc/protocol.ts +3 -3
- package/src/platform/clipboard.ts +4 -2
- package/src/platform/worktree-deps.ts +22 -0
- package/src/platform/worktree-paths.ts +67 -0
- package/src/profile-paths.ts +3 -3
- package/src/pty/assistant-status-detection-loop.ts +2 -2
- package/src/pty/assistant-status-detector.ts +15 -6
- package/src/pty/command-registry.ts +2 -1
- package/src/pty/pty-manager.ts +1 -1
- package/src/services/ai-usage/adapters/claude.ts +5 -5
- package/src/services/ai-usage/adapters/codex.ts +8 -8
- package/src/services/ai-usage/cache.ts +2 -2
- package/src/services/ai-usage/pace.ts +3 -6
- package/src/services/ai-usage/provider.ts +1 -1
- package/src/session-backend/bootstrap.ts +2 -2
- package/src/session-backend/local-session-backend.ts +11 -11
- package/src/session-backend/remote-session-backend.ts +60 -45
- package/src/session-backend/types.ts +2 -2
- package/src/snippets/expand-variables.ts +7 -7
- package/src/snippets/run-shell-var.ts +1 -1
- package/src/snippets/trigger-detector.ts +1 -1
- package/src/state/ai-usage-store.ts +1 -1
- package/src/state/git-tree.ts +8 -4
- package/src/state/layout-tree.ts +8 -5
- package/src/state/reducers/git-mode-state.ts +44 -14
- package/src/state/reducers/git-panel-state.ts +38 -2
- package/src/state/reducers/modal-state.ts +355 -21
- package/src/state/reducers/session-state.ts +60 -1
- package/src/state/reducers/tab-state.ts +151 -85
- package/src/state/selectors.ts +6 -4
- package/src/state/session-catalog.ts +20 -5
- package/src/state/session-persistence.ts +8 -4
- package/src/state/session-worktrees.ts +227 -0
- package/src/state/store.ts +1 -0
- package/src/state/toast-store.ts +95 -0
- package/src/state/types.ts +92 -13
- package/src/state/validation.ts +25 -2
- package/src/terminal-manager/manager-client.ts +15 -13
- package/src/ui/components/git/diff-renderer/fold-strip.tsx +32 -24
- package/src/ui/components/git/diff-renderer/pierre-diff.tsx +1 -1
- package/src/ui/components/git/diff-renderer/prepare-diff.ts +2 -2
- package/src/ui/components/git/diff-renderer/split-view.tsx +39 -11
- package/src/ui/components/git/diff-renderer/stacked-view.tsx +29 -7
- package/src/ui/components/git/diff-renderer/use-diff-prefetch.ts +28 -11
- package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +10 -8
- package/src/ui/components/git/git-panel.tsx +129 -83
- package/src/ui/components/git/git-view.tsx +96 -28
- package/src/ui/components/git/image-diff/image-diff-view.tsx +2 -2
- package/src/ui/components/git/image-diff/terminal-image-pane.tsx +14 -13
- package/src/ui/components/git/pane/git-pane-context-menu.ts +2 -2
- package/src/ui/components/git/pane/git-pane-widget.tsx +6 -4
- package/src/ui/components/layout/session-bar.tsx +135 -117
- package/src/ui/components/layout/sidebar/sidebar.tsx +244 -82
- package/src/ui/components/layout/sidebar/tab-item.tsx +68 -41
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +2 -2
- package/src/ui/components/layout/sidebar/use-sidebar-branch.ts +1 -1
- package/src/ui/components/layout/split-layout.tsx +27 -20
- package/src/ui/components/layout/terminal-pane.tsx +163 -120
- package/src/ui/components/modals/app/ai-usage-modal.tsx +8 -5
- package/src/ui/components/modals/app/help-modal.tsx +29 -20
- package/src/ui/components/modals/git/git-commit-modal.tsx +17 -10
- package/src/ui/components/modals/sessions/create-session-modal.tsx +18 -10
- package/src/ui/components/modals/sessions/session-picker-modal.tsx +36 -28
- package/src/ui/components/modals/shared/form.tsx +6 -6
- package/src/ui/components/modals/shared/modal-shell.tsx +3 -3
- package/src/ui/components/modals/shared/picker.tsx +71 -32
- package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +50 -29
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +209 -25
- package/src/ui/components/modals/themes/theme-picker-modal.tsx +24 -15
- package/src/ui/components/modals/worktree/worktree-move-modal.tsx +111 -0
- package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +14 -9
- package/src/ui/components/overlays/context-menu/context-menu-box.tsx +20 -11
- package/src/ui/components/overlays/context-menu/context-menu-overlay.tsx +69 -34
- package/src/ui/components/overlays/toast/toast-item.tsx +88 -0
- package/src/ui/components/overlays/toast/toast-position.ts +45 -0
- package/src/ui/components/overlays/toast/toast-stack.tsx +30 -0
- package/src/ui/components/overlays/toast/toast-viewport.tsx +24 -0
- package/src/ui/components/overlays/toast/use-toast-animation.ts +64 -0
- package/src/ui/components/primitives/input-field.tsx +1 -1
- package/src/ui/components/primitives/list-item.tsx +23 -7
- package/src/ui/filter-themes.ts +1 -1
- package/src/ui/root.tsx +121 -38
- package/src/ui/session-ordering.ts +2 -2
- package/src/ui/status-bar-model.ts +14 -7
- package/src/ui/terminal-graphics/capabilities.ts +1 -1
- package/src/ui/terminal-graphics/format-fallback.ts +7 -9
- package/src/ui/terminal-graphics/kitty.ts +2 -7
- package/src/update/version-check.ts +1 -1
|
@@ -25,21 +25,154 @@ function clampCursor(value: number, max: number): number {
|
|
|
25
25
|
return Math.max(0, Math.min(max, value))
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function getCurrentWorktreeCount(state: AppState): number {
|
|
29
|
+
if (!(state.currentSessionId != null && state.currentSessionId !== '')) return 0
|
|
30
|
+
return state.sessions.find((entry) => entry.id === state.currentSessionId)?.worktrees?.length ?? 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getCurrentWorktreeIndex(state: AppState): number {
|
|
34
|
+
if (!(state.currentSessionId != null && state.currentSessionId !== '')) return 0
|
|
35
|
+
const session = state.sessions.find((entry) => entry.id === state.currentSessionId)
|
|
36
|
+
return Math.max(
|
|
37
|
+
0,
|
|
38
|
+
(session?.worktrees ?? []).findIndex((worktree) => worktree.id === session?.activeWorktreeId)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getSelectedNewTabAssistant(state: AppState, assistantId?: string) {
|
|
43
|
+
if (assistantId != null && assistantId !== '') {
|
|
44
|
+
return getAllAssistantOptions(state.customCommands).find((entry) => entry.id === assistantId)
|
|
45
|
+
}
|
|
46
|
+
if (state.modal.type !== 'new-tab') return
|
|
47
|
+
return filterAssistants(getAllAssistantOptions(state.customCommands), state.modal.editBuffer)[
|
|
48
|
+
state.modal.selectedIndex
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
export function reduceModalState(state: AppState, action: AppAction): AppState | null {
|
|
29
53
|
switch (action.type) {
|
|
30
|
-
case 'open-new-tab-modal':
|
|
54
|
+
case 'open-new-tab-modal': {
|
|
55
|
+
const targetWorktreeIndex = getCurrentWorktreeIndex(state)
|
|
31
56
|
return {
|
|
32
57
|
...state,
|
|
33
58
|
focusMode: 'command-edit',
|
|
34
59
|
modal: {
|
|
60
|
+
activeField: 'assistant',
|
|
61
|
+
branchError: null,
|
|
62
|
+
branchName: '',
|
|
63
|
+
createWorktree: false,
|
|
35
64
|
cursorPos: 0,
|
|
36
65
|
editBuffer: '',
|
|
37
66
|
editingCommand: null,
|
|
67
|
+
selectedAssistantId: null,
|
|
38
68
|
selectedIndex: 0,
|
|
39
69
|
sessionTargetId: null,
|
|
70
|
+
step: 'assistant',
|
|
71
|
+
targetWorktreeIndex,
|
|
40
72
|
type: 'new-tab',
|
|
73
|
+
worktreeDeleteConfirmId: null,
|
|
74
|
+
worktreeDeleteMessage: null,
|
|
75
|
+
worktreeName: '',
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
case 'enter-new-tab-worktree-create': {
|
|
80
|
+
if (state.modal.type !== 'new-tab') return state
|
|
81
|
+
return {
|
|
82
|
+
...state,
|
|
83
|
+
modal: {
|
|
84
|
+
...state.modal,
|
|
85
|
+
activeField: 'worktree-name',
|
|
86
|
+
createWorktree: true,
|
|
87
|
+
cursorPos: state.modal.worktreeName.length,
|
|
88
|
+
selectedIndex: 0,
|
|
89
|
+
step: 'worktree-create',
|
|
41
90
|
},
|
|
42
91
|
}
|
|
92
|
+
}
|
|
93
|
+
case 'set-new-tab-worktree-delete-state': {
|
|
94
|
+
if (state.modal.type !== 'new-tab') return state
|
|
95
|
+
return {
|
|
96
|
+
...state,
|
|
97
|
+
modal: {
|
|
98
|
+
...state.modal,
|
|
99
|
+
worktreeDeleteConfirmId: action.confirmWorktreeId ?? null,
|
|
100
|
+
worktreeDeleteMessage: action.message,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
case 'set-new-tab-branch-error': {
|
|
105
|
+
if (state.modal.type !== 'new-tab') return state
|
|
106
|
+
return {
|
|
107
|
+
...state,
|
|
108
|
+
modal: {
|
|
109
|
+
...state.modal,
|
|
110
|
+
activeField: 'branch-name',
|
|
111
|
+
branchError: action.message,
|
|
112
|
+
cursorPos: state.modal.branchName.length,
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
case 'select-new-tab-assistant': {
|
|
117
|
+
if (state.modal.type !== 'new-tab' || state.modal.editingCommand !== null) return state
|
|
118
|
+
const option = getSelectedNewTabAssistant(state, action.assistantId)
|
|
119
|
+
if (!option) return state
|
|
120
|
+
const targetWorktreeIndex = getCurrentWorktreeIndex(state)
|
|
121
|
+
const worktreeCount = getCurrentWorktreeCount(state)
|
|
122
|
+
return {
|
|
123
|
+
...state,
|
|
124
|
+
modal: {
|
|
125
|
+
...state.modal,
|
|
126
|
+
activeField: 'target-worktree',
|
|
127
|
+
branchError: null,
|
|
128
|
+
createWorktree: worktreeCount === 0,
|
|
129
|
+
cursorPos: 0,
|
|
130
|
+
selectedAssistantId: option.id,
|
|
131
|
+
selectedIndex: worktreeCount === 0 ? 0 : targetWorktreeIndex,
|
|
132
|
+
step: 'worktree',
|
|
133
|
+
targetWorktreeIndex,
|
|
134
|
+
worktreeDeleteConfirmId: null,
|
|
135
|
+
worktreeDeleteMessage: null,
|
|
136
|
+
worktreeName: state.modal.worktreeName || `wt-${option.label}`,
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
case 'toggle-new-tab-worktree': {
|
|
141
|
+
if (state.modal.type !== 'new-tab' || state.modal.editingCommand !== null) return state
|
|
142
|
+
const createWorktree = !state.modal.createWorktree
|
|
143
|
+
const option = getSelectedNewTabAssistant(state, action.assistantId)
|
|
144
|
+
const defaultName = state.modal.worktreeName || `wt-${option?.label ?? 'assistant'}`
|
|
145
|
+
const worktreeCount = getCurrentWorktreeCount(state)
|
|
146
|
+
const targetWorktreeIndex = getCurrentWorktreeIndex(state)
|
|
147
|
+
let activeField = state.modal.activeField
|
|
148
|
+
if (createWorktree) {
|
|
149
|
+
activeField = state.modal.step === 'worktree' || option ? 'target-worktree' : activeField
|
|
150
|
+
} else if (activeField === 'worktree-name') {
|
|
151
|
+
activeField = 'target-worktree'
|
|
152
|
+
}
|
|
153
|
+
const cursorPos =
|
|
154
|
+
createWorktree && worktreeCount <= 0
|
|
155
|
+
? defaultName.length
|
|
156
|
+
: (state.modal.editBuffer?.length ?? 0)
|
|
157
|
+
return {
|
|
158
|
+
...state,
|
|
159
|
+
modal: {
|
|
160
|
+
...state.modal,
|
|
161
|
+
activeField,
|
|
162
|
+
createWorktree,
|
|
163
|
+
cursorPos,
|
|
164
|
+
selectedAssistantId: option?.id ?? state.modal.selectedAssistantId,
|
|
165
|
+
selectedIndex: createWorktree
|
|
166
|
+
? worktreeCount
|
|
167
|
+
: Math.min(state.modal.selectedIndex, Math.max(0, worktreeCount - 1)),
|
|
168
|
+
step: option && createWorktree ? 'worktree' : state.modal.step,
|
|
169
|
+
targetWorktreeIndex,
|
|
170
|
+
worktreeDeleteConfirmId: null,
|
|
171
|
+
worktreeDeleteMessage: null,
|
|
172
|
+
worktreeName: defaultName,
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
}
|
|
43
176
|
case 'open-edit-custom-command': {
|
|
44
177
|
if (state.modal.type !== 'new-tab') return state
|
|
45
178
|
const current = state.customCommands[action.assistantId] ?? ''
|
|
@@ -67,6 +200,29 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
67
200
|
},
|
|
68
201
|
}
|
|
69
202
|
}
|
|
203
|
+
case 'open-worktree-move-modal': {
|
|
204
|
+
// Overlay: keep focusMode (git when opened via `m`, navigation when opened
|
|
205
|
+
// from a tab menu) so the view underneath stays mounted, like the help
|
|
206
|
+
// modal. deriveModeId routes input to the picker while open.
|
|
207
|
+
return {
|
|
208
|
+
...state,
|
|
209
|
+
modal: {
|
|
210
|
+
deleteSource: false,
|
|
211
|
+
editBuffer: null,
|
|
212
|
+
selectedIndex: 0,
|
|
213
|
+
sessionTargetId: null,
|
|
214
|
+
sourceWorktreeId: action.sourceWorktreeId,
|
|
215
|
+
type: 'worktree-move',
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
case 'toggle-worktree-move-delete': {
|
|
220
|
+
if (state.modal.type !== 'worktree-move') return state
|
|
221
|
+
return {
|
|
222
|
+
...state,
|
|
223
|
+
modal: { ...state.modal, deleteSource: !state.modal.deleteSource },
|
|
224
|
+
}
|
|
225
|
+
}
|
|
70
226
|
case 'open-help-modal': {
|
|
71
227
|
const keymap = getActiveKeymap()
|
|
72
228
|
const scope = action.scope ?? null
|
|
@@ -174,9 +330,10 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
174
330
|
return { ...state, modal: { ...state.modal, actionMessage: action.message } }
|
|
175
331
|
}
|
|
176
332
|
case 'open-snippet-editor': {
|
|
177
|
-
const snippet =
|
|
178
|
-
|
|
179
|
-
|
|
333
|
+
const snippet =
|
|
334
|
+
action.snippetId != null && action.snippetId !== ''
|
|
335
|
+
? state.snippets.find((s) => s.id === action.snippetId)
|
|
336
|
+
: undefined
|
|
180
337
|
const initialName = snippet?.name ?? ''
|
|
181
338
|
return {
|
|
182
339
|
...state,
|
|
@@ -365,8 +522,9 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
365
522
|
}
|
|
366
523
|
case 'close-modal': {
|
|
367
524
|
const closingType = state.modal.type
|
|
368
|
-
//
|
|
369
|
-
|
|
525
|
+
// Pure overlays never flipped focusMode (they render on top of git mode),
|
|
526
|
+
// so leave it alone — closing returns to whatever was underneath.
|
|
527
|
+
if (closingType === 'help' || closingType === 'worktree-move') {
|
|
370
528
|
return { ...state, modal: emptyModal() }
|
|
371
529
|
}
|
|
372
530
|
const nextFocus: AppState['focusMode'] = closingType === 'git-commit' ? 'git' : 'navigation'
|
|
@@ -388,19 +546,28 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
388
546
|
state.modal.type !== 'theme-picker' &&
|
|
389
547
|
state.modal.type !== 'create-session' &&
|
|
390
548
|
state.modal.type !== 'split-picker' &&
|
|
391
|
-
state.modal.type !== 'update-available'
|
|
549
|
+
state.modal.type !== 'update-available' &&
|
|
550
|
+
state.modal.type !== 'worktree-move'
|
|
392
551
|
) {
|
|
393
552
|
return state
|
|
394
553
|
}
|
|
395
554
|
if (state.modal.type === 'create-session' && state.modal.activeField !== 'directory') {
|
|
396
555
|
return state
|
|
397
556
|
}
|
|
557
|
+
if (state.modal.type === 'new-tab' && state.modal.step === 'worktree-create') {
|
|
558
|
+
return state
|
|
559
|
+
}
|
|
398
560
|
let optionCount: number
|
|
399
561
|
if (state.modal.type === 'new-tab') {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
state
|
|
403
|
-
|
|
562
|
+
if (state.modal.step === 'worktree') {
|
|
563
|
+
if (state.modal.activeField === 'worktree-name') return state
|
|
564
|
+
optionCount = getCurrentWorktreeCount(state) + 1
|
|
565
|
+
} else {
|
|
566
|
+
optionCount = filterAssistants(
|
|
567
|
+
getAllAssistantOptions(state.customCommands),
|
|
568
|
+
state.modal.editBuffer
|
|
569
|
+
).length
|
|
570
|
+
}
|
|
404
571
|
} else if (state.modal.type === 'split-picker') {
|
|
405
572
|
optionCount = getAllAssistantOptions(state.customCommands).length
|
|
406
573
|
} else if (state.modal.type === 'create-session') {
|
|
@@ -412,6 +579,8 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
412
579
|
optionCount = filterThemeIds(state.modal.editBuffer).length
|
|
413
580
|
} else if (state.modal.type === 'update-available') {
|
|
414
581
|
optionCount = 2
|
|
582
|
+
} else if (state.modal.type === 'worktree-move') {
|
|
583
|
+
optionCount = Math.max(0, getCurrentWorktreeCount(state) - 1)
|
|
415
584
|
} else {
|
|
416
585
|
const filtered = filterSessions(state.sessions, state.modal.editBuffer)
|
|
417
586
|
optionCount = Math.max(1, filtered.length + 1)
|
|
@@ -424,6 +593,15 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
424
593
|
modal: {
|
|
425
594
|
...state.modal,
|
|
426
595
|
selectedIndex: (state.modal.selectedIndex + action.delta + optionCount) % optionCount,
|
|
596
|
+
...(state.modal.type === 'new-tab' && state.modal.step === 'worktree'
|
|
597
|
+
? {
|
|
598
|
+
createWorktree:
|
|
599
|
+
(state.modal.selectedIndex + action.delta + optionCount) % optionCount ===
|
|
600
|
+
optionCount - 1,
|
|
601
|
+
worktreeDeleteConfirmId: null,
|
|
602
|
+
worktreeDeleteMessage: null,
|
|
603
|
+
}
|
|
604
|
+
: null),
|
|
427
605
|
},
|
|
428
606
|
}
|
|
429
607
|
}
|
|
@@ -436,18 +614,23 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
436
614
|
state.modal.type !== 'create-session' &&
|
|
437
615
|
state.modal.type !== 'split-picker' &&
|
|
438
616
|
state.modal.type !== 'update-available' &&
|
|
617
|
+
state.modal.type !== 'worktree-move' &&
|
|
439
618
|
state.modal.type !== 'help'
|
|
440
619
|
) {
|
|
441
620
|
return state
|
|
442
621
|
}
|
|
622
|
+
if (state.modal.type === 'new-tab' && state.modal.step === 'worktree-create') {
|
|
623
|
+
return state
|
|
624
|
+
}
|
|
443
625
|
let optionCount: number
|
|
444
626
|
if (state.modal.type === 'help') {
|
|
445
627
|
optionCount = state.modal.entryCount
|
|
446
628
|
} else if (state.modal.type === 'new-tab') {
|
|
447
|
-
optionCount =
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
629
|
+
optionCount =
|
|
630
|
+
state.modal.step === 'worktree'
|
|
631
|
+
? getCurrentWorktreeCount(state) + 1
|
|
632
|
+
: filterAssistants(getAllAssistantOptions(state.customCommands), state.modal.editBuffer)
|
|
633
|
+
.length
|
|
451
634
|
} else if (state.modal.type === 'split-picker') {
|
|
452
635
|
optionCount = getAllAssistantOptions(state.customCommands).length
|
|
453
636
|
} else if (state.modal.type === 'create-session') {
|
|
@@ -458,17 +641,48 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
458
641
|
optionCount = filterThemeIds(state.modal.editBuffer).length
|
|
459
642
|
} else if (state.modal.type === 'update-available') {
|
|
460
643
|
optionCount = 2
|
|
644
|
+
} else if (state.modal.type === 'worktree-move') {
|
|
645
|
+
optionCount = Math.max(0, getCurrentWorktreeCount(state) - 1)
|
|
461
646
|
} else {
|
|
462
647
|
optionCount = Math.max(1, filterSessions(state.sessions, state.modal.editBuffer).length + 1)
|
|
463
648
|
}
|
|
464
649
|
if (optionCount === 0) return state
|
|
465
650
|
const clamped = Math.max(0, Math.min(optionCount - 1, action.index))
|
|
651
|
+
if (state.modal.type === 'new-tab' && state.modal.step === 'worktree') {
|
|
652
|
+
if (clamped === state.modal.selectedIndex) return state
|
|
653
|
+
return {
|
|
654
|
+
...state,
|
|
655
|
+
modal: {
|
|
656
|
+
...state.modal,
|
|
657
|
+
createWorktree: clamped === optionCount - 1,
|
|
658
|
+
selectedIndex: clamped,
|
|
659
|
+
worktreeDeleteConfirmId: null,
|
|
660
|
+
worktreeDeleteMessage: null,
|
|
661
|
+
},
|
|
662
|
+
}
|
|
663
|
+
}
|
|
466
664
|
if (clamped === state.modal.selectedIndex) return state
|
|
467
665
|
return { ...state, modal: { ...state.modal, selectedIndex: clamped } }
|
|
468
666
|
}
|
|
469
667
|
case 'move-modal-cursor': {
|
|
470
668
|
if (state.modal.editBuffer === null) return state
|
|
471
|
-
|
|
669
|
+
let editableValue = state.modal.editBuffer
|
|
670
|
+
if (state.modal.type === 'new-tab' && state.modal.editingCommand === null) {
|
|
671
|
+
if (state.modal.activeField === 'worktree-name') {
|
|
672
|
+
editableValue = state.modal.worktreeName
|
|
673
|
+
} else if (state.modal.activeField === 'branch-name') {
|
|
674
|
+
editableValue = state.modal.branchName
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (
|
|
678
|
+
state.modal.type === 'new-tab' &&
|
|
679
|
+
state.modal.editingCommand === null &&
|
|
680
|
+
state.modal.step === 'worktree' &&
|
|
681
|
+
state.modal.activeField === 'target-worktree'
|
|
682
|
+
) {
|
|
683
|
+
return state
|
|
684
|
+
}
|
|
685
|
+
const len = editableValue.length
|
|
472
686
|
const current = state.modal.cursorPos ?? len
|
|
473
687
|
let next = current
|
|
474
688
|
if (action.to === 'home') next = 0
|
|
@@ -482,7 +696,22 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
482
696
|
if (state.modal.editBuffer === null) {
|
|
483
697
|
return state
|
|
484
698
|
}
|
|
485
|
-
|
|
699
|
+
let buffer = state.modal.editBuffer
|
|
700
|
+
if (state.modal.type === 'new-tab' && state.modal.editingCommand === null) {
|
|
701
|
+
if (state.modal.activeField === 'worktree-name') {
|
|
702
|
+
buffer = state.modal.worktreeName
|
|
703
|
+
} else if (state.modal.activeField === 'branch-name') {
|
|
704
|
+
buffer = state.modal.branchName
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (
|
|
708
|
+
state.modal.type === 'new-tab' &&
|
|
709
|
+
state.modal.editingCommand === null &&
|
|
710
|
+
state.modal.step === 'worktree' &&
|
|
711
|
+
state.modal.activeField === 'target-worktree'
|
|
712
|
+
) {
|
|
713
|
+
return state
|
|
714
|
+
}
|
|
486
715
|
const cursor = clampCursor(state.modal.cursorPos ?? buffer.length, buffer.length)
|
|
487
716
|
let nextBuffer: string
|
|
488
717
|
let nextCursor: number
|
|
@@ -495,6 +724,35 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
495
724
|
nextCursor = cursor + action.char.length
|
|
496
725
|
}
|
|
497
726
|
const isNewTabEditing = state.modal.type === 'new-tab' && state.modal.editingCommand !== null
|
|
727
|
+
if (
|
|
728
|
+
state.modal.type === 'new-tab' &&
|
|
729
|
+
state.modal.editingCommand === null &&
|
|
730
|
+
state.modal.activeField === 'worktree-name'
|
|
731
|
+
) {
|
|
732
|
+
return {
|
|
733
|
+
...state,
|
|
734
|
+
modal: {
|
|
735
|
+
...state.modal,
|
|
736
|
+
cursorPos: nextCursor,
|
|
737
|
+
worktreeName: nextBuffer,
|
|
738
|
+
},
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (
|
|
742
|
+
state.modal.type === 'new-tab' &&
|
|
743
|
+
state.modal.editingCommand === null &&
|
|
744
|
+
state.modal.activeField === 'branch-name'
|
|
745
|
+
) {
|
|
746
|
+
return {
|
|
747
|
+
...state,
|
|
748
|
+
modal: {
|
|
749
|
+
...state.modal,
|
|
750
|
+
branchError: null,
|
|
751
|
+
branchName: nextBuffer,
|
|
752
|
+
cursorPos: nextCursor,
|
|
753
|
+
},
|
|
754
|
+
}
|
|
755
|
+
}
|
|
498
756
|
const resetIndex =
|
|
499
757
|
!isNewTabEditing &&
|
|
500
758
|
(state.modal.type === 'session-picker' ||
|
|
@@ -526,6 +784,50 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
526
784
|
},
|
|
527
785
|
}
|
|
528
786
|
}
|
|
787
|
+
if (state.modal.type === 'new-tab') {
|
|
788
|
+
if (state.modal.step === 'worktree-create') {
|
|
789
|
+
const optionCount = getCurrentWorktreeCount(state) + 1
|
|
790
|
+
return {
|
|
791
|
+
...state,
|
|
792
|
+
modal: {
|
|
793
|
+
...state.modal,
|
|
794
|
+
activeField: 'target-worktree',
|
|
795
|
+
branchError: null,
|
|
796
|
+
createWorktree: false,
|
|
797
|
+
cursorPos: 0,
|
|
798
|
+
selectedIndex: Math.min(
|
|
799
|
+
state.modal.targetWorktreeIndex,
|
|
800
|
+
Math.max(0, optionCount - 1)
|
|
801
|
+
),
|
|
802
|
+
step: 'worktree',
|
|
803
|
+
},
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (state.modal.step === 'worktree') {
|
|
807
|
+
const selectedAssistantId = state.modal.selectedAssistantId
|
|
808
|
+
const assistants = filterAssistants(
|
|
809
|
+
getAllAssistantOptions(state.customCommands),
|
|
810
|
+
state.modal.editBuffer
|
|
811
|
+
)
|
|
812
|
+
const assistantIndex = assistants.findIndex(
|
|
813
|
+
(assistant) => assistant.id === selectedAssistantId
|
|
814
|
+
)
|
|
815
|
+
return {
|
|
816
|
+
...state,
|
|
817
|
+
modal: {
|
|
818
|
+
...state.modal,
|
|
819
|
+
activeField: 'assistant',
|
|
820
|
+
createWorktree: false,
|
|
821
|
+
cursorPos: state.modal.editBuffer?.length ?? 0,
|
|
822
|
+
selectedIndex: Math.max(0, assistantIndex),
|
|
823
|
+
step: 'assistant',
|
|
824
|
+
worktreeDeleteConfirmId: null,
|
|
825
|
+
worktreeDeleteMessage: null,
|
|
826
|
+
},
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return { ...state, focusMode: 'navigation', modal: emptyModal() }
|
|
830
|
+
}
|
|
529
831
|
if (state.modal.type === 'create-session' || state.modal.type === 'snippet-editor') {
|
|
530
832
|
return { ...state, focusMode: 'navigation', modal: emptyModal() }
|
|
531
833
|
}
|
|
@@ -534,7 +836,6 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
534
836
|
state.modal.type === 'session-picker' ||
|
|
535
837
|
state.modal.type === 'snippet-picker' ||
|
|
536
838
|
state.modal.type === 'theme-picker' ||
|
|
537
|
-
state.modal.type === 'new-tab' ||
|
|
538
839
|
state.modal.type === 'help'
|
|
539
840
|
) {
|
|
540
841
|
return { ...state, focusMode: 'navigation', modal: emptyModal() }
|
|
@@ -607,6 +908,38 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
607
908
|
},
|
|
608
909
|
}
|
|
609
910
|
}
|
|
911
|
+
if (state.modal.type === 'new-tab') {
|
|
912
|
+
if (state.modal.step === 'assistant') return state
|
|
913
|
+
if (state.modal.step === 'worktree-create') {
|
|
914
|
+
const nextField: typeof state.modal.activeField =
|
|
915
|
+
state.modal.activeField === 'worktree-name' ? 'branch-name' : 'worktree-name'
|
|
916
|
+
const nextValue =
|
|
917
|
+
nextField === 'branch-name' ? state.modal.branchName : state.modal.worktreeName
|
|
918
|
+
return {
|
|
919
|
+
...state,
|
|
920
|
+
modal: {
|
|
921
|
+
...state.modal,
|
|
922
|
+
activeField: nextField,
|
|
923
|
+
cursorPos: nextValue.length,
|
|
924
|
+
selectedIndex: 0,
|
|
925
|
+
},
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
let nextField: typeof state.modal.activeField = 'target-worktree'
|
|
929
|
+
if (state.modal.activeField === 'target-worktree' && state.modal.createWorktree) {
|
|
930
|
+
nextField = 'worktree-name'
|
|
931
|
+
}
|
|
932
|
+
const nextValue =
|
|
933
|
+
nextField === 'worktree-name' ? state.modal.worktreeName : (state.modal.editBuffer ?? '')
|
|
934
|
+
return {
|
|
935
|
+
...state,
|
|
936
|
+
modal: {
|
|
937
|
+
...state.modal,
|
|
938
|
+
activeField: nextField,
|
|
939
|
+
cursorPos: nextValue.length,
|
|
940
|
+
},
|
|
941
|
+
}
|
|
942
|
+
}
|
|
610
943
|
return state
|
|
611
944
|
}
|
|
612
945
|
case 'select-directory': {
|
|
@@ -638,9 +971,10 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
|
|
|
638
971
|
}
|
|
639
972
|
}
|
|
640
973
|
case 'open-rename-tab-modal': {
|
|
641
|
-
const activeTab =
|
|
642
|
-
|
|
643
|
-
|
|
974
|
+
const activeTab =
|
|
975
|
+
state.activeTabId != null && state.activeTabId !== ''
|
|
976
|
+
? state.tabs.find((tab) => tab.id === state.activeTabId)
|
|
977
|
+
: undefined
|
|
644
978
|
if (!activeTab) {
|
|
645
979
|
return state
|
|
646
980
|
}
|
|
@@ -2,6 +2,7 @@ import type { AppAction, AppState } from '../types'
|
|
|
2
2
|
|
|
3
3
|
import { filterSessions } from '../selectors'
|
|
4
4
|
import { restoreWorkspaceState } from '../session-persistence'
|
|
5
|
+
import { withActiveWorktree } from '../session-worktrees'
|
|
5
6
|
|
|
6
7
|
const CLOSED_MODAL = {
|
|
7
8
|
editBuffer: null,
|
|
@@ -57,7 +58,7 @@ export function reduceSessionState(state: AppState, action: AppAction): AppState
|
|
|
57
58
|
const newSessions = state.sessions.filter((session) => session.id !== action.sessionId)
|
|
58
59
|
const nextStatuses = { ...state.sessionStatuses }
|
|
59
60
|
delete nextStatuses[action.sessionId]
|
|
60
|
-
if (action.openSessionPicker) {
|
|
61
|
+
if (action.openSessionPicker === true) {
|
|
61
62
|
const filteredNew = filterSessions(newSessions, state.modal.editBuffer)
|
|
62
63
|
const maxIndex = filteredNew.length
|
|
63
64
|
const clampedIndex = Math.min(state.modal.selectedIndex, maxIndex)
|
|
@@ -120,6 +121,64 @@ export function reduceSessionState(state: AppState, action: AppAction): AppState
|
|
|
120
121
|
sessionStatuses: { ...state.sessionStatuses, [action.sessionId]: action.status },
|
|
121
122
|
}
|
|
122
123
|
}
|
|
124
|
+
case 'add-worktree-record':
|
|
125
|
+
return {
|
|
126
|
+
...state,
|
|
127
|
+
sessions: state.sessions.map((session) => {
|
|
128
|
+
if (session.id !== action.sessionId) return session
|
|
129
|
+
const next = {
|
|
130
|
+
...session,
|
|
131
|
+
activeWorktreeId:
|
|
132
|
+
action.activate === true ? action.worktree.id : session.activeWorktreeId,
|
|
133
|
+
projectPath: action.activate === true ? action.worktree.path : session.projectPath,
|
|
134
|
+
updatedAt: new Date().toISOString(),
|
|
135
|
+
worktrees: [...(session.worktrees ?? []), action.worktree],
|
|
136
|
+
}
|
|
137
|
+
return next.activeWorktreeId != null && next.activeWorktreeId !== ''
|
|
138
|
+
? next
|
|
139
|
+
: withActiveWorktree(next, action.worktree.id)
|
|
140
|
+
}),
|
|
141
|
+
}
|
|
142
|
+
case 'remove-worktree-record':
|
|
143
|
+
return {
|
|
144
|
+
...state,
|
|
145
|
+
sessions: state.sessions.map((session) => {
|
|
146
|
+
if (session.id !== action.sessionId) return session
|
|
147
|
+
const remaining = (session.worktrees ?? []).filter((w) => w.id !== action.worktreeId)
|
|
148
|
+
if (remaining.length === 0) return session
|
|
149
|
+
if (session.activeWorktreeId !== action.worktreeId) {
|
|
150
|
+
return { ...session, updatedAt: new Date().toISOString(), worktrees: remaining }
|
|
151
|
+
}
|
|
152
|
+
return withActiveWorktree(
|
|
153
|
+
{ ...session, activeWorktreeId: remaining[0]?.id, worktrees: remaining },
|
|
154
|
+
remaining[0]?.id ?? ''
|
|
155
|
+
)
|
|
156
|
+
}),
|
|
157
|
+
}
|
|
158
|
+
case 'set-active-worktree':
|
|
159
|
+
return {
|
|
160
|
+
...state,
|
|
161
|
+
sessions: state.sessions.map((session) =>
|
|
162
|
+
session.id === action.sessionId ? withActiveWorktree(session, action.worktreeId) : session
|
|
163
|
+
),
|
|
164
|
+
}
|
|
165
|
+
case 'update-worktree-record':
|
|
166
|
+
return {
|
|
167
|
+
...state,
|
|
168
|
+
sessions: state.sessions.map((session) =>
|
|
169
|
+
session.id === action.sessionId
|
|
170
|
+
? {
|
|
171
|
+
...session,
|
|
172
|
+
updatedAt: new Date().toISOString(),
|
|
173
|
+
worktrees: session.worktrees?.map((worktree) =>
|
|
174
|
+
worktree.id === action.worktreeId
|
|
175
|
+
? { ...worktree, ...action.patch, updatedAt: new Date().toISOString() }
|
|
176
|
+
: worktree
|
|
177
|
+
),
|
|
178
|
+
}
|
|
179
|
+
: session
|
|
180
|
+
),
|
|
181
|
+
}
|
|
123
182
|
default:
|
|
124
183
|
return null
|
|
125
184
|
}
|