@brimveyn/aimux 1.13.2 → 1.14.1

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 (54) hide show
  1. package/package.json +2 -2
  2. package/src/app-runtime/backend-attach-runtime.ts +1 -3
  3. package/src/app-runtime/multi-click-clipboard-guard.ts +24 -12
  4. package/src/app-runtime/pty-write.ts +6 -9
  5. package/src/app-runtime/side-effects.ts +149 -19
  6. package/src/app-runtime/snippet-actions.ts +2 -3
  7. package/src/app-runtime/split-drag-controller.ts +3 -1
  8. package/src/app-runtime/use-backend-runtime.ts +4 -7
  9. package/src/app-runtime/use-mouse-handlers.ts +30 -1
  10. package/src/app-runtime/use-renderer-bindings.ts +4 -12
  11. package/src/app-runtime/use-terminal-resize.ts +7 -22
  12. package/src/app.tsx +0 -6
  13. package/src/config.ts +1 -12
  14. package/src/daemon/daemon.ts +2 -19
  15. package/src/daemon/session-manager.ts +3 -15
  16. package/src/daemon/session-registry.ts +11 -30
  17. package/src/git/worktree-branch-poller.ts +54 -0
  18. package/src/input/modes/types.ts +2 -0
  19. package/src/input/terminal-text-extraction.ts +7 -8
  20. package/src/ipc/manager-protocol.ts +6 -38
  21. package/src/ipc/protocol.ts +9 -37
  22. package/src/pty/pty-manager.ts +20 -31
  23. package/src/pty/terminal-snapshot.ts +43 -0
  24. package/src/session-backend/local-session-backend.ts +7 -31
  25. package/src/session-backend/remote-session-backend.ts +5 -32
  26. package/src/session-backend/types.ts +2 -15
  27. package/src/state/layout-tree.ts +114 -4
  28. package/src/state/reducers/session-state.ts +20 -0
  29. package/src/state/reducers/tab-state.ts +22 -22
  30. package/src/state/reducers/ui-state.ts +0 -3
  31. package/src/state/session-persistence.ts +2 -22
  32. package/src/state/session-worktrees.ts +45 -2
  33. package/src/state/store.ts +0 -3
  34. package/src/state/tab-entries.ts +74 -0
  35. package/src/state/types.ts +4 -15
  36. package/src/state/validation.ts +0 -8
  37. package/src/state/workspace-save.ts +0 -1
  38. package/src/terminal-manager/manager-client.ts +5 -29
  39. package/src/terminal-manager/terminal-manager.ts +2 -17
  40. package/src/ui/components/layout/sidebar/sidebar.tsx +9 -340
  41. package/src/ui/components/layout/sidebar/tab-item.tsx +51 -42
  42. package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +10 -53
  43. package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +30 -0
  44. package/src/ui/components/layout/sidebar/workspace-list.tsx +398 -0
  45. package/src/ui/components/layout/sidebar/worktree-row.tsx +92 -0
  46. package/src/ui/components/layout/split-layout.tsx +20 -39
  47. package/src/ui/components/layout/terminal-pane.tsx +30 -0
  48. package/src/ui/components/layout/top-tab-bar.tsx +304 -0
  49. package/src/ui/components/modals/worktree/worktree-move-modal.tsx +1 -8
  50. package/src/ui/root.tsx +68 -63
  51. package/src/ui/components/layout/session-bar.tsx +0 -296
  52. package/src/ui/components/layout/sidebar/sidebar-group-metadata.ts +0 -44
  53. package/src/ui/components/layout/sidebar/sidebar-scroll.ts +0 -33
  54. package/src/ui/components/layout/sidebar/use-sidebar-branch.ts +0 -36
@@ -3,6 +3,7 @@ import type { MouseEvent as OtuiMouseEvent } from '@opentui/core'
3
3
  import { memo, type ReactNode, useCallback, useMemo } from 'react'
4
4
 
5
5
  import type { TerminalContentOrigin } from '../../../input/raw-input-handler'
6
+ import type { JunctionEdgeInfo, JunctionEdges } from '../../../state/layout-tree'
6
7
  import type { FocusMode, TabSession, TerminalSnapshot, TerminalSpan } from '../../../state/types'
7
8
 
8
9
  import { type MeasuredPaneRect, usePaneSizeReport } from '../../../app-runtime/use-pane-size-report'
@@ -28,8 +29,10 @@ interface TerminalPaneProps {
28
29
  onPaneActivate?: (tabId: string) => void
29
30
  onSeparatorDrag?: (event: OtuiMouseEvent) => boolean
30
31
  onSeparatorDragEnd?: () => void
32
+ onSeparatorDragStart?: (info: JunctionEdgeInfo) => void
31
33
  onLeftEdgeMouseDown?: (event: OtuiMouseEvent) => boolean
32
34
  onMeasure?: (tabId: string, rect: MeasuredPaneRect) => void
35
+ junctionEdges?: JunctionEdges
33
36
  }
34
37
 
35
38
  function getTitle(
@@ -113,6 +116,7 @@ export function TerminalPane({
113
116
  contentOrigin,
114
117
  focusMode,
115
118
  isActive,
119
+ junctionEdges,
116
120
  localScrollbackEnabled,
117
121
  mouseForwardingEnabled,
118
122
  onLeftEdgeMouseDown,
@@ -120,6 +124,7 @@ export function TerminalPane({
120
124
  onPaneActivate,
121
125
  onSeparatorDrag,
122
126
  onSeparatorDragEnd,
127
+ onSeparatorDragStart,
123
128
  onTerminalClick,
124
129
  onTerminalDrag,
125
130
  onTerminalMouseEvent,
@@ -175,6 +180,16 @@ export function TerminalPane({
175
180
  event.y === contentOrigin.y + contentOrigin.rows,
176
181
  [contentOrigin]
177
182
  )
183
+ const getJunctionSideAt = useCallback(
184
+ (event: OtuiMouseEvent): keyof JunctionEdges | null => {
185
+ if (event.x === contentOrigin.x - 1) return 'left'
186
+ if (event.x === contentOrigin.x + contentOrigin.cols) return 'right'
187
+ if (event.y === contentOrigin.y - 1) return 'top'
188
+ if (event.y === contentOrigin.y + contentOrigin.rows) return 'bottom'
189
+ return null
190
+ },
191
+ [contentOrigin]
192
+ )
178
193
  const forwardMouseEvent = useCallback(
179
194
  (event: OtuiMouseEvent) => {
180
195
  if (event.type === 'down' && event.button === 2 && rightClickMenu) {
@@ -195,6 +210,18 @@ export function TerminalPane({
195
210
  return
196
211
  }
197
212
  }
213
+ if (event.type === 'down' && event.button === 0 && junctionEdges && onSeparatorDragStart) {
214
+ const side = getJunctionSideAt(event)
215
+ if (side !== null) {
216
+ const info = junctionEdges[side]
217
+ if (info) {
218
+ event.preventDefault()
219
+ event.stopPropagation()
220
+ onSeparatorDragStart(info)
221
+ return
222
+ }
223
+ }
224
+ }
198
225
  // Absorb left-button clicks on pane borders — they should not focus the
199
226
  // pane or be forwarded to the terminal. This keeps borders available for
200
227
  // resize actions (split separators, sidebar edge) without conflicting
@@ -248,11 +275,14 @@ export function TerminalPane({
248
275
  [
249
276
  canForwardMouse,
250
277
  contentOrigin,
278
+ getJunctionSideAt,
251
279
  isOnPaneBorder,
280
+ junctionEdges,
252
281
  onLeftEdgeMouseDown,
253
282
  onPaneActivate,
254
283
  onSeparatorDrag,
255
284
  onSeparatorDragEnd,
285
+ onSeparatorDragStart,
256
286
  onTerminalClick,
257
287
  onTerminalDrag,
258
288
  onTerminalMouseEvent,
@@ -0,0 +1,304 @@
1
+ import type { MouseEvent as OtuiMouseEvent, ScrollBoxRenderable } from '@opentui/core'
2
+
3
+ import { memo, type ReactNode, useCallback, useMemo, useRef } from 'react'
4
+
5
+ import type { FocusMode, TabSession } from '../../../state/types'
6
+
7
+ import { useWorktreeDivergencePolling } from '../../../git/worktree-divergence-poller'
8
+ import { useAppStore } from '../../../state/app-store'
9
+ import { dispatchGlobal, runSideEffectGlobal } from '../../../state/dispatch-ref'
10
+ import { filterTabsForActiveWorktree } from '../../../state/session-worktrees'
11
+ import { buildTabEntries, type GroupEntry } from '../../../state/tab-entries'
12
+ import { useTheme } from '../../theme'
13
+ import { ContextMenuBox } from '../overlays/context-menu/context-menu-box'
14
+ import { TabItem } from './sidebar/tab-item'
15
+ import { useTopTabBarAutoScroll } from './sidebar/use-top-tab-bar-auto-scroll'
16
+
17
+ interface TopTabBarProps {
18
+ forceVisible?: boolean
19
+ }
20
+
21
+ const ROW_CONTENT_OPTIONS = {
22
+ flexDirection: 'row' as const,
23
+ gap: 0,
24
+ justifyContent: 'flex-start' as const,
25
+ }
26
+
27
+ function getGroupIndicator(active: boolean, focused: boolean): string {
28
+ if (!active) return '·'
29
+ return focused ? '›' : '•'
30
+ }
31
+
32
+ function getGroupIndicatorColor(
33
+ t: ReturnType<typeof useTheme>,
34
+ active: boolean,
35
+ focused: boolean
36
+ ): string {
37
+ if (!active) return t.textMuted
38
+ return focused ? t.primary : t.text
39
+ }
40
+
41
+ const TopTabCell = memo(function TopTabCell({
42
+ active,
43
+ backgroundColor,
44
+ children,
45
+ entryId,
46
+ onActivate,
47
+ }: {
48
+ entryId: string
49
+ active: boolean
50
+ backgroundColor: string | undefined
51
+ onActivate?: (entryId: string) => void
52
+ children: ReactNode
53
+ }) {
54
+ const handleMouseDown = useCallback(
55
+ (event: OtuiMouseEvent) => {
56
+ event.stopPropagation()
57
+ onActivate?.(entryId)
58
+ },
59
+ [onActivate, entryId]
60
+ )
61
+ return (
62
+ <box
63
+ backgroundColor={backgroundColor}
64
+ flexDirection="row"
65
+ flexShrink={0}
66
+ onMouseDown={handleMouseDown}
67
+ data-active={active ? 'true' : undefined}
68
+ >
69
+ {children}
70
+ </box>
71
+ )
72
+ })
73
+
74
+ function GroupTabItem({
75
+ active,
76
+ entry,
77
+ focused,
78
+ indexLabel,
79
+ }: {
80
+ entry: GroupEntry
81
+ active: boolean
82
+ focused: boolean
83
+ indexLabel?: string
84
+ }) {
85
+ const t = useTheme()
86
+ const indicator = getGroupIndicator(active, focused)
87
+ const indicatorColor = getGroupIndicatorColor(t, active, focused)
88
+
89
+ const closeGroup = useCallback(() => {
90
+ for (const tab of entry.tabs) {
91
+ dispatchGlobal({ tabId: tab.id, type: 'close-tab' })
92
+ runSideEffectGlobal({ tabId: tab.id, type: 'close-tab' })
93
+ }
94
+ }, [entry.tabs])
95
+
96
+ const handleCloseMouseDown = useCallback(
97
+ (event: OtuiMouseEvent) => {
98
+ event.stopPropagation()
99
+ closeGroup()
100
+ },
101
+ [closeGroup]
102
+ )
103
+
104
+ const rightClickMenu = useMemo<[string, () => void][]>(
105
+ () => [['Close group', closeGroup]],
106
+ [closeGroup]
107
+ )
108
+
109
+ return (
110
+ <ContextMenuBox
111
+ id={`top-tab-${entry.id}`}
112
+ paddingLeft={1}
113
+ paddingRight={1}
114
+ flexDirection="row"
115
+ alignItems="center"
116
+ rightClickMenu={rightClickMenu}
117
+ >
118
+ <text fg={indicatorColor} selectable={false}>
119
+ {indicator}{' '}
120
+ </text>
121
+ {indexLabel != null && indexLabel !== '' ? (
122
+ <text fg={t.textMuted} selectable={false} wrapMode="none">
123
+ {indexLabel}{' '}
124
+ </text>
125
+ ) : null}
126
+ {entry.tabs.map((tab, i) => {
127
+ const isLeafActive = tab.id === entry.activeLeafId
128
+ return (
129
+ <box key={tab.id} flexDirection="row" flexShrink={0}>
130
+ {i > 0 ? (
131
+ <text fg={t.textMuted} selectable={false} wrapMode="none">
132
+ {' | '}
133
+ </text>
134
+ ) : null}
135
+ <text fg={isLeafActive ? t.text : t.textMuted} selectable={false} wrapMode="none">
136
+ {tab.title}
137
+ </text>
138
+ </box>
139
+ )
140
+ })}
141
+ <box paddingLeft={1} onMouseDown={handleCloseMouseDown}>
142
+ <text fg={t.textMuted} selectable={false}>
143
+ ×
144
+ </text>
145
+ </box>
146
+ </ContextMenuBox>
147
+ )
148
+ }
149
+
150
+ export function TopTabBar({ forceVisible = false }: TopTabBarProps) {
151
+ const t = useTheme()
152
+ const headerBg = t.backgroundPanel
153
+ const tabs = useAppStore((s) => s.tabs)
154
+ const activeTabId = useAppStore((s) => s.activeTabId)
155
+ const bar = useAppStore((s) => s.sessionBar)
156
+ const sidebar = useAppStore((s) => s.sidebar)
157
+ const currentSessionId = useAppStore((s) => s.currentSessionId)
158
+ const sessions = useAppStore((s) => s.sessions)
159
+ const focusMode: FocusMode = useAppStore((s) => s.focusMode)
160
+ const layoutTrees = useAppStore((s) => s.layoutTrees)
161
+ const tabGroupMap = useAppStore((s) => s.tabGroupMap)
162
+
163
+ // Sidebar now also shows worktree chips with divergence — poll whenever
164
+ // either surface is visible.
165
+ useWorktreeDivergencePolling(bar.visible || sidebar.visible || forceVisible)
166
+
167
+ const currentSession = useMemo(
168
+ () =>
169
+ currentSessionId != null && currentSessionId !== ''
170
+ ? sessions.find((s) => s.id === currentSessionId)
171
+ : undefined,
172
+ [currentSessionId, sessions]
173
+ )
174
+
175
+ const visibleTabs = useMemo(
176
+ () => filterTabsForActiveWorktree(tabs, currentSession),
177
+ [tabs, currentSession]
178
+ )
179
+
180
+ const entries = useMemo(
181
+ () => buildTabEntries(visibleTabs, layoutTrees, tabGroupMap, activeTabId),
182
+ [visibleTabs, layoutTrees, tabGroupMap, activeTabId]
183
+ )
184
+
185
+ const activeEntryId = useMemo(() => {
186
+ for (const entry of entries) {
187
+ if (entry.kind === 'single') {
188
+ if (entry.tab.id === activeTabId) return entry.id
189
+ } else if (entry.tabs.some((tab) => tab.id === activeTabId)) {
190
+ return entry.id
191
+ }
192
+ }
193
+ return null
194
+ }, [entries, activeTabId])
195
+
196
+ const scrollRef = useRef<ScrollBoxRenderable | null>(null)
197
+ useTopTabBarAutoScroll({
198
+ activeTabId: activeEntryId,
199
+ idPrefix: 'top-tab-',
200
+ scrollRef,
201
+ visible: bar.visible || forceVisible,
202
+ })
203
+
204
+ const handleEntryActivate = useCallback(
205
+ (entryId: string) => {
206
+ const entry = entries.find((e) => e.id === entryId)
207
+ if (!entry) return
208
+ const targetTabId = entry.kind === 'single' ? entry.tab.id : entry.activeLeafId
209
+ if (targetTabId !== activeTabId) {
210
+ dispatchGlobal({ tabId: targetTabId, type: 'set-active-tab' })
211
+ }
212
+ dispatchGlobal({ focusMode: 'terminal-input', type: 'set-focus-mode' })
213
+ },
214
+ [entries, activeTabId]
215
+ )
216
+
217
+ const handleNewTab = useCallback((e: OtuiMouseEvent) => {
218
+ e.stopPropagation()
219
+ dispatchGlobal({ type: 'open-new-tab-modal' })
220
+ }, [])
221
+
222
+ if (!bar.visible && !forceVisible) return null
223
+ if (entries.length === 0) return null
224
+
225
+ const isFocused = focusMode === 'terminal-input' || focusMode === 'navigation'
226
+
227
+ return (
228
+ <box
229
+ width="100%"
230
+ height={1}
231
+ flexDirection="row"
232
+ flexShrink={0}
233
+ backgroundColor={headerBg}
234
+ overflow="hidden"
235
+ >
236
+ <scrollbox
237
+ ref={scrollRef}
238
+ height={1}
239
+ flexGrow={1}
240
+ flexShrink={1}
241
+ flexBasis={0}
242
+ scrollX
243
+ viewportCulling
244
+ contentOptions={ROW_CONTENT_OPTIONS}
245
+ >
246
+ {entries.map((entry, index) => {
247
+ // [N] is shown only for the first 9 entries — that's the range
248
+ // Leader+1..9 can address.
249
+ const indexLabel = index < 9 ? `[${index + 1}]` : undefined
250
+ if (entry.kind === 'single') {
251
+ const tab: TabSession = entry.tab
252
+ const isActive = tab.id === activeTabId
253
+ return (
254
+ <TopTabCell
255
+ key={entry.id}
256
+ entryId={entry.id}
257
+ active={isActive}
258
+ onActivate={handleEntryActivate}
259
+ backgroundColor={isActive ? t.backgroundElement : undefined}
260
+ >
261
+ <TabItem
262
+ id={`top-tab-${tab.id}`}
263
+ tab={tab}
264
+ active={isActive}
265
+ focused={isFocused}
266
+ indexLabel={indexLabel}
267
+ alwaysShowClose
268
+ />
269
+ </TopTabCell>
270
+ )
271
+ }
272
+ const isActive = entry.tabs.some((tab) => tab.id === activeTabId)
273
+ return (
274
+ <TopTabCell
275
+ key={entry.id}
276
+ entryId={entry.id}
277
+ active={isActive}
278
+ onActivate={handleEntryActivate}
279
+ backgroundColor={isActive ? t.backgroundElement : undefined}
280
+ >
281
+ <GroupTabItem
282
+ entry={entry}
283
+ active={isActive}
284
+ focused={isFocused}
285
+ indexLabel={indexLabel}
286
+ />
287
+ </TopTabCell>
288
+ )
289
+ })}
290
+ <box
291
+ flexDirection="row"
292
+ flexShrink={0}
293
+ paddingLeft={1}
294
+ paddingRight={1}
295
+ onMouseDown={handleNewTab}
296
+ >
297
+ <text fg={t.textMuted} selectable={false}>
298
+ +
299
+ </text>
300
+ </box>
301
+ </scrollbox>
302
+ </box>
303
+ )
304
+ }
@@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react'
3
3
  import type { WorktreeRecord } from '../../../../state/types'
4
4
 
5
5
  import { dispatchGlobal } from '../../../../state/dispatch-ref'
6
+ import { formatDivergence } from '../../../../state/session-worktrees'
6
7
  import { useTheme } from '../../../theme'
7
8
  import { uiTokens } from '../../../ui-tokens'
8
9
  import { ListItem } from '../../primitives/list-item'
@@ -23,14 +24,6 @@ const MOVE_HINTS: [key: string, label: string][] = [
23
24
  ['esc', 'cancel'],
24
25
  ]
25
26
 
26
- function formatDivergence(divergence: { ahead: number; behind: number } | undefined): string {
27
- if (divergence == null) return ''
28
- const parts: string[] = []
29
- if (divergence.ahead > 0) parts.push(`↑${divergence.ahead}`)
30
- if (divergence.behind > 0) parts.push(`↓${divergence.behind}`)
31
- return parts.join(' ')
32
- }
33
-
34
27
  export function WorktreeMoveModal({
35
28
  deleteSource,
36
29
  divergence,
package/src/ui/root.tsx CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  } from '../state/types'
14
14
  import type { ThemeId } from './themes'
15
15
 
16
+ import { useWorktreeBranchPolling } from '../git/worktree-branch-poller'
16
17
  import { useAppStore } from '../state/app-store'
17
18
  import { dispatchGlobal } from '../state/dispatch-ref'
18
19
  import { getGitPaneWidthFromRatio } from '../state/git-pane-sizing'
@@ -20,11 +21,11 @@ import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout
20
21
  import { GitView } from './components/git/git-view'
21
22
  import { buildGitPaneContextMenu } from './components/git/pane/git-pane-context-menu'
22
23
  import { GitPaneWidget } from './components/git/pane/git-pane-widget'
23
- import { SessionBar } from './components/layout/session-bar'
24
24
  import { Sidebar } from './components/layout/sidebar/sidebar'
25
25
  import { SplitLayout } from './components/layout/split-layout'
26
26
  import { StatusBar } from './components/layout/status-bar'
27
27
  import { TerminalPane } from './components/layout/terminal-pane'
28
+ import { TopTabBar } from './components/layout/top-tab-bar'
28
29
  import { AIUsageModal } from './components/modals/app/ai-usage-modal'
29
30
  import { HelpModal } from './components/modals/app/help-modal'
30
31
  import { UpdateAvailableModal } from './components/modals/app/update-available-modal'
@@ -315,7 +316,6 @@ export function RootView({
315
316
  const sessions = useAppStore((s) => s.sessions)
316
317
  const currentSessionId = useAppStore((s) => s.currentSessionId)
317
318
  const worktreeDivergence = useAppStore((s) => s.worktreeDivergence)
318
- const sessionBarPosition = useAppStore((s) => s.sessionBar.position)
319
319
  const gitPaneMode = useAppStore((s) => s.gitPane.mode)
320
320
  const gitPaneVisible = useAppStore((s) => s.gitPane.visible)
321
321
  const gitPanePosition = useAppStore((s) => s.gitPane.position)
@@ -326,6 +326,10 @@ export function RootView({
326
326
  const gitPaneInPaneOnLeft = gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left'
327
327
  const splitChrome = PANE_BORDER * 2
328
328
 
329
+ // Keep every worktree's `branch` in state synchronized with the on-disk
330
+ // HEAD so the sidebar (and divergence calc) never reads a stale value.
331
+ useWorktreeBranchPolling(true)
332
+
329
333
  const handleSidebarEdgeResize = useCallback(
330
334
  (event: MouseEvent): boolean => {
331
335
  onSidebarResizeStart?.({ initialWidth: sidebarWidth, screenStart: event.x })
@@ -389,9 +393,8 @@ export function RootView({
389
393
  if (inGitMode) {
390
394
  return (
391
395
  <box flexDirection="column" width="100%" height="100%" backgroundColor={editorBg}>
392
- {sessionBarPosition === 'top' && <SessionBar forceVisible />}
396
+ <TopTabBar forceVisible />
393
397
  <GitView themeId={themeId} />
394
- {sessionBarPosition === 'bottom' && <SessionBar forceVisible />}
395
398
  <StatusBar />
396
399
  <PendingChordOverlay />
397
400
  <ContextMenuOverlay />
@@ -422,72 +425,74 @@ export function RootView({
422
425
  onMouseDrag={handleRootMouseDrag}
423
426
  onMouseUp={handleRootMouseUp}
424
427
  >
425
- {sessionBarPosition === 'top' && <SessionBar />}
426
428
  <box flexDirection="row" gap={0} padding={0} flexGrow={1}>
427
429
  <Sidebar
428
- onTabActivate={onPaneActivate}
429
430
  onEmbeddedGitResizeStart={onEmbeddedGitResizeStart}
430
431
  onResizeDrag={onSeparatorDrag}
431
432
  onResizeDragEnd={onSeparatorDragEnd}
432
433
  />
433
- {gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left' ? (
434
- <GitPaneInPaneMode
435
- position="left"
436
- ratio={gitPaneRatio}
437
- onGitPaneResizeStart={onGitPaneResizeStart}
438
- />
439
- ) : null}
440
- {activeTree && activeTree.type === 'split' ? (
441
- <SplitLayout
442
- node={activeTree}
443
- tabs={tabs}
444
- activeTabId={activeTabId}
445
- focusMode={focusMode}
446
- contentOrigin={splitContentOrigin}
447
- mouseForwardingEnabled={mouseForwardingEnabled}
448
- localScrollbackEnabled={localScrollbackEnabled}
449
- onTerminalMouseEvent={onTerminalMouseEvent}
450
- onTerminalScrollEvent={onTerminalScrollEvent}
451
- onTerminalClick={onTerminalClick}
452
- onTerminalDrag={onTerminalDrag}
453
- onTerminalMouseUp={onTerminalMouseUp}
454
- onPaneActivate={onPaneActivate}
455
- onSplitResize={onSplitResize}
456
- onSeparatorDragStart={onSeparatorDragStart}
457
- onSeparatorDrag={onSeparatorDrag}
458
- onSeparatorDragEnd={onSeparatorDragEnd}
459
- onLeftEdgeMouseDown={handleTerminalLeftEdgeMouseDown}
460
- onMeasure={onMeasure}
461
- bounds={splitBounds}
462
- />
463
- ) : (
464
- <TerminalPane
465
- tab={activeTab}
466
- tabId={activeTabId ?? undefined}
467
- isActive
468
- focusMode={focusMode}
469
- contentOrigin={contentOrigin}
470
- mouseForwardingEnabled={mouseForwardingEnabled}
471
- localScrollbackEnabled={localScrollbackEnabled}
472
- onTerminalMouseEvent={onTerminalMouseEvent}
473
- onTerminalScrollEvent={onTerminalScrollEvent}
474
- onTerminalClick={onTerminalClick}
475
- onTerminalDrag={onTerminalDrag}
476
- onTerminalMouseUp={onTerminalMouseUp}
477
- onPaneActivate={onPaneActivate}
478
- onLeftEdgeMouseDown={handleTerminalLeftEdgeMouseDown}
479
- onMeasure={onMeasure}
480
- />
481
- )}
482
- {gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'right' ? (
483
- <GitPaneInPaneMode
484
- position="right"
485
- ratio={gitPaneRatio}
486
- onGitPaneResizeStart={onGitPaneResizeStart}
487
- />
488
- ) : null}
434
+ <box flexDirection="column" flexGrow={1}>
435
+ <TopTabBar />
436
+ <box flexDirection="row" gap={0} padding={0} flexGrow={1}>
437
+ {gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left' ? (
438
+ <GitPaneInPaneMode
439
+ position="left"
440
+ ratio={gitPaneRatio}
441
+ onGitPaneResizeStart={onGitPaneResizeStart}
442
+ />
443
+ ) : null}
444
+ {activeTree && activeTree.type === 'split' ? (
445
+ <SplitLayout
446
+ node={activeTree}
447
+ tabs={tabs}
448
+ activeTabId={activeTabId}
449
+ focusMode={focusMode}
450
+ contentOrigin={splitContentOrigin}
451
+ mouseForwardingEnabled={mouseForwardingEnabled}
452
+ localScrollbackEnabled={localScrollbackEnabled}
453
+ onTerminalMouseEvent={onTerminalMouseEvent}
454
+ onTerminalScrollEvent={onTerminalScrollEvent}
455
+ onTerminalClick={onTerminalClick}
456
+ onTerminalDrag={onTerminalDrag}
457
+ onTerminalMouseUp={onTerminalMouseUp}
458
+ onPaneActivate={onPaneActivate}
459
+ onSplitResize={onSplitResize}
460
+ onSeparatorDragStart={onSeparatorDragStart}
461
+ onSeparatorDrag={onSeparatorDrag}
462
+ onSeparatorDragEnd={onSeparatorDragEnd}
463
+ onLeftEdgeMouseDown={handleTerminalLeftEdgeMouseDown}
464
+ onMeasure={onMeasure}
465
+ bounds={splitBounds}
466
+ />
467
+ ) : (
468
+ <TerminalPane
469
+ tab={activeTab}
470
+ tabId={activeTabId ?? undefined}
471
+ isActive
472
+ focusMode={focusMode}
473
+ contentOrigin={contentOrigin}
474
+ mouseForwardingEnabled={mouseForwardingEnabled}
475
+ localScrollbackEnabled={localScrollbackEnabled}
476
+ onTerminalMouseEvent={onTerminalMouseEvent}
477
+ onTerminalScrollEvent={onTerminalScrollEvent}
478
+ onTerminalClick={onTerminalClick}
479
+ onTerminalDrag={onTerminalDrag}
480
+ onTerminalMouseUp={onTerminalMouseUp}
481
+ onPaneActivate={onPaneActivate}
482
+ onLeftEdgeMouseDown={handleTerminalLeftEdgeMouseDown}
483
+ onMeasure={onMeasure}
484
+ />
485
+ )}
486
+ {gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'right' ? (
487
+ <GitPaneInPaneMode
488
+ position="right"
489
+ ratio={gitPaneRatio}
490
+ onGitPaneResizeStart={onGitPaneResizeStart}
491
+ />
492
+ ) : null}
493
+ </box>
494
+ </box>
489
495
  </box>
490
- {sessionBarPosition === 'bottom' && <SessionBar />}
491
496
  <StatusBar />
492
497
  <PendingChordOverlay />
493
498
  <ContextMenuOverlay />