@brimveyn/aimux 1.20.4 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/README.md +27 -25
  2. package/package.json +2 -2
  3. package/src/app-runtime/auto-commit-driver.ts +22 -27
  4. package/src/app-runtime/backend-attach-runtime.ts +43 -36
  5. package/src/app-runtime/backend-runtime-events.ts +107 -88
  6. package/src/app-runtime/editor-actions.ts +201 -0
  7. package/src/app-runtime/git-actions.ts +197 -0
  8. package/src/app-runtime/measure-ref.ts +19 -0
  9. package/src/app-runtime/navigation-actions.ts +213 -0
  10. package/src/app-runtime/project-actions.ts +188 -0
  11. package/src/app-runtime/prompt-injection.ts +123 -0
  12. package/src/app-runtime/selection.ts +29 -0
  13. package/src/app-runtime/settings-actions.ts +156 -0
  14. package/src/app-runtime/setup-actions.ts +237 -0
  15. package/src/app-runtime/side-effect-context.ts +33 -0
  16. package/src/app-runtime/side-effects.ts +305 -1589
  17. package/src/app-runtime/snippet-actions.ts +3 -2
  18. package/src/app-runtime/split-drag-controller.ts +4 -8
  19. package/src/app-runtime/tab-actions.ts +218 -0
  20. package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
  21. package/src/app-runtime/use-auto-commit-driver.ts +14 -13
  22. package/src/app-runtime/use-backend-runtime.ts +12 -11
  23. package/src/app-runtime/use-directory-search.ts +5 -4
  24. package/src/app-runtime/use-mouse-handlers.ts +28 -42
  25. package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
  26. package/src/app-runtime/use-renderer-bindings.ts +2 -1
  27. package/src/app-runtime/use-setup-runner.ts +96 -0
  28. package/src/app-runtime/use-terminal-resize.ts +28 -33
  29. package/src/app-runtime/workspace-actions.ts +366 -0
  30. package/src/app-runtime/workspace-activity.ts +138 -0
  31. package/src/app-runtime/workspace-naming.ts +90 -0
  32. package/src/app.tsx +102 -85
  33. package/src/assets/sounds/bell.wav +0 -0
  34. package/src/assets/sounds/ding.wav +0 -0
  35. package/src/assets/sounds/plane.wav +0 -0
  36. package/src/cli/client/daemon-client.ts +2 -2
  37. package/src/cli/client/project-resolver.ts +112 -0
  38. package/src/cli/commands/project/close.ts +32 -0
  39. package/src/cli/commands/project/create.ts +93 -0
  40. package/src/cli/commands/project/list.ts +25 -0
  41. package/src/cli/commands/project/show.ts +32 -0
  42. package/src/cli/commands/{workspace → project}/switch.ts +19 -19
  43. package/src/cli/commands/tab/await.ts +2 -2
  44. package/src/cli/commands/tab/close.ts +3 -3
  45. package/src/cli/commands/tab/create.ts +74 -73
  46. package/src/cli/commands/tab/focus.ts +3 -3
  47. package/src/cli/commands/tab/list.ts +6 -6
  48. package/src/cli/commands/tab/run.ts +2 -2
  49. package/src/cli/commands/tab/send.ts +2 -2
  50. package/src/cli/commands/tab/snapshot.ts +2 -2
  51. package/src/cli/commands/tab/tail.ts +2 -2
  52. package/src/cli/commands/tab/wait.ts +2 -2
  53. package/src/cli/commands/worker/await.ts +3 -3
  54. package/src/cli/commands/worker/doctor.ts +30 -30
  55. package/src/cli/commands/worker/list.ts +15 -15
  56. package/src/cli/commands/worker/prompt.ts +3 -3
  57. package/src/cli/commands/worker/run.ts +23 -23
  58. package/src/cli/commands/worker/shared.ts +53 -59
  59. package/src/cli/commands/worker/stop.ts +29 -29
  60. package/src/cli/commands/worker/submit.ts +3 -3
  61. package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
  62. package/src/cli/commands/workspace/create.ts +30 -68
  63. package/src/cli/commands/workspace/list.ts +52 -9
  64. package/src/cli/commands/workspace/remove.ts +81 -0
  65. package/src/cli/completion/plan.ts +2 -2
  66. package/src/cli/completion/sources.ts +18 -18
  67. package/src/cli/context.ts +13 -13
  68. package/src/cli/flags.ts +30 -10
  69. package/src/cli/index.ts +26 -16
  70. package/src/cli/output.ts +1 -1
  71. package/src/cli/registry.ts +12 -12
  72. package/src/config/loader.ts +16 -5
  73. package/src/config.ts +149 -101
  74. package/src/daemon/catalog-writer.ts +67 -67
  75. package/src/daemon/daemon.ts +205 -201
  76. package/src/daemon/session-manager.ts +39 -39
  77. package/src/daemon/session-registry.ts +14 -14
  78. package/src/git/divergence.ts +33 -2
  79. package/src/git/git-poller.ts +1 -1
  80. package/src/git/git-status.ts +2 -2
  81. package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
  82. package/src/git/pr-merge.ts +64 -0
  83. package/src/git/pr-status-poller.ts +57 -0
  84. package/src/git/pr-status.ts +227 -0
  85. package/src/git/repo-discovery.ts +1 -1
  86. package/src/git/use-repo-discovery.ts +1 -1
  87. package/src/git/workspace-branch-poller.ts +54 -0
  88. package/src/git/workspace-divergence-poller.ts +65 -0
  89. package/src/git/worktree.ts +29 -0
  90. package/src/index.tsx +11 -6
  91. package/src/input/keymap/help-entries.ts +7 -5
  92. package/src/input/modes/bridge.ts +13 -12
  93. package/src/input/modes/handlers/shared.ts +1 -1
  94. package/src/input/modes/transitions.ts +34 -21
  95. package/src/input/modes/types.ts +48 -31
  96. package/src/ipc/manager-protocol.ts +44 -44
  97. package/src/ipc/protocol.ts +192 -166
  98. package/src/platform/open-url.ts +39 -0
  99. package/src/platform/play-sound.ts +184 -0
  100. package/src/platform/project-search.ts +8 -8
  101. package/src/platform/worktree-paths.ts +8 -6
  102. package/src/pty/assistant-question-extractor.ts +1 -1
  103. package/src/pty/assistant-status-detection-loop.ts +79 -55
  104. package/src/pty/assistant-status-detector.ts +40 -15
  105. package/src/pty/command-registry.ts +81 -1
  106. package/src/restart-terminal-manager.ts +1 -1
  107. package/src/services/ai-usage/provider.ts +9 -2
  108. package/src/services/ai-usage/spawn.ts +3 -1
  109. package/src/session-backend/bootstrap.ts +4 -4
  110. package/src/session-backend/local-session-backend.ts +78 -75
  111. package/src/session-backend/remote-session-backend.ts +64 -52
  112. package/src/session-backend/types.ts +43 -32
  113. package/src/settings/live.ts +90 -0
  114. package/src/settings/search.ts +43 -0
  115. package/src/settings/sections/about.ts +49 -0
  116. package/src/settings/sections/appearance.ts +54 -0
  117. package/src/settings/sections/automation.ts +105 -0
  118. package/src/settings/sections/commands.ts +72 -0
  119. package/src/settings/sections/editor.ts +56 -0
  120. package/src/settings/sections/experimental.ts +57 -0
  121. package/src/settings/sections/git.ts +112 -0
  122. package/src/settings/sections/index.ts +95 -0
  123. package/src/settings/sections/integrations.ts +23 -0
  124. package/src/settings/sections/layout.ts +72 -0
  125. package/src/settings/sections/notifications.ts +99 -0
  126. package/src/settings/sections/setup.ts +54 -0
  127. package/src/settings/sections/status-bar.ts +63 -0
  128. package/src/settings/settings-store.ts +207 -0
  129. package/src/settings/types.ts +108 -0
  130. package/src/snippets/run-shell-var.ts +48 -22
  131. package/src/state/actions.ts +342 -0
  132. package/src/state/app-store.ts +2 -1
  133. package/src/state/bars.ts +75 -0
  134. package/src/state/dispatch-ref.ts +1 -1
  135. package/src/state/git-pane-sizing.ts +0 -9
  136. package/src/state/layout-resize.ts +2 -2
  137. package/src/state/pr-status-store.ts +39 -0
  138. package/src/state/project-catalog.ts +275 -0
  139. package/src/state/project-data.ts +143 -0
  140. package/src/state/{session-persistence.ts → project-persistence.ts} +77 -73
  141. package/src/state/project-save.ts +46 -0
  142. package/src/state/project-workspaces.ts +392 -0
  143. package/src/state/reducers/auto-commit-state.ts +11 -10
  144. package/src/state/reducers/git-commit-modal-state.ts +122 -0
  145. package/src/state/reducers/git-mode-state.ts +1 -1
  146. package/src/state/reducers/git-panel-state.ts +10 -53
  147. package/src/state/reducers/modal-state.ts +290 -681
  148. package/src/state/reducers/multi-repo-state.ts +3 -6
  149. package/src/state/reducers/project-state.ts +312 -0
  150. package/src/state/reducers/settings-state.ts +80 -0
  151. package/src/state/reducers/tab-state.ts +107 -60
  152. package/src/state/reducers/ui-state.ts +86 -15
  153. package/src/state/selectors.ts +49 -37
  154. package/src/state/store.ts +83 -57
  155. package/src/state/types.ts +261 -417
  156. package/src/state/validation.ts +14 -12
  157. package/src/terminal-manager/manager-client.ts +32 -32
  158. package/src/terminal-manager/terminal-manager.ts +24 -24
  159. package/src/ui/components/git/diff-renderer/pierre-diff.tsx +2 -1
  160. package/src/ui/components/git/git-panel.tsx +2 -2
  161. package/src/ui/components/git/git-view.tsx +12 -12
  162. package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
  163. package/src/ui/components/git/pane/git-pane-header.tsx +105 -39
  164. package/src/ui/components/git/pane/git-pane-widget.tsx +37 -23
  165. package/src/ui/components/git/pane/pr-checks-panel.tsx +197 -0
  166. package/src/ui/components/git/pane/pr-state-row.tsx +103 -0
  167. package/src/ui/components/layout/bar.tsx +191 -0
  168. package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
  169. package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
  170. package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
  171. package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
  172. package/src/ui/components/layout/status-bar.tsx +6 -2
  173. package/src/ui/components/layout/terminal-pane.tsx +34 -29
  174. package/src/ui/components/layout/top-tab-bar.tsx +48 -37
  175. package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
  176. package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
  177. package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
  178. package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
  179. package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
  180. package/src/ui/components/modals/shared/form.tsx +34 -22
  181. package/src/ui/components/modals/shared/picker.tsx +10 -10
  182. package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
  183. package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
  184. package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
  185. package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
  186. package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
  187. package/src/ui/components/primitives/input-field.tsx +7 -3
  188. package/src/ui/components/primitives/surface.tsx +3 -0
  189. package/src/ui/components/settings/row-value.tsx +74 -0
  190. package/src/ui/components/settings/settings-row.tsx +78 -0
  191. package/src/ui/components/settings/settings-view.tsx +169 -0
  192. package/src/ui/components/setup/setup-widget.tsx +152 -0
  193. package/src/ui/flash/build-labels.ts +27 -27
  194. package/src/ui/hooks/use-activity-sprite.ts +73 -0
  195. package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
  196. package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
  197. package/src/ui/root.tsx +164 -230
  198. package/src/ui/status-bar-model.ts +48 -34
  199. package/src/ui/terminal-graphics/kitty.ts +28 -2
  200. package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
  201. package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
  202. package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
  203. package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
  204. package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
  205. package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
  206. package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
  207. package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
  208. package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
  209. package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
  210. package/src/ui/terminal-graphics/sprites.ts +246 -0
  211. package/src/ui/truncate.ts +7 -0
  212. package/src/ui/widgets/registry.tsx +21 -0
  213. package/src/ui/widgets/widget-context-menu.ts +85 -0
  214. package/src/update.ts +2 -2
  215. package/src/app-runtime/session-actions.ts +0 -187
  216. package/src/cli/client/workspace-resolver.ts +0 -110
  217. package/src/cli/commands/workspace/close.ts +0 -32
  218. package/src/cli/commands/workspace/show.ts +0 -32
  219. package/src/cli/commands/worktree/create.ts +0 -55
  220. package/src/cli/commands/worktree/list.ts +0 -68
  221. package/src/cli/commands/worktree/remove.ts +0 -81
  222. package/src/git/worktree-branch-poller.ts +0 -54
  223. package/src/git/worktree-divergence-poller.ts +0 -59
  224. package/src/state/reducers/session-state.ts +0 -262
  225. package/src/state/session-catalog.ts +0 -143
  226. package/src/state/session-worktrees.ts +0 -260
  227. package/src/state/workspace-save.ts +0 -45
  228. package/src/ui/components/git/pane/git-pane-context-menu.ts +0 -26
  229. package/src/ui/components/layout/sidebar/sidebar.tsx +0 -163
  230. package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
  231. package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
  232. package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
  233. /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
package/src/ui/root.tsx CHANGED
@@ -1,28 +1,26 @@
1
1
  import type { MouseEvent } from '@opentui/core'
2
2
 
3
- import { useCallback, useMemo } from 'react'
3
+ import { type ReactNode, useCallback, useMemo } from 'react'
4
4
 
5
5
  import type { MeasuredPaneRect } from '../app-runtime/use-pane-size-report'
6
- import type { WorktreeTemplate } from '../config'
7
6
  import type { TerminalContentOrigin } from '../input/raw-input-handler'
8
7
  import type {
8
+ BarSide,
9
9
  FocusMode,
10
10
  ModalState,
11
- SessionRecord,
11
+ ProjectRecord,
12
12
  SnippetRecord,
13
- WorktreeRecord,
13
+ WorkspaceRecord,
14
14
  } from '../state/types'
15
15
  import type { ThemeId } from './themes'
16
16
 
17
- import { useWorktreeBranchPolling } from '../git/worktree-branch-poller'
17
+ import { useWorkspaceBranchPolling } from '../git/workspace-branch-poller'
18
+ import { useAutoCommitModel } from '../settings/live'
18
19
  import { useAppStore } from '../state/app-store'
19
- import { dispatchGlobal } from '../state/dispatch-ref'
20
- import { getGitPaneWidthFromRatio } from '../state/git-pane-sizing'
20
+ import { getBarWidth } from '../state/bars'
21
21
  import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
22
22
  import { GitView } from './components/git/git-view'
23
- import { buildGitPaneContextMenu } from './components/git/pane/git-pane-context-menu'
24
- import { GitPaneWidget } from './components/git/pane/git-pane-widget'
25
- import { Sidebar } from './components/layout/sidebar/sidebar'
23
+ import { Bar, type BarBoundaryResizeInfo } from './components/layout/bar'
26
24
  import { SplitLayout } from './components/layout/split-layout'
27
25
  import { StatusBar } from './components/layout/status-bar'
28
26
  import { TerminalPane } from './components/layout/terminal-pane'
@@ -31,40 +29,41 @@ import { AIUsageModal } from './components/modals/app/ai-usage-modal'
31
29
  import { HelpModal } from './components/modals/app/help-modal'
32
30
  import { UpdateAvailableModal } from './components/modals/app/update-available-modal'
33
31
  import { GitCommitModal } from './components/modals/git/git-commit-modal'
34
- import { CreateSessionModal } from './components/modals/sessions/create-session-modal'
35
- import { SessionNameModal } from './components/modals/sessions/session-name-modal'
36
- import { SessionPickerModal } from './components/modals/sessions/session-picker-modal'
37
- import { WorktreeDeleteConfirm } from './components/modals/shared/worktree-delete-confirm'
32
+ import { CreateProjectModal } from './components/modals/projects/create-project-modal'
33
+ import { ProjectNameModal } from './components/modals/projects/project-name-modal'
34
+ import { ProjectPickerModal } from './components/modals/projects/project-picker-modal'
35
+ import { SettingsSearchModal } from './components/modals/settings/settings-search-modal'
36
+ import { WorkspaceDeleteConfirm } from './components/modals/shared/workspace-delete-confirm'
38
37
  import { SnippetEditorModal } from './components/modals/snippets/snippet-editor-modal'
39
38
  import { SnippetPickerModal } from './components/modals/snippets/snippet-picker-modal'
40
39
  import { NewTabModal } from './components/modals/tabs/new-tab-modal'
41
40
  import { ThemePickerModal } from './components/modals/themes/theme-picker-modal'
42
- import { WorktreeMoveConfirmModal } from './components/modals/worktree/worktree-move-confirm-modal'
43
- import { WorktreeMoveModal } from './components/modals/worktree/worktree-move-modal'
44
- import { ContextMenuBox } from './components/overlays/context-menu/context-menu-box'
41
+ import { CreateWorkspaceModal } from './components/modals/workspace/create-workspace-modal'
42
+ import { WorkspaceMoveConfirmModal } from './components/modals/workspace/workspace-move-confirm-modal'
43
+ import { WorkspaceMoveModal } from './components/modals/workspace/workspace-move-modal'
45
44
  import { ContextMenuOverlay } from './components/overlays/context-menu/context-menu-overlay'
46
45
  import { PendingChordOverlay } from './components/overlays/pending-chord-overlay'
47
46
  import { ToastViewport } from './components/overlays/toast/toast-viewport'
47
+ import { SettingsView } from './components/settings/settings-view'
48
48
  import { useTheme } from './theme'
49
49
 
50
- const EMPTY_WORKTREES: WorktreeRecord[] = []
51
- const EMPTY_BASE_BRANCHES: string[] = []
50
+ const EMPTY_WORKSPACES: WorkspaceRecord[] = []
52
51
 
53
- function getCreateSessionFields(modal: ModalState) {
54
- if (modal.type !== 'create-session') {
55
- return { directoryQuery: '', sessionName: '' }
52
+ function getCreateProjectFields(modal: ModalState) {
53
+ if (modal.type !== 'create-project') {
54
+ return { directoryQuery: '', projectName: '' }
56
55
  }
57
56
 
58
57
  if (modal.activeField === 'directory') {
59
58
  return {
60
59
  directoryQuery: modal.editBuffer ?? '',
61
- sessionName: modal.nameBuffer,
60
+ projectName: modal.nameBuffer,
62
61
  }
63
62
  }
64
63
 
65
64
  return {
66
65
  directoryQuery: modal.nameBuffer,
67
- sessionName: modal.editBuffer ?? '',
66
+ projectName: modal.editBuffer ?? '',
68
67
  }
69
68
  }
70
69
 
@@ -85,18 +84,17 @@ function renderModal(
85
84
  modal: ModalState,
86
85
  options: {
87
86
  customCommands: Record<string, string>
88
- sessions: SessionRecord[]
89
- currentSessionId: string | null
87
+ projects: ProjectRecord[]
88
+ currentProjectId: string | null
90
89
  currentTabCount: number
91
90
  snippets: SnippetRecord[]
92
91
  themeId: ThemeId
93
- createSessionFields: { directoryQuery: string; sessionName: string }
92
+ createProjectFields: { directoryQuery: string; projectName: string }
94
93
  snippetEditorFields: { snippetName: string; snippetTrigger: string; snippetContent: string }
95
94
  focusMode: FocusMode
96
95
  activeAssistant?: string
97
96
  autoCommitModel?: string
98
- worktreeDivergence: Record<string, { ahead: number; behind: number }>
99
- worktreeTemplates: WorktreeTemplate[]
97
+ workspaceDivergence: Record<string, { ahead: number; behind: number }>
100
98
  }
101
99
  ) {
102
100
  switch (modal.type) {
@@ -108,62 +106,72 @@ function renderModal(
108
106
  customCommands={options.customCommands}
109
107
  filter={modal.editBuffer}
110
108
  cursorPos={modal.cursorPos}
111
- currentSessionId={options.currentSessionId}
112
109
  editingCommand={modal.type === 'new-tab' ? modal.editingCommand : null}
113
110
  editBuffer={modal.editBuffer ?? ''}
114
- activeField={modal.type === 'new-tab' ? modal.activeField : 'assistant'}
115
- branchError={modal.type === 'new-tab' ? modal.branchError : null}
116
- branchName={modal.type === 'new-tab' ? modal.branchName : ''}
117
- createWorktree={modal.type === 'new-tab' ? modal.createWorktree : false}
118
- selectedAssistantId={modal.type === 'new-tab' ? modal.selectedAssistantId : null}
119
- step={modal.type === 'new-tab' ? modal.step : 'assistant'}
120
- baseQuery={modal.type === 'new-tab' ? modal.baseQuery : ''}
121
- baseRef={modal.type === 'new-tab' ? modal.baseRef : ''}
122
- baseBranches={modal.type === 'new-tab' ? modal.baseBranches : EMPTY_BASE_BRANCHES}
123
- worktreeDeletePrompt={modal.type === 'new-tab' ? modal.worktreeDeletePrompt : null}
124
- worktrees={
125
- options.currentSessionId != null && options.currentSessionId !== ''
126
- ? (options.sessions.find((session) => session.id === options.currentSessionId)
127
- ?.worktrees ?? EMPTY_WORKTREES)
128
- : EMPTY_WORKTREES
111
+ excludeTerminal={modal.type === 'new-tab' && modal.pendingWorkspace != null}
112
+ />
113
+ )
114
+ case 'create-workspace':
115
+ return (
116
+ <CreateWorkspaceModal
117
+ activeField={modal.activeField}
118
+ prompt={modal.prompt}
119
+ branchError={modal.branchError}
120
+ baseQuery={modal.baseQuery}
121
+ baseRef={modal.baseRef}
122
+ baseBranches={modal.baseBranches}
123
+ cursorPos={modal.cursorPos}
124
+ selectedIndex={modal.selectedIndex}
125
+ workspaces={
126
+ options.currentProjectId != null && options.currentProjectId !== ''
127
+ ? (options.projects.find((project) => project.id === options.currentProjectId)
128
+ ?.workspaces ?? EMPTY_WORKSPACES)
129
+ : EMPTY_WORKSPACES
129
130
  }
130
- worktreeName={modal.type === 'new-tab' ? modal.worktreeName : ''}
131
- worktreeTemplates={options.worktreeTemplates}
132
- allowTemplateShortcut={modal.type === 'new-tab'}
133
131
  />
134
132
  )
135
- case 'session-picker':
133
+ case 'project-picker':
136
134
  return (
137
- <SessionPickerModal
138
- sessions={options.sessions}
135
+ <ProjectPickerModal
136
+ projects={options.projects}
139
137
  selectedIndex={modal.selectedIndex}
140
- currentSessionId={options.currentSessionId}
138
+ currentProjectId={options.currentProjectId}
141
139
  currentTabCount={options.currentTabCount}
142
140
  filter={modal.editBuffer}
143
141
  cursorPos={modal.cursorPos}
144
142
  />
145
143
  )
146
- case 'session-name':
144
+ case 'project-name':
147
145
  return (
148
- <SessionNameModal
146
+ <ProjectNameModal
149
147
  title={
150
- modal.sessionTargetId != null && modal.sessionTargetId !== ''
151
- ? 'Rename workspace'
152
- : 'Create workspace'
148
+ modal.projectTargetId != null && modal.projectTargetId !== ''
149
+ ? 'Rename project'
150
+ : 'Create project'
153
151
  }
154
152
  value={modal.editBuffer ?? ''}
155
153
  />
156
154
  )
157
155
  case 'rename-tab':
158
- return <SessionNameModal title="Rename tab" value={modal.editBuffer ?? ''} />
159
- case 'rename-worktree':
160
- return <SessionNameModal title="Rename worktree" value={modal.editBuffer ?? ''} />
161
- case 'create-session':
156
+ return <ProjectNameModal title="Rename tab" value={modal.editBuffer ?? ''} />
157
+ case 'setting-text':
158
+ return <ProjectNameModal title={modal.settingLabel} value={modal.editBuffer ?? ''} />
159
+ case 'settings-search':
162
160
  return (
163
- <CreateSessionModal
161
+ <SettingsSearchModal
162
+ filter={modal.editBuffer}
163
+ selectedIndex={modal.selectedIndex}
164
+ cursorPos={modal.cursorPos}
165
+ />
166
+ )
167
+ case 'rename-workspace':
168
+ return <ProjectNameModal title="Rename workspace" value={modal.editBuffer ?? ''} />
169
+ case 'create-project':
170
+ return (
171
+ <CreateProjectModal
164
172
  activeField={modal.activeField}
165
- directoryQuery={options.createSessionFields.directoryQuery}
166
- sessionName={options.createSessionFields.sessionName}
173
+ directoryQuery={options.createProjectFields.directoryQuery}
174
+ projectName={options.createProjectFields.projectName}
167
175
  results={modal.directoryResults}
168
176
  selectedIndex={modal.selectedIndex}
169
177
  pendingProjectPath={modal.pendingProjectPath}
@@ -186,7 +194,7 @@ function renderModal(
186
194
  snippetName={options.snippetEditorFields.snippetName}
187
195
  snippetTrigger={options.snippetEditorFields.snippetTrigger}
188
196
  snippetContent={options.snippetEditorFields.snippetContent}
189
- isEditing={modal.sessionTargetId !== null}
197
+ isEditing={modal.projectTargetId !== null}
190
198
  />
191
199
  )
192
200
  case 'theme-picker':
@@ -206,25 +214,25 @@ function renderModal(
206
214
  latestVersion={modal.latestVersion}
207
215
  />
208
216
  )
209
- case 'worktree-move': {
210
- const session =
211
- options.currentSessionId != null && options.currentSessionId !== ''
212
- ? options.sessions.find((entry) => entry.id === options.currentSessionId)
217
+ case 'workspace-move': {
218
+ const project =
219
+ options.currentProjectId != null && options.currentProjectId !== ''
220
+ ? options.projects.find((entry) => entry.id === options.currentProjectId)
213
221
  : undefined
214
222
  return (
215
- <WorktreeMoveModal
216
- deleteSource={modal.type === 'worktree-move' ? modal.deleteSource : false}
217
- divergence={options.worktreeDivergence}
223
+ <WorkspaceMoveModal
224
+ deleteSource={modal.type === 'workspace-move' ? modal.deleteSource : false}
225
+ divergence={options.workspaceDivergence}
218
226
  selectedIndex={modal.selectedIndex}
219
- sourceWorktreeId={modal.sourceWorktreeId}
227
+ sourceWorkspaceId={modal.sourceWorkspaceId}
220
228
  stats={modal.stats}
221
- worktrees={session?.worktrees ?? EMPTY_WORKTREES}
229
+ workspaces={project?.workspaces ?? EMPTY_WORKSPACES}
222
230
  />
223
231
  )
224
232
  }
225
- case 'worktree-move-confirm':
233
+ case 'workspace-move-confirm':
226
234
  return (
227
- <WorktreeMoveConfirmModal
235
+ <WorkspaceMoveConfirmModal
228
236
  variant={modal.variant}
229
237
  files={modal.files}
230
238
  sourceLabel={modal.sourceLabel}
@@ -242,12 +250,12 @@ function renderModal(
242
250
  )
243
251
  case 'ai-usage':
244
252
  return <AIUsageModal />
245
- case 'worktree-delete-confirm':
253
+ case 'workspace-delete-confirm':
246
254
  return (
247
- <WorktreeDeleteConfirm
248
- keybindsModeId="modal.worktree-delete-confirm"
255
+ <WorkspaceDeleteConfirm
256
+ keybindsModeId="modal.workspace-delete-confirm"
249
257
  reason={modal.reason}
250
- worktreeLabel={modal.worktreeLabel}
258
+ workspaceLabel={modal.workspaceLabel}
251
259
  />
252
260
  )
253
261
  case 'git-commit': {
@@ -289,17 +297,8 @@ interface RootViewProps {
289
297
  onTerminalMouseUp?: (event: MouseEvent) => boolean
290
298
  onPaneActivate?: (tabId: string) => void
291
299
  onSplitResize?: (tabId: string, ratio: number, axis: SplitDirection) => void
292
- onSidebarResizeStart?: (info: { initialWidth: number; screenStart: number }) => void
293
- onGitPaneResizeStart?: (info: {
294
- initialWidth: number
295
- screenStart: number
296
- side: 'left' | 'right'
297
- }) => void
298
- onEmbeddedGitResizeStart?: (info: {
299
- containerStart: number
300
- position: 'top' | 'bottom'
301
- totalSize: number
302
- }) => void
300
+ onBarResizeStart?: (info: { initialWidth: number; screenStart: number; side: BarSide }) => void
301
+ onBarBoundaryResizeStart?: (info: BarBoundaryResizeInfo) => void
303
302
  onSeparatorDragStart?: (info: {
304
303
  tabId: string
305
304
  direction: SplitDirection
@@ -317,14 +316,13 @@ export function RootView({
317
316
  contentOrigin,
318
317
  localScrollbackEnabled,
319
318
  mouseForwardingEnabled,
320
- onEmbeddedGitResizeStart,
321
- onGitPaneResizeStart,
319
+ onBarBoundaryResizeStart,
320
+ onBarResizeStart,
322
321
  onMeasure,
323
322
  onPaneActivate,
324
323
  onSeparatorDrag,
325
324
  onSeparatorDragEnd,
326
325
  onSeparatorDragStart,
327
- onSidebarResizeStart,
328
326
  onSplitResize,
329
327
  onTerminalClick,
330
328
  onTerminalDrag,
@@ -345,35 +343,28 @@ export function RootView({
345
343
  const modal = useAppStore((s) => s.modal)
346
344
  const snippets = useAppStore((s) => s.snippets)
347
345
  const customCommands = useAppStore((s) => s.customCommands)
348
- const sessions = useAppStore((s) => s.sessions)
349
- const currentSessionId = useAppStore((s) => s.currentSessionId)
350
- const worktreeDivergence = useAppStore((s) => s.worktreeDivergence)
351
- const worktreeTemplates = useAppStore((s) => s.worktreeTemplates)
352
- const gitPaneMode = useAppStore((s) => s.gitPane.mode)
353
- const gitPaneVisible = useAppStore((s) => s.gitPane.visible)
354
- const gitPanePosition = useAppStore((s) => s.gitPane.position)
355
- const gitPaneRatio = useAppStore((s) => s.gitPane.paneRatio)
356
- const sidebarWidth = useAppStore((s) => s.sidebar.width)
357
- const sidebarVisible = useAppStore((s) => s.sidebar.visible)
346
+ const projects = useAppStore((s) => s.projects)
347
+ const currentProjectId = useAppStore((s) => s.currentProjectId)
348
+ const workspaceDivergence = useAppStore((s) => s.workspaceDivergence)
349
+ const leftBarWidth = useAppStore((s) => getBarWidth(s.bars.left))
358
350
 
359
- const gitPaneInPaneOnLeft = gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left'
360
351
  const splitChrome = PANE_BORDER * 2
361
352
 
362
- // Keep every worktree's `branch` in state synchronized with the on-disk
353
+ // Keep every workspace's `branch` in state synchronized with the on-disk
363
354
  // HEAD so the sidebar (and divergence calc) never reads a stale value.
364
- useWorktreeBranchPolling(true)
355
+ useWorkspaceBranchPolling(true)
365
356
 
366
- const handleSidebarEdgeResize = useCallback(
357
+ // The terminal's own left edge stays a drag target for the left bar, so the
358
+ // resize gesture works from either side of the seam.
359
+ const handleLeftBarEdgeResize = useCallback(
367
360
  (event: MouseEvent): boolean => {
368
- onSidebarResizeStart?.({ initialWidth: sidebarWidth, screenStart: event.x })
361
+ onBarResizeStart?.({ initialWidth: leftBarWidth, screenStart: event.x, side: 'left' })
369
362
  return true
370
363
  },
371
- [onSidebarResizeStart, sidebarWidth]
364
+ [leftBarWidth, onBarResizeStart]
372
365
  )
373
366
  const handleTerminalLeftEdgeMouseDown =
374
- sidebarVisible && !gitPaneInPaneOnLeft && onSidebarResizeStart
375
- ? handleSidebarEdgeResize
376
- : undefined
367
+ leftBarWidth > 0 && onBarResizeStart ? handleLeftBarEdgeResize : undefined
377
368
 
378
369
  const handleRootMouseDrag = useCallback(
379
370
  (event: MouseEvent) => {
@@ -415,66 +406,53 @@ export function RootView({
415
406
  )
416
407
 
417
408
  const activeTab = tabs.find((tab) => tab.id === activeTabId)
409
+ // The generating overlay has always been able to name the model, and nothing
410
+ // ever passed it one.
411
+ const autoCommitModel = useAutoCommitModel(activeTab?.assistant)
418
412
  const activeTree =
419
413
  activeTabId != null && activeTabId !== ''
420
414
  ? getTreeForTab(layoutTrees, tabGroupMap, activeTabId)
421
415
  : null
422
- const createSessionFields = getCreateSessionFields(modal)
416
+ const createProjectFields = getCreateProjectFields(modal)
423
417
  const snippetEditorFields = getSnippetEditorFields(modal)
424
418
 
419
+ // Git mode and settings each swap the pane tree for one full-width view.
420
+ // Everything else on screen — status bar, overlays, modal, toasts — is the
421
+ // same either way, so only the center is a branch: a return per view would
422
+ // mean maintaining the chrome three times.
425
423
  const inGitMode = focusMode === 'git' || modal.type === 'git-commit'
426
- if (inGitMode) {
427
- return (
428
- <box flexDirection="column" width="100%" height="100%" backgroundColor={editorBg}>
429
- <TopTabBar forceVisible />
430
- <GitView themeId={themeId} />
431
- <StatusBar />
432
- <PendingChordOverlay />
433
- <ContextMenuOverlay />
434
- {renderModal(modal, {
435
- activeAssistant: activeTab?.assistant,
436
- createSessionFields,
437
- currentSessionId,
438
- currentTabCount: tabs.length,
439
- customCommands,
440
- focusMode,
441
- sessions,
442
- snippetEditorFields,
443
- snippets,
444
- themeId,
445
- worktreeDivergence,
446
- worktreeTemplates,
447
- })}
448
- <ToastViewport />
449
- </box>
450
- )
451
- }
424
+ // A settings row's text field flips focusMode to command-edit, the same way the
425
+ // commit modal does in git mode, so the screen behind it has to stay mounted:
426
+ // those two modals belong to the screen and are read against it.
427
+ //
428
+ // Not `returnTo === 'settings'`, which is where focus goes and not what is
429
+ // behind: the theme picker also returns here, and it previews each theme as you
430
+ // move. Previewing it on the settings screen is not the answer to "how does this
431
+ // theme look" — so it gets the panes, and closing still comes back here.
432
+ const inSettings =
433
+ focusMode === 'settings' || modal.type === 'setting-text' || modal.type === 'settings-search'
434
+ let replacesPanes: ReactNode = null
435
+ if (inGitMode) replacesPanes = <GitView themeId={themeId} />
436
+ else if (inSettings) replacesPanes = <SettingsView />
452
437
 
453
- return (
454
- <box
455
- flexDirection="column"
456
- width="100%"
457
- height="100%"
458
- backgroundColor={editorBg}
459
- onMouseDrag={handleRootMouseDrag}
460
- onMouseUp={handleRootMouseUp}
461
- >
438
+ const center =
439
+ replacesPanes !== null ? (
440
+ <>
441
+ <TopTabBar panesReplaced />
442
+ {replacesPanes}
443
+ </>
444
+ ) : (
462
445
  <box flexDirection="row" gap={0} padding={0} flexGrow={1}>
463
- <Sidebar
464
- onEmbeddedGitResizeStart={onEmbeddedGitResizeStart}
446
+ <Bar
447
+ side="left"
448
+ onBoundaryResizeStart={onBarBoundaryResizeStart}
449
+ onEdgeResizeStart={onBarResizeStart}
465
450
  onResizeDrag={onSeparatorDrag}
466
451
  onResizeDragEnd={onSeparatorDragEnd}
467
452
  />
468
453
  <box flexDirection="column" flexGrow={1}>
469
454
  <TopTabBar />
470
455
  <box flexDirection="row" gap={0} padding={0} flexGrow={1}>
471
- {gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left' ? (
472
- <GitPaneInPaneMode
473
- position="left"
474
- ratio={gitPaneRatio}
475
- onGitPaneResizeStart={onGitPaneResizeStart}
476
- />
477
- ) : null}
478
456
  {activeTree && activeTree.type === 'split' ? (
479
457
  <SplitLayout
480
458
  node={activeTree}
@@ -517,93 +495,49 @@ export function RootView({
517
495
  onMeasure={onMeasure}
518
496
  />
519
497
  )}
520
- {gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'right' ? (
521
- <GitPaneInPaneMode
522
- position="right"
523
- ratio={gitPaneRatio}
524
- onGitPaneResizeStart={onGitPaneResizeStart}
525
- />
526
- ) : null}
527
498
  </box>
528
499
  </box>
500
+ <Bar
501
+ side="right"
502
+ onBoundaryResizeStart={onBarBoundaryResizeStart}
503
+ onEdgeResizeStart={onBarResizeStart}
504
+ onResizeDrag={onSeparatorDrag}
505
+ onResizeDragEnd={onSeparatorDragEnd}
506
+ />
529
507
  </box>
508
+ )
509
+
510
+ return (
511
+ <box
512
+ flexDirection="column"
513
+ width="100%"
514
+ height="100%"
515
+ backgroundColor={editorBg}
516
+ // Both drag handlers no-op unless a gesture is in flight, so the full-width
517
+ // views inherit them harmlessly — and a drag that was in flight when one
518
+ // opened gets finalised instead of stranded.
519
+ onMouseDrag={handleRootMouseDrag}
520
+ onMouseUp={handleRootMouseUp}
521
+ >
522
+ {center}
530
523
  <StatusBar />
531
524
  <PendingChordOverlay />
532
525
  <ContextMenuOverlay />
533
526
  {renderModal(modal, {
534
- createSessionFields,
535
- currentSessionId,
527
+ activeAssistant: activeTab?.assistant,
528
+ autoCommitModel,
529
+ createProjectFields,
530
+ currentProjectId,
536
531
  currentTabCount: tabs.length,
537
532
  customCommands,
538
533
  focusMode,
539
- sessions,
534
+ projects,
540
535
  snippetEditorFields,
541
536
  snippets,
542
537
  themeId,
543
- worktreeDivergence,
544
- worktreeTemplates,
538
+ workspaceDivergence,
545
539
  })}
546
540
  <ToastViewport />
547
541
  </box>
548
542
  )
549
543
  }
550
-
551
- function GitPaneInPaneMode({
552
- onGitPaneResizeStart,
553
- position,
554
- ratio,
555
- }: {
556
- ratio: number
557
- position: 'left' | 'right'
558
- onGitPaneResizeStart?: (info: {
559
- initialWidth: number
560
- screenStart: number
561
- side: 'left' | 'right'
562
- }) => void
563
- }) {
564
- const tokens = useTheme()
565
- const bg = tokens.backgroundPanel
566
- const gitPane = useAppStore((s) => s.gitPane)
567
- const width = getGitPaneWidthFromRatio(ratio)
568
- const contentWidth = Math.max(1, width - 1)
569
- const gitPaneMenu = buildGitPaneContextMenu(
570
- gitPane,
571
- () => dispatchGlobal({ type: 'toggle-git-pane' }),
572
- (mode, nextPosition) => {
573
- dispatchGlobal({ mode, type: 'set-git-pane-mode' })
574
- dispatchGlobal({ position: nextPosition, type: 'set-git-pane-position' })
575
- }
576
- )
577
- const handleResizeMouseDown = useCallback(
578
- (event: MouseEvent) => {
579
- event.preventDefault()
580
- event.stopPropagation()
581
- onGitPaneResizeStart?.({ initialWidth: width, screenStart: event.x, side: position })
582
- },
583
- [onGitPaneResizeStart, position, width]
584
- )
585
- const handle = (
586
- <box
587
- width={1}
588
- flexShrink={0}
589
- backgroundColor={tokens.border}
590
- onMouseDown={handleResizeMouseDown}
591
- />
592
- )
593
- return (
594
- <box flexDirection="column" width={width} flexShrink={0} backgroundColor={bg} overflow="hidden">
595
- <box flexDirection="row" flexGrow={1} overflow="hidden">
596
- {position === 'right' ? handle : null}
597
- <ContextMenuBox
598
- width={contentWidth}
599
- flexGrow={1}
600
- overflow="hidden"
601
- rightClickMenu={gitPaneMenu}
602
- >
603
- <GitPaneWidget pollingEnabled />
604
- </ContextMenuBox>
605
- {position === 'left' ? handle : null}
606
- </box>
607
- </box>
608
- )
609
- }