@brimveyn/aimux 1.12.2 → 1.13.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 (133) hide show
  1. package/README.md +3 -0
  2. package/package.json +4 -3
  3. package/src/app-runtime/auto-commit-driver.ts +8 -4
  4. package/src/app-runtime/auto-commit-ref.ts +1 -1
  5. package/src/app-runtime/backend-attach-runtime.ts +18 -2
  6. package/src/app-runtime/backend-runtime-events.ts +1 -1
  7. package/src/app-runtime/pty-write.ts +52 -0
  8. package/src/app-runtime/selection-scroll.ts +2 -2
  9. package/src/app-runtime/session-actions.ts +16 -4
  10. package/src/app-runtime/side-effects.ts +598 -102
  11. package/src/app-runtime/snippet-actions.ts +25 -11
  12. package/src/app-runtime/use-auto-commit-driver.ts +7 -4
  13. package/src/app-runtime/use-backend-runtime.ts +3 -3
  14. package/src/app-runtime/use-directory-search.ts +7 -5
  15. package/src/app-runtime/use-mouse-handlers.ts +11 -6
  16. package/src/app-runtime/use-pane-size-report.ts +1 -1
  17. package/src/app-runtime/use-renderer-bindings.ts +143 -8
  18. package/src/app.tsx +25 -9
  19. package/src/auto-commit/headless-commands.ts +4 -6
  20. package/src/daemon/daemon.ts +3 -3
  21. package/src/daemon/runtime-paths.ts +1 -1
  22. package/src/daemon/session-manager.ts +1 -1
  23. package/src/daemon/session-registry.ts +10 -3
  24. package/src/diff-parser/parse-patch-files.ts +18 -18
  25. package/src/git/command-queue.ts +1 -1
  26. package/src/git/divergence.ts +46 -0
  27. package/src/git/git-diff.ts +12 -5
  28. package/src/git/git-poller.ts +33 -11
  29. package/src/git/git-status.ts +14 -3
  30. package/src/git/move-worktree.ts +96 -0
  31. package/src/git/use-repo-discovery.ts +2 -1
  32. package/src/git/worktree-divergence-poller.ts +59 -0
  33. package/src/git/worktree.ts +99 -0
  34. package/src/index.tsx +1 -1
  35. package/src/input/keymap/describe-bindings.ts +27 -9
  36. package/src/input/keymap/key-chord.ts +4 -4
  37. package/src/input/keymap/key-format.ts +2 -2
  38. package/src/input/keymap/sequence-resolver.ts +1 -1
  39. package/src/input/keymap/trie.ts +1 -1
  40. package/src/input/modes/bridge.ts +7 -0
  41. package/src/input/modes/transitions.ts +2 -1
  42. package/src/input/modes/types.ts +13 -1
  43. package/src/input/raw-input-handler.ts +34 -1
  44. package/src/input/terminal-text-extraction.ts +9 -5
  45. package/src/integrations/claude-syntax-overlay.ts +8 -8
  46. package/src/integrations/claude-theme-sync.ts +12 -12
  47. package/src/ipc/protocol.ts +3 -3
  48. package/src/platform/clipboard.ts +18 -2
  49. package/src/platform/worktree-deps.ts +22 -0
  50. package/src/platform/worktree-paths.ts +67 -0
  51. package/src/profile-paths.ts +3 -3
  52. package/src/pty/assistant-status-detection-loop.ts +2 -2
  53. package/src/pty/assistant-status-detector.ts +15 -6
  54. package/src/pty/command-registry.ts +8 -1
  55. package/src/pty/pty-manager.ts +1 -1
  56. package/src/services/ai-usage/adapters/claude.ts +5 -5
  57. package/src/services/ai-usage/adapters/codex.ts +8 -8
  58. package/src/services/ai-usage/cache.ts +2 -2
  59. package/src/services/ai-usage/pace.ts +3 -6
  60. package/src/services/ai-usage/provider.ts +1 -1
  61. package/src/session-backend/bootstrap.ts +2 -2
  62. package/src/session-backend/local-session-backend.ts +11 -11
  63. package/src/session-backend/remote-session-backend.ts +3 -3
  64. package/src/session-backend/types.ts +2 -2
  65. package/src/snippets/expand-variables.ts +112 -0
  66. package/src/snippets/run-shell-var.ts +93 -0
  67. package/src/snippets/trigger-detector.ts +105 -0
  68. package/src/state/ai-usage-store.ts +1 -1
  69. package/src/state/git-tree.ts +8 -4
  70. package/src/state/layout-tree.ts +8 -5
  71. package/src/state/reducers/git-mode-state.ts +44 -14
  72. package/src/state/reducers/git-panel-state.ts +38 -2
  73. package/src/state/reducers/modal-state.ts +385 -24
  74. package/src/state/reducers/session-state.ts +60 -1
  75. package/src/state/reducers/tab-state.ts +151 -85
  76. package/src/state/selectors.ts +6 -4
  77. package/src/state/session-catalog.ts +20 -5
  78. package/src/state/session-persistence.ts +8 -4
  79. package/src/state/session-worktrees.ts +227 -0
  80. package/src/state/snippet-catalog.ts +65 -4
  81. package/src/state/store.ts +1 -0
  82. package/src/state/toast-store.ts +95 -0
  83. package/src/state/types.ts +108 -16
  84. package/src/state/validation.ts +47 -3
  85. package/src/terminal-manager/manager-client.ts +15 -13
  86. package/src/ui/components/git/diff-renderer/fold-strip.tsx +2 -1
  87. package/src/ui/components/git/diff-renderer/pierre-diff.tsx +1 -1
  88. package/src/ui/components/git/diff-renderer/prepare-diff.ts +2 -2
  89. package/src/ui/components/git/diff-renderer/split-view.tsx +6 -4
  90. package/src/ui/components/git/diff-renderer/stacked-view.tsx +6 -4
  91. package/src/ui/components/git/diff-renderer/use-diff-prefetch.ts +19 -6
  92. package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +2 -2
  93. package/src/ui/components/git/git-panel.tsx +17 -10
  94. package/src/ui/components/git/git-view.tsx +79 -19
  95. package/src/ui/components/git/image-diff/image-diff-view.tsx +2 -2
  96. package/src/ui/components/git/image-diff/terminal-image-pane.tsx +5 -4
  97. package/src/ui/components/git/pane/git-pane-context-menu.ts +2 -2
  98. package/src/ui/components/git/pane/git-pane-widget.tsx +6 -4
  99. package/src/ui/components/layout/session-bar.tsx +3 -3
  100. package/src/ui/components/layout/sidebar/sidebar.tsx +162 -36
  101. package/src/ui/components/layout/sidebar/tab-item.tsx +17 -1
  102. package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +2 -2
  103. package/src/ui/components/layout/sidebar/use-sidebar-branch.ts +1 -1
  104. package/src/ui/components/layout/split-layout.tsx +1 -1
  105. package/src/ui/components/layout/terminal-pane.tsx +43 -40
  106. package/src/ui/components/modals/app/ai-usage-modal.tsx +8 -5
  107. package/src/ui/components/modals/app/help-modal.tsx +4 -2
  108. package/src/ui/components/modals/git/git-commit-modal.tsx +2 -2
  109. package/src/ui/components/modals/sessions/create-session-modal.tsx +5 -1
  110. package/src/ui/components/modals/sessions/session-picker-modal.tsx +5 -4
  111. package/src/ui/components/modals/shared/form.tsx +4 -5
  112. package/src/ui/components/modals/shared/modal-shell.tsx +3 -3
  113. package/src/ui/components/modals/shared/picker.tsx +28 -5
  114. package/src/ui/components/modals/snippets/snippet-editor-modal.tsx +10 -6
  115. package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +25 -6
  116. package/src/ui/components/modals/tabs/new-tab-modal.tsx +173 -5
  117. package/src/ui/components/modals/worktree/worktree-move-modal.tsx +94 -0
  118. package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +3 -3
  119. package/src/ui/components/overlays/toast/toast-item.tsx +88 -0
  120. package/src/ui/components/overlays/toast/toast-position.ts +45 -0
  121. package/src/ui/components/overlays/toast/toast-stack.tsx +30 -0
  122. package/src/ui/components/overlays/toast/toast-viewport.tsx +24 -0
  123. package/src/ui/components/overlays/toast/use-toast-animation.ts +64 -0
  124. package/src/ui/components/primitives/input-field.tsx +1 -1
  125. package/src/ui/components/primitives/list-item.tsx +3 -3
  126. package/src/ui/filter-themes.ts +1 -1
  127. package/src/ui/root.tsx +65 -14
  128. package/src/ui/session-ordering.ts +2 -2
  129. package/src/ui/status-bar-model.ts +14 -7
  130. package/src/ui/terminal-graphics/capabilities.ts +1 -1
  131. package/src/ui/terminal-graphics/format-fallback.ts +7 -9
  132. package/src/ui/terminal-graphics/kitty.ts +2 -7
  133. package/src/update/version-check.ts +1 -1
@@ -29,13 +29,16 @@ interface GitPanelProps {
29
29
  pathConfig?: GitPanePathConfig
30
30
  diffCountConfig?: GitPaneDiffCountConfig
31
31
  headOffset?: number
32
+ // When set, the single changed-files section is a worktree fork-point review
33
+ // ("vs <base>") rather than a HEAD~N history walk.
34
+ baseLabel?: string
32
35
  compact?: boolean
33
36
  }
34
37
 
35
- function sectionTitle(section: GitFileSection, headOffset: number): string {
38
+ function sectionTitle(section: GitFileSection, headOffset: number, baseLabel?: string): string {
36
39
  switch (section) {
37
40
  case 'historical':
38
- return `HEAD~${headOffset}`
41
+ return baseLabel != null && baseLabel !== '' ? baseLabel : `HEAD~${headOffset}`
39
42
  case 'staged':
40
43
  return 'Staged Changes'
41
44
  case 'unstaged':
@@ -105,8 +108,8 @@ function renderFileLabel(
105
108
  const displayPath = transform ? transform(file.path) : file.path
106
109
  const { basename, prefix } = splitPath(displayPath)
107
110
  const dir = stripTrailingSlash(prefix)
108
- const showDir = fileListMode === 'flat' && pathConfig.enabled && dir
109
- if (!file.renamedFrom) {
111
+ const showDir = fileListMode === 'flat' && pathConfig.enabled && dir !== ''
112
+ if (!(file.renamedFrom != null && file.renamedFrom !== '')) {
110
113
  return (
111
114
  <text selectable={false} wrapMode="none">
112
115
  <span fg={t.text}>{basename}</span>
@@ -211,7 +214,9 @@ function renderFileRow(
211
214
  // Repo disambiguation prefix: only in flat mode, only when the file came
212
215
  // from a sub-repo (root repo files get an empty prefix).
213
216
  const repoTag =
214
- fileListMode === 'flat' && file.repoPath ? (repoPrefixes[file.repoPath] ?? '') : ''
217
+ fileListMode === 'flat' && file.repoPath != null && file.repoPath !== ''
218
+ ? (repoPrefixes[file.repoPath] ?? '')
219
+ : ''
215
220
  return (
216
221
  <box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
217
222
  <box width={2} flexShrink={0} justifyContent="center">
@@ -238,7 +243,7 @@ function renderTreeSection(
238
243
  section: GitFileSection,
239
244
  title: string,
240
245
  files: GitFileEntry[],
241
- rows: Array<GitTreeFolderRow | GitTreeFileRow>,
246
+ rows: (GitTreeFolderRow | GitTreeFileRow)[],
242
247
  addedW: number,
243
248
  removedW: number,
244
249
  fileListMode: GitFileListMode,
@@ -337,6 +342,7 @@ const DEFAULT_PATH_CONFIG: GitPanePathConfig = { enabled: true }
337
342
  const DEFAULT_DIFF_COUNT_CONFIG: GitPaneDiffCountConfig = { enabled: true }
338
343
 
339
344
  export const GitPanel = memo(function GitPanel({
345
+ baseLabel,
340
346
  collapsedFolders = {},
341
347
  compact = false,
342
348
  diffCountConfig = DEFAULT_DIFF_COUNT_CONFIG,
@@ -350,7 +356,8 @@ export const GitPanel = memo(function GitPanel({
350
356
  const t = useTheme()
351
357
  useTransparent()
352
358
  const repoPrefixes = useAppStore((s) => s.multiRepo.prefixes)
353
- const sectionOrder = headOffset > 0 ? HISTORICAL_SECTION_ORDER : BASE_SECTION_ORDER
359
+ const isSingleSection = headOffset > 0 || (baseLabel != null && baseLabel !== '')
360
+ const sectionOrder = isSingleSection ? HISTORICAL_SECTION_ORDER : BASE_SECTION_ORDER
354
361
  const scrollRef = useRef<ScrollBoxRenderable | null>(null)
355
362
  const tree = useMemo(
356
363
  () => buildGitTreeRows(gitPanel.files, collapsedFolders, fileListMode, compact),
@@ -361,7 +368,7 @@ export const GitPanel = memo(function GitPanel({
361
368
  [gitPanel.files]
362
369
  )
363
370
 
364
- const statusNode = renderStatus(gitPanel, !!projectPath)
371
+ const statusNode = renderStatus(gitPanel, !!(projectPath != null && projectPath !== ''))
365
372
 
366
373
  const hasRemoteTracking = gitPanel.ahead > 0 || gitPanel.behind > 0
367
374
  const toggleSection =
@@ -372,7 +379,7 @@ export const GitPanel = memo(function GitPanel({
372
379
 
373
380
  useEffect(() => {
374
381
  const scrollbox = scrollRef.current
375
- if (!scrollbox || !selectedEntryKey) return
382
+ if (!scrollbox || !(selectedEntryKey != null && selectedEntryKey !== '')) return
376
383
  const selectedIndex = tree.visibleRows.findIndex((row) => row.key === selectedEntryKey)
377
384
  if (selectedIndex < 0) return
378
385
  const viewportHeight = Math.max(1, scrollbox.viewport.height)
@@ -403,7 +410,7 @@ export const GitPanel = memo(function GitPanel({
403
410
  .some((k) => (tree.sections.find((e) => e.section === k)?.files.length ?? 0) > 0)
404
411
  return renderTreeSection(
405
412
  key,
406
- sectionTitle(key, headOffset),
413
+ sectionTitle(key, headOffset, baseLabel),
407
414
  section?.files ?? [],
408
415
  section?.rows ?? [],
409
416
  addedW,
@@ -1,16 +1,18 @@
1
1
  import { useTerminalDimensions } from '@opentui/react'
2
- import { memo, useEffect, useRef } from 'react'
2
+ import { memo, useEffect, useRef, useState } from 'react'
3
3
 
4
4
  import type { DiffData, GitDiffView } from '../../../state/types'
5
5
  import type { ThemeId } from '../../themes'
6
6
 
7
7
  import { diffHash } from '../../../git/diff-hash'
8
+ import { getMergeBase } from '../../../git/divergence'
8
9
  import { fetchDiff } from '../../../git/git-diff'
9
10
  import { useGitPanelPolling } from '../../../git/git-poller'
10
11
  import { useRepoDiscovery } from '../../../git/use-repo-discovery'
11
12
  import { useAppStore } from '../../../state/app-store'
12
13
  import { dispatchGlobal } from '../../../state/dispatch-ref'
13
14
  import { getSelectedGitFile, gitFileKey } from '../../../state/git-tree'
15
+ import { getActiveWorktree, getSessionProjectPath } from '../../../state/session-worktrees'
14
16
  import { setGitDiffScroller } from '../../git-view-controls'
15
17
  import { useTheme } from '../../theme'
16
18
  import { PierreDiff, type PierreDiffHandle } from './diff-renderer'
@@ -64,7 +66,7 @@ const DiffStage = memo(function DiffStage({
64
66
  </box>
65
67
  )
66
68
  }
67
- if (diff.errorMessage) {
69
+ if (diff.errorMessage != null && diff.errorMessage !== '') {
68
70
  return (
69
71
  <box flexGrow={1} padding={1}>
70
72
  <text fg={t.error}>{diff.errorMessage}</text>
@@ -77,7 +79,7 @@ const DiffStage = memo(function DiffStage({
77
79
  }
78
80
 
79
81
  const placeholder = placeholderText(diff)
80
- if (placeholder) {
82
+ if (placeholder != null && placeholder !== '') {
81
83
  return (
82
84
  <box flexGrow={1} padding={1}>
83
85
  <text fg={t.textMuted}>{placeholder}</text>
@@ -87,7 +89,7 @@ const DiffStage = memo(function DiffStage({
87
89
 
88
90
  return (
89
91
  <box flexDirection="column" flexGrow={1} overflow="hidden">
90
- {diff.oldPath ? (
92
+ {diff.oldPath != null && diff.oldPath !== '' ? (
91
93
  <box paddingLeft={1} paddingRight={1}>
92
94
  <text fg={t.textMuted}>
93
95
  renamed: {diff.oldPath} → {diff.path}
@@ -123,13 +125,41 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
123
125
  const focusMode = useAppStore((s) => s.focusMode)
124
126
  const diffRef = useRef<PierreDiffHandle | null>(null)
125
127
 
126
- const currentSession = currentSessionId
127
- ? sessions.find((s) => s.id === currentSessionId)
128
- : undefined
129
- const projectPath = currentSession?.projectPath
128
+ const currentSession =
129
+ currentSessionId != null && currentSessionId !== ''
130
+ ? sessions.find((s) => s.id === currentSessionId)
131
+ : undefined
132
+ const projectPath = getSessionProjectPath(currentSession)
133
+
134
+ // Review-vs-base: when toggled on for a worktree that records a baseRef,
135
+ // resolve the fork point and diff the working tree against it.
136
+ const activeWorktree = getActiveWorktree(currentSession)
137
+ const reviewBaseRef =
138
+ gitMode.reviewBase && activeWorktree?.baseRef != null && activeWorktree.baseRef !== ''
139
+ ? activeWorktree.baseRef
140
+ : null
141
+ const [compareRef, setCompareRef] = useState<string | undefined>(undefined)
142
+ useEffect(() => {
143
+ if (reviewBaseRef == null || projectPath == null || projectPath === '') {
144
+ setCompareRef(undefined)
145
+ return
146
+ }
147
+ let cancelled = false
148
+ void getMergeBase(projectPath, reviewBaseRef).then((mergeBase) => {
149
+ if (!cancelled) setCompareRef(mergeBase)
150
+ })
151
+ return () => {
152
+ cancelled = true
153
+ }
154
+ }, [reviewBaseRef, projectPath])
130
155
 
131
156
  useRepoDiscovery(projectPath)
132
- useGitPanelPolling({ enabled: focusMode === 'git', headOffset: gitMode.headOffset, projectPath })
157
+ useGitPanelPolling({
158
+ compareRef,
159
+ enabled: focusMode === 'git',
160
+ headOffset: gitMode.headOffset,
161
+ projectPath,
162
+ })
133
163
 
134
164
  const fileBarWidth = Math.max(20, Math.floor(dimensions.width * gitPane.diffModeRatio))
135
165
 
@@ -140,8 +170,12 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
140
170
  selectedEntryKey: gitMode.selectedEntryKey,
141
171
  })
142
172
  const selectedDiffKey = selectedFile ? gitFileKey(selectedFile) : null
143
- const diff = selectedDiffKey ? gitMode.diffs[selectedDiffKey] : undefined
144
- const loading = selectedDiffKey ? !!gitMode.loading[selectedDiffKey] : false
173
+ const diff =
174
+ selectedDiffKey != null && selectedDiffKey !== '' ? gitMode.diffs[selectedDiffKey] : undefined
175
+ const loading =
176
+ selectedDiffKey != null && selectedDiffKey !== ''
177
+ ? !!(gitMode.loading[selectedDiffKey] === true)
178
+ : false
145
179
 
146
180
  useEffect(() => {
147
181
  setGitDiffScroller((delta: number) => {
@@ -160,6 +194,7 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
160
194
  }, [])
161
195
 
162
196
  useDiffPrefetch(gitMode.selectedEntryKey, gitPane.prefetchRadius, {
197
+ compareRef,
163
198
  enabled: focusMode === 'git',
164
199
  headOffset: gitMode.headOffset,
165
200
  projectPath,
@@ -168,11 +203,16 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
168
203
 
169
204
  useEffect(() => {
170
205
  if (focusMode !== 'git') return
171
- if (!selectedFile || !projectPath) return
172
- if (!selectedDiffKey) return
206
+ if (!selectedFile || !(projectPath != null && projectPath !== '')) return
207
+ if (!(selectedDiffKey != null && selectedDiffKey !== '')) return
173
208
  if (diff || loading) return
174
209
  dispatchGlobal({ key: selectedDiffKey, loading: true, type: 'git-mode-set-loading' })
175
- void fetchDiff(selectedFile.repoPath ?? projectPath, selectedFile, gitMode.headOffset)
210
+ void fetchDiff(
211
+ selectedFile.repoPath ?? projectPath,
212
+ selectedFile,
213
+ gitMode.headOffset,
214
+ compareRef
215
+ )
176
216
  .then((d) =>
177
217
  dispatchGlobal({
178
218
  diff: d,
@@ -184,7 +224,16 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
184
224
  .catch(() =>
185
225
  dispatchGlobal({ key: selectedDiffKey, loading: false, type: 'git-mode-set-loading' })
186
226
  )
187
- }, [focusMode, projectPath, selectedFile, selectedDiffKey, diff, loading, gitMode.headOffset])
227
+ }, [
228
+ focusMode,
229
+ projectPath,
230
+ selectedFile,
231
+ selectedDiffKey,
232
+ diff,
233
+ loading,
234
+ gitMode.headOffset,
235
+ compareRef,
236
+ ])
188
237
 
189
238
  const pendingPath = gitMode.pendingDeletePath
190
239
  const pendingIsUntracked =
@@ -199,13 +248,13 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
199
248
  }
200
249
  const actionMessage = gitMode.actionMessage
201
250
  let footerNode: React.ReactNode = null
202
- if (pendingHint) {
251
+ if (pendingHint != null && pendingHint !== '') {
203
252
  footerNode = (
204
253
  <text fg={t.warning}>
205
254
  <strong>{pendingHint}</strong>
206
255
  </text>
207
256
  )
208
- } else if (actionMessage) {
257
+ } else if (actionMessage != null && actionMessage !== '') {
209
258
  footerNode = actionMessage.split('\n').map((line, idx) => (
210
259
  <text key={idx} fg={t.primary}>
211
260
  {line}
@@ -213,6 +262,8 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
213
262
  ))
214
263
  }
215
264
 
265
+ const baseLabel = compareRef != null && reviewBaseRef != null ? `vs ${reviewBaseRef}` : undefined
266
+
216
267
  return (
217
268
  <box flexDirection="column" flexGrow={1} overflow="hidden">
218
269
  <box flexDirection="row" flexGrow={1}>
@@ -228,7 +279,7 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
228
279
  <text fg={t.text}>
229
280
  <strong>aimux · git</strong>
230
281
  </text>
231
- {gitPanel.branch ? (
282
+ {gitPanel.branch != null && gitPanel.branch !== '' ? (
232
283
  <box flexDirection="row">
233
284
  <text fg={t.text}>{'\u{e702}'} </text>
234
285
  <text fg={t.text}>{gitPanel.branch}</text>
@@ -242,9 +293,18 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
242
293
  <text fg={t.textMuted}>[ newer · ] older</text>
243
294
  </box>
244
295
  ) : null}
296
+ {baseLabel != null ? (
297
+ <box flexDirection="row" gap={1}>
298
+ <text fg={t.primary}>
299
+ <strong>{baseLabel}</strong>
300
+ </text>
301
+ <text fg={t.textMuted}>b: back</text>
302
+ </box>
303
+ ) : null}
245
304
  <text fg={t.textMuted}>{'·'.repeat(Math.max(0, fileBarWidth - 2))}</text>
246
305
  </box>
247
306
  <GitPanel
307
+ baseLabel={baseLabel}
248
308
  collapsedFolders={gitMode.collapsedFolders}
249
309
  compact={gitPane.treeCompaction}
250
310
  fileListMode={gitPane.fileListMode}
@@ -292,7 +352,7 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
292
352
  />
293
353
  </box>
294
354
  </box>
295
- {footerNode ? (
355
+ {footerNode != null ? (
296
356
  <box paddingLeft={1} paddingRight={1} backgroundColor={sidebarBg} flexDirection="column">
297
357
  {footerNode}
298
358
  </box>
@@ -76,14 +76,14 @@ export const ImageDiffView = memo(function ImageDiffView({ diff }: ImageDiffView
76
76
  const showBoth = before && after
77
77
  return (
78
78
  <box flexDirection="column" flexGrow={1} overflow="hidden" backgroundColor={t.background}>
79
- {diff.oldPath ? (
79
+ {diff.oldPath != null && diff.oldPath !== '' ? (
80
80
  <box paddingLeft={1} paddingRight={1}>
81
81
  <text fg={t.textMuted}>
82
82
  renamed: {diff.oldPath} → {diff.path}
83
83
  </text>
84
84
  </box>
85
85
  ) : null}
86
- {banner ? (
86
+ {banner != null && banner !== '' ? (
87
87
  <box paddingLeft={1} paddingRight={1}>
88
88
  <text fg={protocol === 'kitty' ? t.textMuted : t.warning}>{banner}</text>
89
89
  </box>
@@ -1,4 +1,5 @@
1
- import { type BoxRenderable } from '@opentui/core'
1
+ import type { BoxRenderable } from '@opentui/core'
2
+
2
3
  import { memo, useEffect, useRef, useState } from 'react'
3
4
 
4
5
  import { convertToPng, isPng } from '../../../terminal-graphics/format-fallback'
@@ -47,7 +48,7 @@ export const TerminalImagePane = memo(function TerminalImagePane({
47
48
  let cancelled = false
48
49
  stateRef.current = { imageId: null, lastKey: null, uploaded: false }
49
50
  setError(null)
50
- ;(async () => {
51
+ void (async () => {
51
52
  let pngBytes: Uint8Array | null = null
52
53
  if (mime === 'image/png' || isPng(bytes)) {
53
54
  pngBytes = bytes
@@ -60,7 +61,7 @@ export const TerminalImagePane = memo(function TerminalImagePane({
60
61
  }
61
62
  pngBytes = result.png
62
63
  }
63
- if (cancelled || !pngBytes) return
64
+ if (cancelled) return
64
65
  const id = nextImageId()
65
66
  writeRaw(uploadPngEscape(pngBytes, id))
66
67
  stateRef.current.imageId = id
@@ -76,7 +77,7 @@ export const TerminalImagePane = memo(function TerminalImagePane({
76
77
  }
77
78
  }, [bytes, mime])
78
79
 
79
- if (error) {
80
+ if (error != null && error !== '') {
80
81
  return (
81
82
  <box flexGrow={1} alignItems="center" justifyContent="center" padding={1}>
82
83
  <text fg={t.warning}>({error})</text>
@@ -1,11 +1,11 @@
1
1
  import type { GitPaneMode, GitPanePosition, GitPaneState } from '../../../../state/types'
2
2
  import type { ContextMenuItem } from '../../../context-menu/controller'
3
3
 
4
- const GIT_PANE_MENU_OPTIONS: Array<{
4
+ const GIT_PANE_MENU_OPTIONS: {
5
5
  label: string
6
6
  mode: GitPaneMode
7
7
  position: GitPanePosition
8
- }> = [
8
+ }[] = [
9
9
  { label: 'Move to top', mode: 'embedded', position: 'top' },
10
10
  { label: 'Move to bottom', mode: 'embedded', position: 'bottom' },
11
11
  { label: 'Move to left', mode: 'pane', position: 'left' },
@@ -5,6 +5,7 @@ import type { GitPanelState } from '../../../../state/types'
5
5
  import { useGitPanelPolling } from '../../../../git/git-poller'
6
6
  import { useRepoDiscovery } from '../../../../git/use-repo-discovery'
7
7
  import { useAppStore } from '../../../../state/app-store'
8
+ import { getSessionProjectPath } from '../../../../state/session-worktrees'
8
9
  import { GitPanel } from '../git-panel'
9
10
 
10
11
  interface GitPaneWidgetProps {
@@ -20,10 +21,11 @@ export const GitPaneWidget = memo(function GitPaneWidget({ pollingEnabled }: Git
20
21
  const diffCountConfig = useAppStore((s) => s.gitPane.diffCount)
21
22
  const currentSessionId = useAppStore((s) => s.currentSessionId)
22
23
  const sessions = useAppStore((s) => s.sessions)
23
- const currentSession = currentSessionId
24
- ? sessions.find((s) => s.id === currentSessionId)
25
- : undefined
26
- const projectPath = currentSession?.projectPath
24
+ const currentSession =
25
+ currentSessionId != null && currentSessionId !== ''
26
+ ? sessions.find((s) => s.id === currentSessionId)
27
+ : undefined
28
+ const projectPath = getSessionProjectPath(currentSession)
27
29
 
28
30
  useRepoDiscovery(projectPath)
29
31
  useGitPanelPolling({ enabled: pollingEnabled, headOffset: 0, projectPath })
@@ -61,7 +61,7 @@ export function SessionBar({ forceVisible = false }: SessionBarProps) {
61
61
  }
62
62
 
63
63
  const handleMouseDrag = (event: OtuiMouseEvent) => {
64
- if (!draggingId) return
64
+ if (!(draggingId != null && draggingId !== '')) return
65
65
  const hit = findChipAtX(event.x)
66
66
  if (hit === null) {
67
67
  lastSwapWithRef.current = null
@@ -83,7 +83,7 @@ export function SessionBar({ forceVisible = false }: SessionBarProps) {
83
83
  setDragOrder(null)
84
84
  lastSwapWithRef.current = null
85
85
 
86
- if (!source || !finalOrder) return
86
+ if (source == null || source === '' || !finalOrder) return
87
87
 
88
88
  const changed = !arraysEqual(finalOrder, baselineOrder)
89
89
  if (changed) {
@@ -184,7 +184,7 @@ interface SessionChipProps {
184
184
  onMouseDrag: (event: OtuiMouseEvent) => void
185
185
  onMouseUp: (event: OtuiMouseEvent) => void
186
186
  onMouseDragEnd: (event: OtuiMouseEvent) => void
187
- rightClickMenu: Array<[string, () => void]>
187
+ rightClickMenu: [string, () => void][]
188
188
  }
189
189
 
190
190
  function SessionChip({