@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.
- package/README.md +3 -0
- package/package.json +4 -3
- package/src/app-runtime/auto-commit-driver.ts +8 -4
- package/src/app-runtime/auto-commit-ref.ts +1 -1
- package/src/app-runtime/backend-attach-runtime.ts +29 -13
- package/src/app-runtime/backend-runtime-events.ts +1 -1
- package/src/app-runtime/selection-scroll.ts +2 -2
- package/src/app-runtime/session-actions.ts +16 -4
- package/src/app-runtime/side-effects.ts +563 -85
- package/src/app-runtime/snippet-actions.ts +3 -3
- package/src/app-runtime/use-auto-commit-driver.ts +7 -4
- package/src/app-runtime/use-backend-runtime.ts +3 -3
- package/src/app-runtime/use-directory-search.ts +7 -5
- package/src/app-runtime/use-mouse-handlers.ts +11 -6
- package/src/app-runtime/use-pane-size-report.ts +1 -1
- package/src/app-runtime/use-renderer-bindings.ts +6 -5
- package/src/app.tsx +12 -7
- package/src/auto-commit/headless-commands.ts +4 -6
- package/src/daemon/daemon.ts +21 -11
- package/src/daemon/runtime-paths.ts +1 -1
- package/src/daemon/session-manager.ts +1 -1
- package/src/daemon/session-registry.ts +10 -3
- package/src/diff-parser/parse-patch-files.ts +18 -18
- package/src/git/command-queue.ts +19 -3
- package/src/git/divergence.ts +46 -0
- package/src/git/git-diff.ts +12 -5
- package/src/git/git-poller.ts +33 -11
- package/src/git/git-status.ts +14 -3
- package/src/git/move-worktree.ts +96 -0
- package/src/git/use-repo-discovery.ts +2 -1
- package/src/git/worktree-divergence-poller.ts +59 -0
- package/src/git/worktree.ts +99 -0
- package/src/index.tsx +1 -1
- package/src/input/keymap/describe-bindings.ts +27 -9
- package/src/input/keymap/key-chord.ts +4 -4
- package/src/input/keymap/key-format.ts +2 -2
- package/src/input/keymap/sequence-resolver.ts +1 -1
- package/src/input/keymap/trie.ts +1 -1
- package/src/input/modes/bridge.ts +7 -0
- package/src/input/modes/transitions.ts +2 -1
- package/src/input/modes/types.ts +11 -1
- package/src/input/raw-input-handler.ts +1 -1
- package/src/input/terminal-text-extraction.ts +9 -5
- package/src/integrations/claude-syntax-overlay.ts +8 -8
- package/src/integrations/claude-theme-sync.ts +12 -12
- package/src/ipc/protocol.ts +3 -3
- package/src/platform/clipboard.ts +4 -2
- package/src/platform/worktree-deps.ts +22 -0
- package/src/platform/worktree-paths.ts +67 -0
- package/src/profile-paths.ts +3 -3
- package/src/pty/assistant-status-detection-loop.ts +2 -2
- package/src/pty/assistant-status-detector.ts +15 -6
- package/src/pty/command-registry.ts +2 -1
- package/src/pty/pty-manager.ts +1 -1
- package/src/services/ai-usage/adapters/claude.ts +5 -5
- package/src/services/ai-usage/adapters/codex.ts +8 -8
- package/src/services/ai-usage/cache.ts +2 -2
- package/src/services/ai-usage/pace.ts +3 -6
- package/src/services/ai-usage/provider.ts +1 -1
- package/src/session-backend/bootstrap.ts +2 -2
- package/src/session-backend/local-session-backend.ts +11 -11
- package/src/session-backend/remote-session-backend.ts +60 -45
- package/src/session-backend/types.ts +2 -2
- package/src/snippets/expand-variables.ts +7 -7
- package/src/snippets/run-shell-var.ts +1 -1
- package/src/snippets/trigger-detector.ts +1 -1
- package/src/state/ai-usage-store.ts +1 -1
- package/src/state/git-tree.ts +8 -4
- package/src/state/layout-tree.ts +8 -5
- package/src/state/reducers/git-mode-state.ts +44 -14
- package/src/state/reducers/git-panel-state.ts +38 -2
- package/src/state/reducers/modal-state.ts +355 -21
- package/src/state/reducers/session-state.ts +60 -1
- package/src/state/reducers/tab-state.ts +151 -85
- package/src/state/selectors.ts +6 -4
- package/src/state/session-catalog.ts +20 -5
- package/src/state/session-persistence.ts +8 -4
- package/src/state/session-worktrees.ts +227 -0
- package/src/state/store.ts +1 -0
- package/src/state/toast-store.ts +95 -0
- package/src/state/types.ts +92 -13
- package/src/state/validation.ts +25 -2
- package/src/terminal-manager/manager-client.ts +15 -13
- package/src/ui/components/git/diff-renderer/fold-strip.tsx +32 -24
- package/src/ui/components/git/diff-renderer/pierre-diff.tsx +1 -1
- package/src/ui/components/git/diff-renderer/prepare-diff.ts +2 -2
- package/src/ui/components/git/diff-renderer/split-view.tsx +39 -11
- package/src/ui/components/git/diff-renderer/stacked-view.tsx +29 -7
- package/src/ui/components/git/diff-renderer/use-diff-prefetch.ts +28 -11
- package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +10 -8
- package/src/ui/components/git/git-panel.tsx +129 -83
- package/src/ui/components/git/git-view.tsx +96 -28
- package/src/ui/components/git/image-diff/image-diff-view.tsx +2 -2
- package/src/ui/components/git/image-diff/terminal-image-pane.tsx +14 -13
- package/src/ui/components/git/pane/git-pane-context-menu.ts +2 -2
- package/src/ui/components/git/pane/git-pane-widget.tsx +6 -4
- package/src/ui/components/layout/session-bar.tsx +135 -117
- package/src/ui/components/layout/sidebar/sidebar.tsx +244 -82
- package/src/ui/components/layout/sidebar/tab-item.tsx +68 -41
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +2 -2
- package/src/ui/components/layout/sidebar/use-sidebar-branch.ts +1 -1
- package/src/ui/components/layout/split-layout.tsx +27 -20
- package/src/ui/components/layout/terminal-pane.tsx +163 -120
- package/src/ui/components/modals/app/ai-usage-modal.tsx +8 -5
- package/src/ui/components/modals/app/help-modal.tsx +29 -20
- package/src/ui/components/modals/git/git-commit-modal.tsx +17 -10
- package/src/ui/components/modals/sessions/create-session-modal.tsx +18 -10
- package/src/ui/components/modals/sessions/session-picker-modal.tsx +36 -28
- package/src/ui/components/modals/shared/form.tsx +6 -6
- package/src/ui/components/modals/shared/modal-shell.tsx +3 -3
- package/src/ui/components/modals/shared/picker.tsx +71 -32
- package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +50 -29
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +209 -25
- package/src/ui/components/modals/themes/theme-picker-modal.tsx +24 -15
- package/src/ui/components/modals/worktree/worktree-move-modal.tsx +111 -0
- package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +14 -9
- package/src/ui/components/overlays/context-menu/context-menu-box.tsx +20 -11
- package/src/ui/components/overlays/context-menu/context-menu-overlay.tsx +69 -34
- package/src/ui/components/overlays/toast/toast-item.tsx +88 -0
- package/src/ui/components/overlays/toast/toast-position.ts +45 -0
- package/src/ui/components/overlays/toast/toast-stack.tsx +30 -0
- package/src/ui/components/overlays/toast/toast-viewport.tsx +24 -0
- package/src/ui/components/overlays/toast/use-toast-animation.ts +64 -0
- package/src/ui/components/primitives/input-field.tsx +1 -1
- package/src/ui/components/primitives/list-item.tsx +23 -7
- package/src/ui/filter-themes.ts +1 -1
- package/src/ui/root.tsx +121 -38
- package/src/ui/session-ordering.ts +2 -2
- package/src/ui/status-bar-model.ts +14 -7
- package/src/ui/terminal-graphics/capabilities.ts +1 -1
- package/src/ui/terminal-graphics/format-fallback.ts +7 -9
- package/src/ui/terminal-graphics/kitty.ts +2 -7
- package/src/update/version-check.ts +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { MouseEvent as OtuiMouseEvent } from '@opentui/core'
|
|
2
2
|
|
|
3
|
-
import { memo, type ReactNode } from 'react'
|
|
3
|
+
import { memo, type ReactNode, useCallback, useMemo } from 'react'
|
|
4
4
|
|
|
5
5
|
import type { TerminalContentOrigin } from '../../../input/raw-input-handler'
|
|
6
|
-
import type { TabSession, TerminalSnapshot, TerminalSpan } from '../../../state/types'
|
|
6
|
+
import type { FocusMode, TabSession, TerminalSnapshot, TerminalSpan } from '../../../state/types'
|
|
7
7
|
|
|
8
8
|
import { type MeasuredPaneRect, usePaneSizeReport } from '../../../app-runtime/use-pane-size-report'
|
|
9
9
|
import { logInputDebug } from '../../../debug/input-log'
|
|
@@ -15,7 +15,7 @@ import { ContextMenuBox } from '../overlays/context-menu/context-menu-box'
|
|
|
15
15
|
interface TerminalPaneProps {
|
|
16
16
|
tab?: TabSession
|
|
17
17
|
tabId?: string
|
|
18
|
-
focusMode:
|
|
18
|
+
focusMode: FocusMode
|
|
19
19
|
isActive?: boolean
|
|
20
20
|
contentOrigin: TerminalContentOrigin
|
|
21
21
|
mouseForwardingEnabled: boolean
|
|
@@ -61,15 +61,15 @@ function getBorderColor(isActive: boolean, focusMode: TerminalPaneProps['focusMo
|
|
|
61
61
|
function renderSpan(span: TerminalSpan, key: string): ReactNode {
|
|
62
62
|
let node: ReactNode = span.text
|
|
63
63
|
|
|
64
|
-
if (span.underline) {
|
|
64
|
+
if (span.underline === true) {
|
|
65
65
|
node = <u>{node}</u>
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
if (span.italic) {
|
|
68
|
+
if (span.italic === true) {
|
|
69
69
|
node = <em>{node}</em>
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
if (span.bold) {
|
|
72
|
+
if (span.bold === true) {
|
|
73
73
|
node = <strong>{node}</strong>
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -95,6 +95,8 @@ const TerminalViewport = memo(function TerminalViewport({
|
|
|
95
95
|
return (
|
|
96
96
|
<text fg={t.text}>
|
|
97
97
|
{lines.map((line, lineIndex) => (
|
|
98
|
+
// Terminal rows are a fixed positional grid; the row index is the identity.
|
|
99
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
98
100
|
<span key={`line-${lineIndex}`}>
|
|
99
101
|
{line.spans.map((span, spanIndex) => renderSpan(span, `s-${spanIndex}`))}
|
|
100
102
|
{lineIndex < lines.length - 1 ? '\n' : ''}
|
|
@@ -132,124 +134,169 @@ export function TerminalPane({
|
|
|
132
134
|
const paneIsActive = isActive ?? true
|
|
133
135
|
const canForwardMouse = focusMode === 'terminal-input' && !!tab && mouseForwardingEnabled
|
|
134
136
|
const canUseLocalScrollback = focusMode === 'terminal-input' && !!tab && localScrollbackEnabled
|
|
135
|
-
const rightClickMenu
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
event.
|
|
172
|
-
event.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
event.button ===
|
|
179
|
-
onLeftEdgeMouseDown &&
|
|
180
|
-
event.x === contentOrigin.x - 1
|
|
181
|
-
) {
|
|
182
|
-
if (onLeftEdgeMouseDown(event)) {
|
|
137
|
+
const rightClickMenu = useMemo<ContextMenuItem[] | undefined>(
|
|
138
|
+
() =>
|
|
139
|
+
tabId != null && tabId !== ''
|
|
140
|
+
? [
|
|
141
|
+
[
|
|
142
|
+
'Split vertically',
|
|
143
|
+
() =>
|
|
144
|
+
runSideEffectGlobal({
|
|
145
|
+
direction: 'vertical',
|
|
146
|
+
sourceTabId: tabId,
|
|
147
|
+
type: 'split-pane',
|
|
148
|
+
}),
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
'Split horizontally',
|
|
152
|
+
() =>
|
|
153
|
+
runSideEffectGlobal({
|
|
154
|
+
direction: 'horizontal',
|
|
155
|
+
sourceTabId: tabId,
|
|
156
|
+
type: 'split-pane',
|
|
157
|
+
}),
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
'Close pane',
|
|
161
|
+
() => {
|
|
162
|
+
dispatchGlobal({ tabId, type: 'close-pane' })
|
|
163
|
+
runSideEffectGlobal({ tabId, type: 'close-tab' })
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
]
|
|
167
|
+
: undefined,
|
|
168
|
+
[tabId]
|
|
169
|
+
)
|
|
170
|
+
const isOnPaneBorder = useCallback(
|
|
171
|
+
(event: OtuiMouseEvent) =>
|
|
172
|
+
event.x === contentOrigin.x - 1 ||
|
|
173
|
+
event.x === contentOrigin.x + contentOrigin.cols ||
|
|
174
|
+
event.y === contentOrigin.y - 1 ||
|
|
175
|
+
event.y === contentOrigin.y + contentOrigin.rows,
|
|
176
|
+
[contentOrigin]
|
|
177
|
+
)
|
|
178
|
+
const forwardMouseEvent = useCallback(
|
|
179
|
+
(event: OtuiMouseEvent) => {
|
|
180
|
+
if (event.type === 'down' && event.button === 2 && rightClickMenu) {
|
|
183
181
|
event.preventDefault()
|
|
184
182
|
event.stopPropagation()
|
|
183
|
+
openContextMenu(event.x, event.y, rightClickMenu)
|
|
185
184
|
return
|
|
186
185
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
logInputDebug('pane.mouseDown', {
|
|
199
|
-
button: event.button,
|
|
200
|
-
canForward: canForwardMouse,
|
|
201
|
-
eventType: event.type,
|
|
202
|
-
tabId,
|
|
203
|
-
willClick: !canForwardMouse && !!tab && event.button === 0 && !!onTerminalClick,
|
|
204
|
-
x: event.x,
|
|
205
|
-
y: event.y,
|
|
206
|
-
})
|
|
207
|
-
}
|
|
208
|
-
if (event.type === 'drag') {
|
|
209
|
-
if (onTerminalDrag?.(event, contentOrigin, tabId)) {
|
|
210
|
-
event.preventDefault()
|
|
211
|
-
return
|
|
186
|
+
if (
|
|
187
|
+
event.type === 'down' &&
|
|
188
|
+
event.button === 0 &&
|
|
189
|
+
onLeftEdgeMouseDown &&
|
|
190
|
+
event.x === contentOrigin.x - 1
|
|
191
|
+
) {
|
|
192
|
+
if (onLeftEdgeMouseDown(event)) {
|
|
193
|
+
event.preventDefault()
|
|
194
|
+
event.stopPropagation()
|
|
195
|
+
return
|
|
196
|
+
}
|
|
212
197
|
}
|
|
213
|
-
|
|
198
|
+
// Absorb left-button clicks on pane borders — they should not focus the
|
|
199
|
+
// pane or be forwarded to the terminal. This keeps borders available for
|
|
200
|
+
// resize actions (split separators, sidebar edge) without conflicting
|
|
201
|
+
// with focus-on-click behavior in the content area.
|
|
202
|
+
if (event.type === 'down' && event.button === 0 && isOnPaneBorder(event)) {
|
|
214
203
|
event.preventDefault()
|
|
204
|
+
event.stopPropagation()
|
|
215
205
|
return
|
|
216
206
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
207
|
+
if (event.type === 'down') {
|
|
208
|
+
logInputDebug('pane.mouseDown', {
|
|
209
|
+
button: event.button,
|
|
210
|
+
canForward: canForwardMouse,
|
|
211
|
+
eventType: event.type,
|
|
212
|
+
tabId,
|
|
213
|
+
willClick: !canForwardMouse && !!tab && event.button === 0 && !!onTerminalClick,
|
|
214
|
+
x: event.x,
|
|
215
|
+
y: event.y,
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
if (event.type === 'drag') {
|
|
219
|
+
if (onTerminalDrag?.(event, contentOrigin, tabId) === true) {
|
|
220
|
+
event.preventDefault()
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
if (onSeparatorDrag?.(event) === true) {
|
|
224
|
+
event.preventDefault()
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (event.type === 'up') {
|
|
229
|
+
if (onTerminalMouseUp?.(event) === true) {
|
|
230
|
+
event.preventDefault()
|
|
231
|
+
}
|
|
232
|
+
onSeparatorDragEnd?.()
|
|
233
|
+
}
|
|
234
|
+
if (tabId != null && tabId !== '' && onPaneActivate && event.type === 'down') {
|
|
235
|
+
onPaneActivate(tabId)
|
|
221
236
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (!canForwardMouse) {
|
|
228
|
-
if (tab && event.type === 'down' && event.button === 0 && onTerminalClick) {
|
|
229
|
-
onTerminalClick(event, contentOrigin, tabId)
|
|
237
|
+
if (!canForwardMouse) {
|
|
238
|
+
if (tab && event.type === 'down' && event.button === 0 && onTerminalClick) {
|
|
239
|
+
onTerminalClick(event, contentOrigin, tabId)
|
|
240
|
+
}
|
|
241
|
+
return
|
|
230
242
|
}
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
243
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
244
|
+
event.preventDefault()
|
|
245
|
+
event.stopPropagation()
|
|
246
|
+
onTerminalMouseEvent(event, contentOrigin)
|
|
247
|
+
},
|
|
248
|
+
[
|
|
249
|
+
canForwardMouse,
|
|
250
|
+
contentOrigin,
|
|
251
|
+
isOnPaneBorder,
|
|
252
|
+
onLeftEdgeMouseDown,
|
|
253
|
+
onPaneActivate,
|
|
254
|
+
onSeparatorDrag,
|
|
255
|
+
onSeparatorDragEnd,
|
|
256
|
+
onTerminalClick,
|
|
257
|
+
onTerminalDrag,
|
|
258
|
+
onTerminalMouseEvent,
|
|
259
|
+
onTerminalMouseUp,
|
|
260
|
+
rightClickMenu,
|
|
261
|
+
tab,
|
|
262
|
+
tabId,
|
|
263
|
+
]
|
|
264
|
+
)
|
|
265
|
+
const forwardScrollEvent = useCallback(
|
|
266
|
+
(event: OtuiMouseEvent) => {
|
|
267
|
+
if (!canForwardMouse && !canUseLocalScrollback) {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
242
270
|
|
|
243
|
-
|
|
244
|
-
|
|
271
|
+
event.preventDefault()
|
|
272
|
+
event.stopPropagation()
|
|
245
273
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
274
|
+
if (canForwardMouse) {
|
|
275
|
+
onTerminalMouseEvent(event, contentOrigin)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
250
278
|
|
|
251
|
-
|
|
252
|
-
|
|
279
|
+
onTerminalScrollEvent(event)
|
|
280
|
+
},
|
|
281
|
+
[
|
|
282
|
+
canForwardMouse,
|
|
283
|
+
canUseLocalScrollback,
|
|
284
|
+
contentOrigin,
|
|
285
|
+
onTerminalMouseEvent,
|
|
286
|
+
onTerminalScrollEvent,
|
|
287
|
+
]
|
|
288
|
+
)
|
|
289
|
+
const handleNoTabMouseDown = useCallback((event: OtuiMouseEvent) => {
|
|
290
|
+
event.stopPropagation()
|
|
291
|
+
dispatchGlobal({ type: 'open-new-tab-modal' })
|
|
292
|
+
}, [])
|
|
293
|
+
const handleContentMouseDown = useCallback(
|
|
294
|
+
(e: OtuiMouseEvent) => {
|
|
295
|
+
e.stopPropagation()
|
|
296
|
+
forwardMouseEvent(e)
|
|
297
|
+
},
|
|
298
|
+
[forwardMouseEvent]
|
|
299
|
+
)
|
|
253
300
|
return (
|
|
254
301
|
<box flexDirection="column" flexGrow={1} gap={0}>
|
|
255
302
|
<ContextMenuBox
|
|
@@ -281,10 +328,7 @@ export function TerminalPane({
|
|
|
281
328
|
marginTop={1}
|
|
282
329
|
paddingX={2}
|
|
283
330
|
backgroundColor={t.backgroundPanel}
|
|
284
|
-
onMouseDown={
|
|
285
|
-
event.stopPropagation()
|
|
286
|
-
dispatchGlobal({ type: 'open-new-tab-modal' })
|
|
287
|
-
}}
|
|
331
|
+
onMouseDown={handleNoTabMouseDown}
|
|
288
332
|
>
|
|
289
333
|
<text fg={t.text}>New assistant</text>
|
|
290
334
|
</box>
|
|
@@ -296,10 +340,7 @@ export function TerminalPane({
|
|
|
296
340
|
flexGrow={1}
|
|
297
341
|
width="100%"
|
|
298
342
|
overflow="hidden"
|
|
299
|
-
onMouseDown={
|
|
300
|
-
e.stopPropagation()
|
|
301
|
-
forwardMouseEvent(e)
|
|
302
|
-
}}
|
|
343
|
+
onMouseDown={handleContentMouseDown}
|
|
303
344
|
onMouseUp={forwardMouseEvent}
|
|
304
345
|
onMouseDrag={forwardMouseEvent}
|
|
305
346
|
onMouseScroll={forwardScrollEvent}
|
|
@@ -308,7 +349,7 @@ export function TerminalPane({
|
|
|
308
349
|
</box>
|
|
309
350
|
)}
|
|
310
351
|
</ContextMenuBox>
|
|
311
|
-
{tab?.status === 'disconnected' || tab?.errorMessage ? (
|
|
352
|
+
{tab?.status === 'disconnected' || (tab?.errorMessage != null && tab.errorMessage !== '') ? (
|
|
312
353
|
// Absolutely positioned so it overlays the bordered box instead of
|
|
313
354
|
// consuming a flex row. If it took a row, the rendered terminal area
|
|
314
355
|
// would be one line shorter than the size sent to the PTY/xterm,
|
|
@@ -317,7 +358,9 @@ export function TerminalPane({
|
|
|
317
358
|
{tab?.status === 'disconnected' ? (
|
|
318
359
|
<text fg={t.warning}>Restored snapshot. Press Ctrl+r to restart this workspace.</text>
|
|
319
360
|
) : null}
|
|
320
|
-
{tab?.errorMessage
|
|
361
|
+
{tab?.errorMessage != null && tab?.errorMessage !== '' ? (
|
|
362
|
+
<text fg={t.error}>{tab.errorMessage}</text>
|
|
363
|
+
) : null}
|
|
321
364
|
</box>
|
|
322
365
|
) : null}
|
|
323
366
|
</box>
|
|
@@ -93,7 +93,7 @@ interface ToolSectionProps {
|
|
|
93
93
|
|
|
94
94
|
function ToolSection({ snap, tool }: ToolSectionProps) {
|
|
95
95
|
const t = useTheme()
|
|
96
|
-
const isHardError = Boolean(snap.error) && !snap.stale
|
|
96
|
+
const isHardError = Boolean(snap.error) && !(snap.stale === true)
|
|
97
97
|
const relative = formatRelative(snap.lastUpdated)
|
|
98
98
|
|
|
99
99
|
let body: ReactNode
|
|
@@ -119,7 +119,7 @@ function ToolSection({ snap, tool }: ToolSectionProps) {
|
|
|
119
119
|
<text fg={t.text} selectable={false}>
|
|
120
120
|
{TOOL_TITLE[tool]}
|
|
121
121
|
</text>
|
|
122
|
-
{snap.planTier ? (
|
|
122
|
+
{snap.planTier != null && snap.planTier !== '' ? (
|
|
123
123
|
<text fg={t.textMuted} selectable={false}>
|
|
124
124
|
{snap.planTier}
|
|
125
125
|
</text>
|
|
@@ -145,7 +145,10 @@ function WindowRow({ window }: { window: UsageWindow }) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
const pctText = percent === null ? '—' : `${Math.round(percent)}% used`
|
|
148
|
-
const resetText =
|
|
148
|
+
const resetText =
|
|
149
|
+
window.timeRemaining != null && window.timeRemaining !== ''
|
|
150
|
+
? `Resets in ${window.timeRemaining}`
|
|
151
|
+
: null
|
|
149
152
|
|
|
150
153
|
return (
|
|
151
154
|
<box flexDirection="column" paddingTop={1}>
|
|
@@ -164,7 +167,7 @@ function WindowRow({ window }: { window: UsageWindow }) {
|
|
|
164
167
|
<text fg={t.textMuted} selectable={false}>
|
|
165
168
|
{pctText}
|
|
166
169
|
</text>
|
|
167
|
-
{resetText ? (
|
|
170
|
+
{resetText != null && resetText !== '' ? (
|
|
168
171
|
<text fg={t.textMuted} selectable={false}>
|
|
169
172
|
{resetText}
|
|
170
173
|
</text>
|
|
@@ -181,7 +184,7 @@ function PaceLine({ pace }: { pace: NonNullable<UsageWindow['pace']> }) {
|
|
|
181
184
|
if (paceStageIsBehind(pace.stage)) color = t.warning
|
|
182
185
|
else if (paceStageIsAhead(pace.stage)) color = t.success
|
|
183
186
|
|
|
184
|
-
const suffix = pace.rightText ? ` · ${pace.rightText}` : ''
|
|
187
|
+
const suffix = pace.rightText != null && pace.rightText !== '' ? ` · ${pace.rightText}` : ''
|
|
185
188
|
return (
|
|
186
189
|
<text fg={color} selectable={false}>
|
|
187
190
|
{`Pace: ${pace.label}${suffix}`}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ModeId } from '@brimveyn/aimux-config'
|
|
2
2
|
|
|
3
|
-
import { useLayoutEffect, useMemo } from 'react'
|
|
3
|
+
import { useCallback, useLayoutEffect, useMemo } from 'react'
|
|
4
4
|
|
|
5
5
|
import { collectHelpEntries, HELP_MODE_LABELS } from '../../../../input/keymap/help-entries'
|
|
6
6
|
import { dispatchGlobal } from '../../../../state/dispatch-ref'
|
|
@@ -19,7 +19,7 @@ interface HelpModalProps {
|
|
|
19
19
|
function matchesFilter(needle: string, ...fields: (string | undefined)[]): boolean {
|
|
20
20
|
if (!needle) return true
|
|
21
21
|
const lower = needle.toLowerCase()
|
|
22
|
-
return fields.some((f) => f?.toLowerCase().includes(lower))
|
|
22
|
+
return fields.some((f) => f?.toLowerCase().includes(lower) === true)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function HelpModal({ cursorPos, filter, scope, selectedIndex }: HelpModalProps) {
|
|
@@ -48,22 +48,29 @@ export function HelpModal({ cursorPos, filter, scope, selectedIndex }: HelpModal
|
|
|
48
48
|
dispatchGlobal({ count: filtered.length, type: 'set-help-entry-count' })
|
|
49
49
|
}, [filtered.length])
|
|
50
50
|
|
|
51
|
-
const items
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
const items = useMemo<PickerItem[]>(
|
|
52
|
+
() =>
|
|
53
|
+
filtered.map((entry, index) => ({
|
|
54
|
+
group: entry.modeLabel,
|
|
55
|
+
key: `${entry.mode}::${entry.keysDisplay}::${entry.description ?? ''}::${index}`,
|
|
56
|
+
title: (
|
|
57
|
+
<text fg={t.textMuted} wrapMode="none">
|
|
58
|
+
{entry.description ?? ''}
|
|
59
|
+
</text>
|
|
60
|
+
),
|
|
61
|
+
trailing: (
|
|
62
|
+
<text fg={t.textMuted} wrapMode="none">
|
|
63
|
+
{entry.keysDisplay}
|
|
64
|
+
</text>
|
|
65
|
+
),
|
|
66
|
+
})),
|
|
67
|
+
[filtered, t]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const handleHover = useCallback(
|
|
71
|
+
(index: number) => dispatchGlobal({ index, type: 'set-modal-selection-index' }),
|
|
72
|
+
[]
|
|
73
|
+
)
|
|
67
74
|
|
|
68
75
|
return (
|
|
69
76
|
<Picker
|
|
@@ -75,9 +82,11 @@ export function HelpModal({ cursorPos, filter, scope, selectedIndex }: HelpModal
|
|
|
75
82
|
items={items}
|
|
76
83
|
selectedIndex={selectedIndex}
|
|
77
84
|
emptyState={
|
|
78
|
-
<text fg={t.textMuted}>
|
|
85
|
+
<text fg={t.textMuted}>
|
|
86
|
+
{filter != null && filter !== '' ? 'No matching bindings.' : 'No bindings registered.'}
|
|
87
|
+
</text>
|
|
79
88
|
}
|
|
80
|
-
onHover={
|
|
89
|
+
onHover={handleHover}
|
|
81
90
|
/>
|
|
82
91
|
)
|
|
83
92
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { MouseEvent as OtuiMouseEvent } from '@opentui/core'
|
|
2
|
+
|
|
1
3
|
import { isAutoCommitEnabled } from '@brimveyn/aimux-config'
|
|
2
|
-
import { useEffect, useState } from 'react'
|
|
4
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
3
5
|
|
|
4
6
|
import type { ModeId } from '../../../../input/modes/types'
|
|
5
7
|
|
|
@@ -90,7 +92,7 @@ export function GitCommitModal({
|
|
|
90
92
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
91
93
|
const bgKind = useAppStore((s) => {
|
|
92
94
|
const id = s.currentSessionId
|
|
93
|
-
return id ? s.autoCommit.bySession[id]?.kind : undefined
|
|
95
|
+
return id != null && id !== '' ? s.autoCommit.bySession[id]?.kind : undefined
|
|
94
96
|
})
|
|
95
97
|
const isBgGenerating = bgKind === 'generating'
|
|
96
98
|
const showBgSpinner = isBgGenerating && !isConfirm && !isGenerating
|
|
@@ -99,9 +101,9 @@ export function GitCommitModal({
|
|
|
99
101
|
(s) => s.gitPanel.files.filter((f) => f.section === 'staged').length
|
|
100
102
|
)
|
|
101
103
|
|
|
102
|
-
const onAutoCommitClick = (): void => {
|
|
104
|
+
const onAutoCommitClick = useCallback((): void => {
|
|
103
105
|
const hasTitle = title.trim().length > 0
|
|
104
|
-
if (hasTitle || !currentSessionId) {
|
|
106
|
+
if (hasTitle || !(currentSessionId != null && currentSessionId !== '')) {
|
|
105
107
|
dispatchGlobal({ type: 'git-commit-enter-confirm' })
|
|
106
108
|
return
|
|
107
109
|
}
|
|
@@ -113,7 +115,16 @@ export function GitCommitModal({
|
|
|
113
115
|
if (bgKind !== 'generating') {
|
|
114
116
|
runSideEffectGlobal({ sessionId: currentSessionId, type: 'generate-auto-commit-now' })
|
|
115
117
|
}
|
|
116
|
-
}
|
|
118
|
+
}, [bgKind, currentSessionId, title])
|
|
119
|
+
|
|
120
|
+
const handleAutoCommitMouseDown = useCallback(
|
|
121
|
+
(event: OtuiMouseEvent) => {
|
|
122
|
+
event.preventDefault()
|
|
123
|
+
event.stopPropagation()
|
|
124
|
+
onAutoCommitClick()
|
|
125
|
+
},
|
|
126
|
+
[onAutoCommitClick]
|
|
127
|
+
)
|
|
117
128
|
|
|
118
129
|
const modeId = pickModeId(stage)
|
|
119
130
|
const shellTitle = pickShellTitle(stage)
|
|
@@ -169,11 +180,7 @@ export function GitCommitModal({
|
|
|
169
180
|
flexDirection="row"
|
|
170
181
|
gap={1}
|
|
171
182
|
alignItems="center"
|
|
172
|
-
onMouseDown={
|
|
173
|
-
event.preventDefault()
|
|
174
|
-
event.stopPropagation()
|
|
175
|
-
onAutoCommitClick()
|
|
176
|
-
}}
|
|
183
|
+
onMouseDown={handleAutoCommitMouseDown}
|
|
177
184
|
>
|
|
178
185
|
{showBgSpinner ? <text fg={t.primary}>{bgSpinnerFrame}</text> : null}
|
|
179
186
|
<text fg={t.text}>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
|
|
1
3
|
import type { DirectoryResult } from '../../../../state/types'
|
|
2
4
|
|
|
3
5
|
import { abbreviatePath } from '../../../path-format'
|
|
@@ -41,15 +43,17 @@ export function CreateSessionModal({
|
|
|
41
43
|
const dirActive = activeField === 'directory'
|
|
42
44
|
const nameActive = activeField === 'name'
|
|
43
45
|
|
|
44
|
-
const items
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
const items = useMemo<FormOptionItem[]>(
|
|
47
|
+
() =>
|
|
48
|
+
results.map((result) => ({
|
|
49
|
+
key: result.path,
|
|
50
|
+
leading: <text fg={getDirectoryResultColor(result)}>{getDirectoryResultIcon(result)}</text>,
|
|
51
|
+
title: (active) => (
|
|
52
|
+
<text fg={active ? t.text : t.textMuted}>{abbreviatePath(result.path)}</text>
|
|
53
|
+
),
|
|
54
|
+
})),
|
|
55
|
+
[results, t]
|
|
56
|
+
)
|
|
53
57
|
|
|
54
58
|
return (
|
|
55
59
|
<Form
|
|
@@ -62,7 +66,11 @@ export function CreateSessionModal({
|
|
|
62
66
|
label="Search projects"
|
|
63
67
|
placeholder="Type a project name..."
|
|
64
68
|
value={directoryQuery}
|
|
65
|
-
displayValue={
|
|
69
|
+
displayValue={
|
|
70
|
+
pendingProjectPath != null && pendingProjectPath !== ''
|
|
71
|
+
? abbreviatePath(pendingProjectPath)
|
|
72
|
+
: directoryQuery
|
|
73
|
+
}
|
|
66
74
|
items={items}
|
|
67
75
|
selectedIndex={selectedIndex}
|
|
68
76
|
maxVisibleRows={VISIBLE_ROWS}
|