@brimveyn/aimux 1.16.0 → 1.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brimveyn/aimux",
3
- "version": "1.16.0",
3
+ "version": "1.16.2",
4
4
  "description": "A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode side-by-side with tabbed navigation, split panes, and persistent sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -1610,7 +1610,6 @@ async function runDeleteWorktree(
1610
1610
  if (tabsInWorktree.length > 0 && !force && !closeTabs) {
1611
1611
  throw new ActiveWorktreeTabsError(tabsInWorktree.length)
1612
1612
  }
1613
- disposeWorktreeTabs(ctx, worktreeId)
1614
1613
 
1615
1614
  const repoPath = resolveWorktreeGitDir(session, worktree)
1616
1615
  const isAimuxTemp = worktree.source === 'aimux-temp' && worktree.createdByAimux
@@ -1624,15 +1623,14 @@ async function runDeleteWorktree(
1624
1623
  }
1625
1624
  }
1626
1625
 
1626
+ // Run git ops FIRST. If any throws (dirty worktree, etc.) the catch in the
1627
+ // delete-worktree side effect handler re-prompts force or toasts the error —
1628
+ // tabs stay open and the row stays in the sidebar, so the UI matches reality
1629
+ // instead of leaving tabs closed against a worktree that still exists.
1627
1630
  if (isAimuxTemp && isInsideAimuxWorktreeRoot(worktree.path) && !existsSync(worktree.path)) {
1628
1631
  // The dir vanished but git may still pin the branch to a stale worktree entry.
1629
1632
  await pruneGitWorktrees(repoPath)
1630
- await cleanupAimuxBranch()
1631
- removeWorktreeRecordFromSession(ctx, sessionId, session, worktreeId)
1632
- return
1633
- }
1634
-
1635
- if (isAimuxTemp && isInsideAimuxWorktreeRoot(worktree.path)) {
1633
+ } else if (isAimuxTemp && isInsideAimuxWorktreeRoot(worktree.path)) {
1636
1634
  await assertSafeAimuxWorktreePath(worktree.path)
1637
1635
  await removeGitWorktree({ force, repoPath, targetPath: worktree.path })
1638
1636
  } else if (worktree.source === 'aimux-temp' || worktree.createdByAimux) {
@@ -1640,7 +1638,15 @@ async function runDeleteWorktree(
1640
1638
  }
1641
1639
 
1642
1640
  await cleanupAimuxBranch()
1643
- removeWorktreeRecordFromSession(ctx, sessionId, session, worktreeId)
1641
+
1642
+ // Only after git success: close tabs + retire the record. Re-read state so
1643
+ // we don't operate on the snapshot captured before the awaits above.
1644
+ disposeWorktreeTabs({ ...ctx, state: ctx.getState() }, worktreeId)
1645
+ const latest = ctx.getState()
1646
+ const latestSession = latest.sessions.find((entry) => entry.id === sessionId)
1647
+ if (latestSession) {
1648
+ removeWorktreeRecordFromSession({ ...ctx, state: latest }, sessionId, latestSession, worktreeId)
1649
+ }
1644
1650
  }
1645
1651
 
1646
1652
  // A worktree's stored repoRoot can point at a sibling worktree that was since
@@ -19,6 +19,7 @@ export const HELP_MODE_LABELS: { modeId: ModeId; label: string }[] = [
19
19
  { label: 'Workspace name', modeId: 'modal.session-name' },
20
20
  { label: 'Create workspace', modeId: 'modal.create-session' },
21
21
  { label: 'Rename tab', modeId: 'modal.rename-tab' },
22
+ { label: 'Rename worktree', modeId: 'modal.rename-worktree' },
22
23
  { label: 'Snippet picker', modeId: 'modal.snippet-picker.filtering' },
23
24
  { label: 'Snippet picker — filter', modeId: 'modal.snippet-picker.filtering' },
24
25
  { label: 'Snippet editor', modeId: 'modal.snippet-editor' },
@@ -15,6 +15,7 @@ const COMMAND_EDIT_MODE_IDS: Partial<Record<SupportedModalType, ModeId>> = {
15
15
  'help': 'modal.help.filtering',
16
16
  'new-tab': 'modal.new-tab.command-edit',
17
17
  'rename-tab': 'modal.rename-tab',
18
+ 'rename-worktree': 'modal.rename-worktree',
18
19
  'session-name': 'modal.session-name',
19
20
  'session-picker': 'modal.session-picker.filtering',
20
21
  'snippet-editor': 'modal.snippet-editor',
@@ -16,6 +16,7 @@ const TRANSITIONS: Record<ModeId, readonly ModeId[]> = {
16
16
  'modal.new-tab.editing-command': ['navigation', 'modal.new-tab.command-edit'],
17
17
  'modal.new-tab.worktree-delete-confirm': ['navigation', 'modal.new-tab.command-edit'],
18
18
  'modal.rename-tab': ['navigation'],
19
+ 'modal.rename-worktree': ['navigation'],
19
20
  'modal.session-name': ['modal.session-picker.filtering', 'navigation'],
20
21
  'modal.session-picker.filtering': ['navigation', 'modal.session-name', 'modal.create-session'],
21
22
  'modal.snippet-editor': ['navigation', 'modal.snippet-picker.filtering'],
@@ -35,6 +36,7 @@ const TRANSITIONS: Record<ModeId, readonly ModeId[]> = {
35
36
  'modal.snippet-picker.filtering',
36
37
  'modal.theme-picker.filtering',
37
38
  'modal.rename-tab',
39
+ 'modal.rename-worktree',
38
40
  'modal.update-available',
39
41
  'modal.ai-usage',
40
42
  'modal.worktree-delete-confirm',
@@ -15,6 +15,7 @@ export type ModeId =
15
15
  | 'modal.session-name'
16
16
  | 'modal.create-session'
17
17
  | 'modal.rename-tab'
18
+ | 'modal.rename-worktree'
18
19
  | 'modal.snippet-picker.filtering'
19
20
  | 'modal.snippet-editor'
20
21
  | 'modal.theme-picker.filtering'
@@ -1217,6 +1217,20 @@ export function reduceModalState(state: AppState, action: AppAction): AppState |
1217
1217
  },
1218
1218
  }
1219
1219
  }
1220
+ case 'open-rename-worktree-modal': {
1221
+ return {
1222
+ ...state,
1223
+ focusMode: 'command-edit',
1224
+ modal: {
1225
+ cursorPos: action.initialName.length,
1226
+ editBuffer: action.initialName,
1227
+ selectedIndex: 0,
1228
+ sessionTargetId: action.worktreeId,
1229
+ type: 'rename-worktree',
1230
+ worktreeSessionId: action.sessionId,
1231
+ },
1232
+ }
1233
+ }
1220
1234
  default:
1221
1235
  return null
1222
1236
  }
@@ -38,6 +38,7 @@ export type ModalType =
38
38
  | 'session-name'
39
39
  | 'create-session'
40
40
  | 'rename-tab'
41
+ | 'rename-worktree'
41
42
  | 'snippet-picker'
42
43
  | 'snippet-editor'
43
44
  | 'theme-picker'
@@ -365,6 +366,11 @@ export interface ModalRenameTab extends ModalBase {
365
366
  type: 'rename-tab'
366
367
  }
367
368
 
369
+ export interface ModalRenameWorktree extends ModalBase {
370
+ type: 'rename-worktree'
371
+ worktreeSessionId: string
372
+ }
373
+
368
374
  export interface ModalSnippetPicker extends ModalBase {
369
375
  type: 'snippet-picker'
370
376
  /**
@@ -483,6 +489,7 @@ export type ModalState =
483
489
  | ModalSessionPicker
484
490
  | ModalSessionName
485
491
  | ModalRenameTab
492
+ | ModalRenameWorktree
486
493
  | ModalSnippetPicker
487
494
  | ModalThemePicker
488
495
  | ModalHelp
@@ -598,6 +605,12 @@ export type ModalAction =
598
605
  | { type: 'switch-create-session-field' }
599
606
  | { type: 'select-directory' }
600
607
  | { type: 'open-rename-tab-modal' }
608
+ | {
609
+ type: 'open-rename-worktree-modal'
610
+ sessionId: string
611
+ worktreeId: string
612
+ initialName: string
613
+ }
601
614
  | { type: 'open-snippet-picker' }
602
615
  | { type: 'open-snippet-editor'; snippetId?: string }
603
616
  | { type: 'set-help-entry-count'; count: number }
@@ -63,9 +63,20 @@ export const WorktreeRow = memo(function WorktreeRow({
63
63
  )
64
64
 
65
65
  const rightClickMenu = useMemo<[string, () => void][] | undefined>(() => {
66
- if (worktree.source === 'primary') return
67
- return [
66
+ const entries: [string, () => void][] = [
68
67
  [
68
+ 'Rename',
69
+ () =>
70
+ dispatchGlobal({
71
+ initialName: worktree.name,
72
+ sessionId: session.id,
73
+ type: 'open-rename-worktree-modal',
74
+ worktreeId: worktree.id,
75
+ }),
76
+ ],
77
+ ]
78
+ if (worktree.source !== 'primary') {
79
+ entries.push([
69
80
  'Remove worktree',
70
81
  () =>
71
82
  // Always confirm first. Confirming routes through the full delete side
@@ -83,8 +94,9 @@ export const WorktreeRow = memo(function WorktreeRow({
83
94
  worktreeId: worktree.id,
84
95
  worktreeLabel: worktree.branch ?? worktree.name,
85
96
  }),
86
- ],
87
- ]
97
+ ])
98
+ }
99
+ return entries
88
100
  }, [session.id, worktree.branch, worktree.id, worktree.name, worktree.source])
89
101
 
90
102
  let bgColor: string | undefined
package/src/ui/root.tsx CHANGED
@@ -156,6 +156,8 @@ function renderModal(
156
156
  )
157
157
  case 'rename-tab':
158
158
  return <SessionNameModal title="Rename tab" value={modal.editBuffer ?? ''} />
159
+ case 'rename-worktree':
160
+ return <SessionNameModal title="Rename worktree" value={modal.editBuffer ?? ''} />
159
161
  case 'create-session':
160
162
  return (
161
163
  <CreateSessionModal
@@ -150,6 +150,8 @@ function deriveCommandEditModeId(modalType: AppState['modal']['type']): ModeId |
150
150
  return 'modal.new-tab.command-edit'
151
151
  case 'rename-tab':
152
152
  return 'modal.rename-tab'
153
+ case 'rename-worktree':
154
+ return 'modal.rename-worktree'
153
155
  case 'session-name':
154
156
  return 'modal.session-name'
155
157
  case 'session-picker':