@brimveyn/aimux 1.21.0 → 1.22.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 +27 -25
- package/package.json +2 -2
- package/src/app-runtime/auto-commit-driver.ts +22 -27
- package/src/app-runtime/backend-attach-runtime.ts +43 -36
- package/src/app-runtime/backend-runtime-events.ts +107 -88
- package/src/app-runtime/editor-actions.ts +201 -0
- package/src/app-runtime/git-actions.ts +197 -0
- package/src/app-runtime/measure-ref.ts +19 -0
- package/src/app-runtime/navigation-actions.ts +213 -0
- package/src/app-runtime/project-actions.ts +188 -0
- package/src/app-runtime/prompt-injection.ts +123 -0
- package/src/app-runtime/selection.ts +29 -0
- package/src/app-runtime/settings-actions.ts +156 -0
- package/src/app-runtime/setup-actions.ts +237 -0
- package/src/app-runtime/side-effect-context.ts +33 -0
- package/src/app-runtime/side-effects.ts +302 -1541
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/tab-actions.ts +218 -0
- package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
- package/src/app-runtime/use-auto-commit-driver.ts +14 -13
- package/src/app-runtime/use-backend-runtime.ts +12 -11
- package/src/app-runtime/use-directory-search.ts +5 -4
- package/src/app-runtime/use-mouse-handlers.ts +2 -1
- package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app-runtime/use-setup-runner.ts +96 -0
- package/src/app-runtime/use-terminal-resize.ts +18 -14
- package/src/app-runtime/workspace-actions.ts +366 -0
- package/src/app-runtime/workspace-activity.ts +138 -0
- package/src/app-runtime/workspace-naming.ts +90 -0
- package/src/app.tsx +93 -48
- package/src/assets/sounds/bell.wav +0 -0
- package/src/assets/sounds/ding.wav +0 -0
- package/src/assets/sounds/plane.wav +0 -0
- package/src/cli/client/daemon-client.ts +2 -2
- package/src/cli/client/project-resolver.ts +112 -0
- package/src/cli/commands/project/close.ts +32 -0
- package/src/cli/commands/project/create.ts +93 -0
- package/src/cli/commands/project/list.ts +25 -0
- package/src/cli/commands/project/show.ts +32 -0
- package/src/cli/commands/{workspace → project}/switch.ts +19 -19
- package/src/cli/commands/tab/await.ts +2 -2
- package/src/cli/commands/tab/close.ts +3 -3
- package/src/cli/commands/tab/create.ts +74 -73
- package/src/cli/commands/tab/focus.ts +3 -3
- package/src/cli/commands/tab/list.ts +6 -6
- package/src/cli/commands/tab/run.ts +2 -2
- package/src/cli/commands/tab/send.ts +2 -2
- package/src/cli/commands/tab/snapshot.ts +2 -2
- package/src/cli/commands/tab/tail.ts +2 -2
- package/src/cli/commands/tab/wait.ts +2 -2
- package/src/cli/commands/worker/await.ts +3 -3
- package/src/cli/commands/worker/doctor.ts +30 -30
- package/src/cli/commands/worker/list.ts +15 -15
- package/src/cli/commands/worker/prompt.ts +3 -3
- package/src/cli/commands/worker/run.ts +23 -23
- package/src/cli/commands/worker/shared.ts +53 -59
- package/src/cli/commands/worker/stop.ts +29 -29
- package/src/cli/commands/worker/submit.ts +3 -3
- package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
- package/src/cli/commands/workspace/create.ts +30 -68
- package/src/cli/commands/workspace/list.ts +52 -9
- package/src/cli/commands/workspace/remove.ts +81 -0
- package/src/cli/completion/plan.ts +2 -2
- package/src/cli/completion/sources.ts +18 -18
- package/src/cli/context.ts +13 -13
- package/src/cli/flags.ts +30 -10
- package/src/cli/index.ts +26 -16
- package/src/cli/output.ts +1 -1
- package/src/cli/registry.ts +12 -12
- package/src/config/loader.ts +16 -5
- package/src/config.ts +70 -112
- package/src/daemon/catalog-writer.ts +67 -67
- package/src/daemon/daemon.ts +205 -201
- package/src/daemon/session-manager.ts +39 -39
- package/src/daemon/session-registry.ts +14 -14
- package/src/git/divergence.ts +33 -2
- package/src/git/git-poller.ts +1 -1
- package/src/git/git-status.ts +2 -2
- package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
- package/src/git/pr-status.ts +1 -1
- package/src/git/repo-discovery.ts +1 -1
- package/src/git/use-repo-discovery.ts +1 -1
- package/src/git/workspace-branch-poller.ts +54 -0
- package/src/git/workspace-divergence-poller.ts +65 -0
- package/src/git/worktree.ts +29 -0
- package/src/index.tsx +4 -4
- package/src/input/keymap/help-entries.ts +7 -5
- package/src/input/modes/bridge.ts +13 -12
- package/src/input/modes/handlers/shared.ts +1 -1
- package/src/input/modes/transitions.ts +34 -21
- package/src/input/modes/types.ts +48 -31
- package/src/ipc/manager-protocol.ts +44 -44
- package/src/ipc/protocol.ts +192 -166
- package/src/platform/play-sound.ts +184 -0
- package/src/platform/project-search.ts +8 -8
- package/src/platform/worktree-paths.ts +8 -6
- package/src/pty/assistant-question-extractor.ts +1 -1
- package/src/pty/assistant-status-detection-loop.ts +79 -55
- package/src/pty/assistant-status-detector.ts +40 -15
- package/src/pty/command-registry.ts +81 -1
- package/src/restart-terminal-manager.ts +1 -1
- package/src/services/ai-usage/provider.ts +9 -2
- package/src/services/ai-usage/spawn.ts +19 -6
- package/src/session-backend/bootstrap.ts +4 -4
- package/src/session-backend/local-session-backend.ts +78 -75
- package/src/session-backend/remote-session-backend.ts +64 -52
- package/src/session-backend/types.ts +43 -32
- package/src/settings/live.ts +90 -0
- package/src/settings/search.ts +43 -0
- package/src/settings/sections/about.ts +49 -0
- package/src/settings/sections/appearance.ts +54 -0
- package/src/settings/sections/automation.ts +105 -0
- package/src/settings/sections/commands.ts +72 -0
- package/src/settings/sections/editor.ts +56 -0
- package/src/settings/sections/experimental.ts +57 -0
- package/src/settings/sections/git.ts +112 -0
- package/src/settings/sections/index.ts +95 -0
- package/src/settings/sections/integrations.ts +23 -0
- package/src/settings/sections/layout.ts +72 -0
- package/src/settings/sections/notifications.ts +99 -0
- package/src/settings/sections/setup.ts +54 -0
- package/src/settings/sections/status-bar.ts +63 -0
- package/src/settings/settings-store.ts +207 -0
- package/src/settings/types.ts +108 -0
- package/src/snippets/run-shell-var.ts +48 -22
- package/src/state/actions.ts +342 -0
- package/src/state/app-store.ts +2 -1
- package/src/state/bars.ts +1 -1
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/layout-resize.ts +2 -2
- package/src/state/project-catalog.ts +275 -0
- package/src/state/project-data.ts +143 -0
- package/src/state/{session-persistence.ts → project-persistence.ts} +74 -68
- package/src/state/project-save.ts +46 -0
- package/src/state/project-workspaces.ts +392 -0
- package/src/state/reducers/auto-commit-state.ts +11 -10
- package/src/state/reducers/git-commit-modal-state.ts +122 -0
- package/src/state/reducers/git-mode-state.ts +1 -1
- package/src/state/reducers/git-panel-state.ts +10 -6
- package/src/state/reducers/modal-state.ts +290 -681
- package/src/state/reducers/multi-repo-state.ts +3 -6
- package/src/state/reducers/project-state.ts +312 -0
- package/src/state/reducers/settings-state.ts +80 -0
- package/src/state/reducers/tab-state.ts +107 -60
- package/src/state/reducers/ui-state.ts +4 -3
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +57 -24
- package/src/state/types.ts +244 -405
- package/src/state/validation.ts +14 -12
- package/src/terminal-manager/manager-client.ts +32 -32
- package/src/terminal-manager/terminal-manager.ts +24 -24
- package/src/ui/components/git/git-panel.tsx +2 -2
- package/src/ui/components/git/git-view.tsx +12 -12
- package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
- package/src/ui/components/git/pane/git-pane-widget.tsx +7 -7
- package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
- package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
- package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
- package/src/ui/components/layout/status-bar.tsx +6 -2
- package/src/ui/components/layout/terminal-pane.tsx +34 -29
- package/src/ui/components/layout/top-tab-bar.tsx +46 -36
- package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
- package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
- package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
- package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
- package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
- package/src/ui/components/modals/shared/form.tsx +34 -22
- package/src/ui/components/modals/shared/picker.tsx +10 -10
- package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
- package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
- package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
- package/src/ui/components/primitives/input-field.tsx +7 -3
- package/src/ui/components/primitives/surface.tsx +3 -0
- package/src/ui/components/settings/row-value.tsx +74 -0
- package/src/ui/components/settings/settings-row.tsx +78 -0
- package/src/ui/components/settings/settings-view.tsx +169 -0
- package/src/ui/components/setup/setup-widget.tsx +152 -0
- package/src/ui/flash/build-labels.ts +27 -27
- package/src/ui/hooks/use-activity-sprite.ts +73 -0
- package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
- package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
- package/src/ui/root.tsx +139 -121
- package/src/ui/status-bar-model.ts +48 -34
- package/src/ui/terminal-graphics/kitty.ts +28 -2
- package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
- package/src/ui/terminal-graphics/sprites.ts +246 -0
- package/src/ui/truncate.ts +7 -0
- package/src/ui/widgets/registry.tsx +6 -3
- package/src/ui/widgets/widget-context-menu.ts +26 -10
- package/src/update.ts +2 -2
- package/src/app-runtime/session-actions.ts +0 -187
- package/src/cli/client/workspace-resolver.ts +0 -110
- package/src/cli/commands/workspace/close.ts +0 -32
- package/src/cli/commands/workspace/show.ts +0 -32
- package/src/cli/commands/worktree/create.ts +0 -55
- package/src/cli/commands/worktree/list.ts +0 -68
- package/src/cli/commands/worktree/remove.ts +0 -81
- package/src/git/worktree-branch-poller.ts +0 -54
- package/src/git/worktree-divergence-poller.ts +0 -59
- package/src/state/reducers/session-state.ts +0 -262
- package/src/state/session-catalog.ts +0 -143
- package/src/state/session-worktrees.ts +0 -260
- package/src/state/workspace-save.ts +0 -43
- package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
- package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
- package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
- /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
|
@@ -1,248 +1,72 @@
|
|
|
1
1
|
import { useCallback, useMemo } from 'react'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import type { AssistantId, WorktreeRecord } from '../../../../state/types'
|
|
3
|
+
import type { AssistantId } from '../../../../state/types'
|
|
5
4
|
|
|
6
5
|
import { getAllAssistantOptions, getAssistantOption } from '../../../../pty/command-registry'
|
|
7
6
|
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
8
|
-
import {
|
|
9
|
-
buildBaseRefOptions,
|
|
10
|
-
filterAssistants,
|
|
11
|
-
getTemplateNoneOffset,
|
|
12
|
-
} from '../../../../state/selectors'
|
|
7
|
+
import { getNewTabAssistantOptions } from '../../../../state/selectors'
|
|
13
8
|
import { useTheme } from '../../../theme'
|
|
14
9
|
import { uiTokens } from '../../../ui-tokens'
|
|
15
|
-
import {
|
|
10
|
+
import { Form, TextField } from '../shared/form'
|
|
16
11
|
import { Picker, type PickerItem } from '../shared/picker'
|
|
17
|
-
import { WorktreeDeleteConfirm } from '../shared/worktree-delete-confirm'
|
|
18
12
|
|
|
19
13
|
interface NewTabModalProps {
|
|
20
14
|
selectedIndex: number
|
|
21
15
|
customCommands: Record<string, string>
|
|
22
16
|
filter: string | null
|
|
23
17
|
cursorPos?: number
|
|
24
|
-
currentSessionId: string | null
|
|
25
18
|
editingCommand: AssistantId | null
|
|
26
19
|
editBuffer: string
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
branchName: string
|
|
30
|
-
createWorktree: boolean
|
|
31
|
-
selectedAssistantId: AssistantId | null
|
|
32
|
-
step: 'assistant' | 'worktree' | 'worktree-create' | 'template'
|
|
33
|
-
worktreeDeletePrompt: { worktreeId: string; reason: string } | null
|
|
34
|
-
worktrees: WorktreeRecord[]
|
|
35
|
-
worktreeName: string
|
|
36
|
-
baseQuery: string
|
|
37
|
-
baseRef: string
|
|
38
|
-
baseBranches: string[]
|
|
39
|
-
worktreeTemplates: WorktreeTemplate[]
|
|
40
|
-
/**
|
|
41
|
-
* True when this modal is the worktree-aware new-tab flow. False when the
|
|
42
|
-
* component is reused for `split-picker`, which can only choose an
|
|
43
|
-
* assistant — the template shortcut entry must be hidden there.
|
|
44
|
-
*/
|
|
45
|
-
allowTemplateShortcut: boolean
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const TEMPLATE_SHORTCUT_KEY = '__template-shortcut__'
|
|
49
|
-
|
|
50
|
-
function getDeleteBlockedReason({
|
|
51
|
-
currentSessionId,
|
|
52
|
-
worktree,
|
|
53
|
-
worktrees,
|
|
54
|
-
}: {
|
|
55
|
-
currentSessionId: string | null
|
|
56
|
-
worktree: WorktreeRecord | undefined
|
|
57
|
-
worktrees: WorktreeRecord[]
|
|
58
|
-
}): string | null {
|
|
59
|
-
if (!worktree) return null
|
|
60
|
-
if (!(currentSessionId != null && currentSessionId !== ''))
|
|
61
|
-
return 'Cannot delete: no active session.'
|
|
62
|
-
if (worktrees.length <= 1) return 'Cannot delete: at least one worktree must remain.'
|
|
63
|
-
if (worktree.source === 'primary') return 'Cannot delete: root worktree is required.'
|
|
64
|
-
if (worktree.source === 'aimux-temp' && !worktree.createdByAimux) {
|
|
65
|
-
return 'Cannot delete: this temp worktree was not created by Aimux.'
|
|
66
|
-
}
|
|
67
|
-
return null
|
|
20
|
+
/** Chained from `<C-p>`: a shell cannot take the prompt, so Terminal is hidden. */
|
|
21
|
+
excludeTerminal: boolean
|
|
68
22
|
}
|
|
69
23
|
|
|
70
24
|
export function NewTabModal({
|
|
71
|
-
activeField,
|
|
72
|
-
allowTemplateShortcut,
|
|
73
|
-
baseBranches,
|
|
74
|
-
baseQuery,
|
|
75
|
-
baseRef,
|
|
76
|
-
branchError,
|
|
77
|
-
branchName,
|
|
78
|
-
createWorktree,
|
|
79
|
-
currentSessionId,
|
|
80
25
|
cursorPos,
|
|
81
26
|
customCommands,
|
|
82
27
|
editBuffer,
|
|
83
28
|
editingCommand,
|
|
29
|
+
excludeTerminal,
|
|
84
30
|
filter,
|
|
85
|
-
selectedAssistantId,
|
|
86
31
|
selectedIndex,
|
|
87
|
-
step,
|
|
88
|
-
worktreeDeletePrompt,
|
|
89
|
-
worktreeName,
|
|
90
|
-
worktrees,
|
|
91
|
-
worktreeTemplates,
|
|
92
32
|
}: NewTabModalProps) {
|
|
93
33
|
const t = useTheme()
|
|
94
34
|
const options = useMemo(() => getAllAssistantOptions(customCommands), [customCommands])
|
|
95
|
-
const filtered = useMemo(
|
|
35
|
+
const filtered = useMemo(
|
|
36
|
+
() => getNewTabAssistantOptions(customCommands, filter, excludeTerminal),
|
|
37
|
+
[customCommands, excludeTerminal, filter]
|
|
38
|
+
)
|
|
96
39
|
|
|
97
40
|
const handleHover = useCallback(
|
|
98
41
|
(index: number) => dispatchGlobal({ index, type: 'set-modal-selection-index' }),
|
|
99
42
|
[]
|
|
100
43
|
)
|
|
101
44
|
|
|
102
|
-
const
|
|
103
|
-
() =>
|
|
104
|
-
|
|
45
|
+
const items = useMemo<PickerItem[]>(
|
|
46
|
+
() =>
|
|
47
|
+
filtered.map((option, index) => {
|
|
105
48
|
const active = index === selectedIndex
|
|
106
|
-
const
|
|
107
|
-
const blockedReason = getDeleteBlockedReason({ currentSessionId, worktree, worktrees })
|
|
108
|
-
const canDelete = blockedReason == null || blockedReason === ''
|
|
49
|
+
const customCmd = customCommands[option.id]
|
|
109
50
|
return {
|
|
110
|
-
key:
|
|
51
|
+
key: option.id,
|
|
111
52
|
onClick: () => {
|
|
112
53
|
dispatchGlobal({ index, type: 'set-modal-selection-index' })
|
|
113
54
|
runSideEffectGlobal({ type: 'launch-selected-assistant' })
|
|
114
55
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
? () => {
|
|
118
|
-
dispatchGlobal({ index, type: 'set-modal-selection-index' })
|
|
119
|
-
runSideEffectGlobal({
|
|
120
|
-
force: false,
|
|
121
|
-
sessionId: currentSessionId,
|
|
122
|
-
type: 'delete-worktree',
|
|
123
|
-
worktreeId: worktree.id,
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
: undefined,
|
|
56
|
+
onEdit: () =>
|
|
57
|
+
dispatchGlobal({ assistantId: option.id, type: 'open-edit-custom-command' }),
|
|
127
58
|
subtitle: (
|
|
128
59
|
<box flexDirection="column">
|
|
129
|
-
<text fg={t.textMuted}>{
|
|
130
|
-
|
|
60
|
+
<text fg={t.textMuted}>{option.description}</text>
|
|
61
|
+
{customCmd != null && customCmd !== '' ? (
|
|
62
|
+
<text fg={t.primary}>{customCmd}</text>
|
|
63
|
+
) : null}
|
|
131
64
|
</box>
|
|
132
65
|
),
|
|
133
|
-
title: <text fg={active ? t.text : t.textMuted}>{label}</text>,
|
|
66
|
+
title: <text fg={active ? t.text : t.textMuted}>{option.label}</text>,
|
|
134
67
|
}
|
|
135
68
|
}),
|
|
136
|
-
|
|
137
|
-
key: '__create-worktree__',
|
|
138
|
-
onClick: () => {
|
|
139
|
-
dispatchGlobal({ type: 'enter-new-tab-worktree-create' })
|
|
140
|
-
runSideEffectGlobal({ type: 'load-new-tab-base-branches' })
|
|
141
|
-
},
|
|
142
|
-
subtitle: <text fg={t.textMuted}>Create an Aimux temp worktree</text>,
|
|
143
|
-
title: <text fg={createWorktree ? t.text : t.textMuted}>Create new worktree</text>,
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
[createWorktree, currentSessionId, selectedIndex, t, worktrees]
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
const noneOffset = getTemplateNoneOffset(selectedAssistantId)
|
|
150
|
-
const showNoneOption = noneOffset === 1
|
|
151
|
-
const templateItems = useMemo<PickerItem[]>(() => {
|
|
152
|
-
const noneItem: PickerItem[] = showNoneOption
|
|
153
|
-
? [
|
|
154
|
-
{
|
|
155
|
-
key: '__none__',
|
|
156
|
-
onClick: () => {
|
|
157
|
-
dispatchGlobal({ index: 0, type: 'set-modal-selection-index' })
|
|
158
|
-
runSideEffectGlobal({ type: 'launch-selected-assistant' })
|
|
159
|
-
},
|
|
160
|
-
subtitle: <text fg={t.textMuted}>Single pane — current assistant only</text>,
|
|
161
|
-
title: <text fg={selectedIndex === 0 ? t.text : t.textMuted}>None</text>,
|
|
162
|
-
},
|
|
163
|
-
]
|
|
164
|
-
: []
|
|
165
|
-
return [
|
|
166
|
-
...noneItem,
|
|
167
|
-
...worktreeTemplates.map((template, idx) => {
|
|
168
|
-
const itemIndex = idx + noneOffset
|
|
169
|
-
const active = itemIndex === selectedIndex
|
|
170
|
-
const tabCount = template.tabs.length
|
|
171
|
-
const paneCount = template.tabs.reduce((sum, tab) => sum + tab.panes.length, 0)
|
|
172
|
-
return {
|
|
173
|
-
key: template.id,
|
|
174
|
-
onClick: () => {
|
|
175
|
-
dispatchGlobal({ index: itemIndex, type: 'set-modal-selection-index' })
|
|
176
|
-
runSideEffectGlobal({ type: 'launch-selected-assistant' })
|
|
177
|
-
},
|
|
178
|
-
subtitle:
|
|
179
|
-
template.description != null && template.description !== '' ? (
|
|
180
|
-
<text fg={t.textMuted}>{template.description}</text>
|
|
181
|
-
) : (
|
|
182
|
-
<text fg={t.textMuted}>
|
|
183
|
-
{tabCount} tab{tabCount === 1 ? '' : 's'}, {paneCount} pane
|
|
184
|
-
{paneCount === 1 ? '' : 's'}
|
|
185
|
-
</text>
|
|
186
|
-
),
|
|
187
|
-
title: <text fg={active ? t.text : t.textMuted}>{template.name}</text>,
|
|
188
|
-
}
|
|
189
|
-
}),
|
|
190
|
-
]
|
|
191
|
-
}, [noneOffset, selectedIndex, showNoneOption, t, worktreeTemplates])
|
|
192
|
-
|
|
193
|
-
const showShortcutEntry = allowTemplateShortcut && worktreeTemplates.length > 0
|
|
194
|
-
const items = useMemo<PickerItem[]>(() => {
|
|
195
|
-
const assistantItems: PickerItem[] = filtered.map((option, index) => {
|
|
196
|
-
const active = index === selectedIndex
|
|
197
|
-
const customCmd = customCommands[option.id]
|
|
198
|
-
return {
|
|
199
|
-
key: option.id,
|
|
200
|
-
onClick: () => dispatchGlobal({ assistantId: option.id, type: 'select-new-tab-assistant' }),
|
|
201
|
-
onEdit: () => dispatchGlobal({ assistantId: option.id, type: 'open-edit-custom-command' }),
|
|
202
|
-
subtitle: (
|
|
203
|
-
<box flexDirection="column">
|
|
204
|
-
<text fg={t.textMuted}>{option.description}</text>
|
|
205
|
-
{customCmd != null && customCmd !== '' ? <text fg={t.primary}>{customCmd}</text> : null}
|
|
206
|
-
</box>
|
|
207
|
-
),
|
|
208
|
-
title: <text fg={active ? t.text : t.textMuted}>{option.label}</text>,
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
|
-
if (!showShortcutEntry) return assistantItems
|
|
212
|
-
const shortcutIndex = filtered.length
|
|
213
|
-
const shortcutActive = shortcutIndex === selectedIndex
|
|
214
|
-
return [
|
|
215
|
-
...assistantItems,
|
|
216
|
-
{
|
|
217
|
-
key: TEMPLATE_SHORTCUT_KEY,
|
|
218
|
-
onClick: () => {
|
|
219
|
-
dispatchGlobal({ index: shortcutIndex, type: 'set-modal-selection-index' })
|
|
220
|
-
dispatchGlobal({ type: 'enter-new-tab-template-shortcut' })
|
|
221
|
-
},
|
|
222
|
-
subtitle: (
|
|
223
|
-
<text fg={t.textMuted}>Create a worktree and pick a template — no assistant step</text>
|
|
224
|
-
),
|
|
225
|
-
title: <text fg={shortcutActive ? t.text : t.textMuted}>Worktree from template…</text>,
|
|
226
|
-
},
|
|
227
|
-
]
|
|
228
|
-
}, [customCommands, filtered, showShortcutEntry, selectedIndex, t])
|
|
229
|
-
|
|
230
|
-
const baseItems = useMemo<FormOptionItem[]>(
|
|
231
|
-
() =>
|
|
232
|
-
buildBaseRefOptions(worktrees, baseBranches, baseQuery).map((option) => ({
|
|
233
|
-
key: option.ref,
|
|
234
|
-
leading: (
|
|
235
|
-
<text fg={option.kind === 'worktree' ? t.warning : t.textMuted}>
|
|
236
|
-
{option.kind === 'worktree' ? '\u{e728}' : '\u{e702}'}
|
|
237
|
-
</text>
|
|
238
|
-
),
|
|
239
|
-
subtitle:
|
|
240
|
-
option.kind === 'worktree' ? (
|
|
241
|
-
<text fg={t.textMuted}>worktree: {option.detail}</text>
|
|
242
|
-
) : null,
|
|
243
|
-
title: (active) => <text fg={active ? t.text : t.textMuted}>{option.label}</text>,
|
|
244
|
-
})),
|
|
245
|
-
[baseBranches, baseQuery, t, worktrees]
|
|
69
|
+
[customCommands, filtered, selectedIndex, t]
|
|
246
70
|
)
|
|
247
71
|
|
|
248
72
|
if (editingCommand !== null) {
|
|
@@ -264,142 +88,24 @@ export function NewTabModal({
|
|
|
264
88
|
)
|
|
265
89
|
}
|
|
266
90
|
|
|
267
|
-
if (step === 'template') {
|
|
268
|
-
return (
|
|
269
|
-
<Picker
|
|
270
|
-
title="Pick worktree template"
|
|
271
|
-
keybindsModeId="modal.new-tab.command-edit"
|
|
272
|
-
width={uiTokens.modalWidth.md}
|
|
273
|
-
gap={1}
|
|
274
|
-
filter={null}
|
|
275
|
-
items={templateItems}
|
|
276
|
-
selectedIndex={selectedIndex}
|
|
277
|
-
emptyState={<text fg={t.textMuted}>No templates configured.</text>}
|
|
278
|
-
onHover={handleHover}
|
|
279
|
-
footer={
|
|
280
|
-
<box flexDirection="column">
|
|
281
|
-
<text fg={t.textMuted}>Step 4/4: choose template</text>
|
|
282
|
-
<text fg={t.textMuted}>Enter launches, Esc returns to worktree settings</text>
|
|
283
|
-
</box>
|
|
284
|
-
}
|
|
285
|
-
/>
|
|
286
|
-
)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (step === 'worktree-create') {
|
|
290
|
-
const selectedAssistant =
|
|
291
|
-
options.find((option) => option.id === selectedAssistantId) ?? options[0]
|
|
292
|
-
const baseActive = activeField === 'base'
|
|
293
|
-
return (
|
|
294
|
-
<Form
|
|
295
|
-
title={`New worktree: ${selectedAssistant?.label ?? 'assistant'}`}
|
|
296
|
-
keybindsModeId="modal.new-tab.command-edit"
|
|
297
|
-
width={uiTokens.modalWidth.xl}
|
|
298
|
-
>
|
|
299
|
-
<box flexDirection="column" gap={1}>
|
|
300
|
-
<TextField
|
|
301
|
-
active={activeField === 'worktree-name'}
|
|
302
|
-
label="Worktree name"
|
|
303
|
-
value={worktreeName}
|
|
304
|
-
cursorPos={activeField === 'worktree-name' ? cursorPos : undefined}
|
|
305
|
-
placeholder="my-feature"
|
|
306
|
-
/>
|
|
307
|
-
<box flexDirection="column">
|
|
308
|
-
<TextField
|
|
309
|
-
active={activeField === 'branch-name'}
|
|
310
|
-
label="Branch name"
|
|
311
|
-
value={branchName}
|
|
312
|
-
cursorPos={activeField === 'branch-name' ? cursorPos : undefined}
|
|
313
|
-
placeholder="aimux/my-feature"
|
|
314
|
-
/>
|
|
315
|
-
{branchError != null && branchError !== '' ? (
|
|
316
|
-
<text fg={t.error}>{branchError}</text>
|
|
317
|
-
) : null}
|
|
318
|
-
</box>
|
|
319
|
-
<AutoComplete
|
|
320
|
-
active={baseActive}
|
|
321
|
-
label="Base (fork from)"
|
|
322
|
-
placeholder="branch or worktree to fork from..."
|
|
323
|
-
value={baseQuery}
|
|
324
|
-
displayValue={baseRef !== '' ? baseRef : 'current branch'}
|
|
325
|
-
items={baseItems}
|
|
326
|
-
selectedIndex={selectedIndex}
|
|
327
|
-
cursorPos={baseActive ? cursorPos : undefined}
|
|
328
|
-
maxVisibleRows={6}
|
|
329
|
-
onHover={handleHover}
|
|
330
|
-
emptyState={<text fg={t.textMuted}>No branches found</text>}
|
|
331
|
-
/>
|
|
332
|
-
<text fg={t.textMuted}>Step 3/3: configure new worktree · Tab switches fields</text>
|
|
333
|
-
</box>
|
|
334
|
-
</Form>
|
|
335
|
-
)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (step === 'worktree') {
|
|
339
|
-
const selectedAssistant =
|
|
340
|
-
options.find((option) => option.id === selectedAssistantId) ??
|
|
341
|
-
filtered[selectedIndex] ??
|
|
342
|
-
options[0]
|
|
343
|
-
|
|
344
|
-
if (worktreeDeletePrompt != null) {
|
|
345
|
-
const target = worktrees.find((worktree) => worktree.id === worktreeDeletePrompt.worktreeId)
|
|
346
|
-
const label = target?.branch ?? target?.name ?? 'this worktree'
|
|
347
|
-
return (
|
|
348
|
-
<WorktreeDeleteConfirm
|
|
349
|
-
keybindsModeId="modal.new-tab.worktree-delete-confirm"
|
|
350
|
-
reason={worktreeDeletePrompt.reason}
|
|
351
|
-
worktreeLabel={label}
|
|
352
|
-
/>
|
|
353
|
-
)
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const selectedWorktree = worktrees[selectedIndex]
|
|
357
|
-
const deleteBlockedReason = getDeleteBlockedReason({
|
|
358
|
-
currentSessionId,
|
|
359
|
-
worktree: selectedWorktree,
|
|
360
|
-
worktrees,
|
|
361
|
-
})
|
|
362
|
-
return (
|
|
363
|
-
<Picker
|
|
364
|
-
title={`New assistant: ${selectedAssistant?.label ?? 'assistant'}`}
|
|
365
|
-
keybindsModeId="modal.new-tab.command-edit"
|
|
366
|
-
width={uiTokens.modalWidth.md}
|
|
367
|
-
gap={1}
|
|
368
|
-
filter={null}
|
|
369
|
-
items={worktreeItems}
|
|
370
|
-
selectedIndex={selectedIndex}
|
|
371
|
-
emptyState={<text fg={t.textMuted}>No worktrees.</text>}
|
|
372
|
-
onHover={handleHover}
|
|
373
|
-
footer={
|
|
374
|
-
<box flexDirection="column">
|
|
375
|
-
{deleteBlockedReason != null && deleteBlockedReason !== '' ? (
|
|
376
|
-
<text fg={t.textMuted}>{deleteBlockedReason}</text>
|
|
377
|
-
) : null}
|
|
378
|
-
<text fg={t.textMuted}>Step 2/2: choose worktree</text>
|
|
379
|
-
<text fg={t.textMuted}>Enter launches, Ctrl+d deletes selected worktree</text>
|
|
380
|
-
</box>
|
|
381
|
-
}
|
|
382
|
-
/>
|
|
383
|
-
)
|
|
384
|
-
}
|
|
385
|
-
|
|
386
91
|
return (
|
|
387
92
|
<Picker
|
|
388
|
-
title="New
|
|
93
|
+
title="New tab: choose assistant"
|
|
389
94
|
keybindsModeId="modal.new-tab.command-edit"
|
|
390
95
|
width={uiTokens.modalWidth.md}
|
|
391
96
|
gap={1}
|
|
392
97
|
filter={filter}
|
|
393
|
-
cursorPos={
|
|
98
|
+
cursorPos={cursorPos}
|
|
394
99
|
items={items}
|
|
395
100
|
selectedIndex={selectedIndex}
|
|
396
101
|
emptyState={<text fg={t.textMuted}>No matching assistants.</text>}
|
|
397
102
|
onHover={handleHover}
|
|
398
103
|
footer={
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
104
|
+
<text fg={t.textMuted}>
|
|
105
|
+
{excludeTerminal
|
|
106
|
+
? 'Enter launches in the new workspace and sends your prompt'
|
|
107
|
+
: 'Enter launches in the active workspace'}
|
|
108
|
+
</text>
|
|
403
109
|
}
|
|
404
110
|
/>
|
|
405
111
|
)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { WorkspaceRecord } from '../../../../state/types'
|
|
4
|
+
|
|
5
|
+
import { dispatchGlobal } from '../../../../state/dispatch-ref'
|
|
6
|
+
import { buildBaseRefOptions } from '../../../../state/selectors'
|
|
7
|
+
import { useTheme } from '../../../theme'
|
|
8
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
9
|
+
import { AutoComplete, Form, type FormOptionItem, TextField } from '../shared/form'
|
|
10
|
+
|
|
11
|
+
/** The box is this tall from the start: a one-line slot asks for a one-line task. */
|
|
12
|
+
const PROMPT_LINES = 5
|
|
13
|
+
|
|
14
|
+
interface CreateWorkspaceModalProps {
|
|
15
|
+
activeField: 'prompt' | 'base'
|
|
16
|
+
prompt: string
|
|
17
|
+
branchError: string | null
|
|
18
|
+
baseQuery: string
|
|
19
|
+
baseRef: string
|
|
20
|
+
baseBranches: string[]
|
|
21
|
+
cursorPos?: number
|
|
22
|
+
selectedIndex: number
|
|
23
|
+
workspaces: WorkspaceRecord[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function CreateWorkspaceModal({
|
|
27
|
+
activeField,
|
|
28
|
+
baseBranches,
|
|
29
|
+
baseQuery,
|
|
30
|
+
baseRef,
|
|
31
|
+
branchError,
|
|
32
|
+
cursorPos,
|
|
33
|
+
prompt,
|
|
34
|
+
selectedIndex,
|
|
35
|
+
workspaces,
|
|
36
|
+
}: CreateWorkspaceModalProps) {
|
|
37
|
+
const t = useTheme()
|
|
38
|
+
|
|
39
|
+
const handleHover = useCallback(
|
|
40
|
+
(index: number) => dispatchGlobal({ index, type: 'set-modal-selection-index' }),
|
|
41
|
+
[]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const baseItems = useMemo<FormOptionItem[]>(
|
|
45
|
+
() =>
|
|
46
|
+
buildBaseRefOptions(workspaces, baseBranches, baseQuery).map((option) => ({
|
|
47
|
+
key: option.ref,
|
|
48
|
+
leading: (
|
|
49
|
+
<text fg={option.kind === 'workspace' ? t.warning : t.textMuted}>
|
|
50
|
+
{option.kind === 'workspace' ? '\u{e728}' : '\u{e702}'}
|
|
51
|
+
</text>
|
|
52
|
+
),
|
|
53
|
+
subtitle:
|
|
54
|
+
option.kind === 'workspace' ? (
|
|
55
|
+
<text fg={t.textMuted}>workspace: {option.detail}</text>
|
|
56
|
+
) : null,
|
|
57
|
+
title: (active) => <text fg={active ? t.text : t.textMuted}>{option.label}</text>,
|
|
58
|
+
})),
|
|
59
|
+
[baseBranches, baseQuery, t, workspaces]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const baseActive = activeField === 'base'
|
|
63
|
+
return (
|
|
64
|
+
<Form
|
|
65
|
+
title="New workspace"
|
|
66
|
+
keybindsModeId="modal.create-workspace"
|
|
67
|
+
width={uiTokens.modalWidth.xl}
|
|
68
|
+
>
|
|
69
|
+
<box flexDirection="column" gap={1}>
|
|
70
|
+
<box flexDirection="column">
|
|
71
|
+
<TextField
|
|
72
|
+
active={activeField === 'prompt'}
|
|
73
|
+
label="What do you want to work on?"
|
|
74
|
+
description="Sent to the assistant, and names the workspace and its branch."
|
|
75
|
+
value={prompt}
|
|
76
|
+
cursorPos={activeField === 'prompt' ? cursorPos : undefined}
|
|
77
|
+
placeholder="Describe the task..."
|
|
78
|
+
minLines={PROMPT_LINES}
|
|
79
|
+
/>
|
|
80
|
+
{branchError != null && branchError !== '' ? (
|
|
81
|
+
<text fg={t.error}>{branchError}</text>
|
|
82
|
+
) : null}
|
|
83
|
+
</box>
|
|
84
|
+
<AutoComplete
|
|
85
|
+
active={baseActive}
|
|
86
|
+
label="Base (fork from)"
|
|
87
|
+
placeholder="branch or workspace to fork from..."
|
|
88
|
+
value={baseQuery}
|
|
89
|
+
displayValue={baseRef !== '' ? baseRef : 'current branch'}
|
|
90
|
+
items={baseItems}
|
|
91
|
+
selectedIndex={selectedIndex}
|
|
92
|
+
cursorPos={baseActive ? cursorPos : undefined}
|
|
93
|
+
maxVisibleRows={6}
|
|
94
|
+
onHover={handleHover}
|
|
95
|
+
emptyState={<text fg={t.textMuted}>No branches found</text>}
|
|
96
|
+
/>
|
|
97
|
+
<text fg={t.textMuted}>Ctrl+Enter newline · Tab picks a base · Enter creates</text>
|
|
98
|
+
</box>
|
|
99
|
+
</Form>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
@@ -2,7 +2,7 @@ import { useTheme } from '../../../theme'
|
|
|
2
2
|
import { uiTokens } from '../../../ui-tokens'
|
|
3
3
|
import { Form } from '../shared/form'
|
|
4
4
|
|
|
5
|
-
interface
|
|
5
|
+
interface WorkspaceMoveConfirmModalProps {
|
|
6
6
|
variant: 'stash-target' | 'keep-conflicts'
|
|
7
7
|
files: string[]
|
|
8
8
|
sourceLabel: string
|
|
@@ -12,17 +12,17 @@ interface WorktreeMoveConfirmModalProps {
|
|
|
12
12
|
const MAX_LISTED_FILES = 8
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Confirmation dialog after a recoverable move failure. Both
|
|
15
|
+
* Confirmation dialog after a recoverable move failure. Both workspaces are
|
|
16
16
|
* already back in their original state; confirming re-runs the move with the
|
|
17
17
|
* flag matching the variant (stash the target's changes / keep the conflict
|
|
18
18
|
* markers in the target).
|
|
19
19
|
*/
|
|
20
|
-
export function
|
|
20
|
+
export function WorkspaceMoveConfirmModal({
|
|
21
21
|
files,
|
|
22
22
|
sourceLabel,
|
|
23
23
|
targetLabel,
|
|
24
24
|
variant,
|
|
25
|
-
}:
|
|
25
|
+
}: WorkspaceMoveConfirmModalProps) {
|
|
26
26
|
const t = useTheme()
|
|
27
27
|
const listed = files.slice(0, MAX_LISTED_FILES)
|
|
28
28
|
const remaining = files.length - listed.length
|
|
@@ -30,7 +30,7 @@ export function WorktreeMoveConfirmModal({
|
|
|
30
30
|
return (
|
|
31
31
|
<Form
|
|
32
32
|
title={isStash ? 'Target has conflicting changes' : 'Move hit conflicts'}
|
|
33
|
-
keybindsModeId="modal.
|
|
33
|
+
keybindsModeId="modal.workspace-move-confirm"
|
|
34
34
|
width={uiTokens.modalWidth.md}
|
|
35
35
|
footer={
|
|
36
36
|
<text fg={t.textMuted}>
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { useCallback, useMemo } from 'react'
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { ModalWorkspaceMove, WorkspaceRecord } from '../../../../state/types'
|
|
4
4
|
|
|
5
5
|
import { dispatchGlobal } from '../../../../state/dispatch-ref'
|
|
6
|
-
import { formatDivergence } from '../../../../state/
|
|
6
|
+
import { formatDivergence } from '../../../../state/project-workspaces'
|
|
7
7
|
import { useTheme } from '../../../theme'
|
|
8
8
|
import { uiTokens } from '../../../ui-tokens'
|
|
9
9
|
import { ListItem } from '../../primitives/list-item'
|
|
10
10
|
import { ModalShell } from '../shared/modal-shell'
|
|
11
11
|
|
|
12
|
-
interface
|
|
12
|
+
interface WorkspaceMoveModalProps {
|
|
13
13
|
deleteSource: boolean
|
|
14
14
|
divergence: Record<string, { ahead: number; behind: number }>
|
|
15
15
|
selectedIndex: number
|
|
16
|
-
|
|
17
|
-
stats:
|
|
18
|
-
|
|
16
|
+
sourceWorkspaceId: string
|
|
17
|
+
stats: ModalWorkspaceMove['stats']
|
|
18
|
+
workspaces: WorkspaceRecord[]
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const MOVE_HINTS: [key: string, label: string][] = [
|
|
@@ -25,37 +25,37 @@ const MOVE_HINTS: [key: string, label: string][] = [
|
|
|
25
25
|
['esc', 'cancel'],
|
|
26
26
|
]
|
|
27
27
|
|
|
28
|
-
export function
|
|
28
|
+
export function WorkspaceMoveModal({
|
|
29
29
|
deleteSource,
|
|
30
30
|
divergence,
|
|
31
31
|
selectedIndex,
|
|
32
|
-
|
|
32
|
+
sourceWorkspaceId,
|
|
33
33
|
stats,
|
|
34
|
-
|
|
35
|
-
}:
|
|
34
|
+
workspaces,
|
|
35
|
+
}: WorkspaceMoveModalProps) {
|
|
36
36
|
const t = useTheme()
|
|
37
37
|
const source = useMemo(
|
|
38
|
-
() =>
|
|
39
|
-
[
|
|
38
|
+
() => workspaces.find((w) => w.id === sourceWorkspaceId),
|
|
39
|
+
[sourceWorkspaceId, workspaces]
|
|
40
40
|
)
|
|
41
41
|
const targets = useMemo(
|
|
42
|
-
() =>
|
|
43
|
-
[
|
|
42
|
+
() => workspaces.filter((w) => w.id !== sourceWorkspaceId),
|
|
43
|
+
[sourceWorkspaceId, workspaces]
|
|
44
44
|
)
|
|
45
45
|
const sourceLabel =
|
|
46
|
-
source?.branch != null && source.branch !== '' ? source.branch : (source?.name ?? '
|
|
46
|
+
source?.branch != null && source.branch !== '' ? source.branch : (source?.name ?? 'workspace')
|
|
47
47
|
// What the move would carry: commits ahead of the fork point (when known) plus
|
|
48
48
|
// uncommitted files, loaded async after the modal opens.
|
|
49
49
|
const sourcePreview = useMemo(() => {
|
|
50
50
|
const parts: string[] = []
|
|
51
|
-
const ahead = divergence[
|
|
51
|
+
const ahead = divergence[sourceWorkspaceId]?.ahead ?? 0
|
|
52
52
|
if (ahead > 0) parts.push(`${ahead} commit(s)`)
|
|
53
53
|
if (stats.kind === 'ready') {
|
|
54
|
-
const dirty = stats.dirtyFiles[
|
|
54
|
+
const dirty = stats.dirtyFiles[sourceWorkspaceId] ?? 0
|
|
55
55
|
if (dirty > 0) parts.push(`${dirty} uncommitted file(s)`)
|
|
56
56
|
}
|
|
57
57
|
return parts.join(' · ')
|
|
58
|
-
}, [divergence,
|
|
58
|
+
}, [divergence, sourceWorkspaceId, stats])
|
|
59
59
|
const handleSelectIndex = useCallback(
|
|
60
60
|
(index: number) => dispatchGlobal({ index, type: 'set-modal-selection-index' }),
|
|
61
61
|
[]
|
|
@@ -63,7 +63,7 @@ export function WorktreeMoveModal({
|
|
|
63
63
|
return (
|
|
64
64
|
<ModalShell
|
|
65
65
|
title={`Move ${sourceLabel} →`}
|
|
66
|
-
keybindsModeId="modal.
|
|
66
|
+
keybindsModeId="modal.workspace-move"
|
|
67
67
|
width={uiTokens.modalWidth.md}
|
|
68
68
|
footer={
|
|
69
69
|
<box flexDirection="column" gap={1} marginTop={1}>
|
|
@@ -87,18 +87,20 @@ export function WorktreeMoveModal({
|
|
|
87
87
|
<box flexDirection="column" marginTop={1}>
|
|
88
88
|
{sourcePreview !== '' ? <text fg={t.textMuted}>will move: {sourcePreview}</text> : null}
|
|
89
89
|
{targets.length === 0 ? (
|
|
90
|
-
<text fg={t.textMuted}>No other
|
|
90
|
+
<text fg={t.textMuted}>No other workspace to move into.</text>
|
|
91
91
|
) : (
|
|
92
|
-
targets.map((
|
|
92
|
+
targets.map((workspace, index) => {
|
|
93
93
|
const active = index === selectedIndex
|
|
94
94
|
const label =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
workspace.branch != null && workspace.branch !== ''
|
|
96
|
+
? workspace.branch
|
|
97
|
+
: workspace.name
|
|
98
|
+
const ahead = formatDivergence(divergence[workspace.id])
|
|
99
|
+
const dirty = stats.kind === 'ready' && (stats.dirtyFiles[workspace.id] ?? 0) > 0
|
|
98
100
|
return (
|
|
99
101
|
<ListItem
|
|
100
|
-
key={
|
|
101
|
-
id={
|
|
102
|
+
key={workspace.id}
|
|
103
|
+
id={workspace.id}
|
|
102
104
|
index={index}
|
|
103
105
|
active={active}
|
|
104
106
|
onHoverIndex={handleSelectIndex}
|
|
@@ -106,7 +108,7 @@ export function WorktreeMoveModal({
|
|
|
106
108
|
title={
|
|
107
109
|
<text fg={active ? t.text : t.textMuted} wrapMode="none">
|
|
108
110
|
{label}
|
|
109
|
-
{
|
|
111
|
+
{workspace.source === 'primary' ? ' (primary)' : ''}
|
|
110
112
|
{ahead !== '' ? ` ${ahead}` : ''}
|
|
111
113
|
{dirty ? <span fg={t.warning}> ●</span> : null}
|
|
112
114
|
</text>
|