@brimveyn/aimux 1.12.6 → 1.13.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.
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 +29 -13
  6. package/src/app-runtime/backend-runtime-events.ts +1 -1
  7. package/src/app-runtime/selection-scroll.ts +2 -2
  8. package/src/app-runtime/session-actions.ts +16 -4
  9. package/src/app-runtime/side-effects.ts +563 -85
  10. package/src/app-runtime/snippet-actions.ts +3 -3
  11. package/src/app-runtime/use-auto-commit-driver.ts +7 -4
  12. package/src/app-runtime/use-backend-runtime.ts +3 -3
  13. package/src/app-runtime/use-directory-search.ts +7 -5
  14. package/src/app-runtime/use-mouse-handlers.ts +11 -6
  15. package/src/app-runtime/use-pane-size-report.ts +1 -1
  16. package/src/app-runtime/use-renderer-bindings.ts +6 -5
  17. package/src/app.tsx +12 -7
  18. package/src/auto-commit/headless-commands.ts +4 -6
  19. package/src/daemon/daemon.ts +21 -11
  20. package/src/daemon/runtime-paths.ts +1 -1
  21. package/src/daemon/session-manager.ts +1 -1
  22. package/src/daemon/session-registry.ts +10 -3
  23. package/src/diff-parser/parse-patch-files.ts +18 -18
  24. package/src/git/command-queue.ts +19 -3
  25. package/src/git/divergence.ts +46 -0
  26. package/src/git/git-diff.ts +12 -5
  27. package/src/git/git-poller.ts +33 -11
  28. package/src/git/git-status.ts +14 -3
  29. package/src/git/move-worktree.ts +96 -0
  30. package/src/git/use-repo-discovery.ts +2 -1
  31. package/src/git/worktree-divergence-poller.ts +59 -0
  32. package/src/git/worktree.ts +99 -0
  33. package/src/index.tsx +1 -1
  34. package/src/input/keymap/describe-bindings.ts +27 -9
  35. package/src/input/keymap/key-chord.ts +4 -4
  36. package/src/input/keymap/key-format.ts +2 -2
  37. package/src/input/keymap/sequence-resolver.ts +1 -1
  38. package/src/input/keymap/trie.ts +1 -1
  39. package/src/input/modes/bridge.ts +7 -0
  40. package/src/input/modes/transitions.ts +2 -1
  41. package/src/input/modes/types.ts +11 -1
  42. package/src/input/raw-input-handler.ts +1 -1
  43. package/src/input/terminal-text-extraction.ts +9 -5
  44. package/src/integrations/claude-syntax-overlay.ts +8 -8
  45. package/src/integrations/claude-theme-sync.ts +12 -12
  46. package/src/ipc/protocol.ts +3 -3
  47. package/src/platform/clipboard.ts +4 -2
  48. package/src/platform/worktree-deps.ts +22 -0
  49. package/src/platform/worktree-paths.ts +67 -0
  50. package/src/profile-paths.ts +3 -3
  51. package/src/pty/assistant-status-detection-loop.ts +2 -2
  52. package/src/pty/assistant-status-detector.ts +15 -6
  53. package/src/pty/command-registry.ts +2 -1
  54. package/src/pty/pty-manager.ts +1 -1
  55. package/src/services/ai-usage/adapters/claude.ts +5 -5
  56. package/src/services/ai-usage/adapters/codex.ts +8 -8
  57. package/src/services/ai-usage/cache.ts +2 -2
  58. package/src/services/ai-usage/pace.ts +3 -6
  59. package/src/services/ai-usage/provider.ts +1 -1
  60. package/src/session-backend/bootstrap.ts +2 -2
  61. package/src/session-backend/local-session-backend.ts +11 -11
  62. package/src/session-backend/remote-session-backend.ts +60 -45
  63. package/src/session-backend/types.ts +2 -2
  64. package/src/snippets/expand-variables.ts +7 -7
  65. package/src/snippets/run-shell-var.ts +1 -1
  66. package/src/snippets/trigger-detector.ts +1 -1
  67. package/src/state/ai-usage-store.ts +1 -1
  68. package/src/state/git-tree.ts +8 -4
  69. package/src/state/layout-tree.ts +8 -5
  70. package/src/state/reducers/git-mode-state.ts +44 -14
  71. package/src/state/reducers/git-panel-state.ts +38 -2
  72. package/src/state/reducers/modal-state.ts +355 -21
  73. package/src/state/reducers/session-state.ts +60 -1
  74. package/src/state/reducers/tab-state.ts +151 -85
  75. package/src/state/selectors.ts +6 -4
  76. package/src/state/session-catalog.ts +20 -5
  77. package/src/state/session-persistence.ts +8 -4
  78. package/src/state/session-worktrees.ts +227 -0
  79. package/src/state/store.ts +1 -0
  80. package/src/state/toast-store.ts +95 -0
  81. package/src/state/types.ts +92 -13
  82. package/src/state/validation.ts +25 -2
  83. package/src/terminal-manager/manager-client.ts +15 -13
  84. package/src/ui/components/git/diff-renderer/fold-strip.tsx +32 -24
  85. package/src/ui/components/git/diff-renderer/pierre-diff.tsx +1 -1
  86. package/src/ui/components/git/diff-renderer/prepare-diff.ts +2 -2
  87. package/src/ui/components/git/diff-renderer/split-view.tsx +39 -11
  88. package/src/ui/components/git/diff-renderer/stacked-view.tsx +29 -7
  89. package/src/ui/components/git/diff-renderer/use-diff-prefetch.ts +28 -11
  90. package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +10 -8
  91. package/src/ui/components/git/git-panel.tsx +129 -83
  92. package/src/ui/components/git/git-view.tsx +96 -28
  93. package/src/ui/components/git/image-diff/image-diff-view.tsx +2 -2
  94. package/src/ui/components/git/image-diff/terminal-image-pane.tsx +14 -13
  95. package/src/ui/components/git/pane/git-pane-context-menu.ts +2 -2
  96. package/src/ui/components/git/pane/git-pane-widget.tsx +6 -4
  97. package/src/ui/components/layout/session-bar.tsx +135 -117
  98. package/src/ui/components/layout/sidebar/sidebar.tsx +244 -82
  99. package/src/ui/components/layout/sidebar/tab-item.tsx +68 -41
  100. package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +2 -2
  101. package/src/ui/components/layout/sidebar/use-sidebar-branch.ts +1 -1
  102. package/src/ui/components/layout/split-layout.tsx +27 -20
  103. package/src/ui/components/layout/terminal-pane.tsx +163 -120
  104. package/src/ui/components/modals/app/ai-usage-modal.tsx +8 -5
  105. package/src/ui/components/modals/app/help-modal.tsx +29 -20
  106. package/src/ui/components/modals/git/git-commit-modal.tsx +17 -10
  107. package/src/ui/components/modals/sessions/create-session-modal.tsx +18 -10
  108. package/src/ui/components/modals/sessions/session-picker-modal.tsx +36 -28
  109. package/src/ui/components/modals/shared/form.tsx +6 -6
  110. package/src/ui/components/modals/shared/modal-shell.tsx +3 -3
  111. package/src/ui/components/modals/shared/picker.tsx +71 -32
  112. package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +50 -29
  113. package/src/ui/components/modals/tabs/new-tab-modal.tsx +209 -25
  114. package/src/ui/components/modals/themes/theme-picker-modal.tsx +24 -15
  115. package/src/ui/components/modals/worktree/worktree-move-modal.tsx +111 -0
  116. package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +14 -9
  117. package/src/ui/components/overlays/context-menu/context-menu-box.tsx +20 -11
  118. package/src/ui/components/overlays/context-menu/context-menu-overlay.tsx +69 -34
  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 +23 -7
  126. package/src/ui/filter-themes.ts +1 -1
  127. package/src/ui/root.tsx +121 -38
  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
@@ -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 })
@@ -1,6 +1,6 @@
1
1
  import type { BoxRenderable, MouseEvent as OtuiMouseEvent } from '@opentui/core'
2
2
 
3
- import { useMemo, useRef, useState } from 'react'
3
+ import { memo, useCallback, useMemo, useRef, useState } from 'react'
4
4
 
5
5
  import type { SessionRecord, SessionStatus } from '../../../state/types'
6
6
 
@@ -31,59 +31,56 @@ export function SessionBar({ forceVisible = false }: SessionBarProps) {
31
31
  const chipRefs = useRef(new Map<string, BoxRenderable>())
32
32
 
33
33
  const ordered = useMemo(() => orderSessionsForDisplay(sessions), [sessions])
34
- if ((!bar.visible && !forceVisible) || ordered.length === 0) return null
34
+ const baselineOrder = useMemo(() => ordered.map((s) => s.id), [ordered])
35
35
 
36
- const visibleSessions =
37
- dragOrder !== null
38
- ? dragOrder
39
- .map((id) => ordered.find((s) => s.id === id))
40
- .filter((s): s is SessionRecord => !!s)
41
- : ordered
42
-
43
- const baselineOrder = ordered.map((s) => s.id)
44
-
45
- function setChipRef(id: string, ref: BoxRenderable | null): void {
36
+ const setChipRef = useCallback((id: string, ref: BoxRenderable | null): void => {
46
37
  if (ref) chipRefs.current.set(id, ref)
47
38
  else chipRefs.current.delete(id)
48
- }
39
+ }, [])
49
40
 
50
- function findChipAtX(x: number): string | null {
41
+ const findChipAtX = useCallback((x: number): string | null => {
51
42
  for (const [id, ref] of chipRefs.current) {
52
43
  if (x >= ref.x && x < ref.x + ref.width) return id
53
44
  }
54
45
  return null
55
- }
46
+ }, [])
56
47
 
57
- const handleMouseDown = (id: string) => {
58
- setDraggingId(id)
59
- setDragOrder(baselineOrder)
60
- lastSwapWithRef.current = null
61
- }
62
-
63
- const handleMouseDrag = (event: OtuiMouseEvent) => {
64
- if (!draggingId) return
65
- const hit = findChipAtX(event.x)
66
- if (hit === null) {
67
- lastSwapWithRef.current = null
68
- return
69
- }
70
- if (hit === draggingId) {
48
+ const handleMouseDown = useCallback(
49
+ (id: string) => {
50
+ setDraggingId(id)
51
+ setDragOrder(baselineOrder)
71
52
  lastSwapWithRef.current = null
72
- return
73
- }
74
- if (hit === lastSwapWithRef.current) return
75
- setDragOrder((prev) => (prev ? moveIdToIdPosition(prev, draggingId, hit) : prev))
76
- lastSwapWithRef.current = hit
77
- }
53
+ },
54
+ [baselineOrder]
55
+ )
78
56
 
79
- const commitDrop = () => {
57
+ const handleMouseDrag = useCallback(
58
+ (event: OtuiMouseEvent) => {
59
+ if (!(draggingId != null && draggingId !== '')) return
60
+ const hit = findChipAtX(event.x)
61
+ if (hit === null) {
62
+ lastSwapWithRef.current = null
63
+ return
64
+ }
65
+ if (hit === draggingId) {
66
+ lastSwapWithRef.current = null
67
+ return
68
+ }
69
+ if (hit === lastSwapWithRef.current) return
70
+ setDragOrder((prev) => (prev ? moveIdToIdPosition(prev, draggingId, hit) : prev))
71
+ lastSwapWithRef.current = hit
72
+ },
73
+ [draggingId, findChipAtX]
74
+ )
75
+
76
+ const commitDrop = useCallback(() => {
80
77
  const source = draggingId
81
78
  const finalOrder = dragOrder
82
79
  setDraggingId(null)
83
80
  setDragOrder(null)
84
81
  lastSwapWithRef.current = null
85
82
 
86
- if (!source || !finalOrder) return
83
+ if (source == null || source === '' || !finalOrder) return
87
84
 
88
85
  const changed = !arraysEqual(finalOrder, baselineOrder)
89
86
  if (changed) {
@@ -95,13 +92,27 @@ export function SessionBar({ forceVisible = false }: SessionBarProps) {
95
92
  if (idx >= 0) {
96
93
  runSideEffectGlobal({ index: idx + 1, type: 'switch-session-by-index' })
97
94
  }
98
- }
95
+ }, [baselineOrder, dragOrder, draggingId])
99
96
 
100
- const cancelDrag = () => {
97
+ const cancelDrag = useCallback(() => {
101
98
  setDraggingId(null)
102
99
  setDragOrder(null)
103
100
  lastSwapWithRef.current = null
104
- }
101
+ }, [])
102
+
103
+ const handleNewSession = useCallback((e: OtuiMouseEvent) => {
104
+ e.stopPropagation()
105
+ dispatchGlobal({ returnToSessionPicker: false, type: 'open-create-session-modal' })
106
+ }, [])
107
+
108
+ if ((!bar.visible && !forceVisible) || ordered.length === 0) return null
109
+
110
+ const visibleSessions =
111
+ dragOrder !== null
112
+ ? dragOrder
113
+ .map((id) => ordered.find((s) => s.id === id))
114
+ .filter((s): s is SessionRecord => !!s)
115
+ : ordered
105
116
 
106
117
  return (
107
118
  <box
@@ -112,50 +123,28 @@ export function SessionBar({ forceVisible = false }: SessionBarProps) {
112
123
  paddingRight={1}
113
124
  backgroundColor={headerBg}
114
125
  >
115
- {visibleSessions.map((session) => {
116
- const displayIndex = baselineOrder.indexOf(session.id) + 1
117
- return (
118
- <SessionChip
119
- key={session.id}
120
- session={session}
121
- index={displayIndex}
122
- active={session.id === currentId}
123
- status={statusMap[session.id] ?? IDLE_SESSION_STATUS}
124
- dragging={draggingId === session.id}
125
- onRef={(r) => setChipRef(session.id, r)}
126
- onMouseDown={() => handleMouseDown(session.id)}
127
- onMouseDrag={handleMouseDrag}
128
- onMouseUp={commitDrop}
129
- onMouseDragEnd={cancelDrag}
130
- rightClickMenu={[
131
- [
132
- 'Rename workspace',
133
- () =>
134
- dispatchGlobal({
135
- initialName: session.name,
136
- returnToSessionPicker: false,
137
- sessionTargetId: session.id,
138
- type: 'open-session-name-modal',
139
- }),
140
- ],
141
- [
142
- 'Delete workspace',
143
- () => runSideEffectGlobal({ sessionId: session.id, type: 'delete-session' }),
144
- ],
145
- ]}
146
- />
147
- )
148
- })}
126
+ {visibleSessions.map((session) => (
127
+ <SessionChip
128
+ key={session.id}
129
+ session={session}
130
+ index={baselineOrder.indexOf(session.id) + 1}
131
+ active={session.id === currentId}
132
+ status={statusMap[session.id] ?? IDLE_SESSION_STATUS}
133
+ dragging={draggingId === session.id}
134
+ setChipRef={setChipRef}
135
+ onDragStart={handleMouseDown}
136
+ onDrag={handleMouseDrag}
137
+ onDrop={commitDrop}
138
+ onDragCancel={cancelDrag}
139
+ />
140
+ ))}
149
141
  <box flexGrow={1} />
150
142
  <box
151
143
  flexDirection="row"
152
144
  paddingLeft={1}
153
145
  paddingRight={1}
154
146
  backgroundColor={t.backgroundElement}
155
- onMouseDown={(e) => {
156
- e.stopPropagation()
157
- dispatchGlobal({ returnToSessionPicker: false, type: 'open-create-session-modal' })
158
- }}
147
+ onMouseDown={handleNewSession}
159
148
  >
160
149
  <text fg={t.text} selectable={false}>
161
150
  + New
@@ -179,25 +168,23 @@ interface SessionChipProps {
179
168
  active: boolean
180
169
  status: SessionStatus
181
170
  dragging: boolean
182
- onRef: (ref: BoxRenderable | null) => void
183
- onMouseDown: (event: OtuiMouseEvent) => void
184
- onMouseDrag: (event: OtuiMouseEvent) => void
185
- onMouseUp: (event: OtuiMouseEvent) => void
186
- onMouseDragEnd: (event: OtuiMouseEvent) => void
187
- rightClickMenu: Array<[string, () => void]>
171
+ setChipRef: (id: string, ref: BoxRenderable | null) => void
172
+ onDragStart: (id: string) => void
173
+ onDrag: (event: OtuiMouseEvent) => void
174
+ onDrop: () => void
175
+ onDragCancel: () => void
188
176
  }
189
177
 
190
- function SessionChip({
178
+ const SessionChip = memo(function SessionChip({
191
179
  active,
192
180
  dragging,
193
181
  index,
194
- onMouseDown,
195
- onMouseDrag,
196
- onMouseDragEnd,
197
- onMouseUp,
198
- onRef,
199
- rightClickMenu,
182
+ onDrag,
183
+ onDragCancel,
184
+ onDragStart,
185
+ onDrop,
200
186
  session,
187
+ setChipRef,
201
188
  status,
202
189
  }: SessionChipProps) {
203
190
  const t = useTheme()
@@ -212,30 +199,68 @@ function SessionChip({
212
199
  const waitingColor = t.warning
213
200
  const [hovered, setHovered] = useState(false)
214
201
 
202
+ const handleRef = useCallback(
203
+ (r: BoxRenderable | null) => setChipRef(session.id, r),
204
+ [setChipRef, session.id]
205
+ )
206
+ const handleMouseDown = useCallback(
207
+ (e: OtuiMouseEvent) => {
208
+ e.preventDefault()
209
+ onDragStart(session.id)
210
+ },
211
+ [onDragStart, session.id]
212
+ )
213
+ const handleMouseUp = useCallback(
214
+ (e: OtuiMouseEvent) => {
215
+ e.preventDefault()
216
+ onDrop()
217
+ },
218
+ [onDrop]
219
+ )
220
+ const handleMouseOver = useCallback(() => setHovered(true), [])
221
+ const handleMouseOut = useCallback(() => setHovered(false), [])
222
+ const handleDeleteMouseDown = useCallback(
223
+ (event: OtuiMouseEvent) => {
224
+ event.preventDefault()
225
+ event.stopPropagation()
226
+ runSideEffectGlobal({ sessionId: session.id, type: 'delete-session' })
227
+ },
228
+ [session.id]
229
+ )
230
+ const rightClickMenu = useMemo<[string, () => void][]>(
231
+ () => [
232
+ [
233
+ 'Rename workspace',
234
+ () =>
235
+ dispatchGlobal({
236
+ initialName: session.name,
237
+ returnToSessionPicker: false,
238
+ sessionTargetId: session.id,
239
+ type: 'open-session-name-modal',
240
+ }),
241
+ ],
242
+ [
243
+ 'Delete workspace',
244
+ () => runSideEffectGlobal({ sessionId: session.id, type: 'delete-session' }),
245
+ ],
246
+ ],
247
+ [session.id, session.name]
248
+ )
249
+
215
250
  return (
216
251
  <ContextMenuBox
217
- ref={onRef}
252
+ ref={handleRef}
218
253
  flexDirection="row"
219
254
  paddingLeft={1}
220
255
  paddingRight={1}
221
256
  backgroundColor={bgColor}
222
257
  rightClickMenu={rightClickMenu}
223
- onMouseOver={() => setHovered(true)}
224
- onMouseOut={() => setHovered(false)}
225
- onMouseDown={(e) => {
226
- e.preventDefault()
227
- onMouseDown(e)
228
- }}
229
- onMouseDrag={(e) => {
230
- onMouseDrag(e)
231
- }}
232
- onMouseUp={(e) => {
233
- e.preventDefault()
234
- onMouseUp(e)
235
- }}
236
- onMouseDragEnd={(e) => {
237
- onMouseDragEnd(e)
238
- }}
258
+ onMouseOver={handleMouseOver}
259
+ onMouseOut={handleMouseOut}
260
+ onMouseDown={handleMouseDown}
261
+ onMouseDrag={onDrag}
262
+ onMouseUp={handleMouseUp}
263
+ onMouseDragEnd={onDragCancel}
239
264
  >
240
265
  {showWaiting ? (
241
266
  <text fg={waitingColor} selectable={false}>
@@ -256,14 +281,7 @@ function SessionChip({
256
281
  [{index}] {session.name}
257
282
  </text>
258
283
  {hovered ? (
259
- <box
260
- paddingLeft={1}
261
- onMouseDown={(event) => {
262
- event.preventDefault()
263
- event.stopPropagation()
264
- runSideEffectGlobal({ sessionId: session.id, type: 'delete-session' })
265
- }}
266
- >
284
+ <box paddingLeft={1} onMouseDown={handleDeleteMouseDown}>
267
285
  <text fg={t.textMuted} selectable={false}>
268
286
  ×
269
287
  </text>
@@ -275,4 +293,4 @@ function SessionChip({
275
293
  )}
276
294
  </ContextMenuBox>
277
295
  )
278
- }
296
+ })