@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,10 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events'
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
ProjectSnapshotV1,
|
|
4
5
|
TabSession,
|
|
5
6
|
TerminalModeState,
|
|
6
7
|
TerminalSnapshot,
|
|
7
|
-
WorkspaceSnapshotV1,
|
|
8
8
|
} from '../state/types'
|
|
9
9
|
|
|
10
10
|
import { logDebug } from '../debug/input-log'
|
|
@@ -12,110 +12,110 @@ import { SessionRegistry } from './session-registry'
|
|
|
12
12
|
|
|
13
13
|
interface SessionManagerEvents {
|
|
14
14
|
render: [
|
|
15
|
-
|
|
15
|
+
projectId: string,
|
|
16
16
|
tabId: string,
|
|
17
17
|
viewport: TerminalSnapshot,
|
|
18
18
|
terminalModes: TerminalModeState,
|
|
19
19
|
]
|
|
20
|
-
exit: [
|
|
21
|
-
error: [
|
|
20
|
+
exit: [projectId: string, tabId: string, exitCode: number]
|
|
21
|
+
error: [projectId: string, tabId: string, message: string]
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export class SessionManager extends EventEmitter<SessionManagerEvents> {
|
|
25
25
|
private registries = new Map<string, SessionRegistry>()
|
|
26
26
|
|
|
27
|
-
private getOrCreateRegistry(
|
|
28
|
-
const existing = this.registries.get(
|
|
27
|
+
private getOrCreateRegistry(projectId: string): SessionRegistry {
|
|
28
|
+
const existing = this.registries.get(projectId)
|
|
29
29
|
if (existing) {
|
|
30
30
|
return existing
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const registry = new SessionRegistry()
|
|
34
34
|
registry.on('render', (tabId, viewport, terminalModes) => {
|
|
35
|
-
this.emit('render',
|
|
35
|
+
this.emit('render', projectId, tabId, viewport, terminalModes)
|
|
36
36
|
})
|
|
37
37
|
registry.on('exit', (tabId, exitCode) => {
|
|
38
|
-
this.emit('exit',
|
|
38
|
+
this.emit('exit', projectId, tabId, exitCode)
|
|
39
39
|
})
|
|
40
40
|
registry.on('error', (tabId, message) => {
|
|
41
|
-
this.emit('error',
|
|
41
|
+
this.emit('error', projectId, tabId, message)
|
|
42
42
|
})
|
|
43
|
-
this.registries.set(
|
|
43
|
+
this.registries.set(projectId, registry)
|
|
44
44
|
return registry
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
attachSession(
|
|
47
|
+
attachSession(projectId: string, snapshot?: ProjectSnapshotV1) {
|
|
48
48
|
logDebug('daemon.sessionManager.attachSession', {
|
|
49
49
|
hasSnapshot: !!snapshot,
|
|
50
|
-
|
|
50
|
+
projectId,
|
|
51
51
|
snapshotTabs: snapshot?.tabs.length ?? 0,
|
|
52
52
|
})
|
|
53
|
-
return this.getOrCreateRegistry(
|
|
53
|
+
return this.getOrCreateRegistry(projectId).attachFromSnapshot(snapshot)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
createTab(
|
|
56
|
+
createTab(projectId: string, options: Parameters<SessionRegistry['createSession']>[0]): void {
|
|
57
57
|
logDebug('daemon.sessionManager.createTab', {
|
|
58
58
|
command: options.command,
|
|
59
|
-
|
|
59
|
+
projectId,
|
|
60
60
|
tabId: options.tabId,
|
|
61
61
|
title: options.title,
|
|
62
62
|
})
|
|
63
|
-
this.getOrCreateRegistry(
|
|
63
|
+
this.getOrCreateRegistry(projectId).createSession(options)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
write(
|
|
67
|
-
this.getOrCreateRegistry(
|
|
66
|
+
write(projectId: string, tabId: string, data: string): void {
|
|
67
|
+
this.getOrCreateRegistry(projectId).write(tabId, data)
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
updateTabMetadata(
|
|
71
|
-
|
|
71
|
+
projectId: string,
|
|
72
72
|
tabId: string,
|
|
73
73
|
patch: { title?: string; autoRenameStatus?: 'eligible' | 'attempted' }
|
|
74
74
|
): void {
|
|
75
|
-
this.getOrCreateRegistry(
|
|
75
|
+
this.getOrCreateRegistry(projectId).updateTabMetadata(tabId, patch)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
resize(
|
|
79
|
-
this.getOrCreateRegistry(
|
|
78
|
+
resize(projectId: string, cols: number, rows: number, options?: { sync?: boolean }): void {
|
|
79
|
+
this.getOrCreateRegistry(projectId).resizeAll(cols, rows, options)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
resizeTab(
|
|
83
|
-
|
|
83
|
+
projectId: string,
|
|
84
84
|
tabId: string,
|
|
85
85
|
cols: number,
|
|
86
86
|
rows: number,
|
|
87
87
|
options?: { sync?: boolean }
|
|
88
88
|
): void {
|
|
89
|
-
this.getOrCreateRegistry(
|
|
89
|
+
this.getOrCreateRegistry(projectId).resizeTab(tabId, cols, rows, options)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
scroll(
|
|
93
|
-
this.getOrCreateRegistry(
|
|
92
|
+
scroll(projectId: string, tabId: string, deltaLines: number): void {
|
|
93
|
+
this.getOrCreateRegistry(projectId).scrollViewport(tabId, deltaLines)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
scrollToBottom(
|
|
97
|
-
this.getOrCreateRegistry(
|
|
96
|
+
scrollToBottom(projectId: string, tabId: string): void {
|
|
97
|
+
this.getOrCreateRegistry(projectId).scrollViewportToBottom(tabId)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
setActiveTab(
|
|
101
|
-
this.getOrCreateRegistry(
|
|
100
|
+
setActiveTab(projectId: string, tabId: string | null): void {
|
|
101
|
+
this.getOrCreateRegistry(projectId).setActiveTab(tabId)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
closeTab(
|
|
105
|
-
this.getOrCreateRegistry(
|
|
104
|
+
closeTab(projectId: string, tabId: string): void {
|
|
105
|
+
this.getOrCreateRegistry(projectId).closeTab(tabId)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
disposeSession(
|
|
108
|
+
disposeSession(projectId: string): void {
|
|
109
109
|
logDebug('daemon.sessionManager.disposeSession', {
|
|
110
|
-
hadRegistry: this.registries.has(
|
|
111
|
-
|
|
110
|
+
hadRegistry: this.registries.has(projectId),
|
|
111
|
+
projectId,
|
|
112
112
|
})
|
|
113
|
-
const registry = this.registries.get(
|
|
113
|
+
const registry = this.registries.get(projectId)
|
|
114
114
|
if (!registry) {
|
|
115
115
|
return
|
|
116
116
|
}
|
|
117
117
|
registry.disposeAll()
|
|
118
|
-
this.registries.delete(
|
|
118
|
+
this.registries.delete(projectId)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
disposeAll(): void {
|
|
@@ -143,7 +143,7 @@ export class SessionManager extends EventEmitter<SessionManagerEvents> {
|
|
|
143
143
|
return [...this.registries.keys()]
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
listTabs(
|
|
147
|
-
return this.registries.get(
|
|
146
|
+
listTabs(projectId: string): TabSession[] {
|
|
147
|
+
return this.registries.get(projectId)?.listTabs() ?? []
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events'
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
ProjectSnapshotV1,
|
|
4
5
|
TabSession,
|
|
5
6
|
TerminalModeState,
|
|
6
7
|
TerminalSnapshot,
|
|
7
|
-
WorkspaceSnapshotV1,
|
|
8
8
|
} from '../state/types'
|
|
9
9
|
|
|
10
10
|
import { logDebug } from '../debug/input-log'
|
|
@@ -12,8 +12,8 @@ import { PtyManager } from '../pty/pty-manager'
|
|
|
12
12
|
import {
|
|
13
13
|
normalizeGroupedTabOrder,
|
|
14
14
|
restoreLayoutTrees,
|
|
15
|
-
|
|
16
|
-
} from '../state/
|
|
15
|
+
restoreTabsFromProject,
|
|
16
|
+
} from '../state/project-persistence'
|
|
17
17
|
import { createDefaultTerminalModes } from '../state/terminal-modes'
|
|
18
18
|
|
|
19
19
|
interface SessionRegistryEvents {
|
|
@@ -65,7 +65,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
65
65
|
})
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
attachFromSnapshot(snapshot:
|
|
68
|
+
attachFromSnapshot(snapshot: ProjectSnapshotV1 | undefined): {
|
|
69
69
|
tabs: TabSession[]
|
|
70
70
|
activeTabId: string | null
|
|
71
71
|
} {
|
|
@@ -75,7 +75,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
75
75
|
snapshotTabs: snapshot?.tabs.length ?? 0,
|
|
76
76
|
})
|
|
77
77
|
if (this.tabs.size === 0 && snapshot) {
|
|
78
|
-
const restoredTabs =
|
|
78
|
+
const restoredTabs = restoreTabsFromProject(snapshot)
|
|
79
79
|
for (const tab of restoredTabs) {
|
|
80
80
|
this.tabs.set(tab.id, tab)
|
|
81
81
|
}
|
|
@@ -93,7 +93,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
93
93
|
existing.title = persisted.title
|
|
94
94
|
existing.autoRenameStatus = persisted.autoRenameStatus
|
|
95
95
|
}
|
|
96
|
-
existing.
|
|
96
|
+
existing.workspaceId = persisted.workspaceId
|
|
97
97
|
existing.workerName = persisted.workerName
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -116,10 +116,10 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
116
116
|
.map((persistedTab) => this.tabs.get(persistedTab.id))
|
|
117
117
|
.filter((tab): tab is TabSession => tab !== undefined)
|
|
118
118
|
// The registry — not the snapshot — is the source of truth for *which*
|
|
119
|
-
// tabs exist. A tab spawned by a sibling CLI after this
|
|
119
|
+
// tabs exist. A tab spawned by a sibling CLI after this project's
|
|
120
120
|
// snapshot was last persisted lives in `this.tabs` but is absent from
|
|
121
121
|
// `snapshot.tabs`; ordering by the snapshot alone would silently drop it
|
|
122
|
-
// from the attach result, so the UI would render an empty/stale
|
|
122
|
+
// from the attach result, so the UI would render an empty/stale project
|
|
123
123
|
// on switch. Append any live tab the snapshot doesn't mention (in
|
|
124
124
|
// registry order) so membership stays authoritative while the snapshot
|
|
125
125
|
// still supplies ordering + layout for the tabs it captured.
|
|
@@ -151,12 +151,12 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
151
151
|
env?: Record<string, string>
|
|
152
152
|
autoRenameStatus?: 'eligible' | 'attempted'
|
|
153
153
|
/**
|
|
154
|
-
*
|
|
154
|
+
* Workspace this tab belongs to (for UI grouping). Stored on the
|
|
155
155
|
* TabSession so an attach-time replay surfaces it inside the right
|
|
156
|
-
*
|
|
156
|
+
* workspace column. Optional — tabs not bound to a workspace are valid.
|
|
157
157
|
*/
|
|
158
|
-
|
|
159
|
-
/**
|
|
158
|
+
workspaceId?: string
|
|
159
|
+
/** Project-scoped orchestration handle; does not affect PTY behavior. */
|
|
160
160
|
workerName?: string
|
|
161
161
|
}): void {
|
|
162
162
|
logDebug('daemon.registry.createSession', {
|
|
@@ -179,7 +179,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
179
179
|
terminalModes: createDefaultTerminalModes(),
|
|
180
180
|
title: options.title,
|
|
181
181
|
workerName: options.workerName,
|
|
182
|
-
|
|
182
|
+
workspaceId: options.workspaceId,
|
|
183
183
|
})
|
|
184
184
|
} else {
|
|
185
185
|
existing.status = 'starting'
|
|
@@ -192,7 +192,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
192
192
|
existing.title = options.title
|
|
193
193
|
existing.command = [options.command, ...(options.args ?? [])].join(' ')
|
|
194
194
|
existing.autoRenameStatus = options.autoRenameStatus
|
|
195
|
-
if (options.
|
|
195
|
+
if (options.workspaceId !== undefined) existing.workspaceId = options.workspaceId
|
|
196
196
|
if (options.workerName !== undefined) existing.workerName = options.workerName
|
|
197
197
|
}
|
|
198
198
|
|
package/src/git/divergence.ts
CHANGED
|
@@ -16,7 +16,7 @@ export function parseDivergenceCount(output: string): BranchDivergence | undefin
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// Commits a
|
|
19
|
+
// Commits a workspace branch is ahead/behind the ref it forked from. Returns
|
|
20
20
|
// undefined when the refs can't be compared (missing ref, not a repo, …) so
|
|
21
21
|
// callers can simply render nothing.
|
|
22
22
|
export async function getBranchDivergence(
|
|
@@ -32,8 +32,39 @@ export async function getBranchDivergence(
|
|
|
32
32
|
return parseDivergenceCount(result.text())
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Parses `git diff --shortstat`, e.g.
|
|
36
|
+
// " 7 files changed, 149 insertions(+), 629 deletions(-)". Either side may be
|
|
37
|
+
// absent (a pure addition reports no deletions line at all).
|
|
38
|
+
export function parseShortstat(output: string): { added: number; removed: number } | undefined {
|
|
39
|
+
const trimmed = output.trim()
|
|
40
|
+
if (trimmed === '') return undefined
|
|
41
|
+
const added = /(\d+) insertions?\(\+\)/.exec(trimmed)
|
|
42
|
+
const removed = /(\d+) deletions?\(-\)/.exec(trimmed)
|
|
43
|
+
if (!added && !removed) return undefined
|
|
44
|
+
return {
|
|
45
|
+
added: Number.parseInt(added?.[1] ?? '0', 10),
|
|
46
|
+
removed: Number.parseInt(removed?.[1] ?? '0', 10),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Lines a workspace has changed since it forked, measured from the merge base
|
|
52
|
+
* to the *working tree* — so commits, staged and unstaged work all count. That
|
|
53
|
+
* is the number the sidebar shows, and it is what a squash-move would land.
|
|
54
|
+
*/
|
|
55
|
+
export async function getWorkspaceDiffStat(
|
|
56
|
+
workspacePath: string,
|
|
57
|
+
baseRef: string
|
|
58
|
+
): Promise<{ added: number; removed: number } | undefined> {
|
|
59
|
+
const mergeBase = await getMergeBase(workspacePath, baseRef)
|
|
60
|
+
if (mergeBase == null) return undefined
|
|
61
|
+
const result = await $`git -C ${workspacePath} diff --shortstat ${mergeBase}`.quiet().nothrow()
|
|
62
|
+
if (result.exitCode !== 0) return undefined
|
|
63
|
+
return parseShortstat(result.text())
|
|
64
|
+
}
|
|
65
|
+
|
|
35
66
|
// Fork point: the most recent commit shared by baseRef and ref. Diffing the
|
|
36
|
-
// working tree against this shows everything the
|
|
67
|
+
// working tree against this shows everything the workspace changed since it
|
|
37
68
|
// branched off (commits + staged + unstaged) — i.e. what a squash-move lands.
|
|
38
69
|
export async function getMergeBase(
|
|
39
70
|
repoPath: string,
|
package/src/git/git-poller.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface Options {
|
|
|
13
13
|
enabled: boolean
|
|
14
14
|
projectPath: string | undefined
|
|
15
15
|
headOffset: number
|
|
16
|
-
// When set, diff the working tree against this ref (
|
|
16
|
+
// When set, diff the working tree against this ref (workspace fork point for
|
|
17
17
|
// review-vs-base) instead of HEAD/HEAD~N.
|
|
18
18
|
compareRef?: string
|
|
19
19
|
}
|
package/src/git/git-status.ts
CHANGED
|
@@ -199,7 +199,7 @@ async function annotateUntrackedCounts(cwd: string, files: GitFileEntry[]): Prom
|
|
|
199
199
|
|
|
200
200
|
interface CollectOptions {
|
|
201
201
|
headOffset?: number
|
|
202
|
-
// Explicit ref to diff the working tree against (e.g. a
|
|
202
|
+
// Explicit ref to diff the working tree against (e.g. a workspace's fork
|
|
203
203
|
// point). Takes precedence over headOffset when set.
|
|
204
204
|
compareRef?: string
|
|
205
205
|
}
|
|
@@ -266,7 +266,7 @@ async function collectAgainstHistorical(
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
// Diffs the working tree against an arbitrary ref (a HEAD~N commit, or a
|
|
269
|
-
//
|
|
269
|
+
// workspace's fork point for review-vs-base), surfacing changed files as a
|
|
270
270
|
// single `historical` section plus any untracked files.
|
|
271
271
|
async function collectAgainstRef(cwd: string, ref: string): Promise<GitCollectResult> {
|
|
272
272
|
const [statusResult, nameStatus, numstat, untrackedStatus] = await Promise.all([
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface MoveWorkspaceOptions {
|
|
4
4
|
sourcePath: string
|
|
5
5
|
sourceBranch: string
|
|
6
6
|
targetPath: string
|
|
@@ -10,7 +10,7 @@ export interface MoveWorktreeOptions {
|
|
|
10
10
|
keepConflicts?: boolean
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export type
|
|
13
|
+
export type MoveWorkspaceResult =
|
|
14
14
|
| { kind: 'ok'; filesChanged: number; stashedTarget: boolean }
|
|
15
15
|
/** Target dirty/untracked files would be overwritten by the move; nothing was touched. */
|
|
16
16
|
| { kind: 'needs-stash'; files: string[] }
|
|
@@ -76,8 +76,8 @@ function parseOverwrittenFiles(output: string): string[] {
|
|
|
76
76
|
return files
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// Squashes everything a source
|
|
80
|
-
// target
|
|
79
|
+
// Squashes everything a source workspace changed since its fork point into the
|
|
80
|
+
// target workspace's working tree (staged, uncommitted) — committed work plus
|
|
81
81
|
// any uncommitted/untracked changes. The source is left exactly as it was; the
|
|
82
82
|
// caller decides whether to delete it. The target may be dirty as long as its
|
|
83
83
|
// local changes don't overlap the incoming ones: on overlap the move stops
|
|
@@ -85,7 +85,7 @@ function parseOverwrittenFiles(output: string): string[] {
|
|
|
85
85
|
// conflict nothing is kept — target reset, source restored — unless
|
|
86
86
|
// keepConflicts is set, in which case the conflicted squash state is left in
|
|
87
87
|
// the target for manual resolution.
|
|
88
|
-
export async function
|
|
88
|
+
export async function moveWorkspace(opts: MoveWorkspaceOptions): Promise<MoveWorkspaceResult> {
|
|
89
89
|
const { sourceBranch, sourcePath, targetPath } = opts
|
|
90
90
|
try {
|
|
91
91
|
const headResult = await $`git -C ${sourcePath} rev-parse HEAD`.quiet().nothrow()
|
package/src/git/pr-status.ts
CHANGED
|
@@ -213,7 +213,7 @@ export async function collectPrStatus(cwd: string): Promise<PrStatusResult> {
|
|
|
213
213
|
if (!result.ok) {
|
|
214
214
|
// `gh` exits non-zero for every "there is simply nothing to show" case too:
|
|
215
215
|
// no PR on the branch, no GitHub remote, or a directory that isn't a repo
|
|
216
|
-
// at all (aimux
|
|
216
|
+
// at all (aimux projects can point anywhere). None of those is an error.
|
|
217
217
|
const stderr = result.stderr.toLowerCase()
|
|
218
218
|
if (NOT_AN_ERROR.some((needle) => stderr.includes(needle))) return { kind: 'no-pr' }
|
|
219
219
|
return { kind: 'error', message: (result.error ?? 'gh failed').slice(0, 200) }
|
|
@@ -79,7 +79,7 @@ const cache = new Map<string, CacheEntry>()
|
|
|
79
79
|
/**
|
|
80
80
|
* Cached variant. The cache key is `projectPath` + `maxDepth`: a depth change
|
|
81
81
|
* invalidates the entry. The cache persists until `invalidateRepoCache()` or
|
|
82
|
-
* process exit — discovery is one-shot per
|
|
82
|
+
* process exit — discovery is one-shot per project per depth setting.
|
|
83
83
|
*/
|
|
84
84
|
export async function discoverRepos(projectPath: string, maxDepth = 1): Promise<DiscoveredRepo[]> {
|
|
85
85
|
const existing = cache.get(projectPath)
|
|
@@ -5,7 +5,7 @@ import { dispatchGlobal } from '../state/dispatch-ref'
|
|
|
5
5
|
import { discoverRepos } from './repo-discovery'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Discover nested git repos inside `projectPath` once per
|
|
8
|
+
* Discover nested git repos inside `projectPath` once per project and push
|
|
9
9
|
* the result into state. No-op when the multi-repo config flag is off.
|
|
10
10
|
*/
|
|
11
11
|
export function useRepoDiscovery(projectPath: string | undefined): void {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import { appStore } from '../state/app-store'
|
|
4
|
+
import { dispatchGlobal } from '../state/dispatch-ref'
|
|
5
|
+
import { getCurrentBranch } from '../ui/git-branch'
|
|
6
|
+
|
|
7
|
+
const INTERVAL_MS = 4000
|
|
8
|
+
|
|
9
|
+
// Single source of truth for "what git branch is each workspace on right now".
|
|
10
|
+
// Polls every known workspace's path and dispatches update-workspace-record
|
|
11
|
+
// when the live branch differs from the stored one. Components read
|
|
12
|
+
// `workspace.branch` from state — no per-component polling, no flickers.
|
|
13
|
+
//
|
|
14
|
+
// Runs once when enabled; reads projects from the store on each tick so
|
|
15
|
+
// project updates do NOT re-create the effect (which would otherwise feedback
|
|
16
|
+
// off our own dispatches).
|
|
17
|
+
export function useWorkspaceBranchPolling(enabled: boolean): void {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!enabled) return
|
|
20
|
+
|
|
21
|
+
let cancelled = false
|
|
22
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
23
|
+
|
|
24
|
+
const tick = async () => {
|
|
25
|
+
const projects = appStore.getState().projects
|
|
26
|
+
await Promise.all(
|
|
27
|
+
projects.flatMap((project) =>
|
|
28
|
+
(project.workspaces ?? []).map(async (workspace) => {
|
|
29
|
+
if (workspace.path == null || workspace.path === '') return
|
|
30
|
+
const branch = await getCurrentBranch(workspace.path)
|
|
31
|
+
if (cancelled) return
|
|
32
|
+
if (branch != null && branch !== workspace.branch) {
|
|
33
|
+
dispatchGlobal({
|
|
34
|
+
patch: { branch },
|
|
35
|
+
projectId: project.id,
|
|
36
|
+
type: 'update-workspace-record',
|
|
37
|
+
workspaceId: workspace.id,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
if (cancelled) return
|
|
44
|
+
timer = setTimeout(() => void tick(), INTERVAL_MS)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void tick()
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
cancelled = true
|
|
51
|
+
if (timer != null) clearTimeout(timer)
|
|
52
|
+
}
|
|
53
|
+
}, [enabled])
|
|
54
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { BranchDivergence } from '../state/types'
|
|
4
|
+
|
|
5
|
+
import { useAppStore } from '../state/app-store'
|
|
6
|
+
import { dispatchGlobal } from '../state/dispatch-ref'
|
|
7
|
+
import { getBranchDivergence, getWorkspaceDiffStat } from './divergence'
|
|
8
|
+
|
|
9
|
+
const INTERVAL_MS = 4000
|
|
10
|
+
|
|
11
|
+
// Polls per-workspace base divergence for the current project while enabled and
|
|
12
|
+
// dispatches it into workspaceDivergence. Only workspaces that record a baseRef
|
|
13
|
+
// (the aimux-created ones) are measured; the primary and externally-discovered
|
|
14
|
+
// workspaces have no recorded base and are left out.
|
|
15
|
+
export function useWorkspaceDivergencePolling(enabled: boolean): void {
|
|
16
|
+
const currentProjectId = useAppStore((s) => s.currentProjectId)
|
|
17
|
+
const projects = useAppStore((s) => s.projects)
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!enabled) return
|
|
21
|
+
const project =
|
|
22
|
+
currentProjectId != null && currentProjectId !== ''
|
|
23
|
+
? projects.find((s) => s.id === currentProjectId)
|
|
24
|
+
: undefined
|
|
25
|
+
const targets = (project?.workspaces ?? []).filter(
|
|
26
|
+
(w) => w.baseRef != null && w.baseRef !== '' && w.branch != null && w.branch !== ''
|
|
27
|
+
)
|
|
28
|
+
if (targets.length === 0) return
|
|
29
|
+
|
|
30
|
+
let cancelled = false
|
|
31
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
32
|
+
|
|
33
|
+
const tick = async () => {
|
|
34
|
+
const entries = await Promise.all(
|
|
35
|
+
targets.map(async (workspace) => {
|
|
36
|
+
const base = workspace.baseRef
|
|
37
|
+
const branch = workspace.branch
|
|
38
|
+
if (base == null || branch == null) return null
|
|
39
|
+
// Commits come from the repo root (comparing two refs); lines come
|
|
40
|
+
// from the workspace itself, so uncommitted work is counted too.
|
|
41
|
+
const [divergence, stat] = await Promise.all([
|
|
42
|
+
getBranchDivergence(workspace.repoRoot, base, branch),
|
|
43
|
+
getWorkspaceDiffStat(workspace.path, base),
|
|
44
|
+
])
|
|
45
|
+
if (divergence == null) return null
|
|
46
|
+
return [workspace.id, { ...divergence, ...stat }] as const
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
if (cancelled) return
|
|
50
|
+
const next: Record<string, BranchDivergence> = {}
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry != null) next[entry[0]] = entry[1]
|
|
53
|
+
}
|
|
54
|
+
dispatchGlobal({ divergence: next, type: 'set-workspace-divergence' })
|
|
55
|
+
timer = setTimeout(() => void tick(), INTERVAL_MS)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
void tick()
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true
|
|
62
|
+
if (timer != null) clearTimeout(timer)
|
|
63
|
+
}
|
|
64
|
+
}, [enabled, currentProjectId, projects])
|
|
65
|
+
}
|
package/src/git/worktree.ts
CHANGED
|
@@ -69,6 +69,35 @@ export async function resolveGitRef(repoPath: string, ref: string): Promise<stri
|
|
|
69
69
|
return result.text().trim() || undefined
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// The branch a new workspace forks from unless the user picks another base.
|
|
73
|
+
// `origin/HEAD` is the only authoritative answer; it is missing on repos cloned
|
|
74
|
+
// with --no-checkout or created locally, hence the name probes behind it.
|
|
75
|
+
export async function getDefaultBranch(repoPath: string): Promise<string | undefined> {
|
|
76
|
+
const head = await $`git -C ${repoPath} symbolic-ref --short refs/remotes/origin/HEAD`
|
|
77
|
+
.quiet()
|
|
78
|
+
.nothrow()
|
|
79
|
+
if (head.exitCode === 0) {
|
|
80
|
+
const ref = head.text().trim()
|
|
81
|
+
const short = ref.startsWith('origin/') ? ref.slice('origin/'.length) : ref
|
|
82
|
+
if (short !== '') return short
|
|
83
|
+
}
|
|
84
|
+
const locals = await listLocalBranches(repoPath)
|
|
85
|
+
return ['main', 'master'].find((name) => locals.includes(name))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Rename a local branch. Returns false (without throwing) when git refuses —
|
|
89
|
+
// the target name already exists, most likely — so a failed rename leaves the
|
|
90
|
+
// workspace on the branch it was created with instead of losing it.
|
|
91
|
+
export async function renameGitBranch(
|
|
92
|
+
repoPath: string,
|
|
93
|
+
from: string,
|
|
94
|
+
to: string
|
|
95
|
+
): Promise<boolean> {
|
|
96
|
+
if (from === '' || to === '' || from === to) return false
|
|
97
|
+
const result = await $`git -C ${repoPath} branch -m ${from} ${to}`.quiet().nothrow()
|
|
98
|
+
return result.exitCode === 0
|
|
99
|
+
}
|
|
100
|
+
|
|
72
101
|
export async function createGitWorktree({
|
|
73
102
|
baseRef,
|
|
74
103
|
branchName,
|
package/src/index.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const command = process.argv[2]
|
|
6
6
|
|
|
7
7
|
// Shell completion. First branch and cheapest: it loads the CLI registry and
|
|
8
|
-
// nothing else — no daemon client, no
|
|
8
|
+
// nothing else — no daemon client, no project backend, no renderer.
|
|
9
9
|
if (command === '__complete') {
|
|
10
10
|
const { runComplete } = await import('./cli/completion/entry')
|
|
11
11
|
process.exit(await runComplete(process.argv.slice(3)))
|
|
@@ -19,7 +19,7 @@ if (command === 'completion') {
|
|
|
19
19
|
// CLI control plane (docs/reference/cli.md). Branch BEFORE the UI bootstrap so
|
|
20
20
|
// `aimux tab list` from a non-TTY shell never spins up the React renderer.
|
|
21
21
|
// Dynamic import keeps the CLI code out of the UI's cold-start cost.
|
|
22
|
-
const CLI_GROUPS = new Set(['tab', '
|
|
22
|
+
const CLI_GROUPS = new Set(['tab', 'project', 'workspace', 'worker'])
|
|
23
23
|
if (typeof command === 'string' && CLI_GROUPS.has(command)) {
|
|
24
24
|
const { runCli } = await import('./cli')
|
|
25
25
|
process.exit(await runCli(process.argv.slice(2)))
|
|
@@ -96,7 +96,7 @@ const [
|
|
|
96
96
|
import('./session-backend/bootstrap'),
|
|
97
97
|
import('./cli/completion/install'),
|
|
98
98
|
])
|
|
99
|
-
const resolvedConfig = await loadUserConfig()
|
|
99
|
+
const { resolved: resolvedConfig, user: userConfig } = await loadUserConfig()
|
|
100
100
|
|
|
101
101
|
// First launch (and after every upgrade): drop the shell completion script in
|
|
102
102
|
// the conventional location for $SHELL. Silent and best-effort — it writes one
|
|
@@ -154,4 +154,4 @@ const backend = await createSessionBackend({
|
|
|
154
154
|
})
|
|
155
155
|
logDebug('index.backendReady', { backend: backend.constructor.name, runtimeProfile })
|
|
156
156
|
|
|
157
|
-
root.render(<App backend={backend} resolvedConfig={resolvedConfig} />)
|
|
157
|
+
root.render(<App backend={backend} resolvedConfig={resolvedConfig} userConfig={userConfig} />)
|
|
@@ -11,15 +11,17 @@ export const HELP_MODE_LABELS: { modeId: ModeId; label: string }[] = [
|
|
|
11
11
|
{ label: 'Navigation', modeId: 'navigation' },
|
|
12
12
|
{ label: 'Terminal input', modeId: 'terminal-input' },
|
|
13
13
|
{ label: 'Git mode', modeId: 'git-mode' },
|
|
14
|
+
{ label: 'Settings', modeId: 'settings' },
|
|
14
15
|
{ label: 'Git commit', modeId: 'modal.git-commit' },
|
|
15
16
|
{ label: 'New tab', modeId: 'modal.new-tab.command-edit' },
|
|
16
17
|
{ label: 'New tab — command', modeId: 'modal.new-tab.command-edit' },
|
|
17
|
-
{ label: '
|
|
18
|
-
{ label: '
|
|
19
|
-
{ label: '
|
|
20
|
-
{ label: 'Create
|
|
18
|
+
{ label: 'Project picker', modeId: 'modal.project-picker.filtering' },
|
|
19
|
+
{ label: 'Project picker — filter', modeId: 'modal.project-picker.filtering' },
|
|
20
|
+
{ label: 'Project name', modeId: 'modal.project-name' },
|
|
21
|
+
{ label: 'Create project', modeId: 'modal.create-project' },
|
|
22
|
+
{ label: 'Create workspace', modeId: 'modal.create-workspace' },
|
|
21
23
|
{ label: 'Rename tab', modeId: 'modal.rename-tab' },
|
|
22
|
-
{ label: 'Rename
|
|
24
|
+
{ label: 'Rename workspace', modeId: 'modal.rename-workspace' },
|
|
23
25
|
{ label: 'Snippet picker', modeId: 'modal.snippet-picker.filtering' },
|
|
24
26
|
{ label: 'Snippet picker — filter', modeId: 'modal.snippet-picker.filtering' },
|
|
25
27
|
{ label: 'Snippet editor', modeId: 'modal.snippet-editor' },
|