@brimveyn/aimux 1.12.6 → 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.
- 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 +18 -2
- 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 +538 -83
- 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 +3 -3
- 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 +1 -1
- 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 +3 -3
- 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 +2 -1
- 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 +6 -4
- package/src/ui/components/git/diff-renderer/stacked-view.tsx +6 -4
- package/src/ui/components/git/diff-renderer/use-diff-prefetch.ts +19 -6
- package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +2 -2
- package/src/ui/components/git/git-panel.tsx +17 -10
- package/src/ui/components/git/git-view.tsx +79 -19
- package/src/ui/components/git/image-diff/image-diff-view.tsx +2 -2
- package/src/ui/components/git/image-diff/terminal-image-pane.tsx +5 -4
- 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 +3 -3
- package/src/ui/components/layout/sidebar/sidebar.tsx +162 -36
- package/src/ui/components/layout/sidebar/tab-item.tsx +17 -1
- 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 +1 -1
- package/src/ui/components/layout/terminal-pane.tsx +43 -40
- package/src/ui/components/modals/app/ai-usage-modal.tsx +8 -5
- package/src/ui/components/modals/app/help-modal.tsx +4 -2
- package/src/ui/components/modals/git/git-commit-modal.tsx +2 -2
- package/src/ui/components/modals/sessions/create-session-modal.tsx +5 -1
- package/src/ui/components/modals/sessions/session-picker-modal.tsx +5 -4
- package/src/ui/components/modals/shared/form.tsx +4 -5
- package/src/ui/components/modals/shared/modal-shell.tsx +3 -3
- package/src/ui/components/modals/shared/picker.tsx +28 -5
- package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +11 -3
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +173 -5
- package/src/ui/components/modals/worktree/worktree-move-modal.tsx +94 -0
- package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +3 -3
- 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 +3 -3
- package/src/ui/filter-themes.ts +1 -1
- package/src/ui/root.tsx +57 -3
- 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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Toast, TOAST_CONFIG, type ToastPosition } from '../../../../state/toast-store'
|
|
2
|
+
import { ToastItem } from './toast-item'
|
|
3
|
+
import { parseToastPosition, stackContainerProps } from './toast-position'
|
|
4
|
+
|
|
5
|
+
const EDGE_MARGIN = 1
|
|
6
|
+
|
|
7
|
+
interface ToastStackProps {
|
|
8
|
+
position: ToastPosition
|
|
9
|
+
toasts: Toast[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// One corner's column of toasts. Newest sits nearest the anchored edge; the stack
|
|
13
|
+
// is capped at maxVisible (older toasts drop off, though they usually auto-dismiss).
|
|
14
|
+
export function ToastStack({ position, toasts }: ToastStackProps) {
|
|
15
|
+
const anchor = parseToastPosition(position)
|
|
16
|
+
const recent = toasts.slice(-TOAST_CONFIG.maxVisible)
|
|
17
|
+
const ordered = anchor.vertical === 'top' ? [...recent].reverse() : recent
|
|
18
|
+
return (
|
|
19
|
+
<box
|
|
20
|
+
position="absolute"
|
|
21
|
+
flexDirection="column"
|
|
22
|
+
gap={TOAST_CONFIG.gap}
|
|
23
|
+
{...stackContainerProps(anchor, EDGE_MARGIN)}
|
|
24
|
+
>
|
|
25
|
+
{ordered.map((toast) => (
|
|
26
|
+
<ToastItem key={toast.id} toast={toast} anchor={anchor} />
|
|
27
|
+
))}
|
|
28
|
+
</box>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Toast, type ToastPosition, useToastStore } from '../../../../state/toast-store'
|
|
2
|
+
import { ToastStack } from './toast-stack'
|
|
3
|
+
|
|
4
|
+
// Mounted once at the root. Groups active toasts by position and renders one
|
|
5
|
+
// absolutely-positioned stack per occupied corner, on top of everything else.
|
|
6
|
+
export function ToastViewport() {
|
|
7
|
+
const toasts = useToastStore((state) => state.toasts)
|
|
8
|
+
if (toasts.length === 0) return null
|
|
9
|
+
|
|
10
|
+
const byPosition = new Map<ToastPosition, Toast[]>()
|
|
11
|
+
for (const entry of toasts) {
|
|
12
|
+
const group = byPosition.get(entry.position) ?? []
|
|
13
|
+
group.push(entry)
|
|
14
|
+
byPosition.set(entry.position, group)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
{[...byPosition].map(([position, group]) => (
|
|
20
|
+
<ToastStack key={position} position={position} toasts={group} />
|
|
21
|
+
))}
|
|
22
|
+
</>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
const STEP_MS = 28
|
|
4
|
+
// Normalized distance off-screen: 1 = fully hidden, 0 = resting. Eased so the
|
|
5
|
+
// toast decelerates in and accelerates out.
|
|
6
|
+
const ENTER_FRAMES = [1, 0.55, 0.3, 0.15, 0.05, 0]
|
|
7
|
+
const EXIT_FRAMES = [0.2, 0.45, 0.72, 1]
|
|
8
|
+
|
|
9
|
+
interface ToastAnimationOptions {
|
|
10
|
+
reduceMotion: boolean
|
|
11
|
+
onExited: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Drives a toast's slide via a frame loop (terminals have no opacity/transform,
|
|
16
|
+
* so motion is a stepped integer offset). Returns `away` in [0,1] — multiply by a
|
|
17
|
+
* pixel distance to get the cell offset — and `requestExit` to slide back out.
|
|
18
|
+
*/
|
|
19
|
+
export function useToastAnimation({ onExited, reduceMotion }: ToastAnimationOptions): {
|
|
20
|
+
away: number
|
|
21
|
+
requestExit: () => void
|
|
22
|
+
} {
|
|
23
|
+
const [away, setAway] = useState(reduceMotion ? 0 : 1)
|
|
24
|
+
const exitingRef = useRef(false)
|
|
25
|
+
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
26
|
+
const onExitedRef = useRef(onExited)
|
|
27
|
+
onExitedRef.current = onExited
|
|
28
|
+
|
|
29
|
+
const runFrames = useCallback((frames: number[], done?: () => void) => {
|
|
30
|
+
if (timerRef.current != null) clearInterval(timerRef.current)
|
|
31
|
+
let index = 0
|
|
32
|
+
setAway(frames[0] ?? 0)
|
|
33
|
+
timerRef.current = setInterval(() => {
|
|
34
|
+
index += 1
|
|
35
|
+
if (index >= frames.length) {
|
|
36
|
+
if (timerRef.current != null) clearInterval(timerRef.current)
|
|
37
|
+
timerRef.current = null
|
|
38
|
+
done?.()
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
setAway(frames[index] ?? 0)
|
|
42
|
+
}, STEP_MS)
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (reduceMotion) return
|
|
47
|
+
runFrames(ENTER_FRAMES)
|
|
48
|
+
return () => {
|
|
49
|
+
if (timerRef.current != null) clearInterval(timerRef.current)
|
|
50
|
+
}
|
|
51
|
+
}, [reduceMotion, runFrames])
|
|
52
|
+
|
|
53
|
+
const requestExit = useCallback(() => {
|
|
54
|
+
if (exitingRef.current) return
|
|
55
|
+
exitingRef.current = true
|
|
56
|
+
if (reduceMotion) {
|
|
57
|
+
onExitedRef.current()
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
runFrames(EXIT_FRAMES, () => onExitedRef.current())
|
|
61
|
+
}, [reduceMotion, runFrames])
|
|
62
|
+
|
|
63
|
+
return { away, requestExit }
|
|
64
|
+
}
|
|
@@ -12,7 +12,7 @@ export function InputField({ active, cursorPos, placeholder, value }: InputField
|
|
|
12
12
|
const t = useTheme()
|
|
13
13
|
const fg = active ? t.text : t.textMuted
|
|
14
14
|
if (!active) {
|
|
15
|
-
const showPlaceholder = !value && !!placeholder
|
|
15
|
+
const showPlaceholder = !value && !!(placeholder != null && placeholder !== '')
|
|
16
16
|
return (
|
|
17
17
|
<Surface tone="input" padding={1}>
|
|
18
18
|
<text fg={showPlaceholder ? t.textMuted : fg}>{showPlaceholder ? placeholder : value}</text>
|
|
@@ -40,11 +40,11 @@ export function ListItem({
|
|
|
40
40
|
</>
|
|
41
41
|
)}
|
|
42
42
|
{leading}
|
|
43
|
-
{leading ? <text> </text> : null}
|
|
43
|
+
{leading != null ? <text> </text> : null}
|
|
44
44
|
<box flexGrow={isRow ? 0 : 1}>{title}</box>
|
|
45
|
-
{trailing ? <box>{trailing}</box> : null}
|
|
45
|
+
{trailing != null ? <box>{trailing}</box> : null}
|
|
46
46
|
</box>
|
|
47
|
-
{subtitle ? <box paddingLeft={2}>{subtitle}</box> : null}
|
|
47
|
+
{subtitle != null ? <box paddingLeft={2}>{subtitle}</box> : null}
|
|
48
48
|
</box>
|
|
49
49
|
)
|
|
50
50
|
return (
|
package/src/ui/filter-themes.ts
CHANGED
|
@@ -8,7 +8,7 @@ export function themeDisplayName(id: ThemeId): string {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function filterThemeIds(filter: string | null): ThemeId[] {
|
|
11
|
-
if (!filter) return THEME_IDS
|
|
11
|
+
if (!(filter != null && filter !== '')) return THEME_IDS
|
|
12
12
|
const needle = filter.toLowerCase()
|
|
13
13
|
return THEME_IDS.filter((id) => {
|
|
14
14
|
return id.toLowerCase().includes(needle) || themeDisplayName(id).toLowerCase().includes(needle)
|
package/src/ui/root.tsx
CHANGED
|
@@ -28,9 +28,11 @@ import { SnippetEditorModal } from './components/modals/snippets/snippet-editor-
|
|
|
28
28
|
import { SnippetPickerModal } from './components/modals/snippets/snippet-picker-modal'
|
|
29
29
|
import { NewTabModal } from './components/modals/tabs/new-tab-modal'
|
|
30
30
|
import { ThemePickerModal } from './components/modals/themes/theme-picker-modal'
|
|
31
|
+
import { WorktreeMoveModal } from './components/modals/worktree/worktree-move-modal'
|
|
31
32
|
import { ContextMenuBox } from './components/overlays/context-menu/context-menu-box'
|
|
32
33
|
import { ContextMenuOverlay } from './components/overlays/context-menu/context-menu-overlay'
|
|
33
34
|
import { PendingChordOverlay } from './components/overlays/pending-chord-overlay'
|
|
35
|
+
import { ToastViewport } from './components/overlays/toast/toast-viewport'
|
|
34
36
|
import { useTheme } from './theme'
|
|
35
37
|
|
|
36
38
|
function getCreateSessionFields(modal: ModalState) {
|
|
@@ -78,6 +80,7 @@ function renderModal(
|
|
|
78
80
|
focusMode: FocusMode
|
|
79
81
|
activeAssistant?: string
|
|
80
82
|
autoCommitModel?: string
|
|
83
|
+
worktreeDivergence: Record<string, { ahead: number; behind: number }>
|
|
81
84
|
}
|
|
82
85
|
) {
|
|
83
86
|
switch (modal.type) {
|
|
@@ -89,8 +92,24 @@ function renderModal(
|
|
|
89
92
|
customCommands={options.customCommands}
|
|
90
93
|
filter={modal.editBuffer}
|
|
91
94
|
cursorPos={modal.cursorPos}
|
|
95
|
+
currentSessionId={options.currentSessionId}
|
|
92
96
|
editingCommand={modal.type === 'new-tab' ? modal.editingCommand : null}
|
|
93
97
|
editBuffer={modal.editBuffer ?? ''}
|
|
98
|
+
activeField={modal.type === 'new-tab' ? modal.activeField : 'assistant'}
|
|
99
|
+
branchError={modal.type === 'new-tab' ? modal.branchError : null}
|
|
100
|
+
branchName={modal.type === 'new-tab' ? modal.branchName : ''}
|
|
101
|
+
createWorktree={modal.type === 'new-tab' ? modal.createWorktree : false}
|
|
102
|
+
selectedAssistantId={modal.type === 'new-tab' ? modal.selectedAssistantId : null}
|
|
103
|
+
step={modal.type === 'new-tab' ? modal.step : 'assistant'}
|
|
104
|
+
worktreeDeleteConfirmId={modal.type === 'new-tab' ? modal.worktreeDeleteConfirmId : null}
|
|
105
|
+
worktreeDeleteMessage={modal.type === 'new-tab' ? modal.worktreeDeleteMessage : null}
|
|
106
|
+
worktrees={
|
|
107
|
+
options.currentSessionId != null && options.currentSessionId !== ''
|
|
108
|
+
? (options.sessions.find((session) => session.id === options.currentSessionId)
|
|
109
|
+
?.worktrees ?? [])
|
|
110
|
+
: []
|
|
111
|
+
}
|
|
112
|
+
worktreeName={modal.type === 'new-tab' ? modal.worktreeName : ''}
|
|
94
113
|
/>
|
|
95
114
|
)
|
|
96
115
|
case 'session-picker':
|
|
@@ -107,7 +126,11 @@ function renderModal(
|
|
|
107
126
|
case 'session-name':
|
|
108
127
|
return (
|
|
109
128
|
<SessionNameModal
|
|
110
|
-
title={
|
|
129
|
+
title={
|
|
130
|
+
modal.sessionTargetId != null && modal.sessionTargetId !== ''
|
|
131
|
+
? 'Rename workspace'
|
|
132
|
+
: 'Create workspace'
|
|
133
|
+
}
|
|
111
134
|
value={modal.editBuffer ?? ''}
|
|
112
135
|
/>
|
|
113
136
|
)
|
|
@@ -161,6 +184,29 @@ function renderModal(
|
|
|
161
184
|
latestVersion={modal.latestVersion}
|
|
162
185
|
/>
|
|
163
186
|
)
|
|
187
|
+
case 'worktree-move': {
|
|
188
|
+
const session =
|
|
189
|
+
options.currentSessionId != null && options.currentSessionId !== ''
|
|
190
|
+
? options.sessions.find((entry) => entry.id === options.currentSessionId)
|
|
191
|
+
: undefined
|
|
192
|
+
const worktrees = session?.worktrees ?? []
|
|
193
|
+
const sourceId = modal.sourceWorktreeId
|
|
194
|
+
const source = worktrees.find((w) => w.id === sourceId)
|
|
195
|
+
const targets = worktrees.filter((w) => w.id !== sourceId)
|
|
196
|
+
const sourceLabel =
|
|
197
|
+
source?.branch != null && source.branch !== ''
|
|
198
|
+
? source.branch
|
|
199
|
+
: (source?.name ?? 'worktree')
|
|
200
|
+
return (
|
|
201
|
+
<WorktreeMoveModal
|
|
202
|
+
deleteSource={modal.type === 'worktree-move' ? modal.deleteSource : false}
|
|
203
|
+
divergence={options.worktreeDivergence}
|
|
204
|
+
selectedIndex={modal.selectedIndex}
|
|
205
|
+
sourceLabel={sourceLabel}
|
|
206
|
+
targets={targets}
|
|
207
|
+
/>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
164
210
|
case 'help':
|
|
165
211
|
return (
|
|
166
212
|
<HelpModal
|
|
@@ -266,6 +312,7 @@ export function RootView({
|
|
|
266
312
|
const customCommands = useAppStore((s) => s.customCommands)
|
|
267
313
|
const sessions = useAppStore((s) => s.sessions)
|
|
268
314
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
315
|
+
const worktreeDivergence = useAppStore((s) => s.worktreeDivergence)
|
|
269
316
|
const sessionBarPosition = useAppStore((s) => s.sessionBar.position)
|
|
270
317
|
const gitPaneMode = useAppStore((s) => s.gitPane.mode)
|
|
271
318
|
const gitPaneVisible = useAppStore((s) => s.gitPane.visible)
|
|
@@ -284,7 +331,10 @@ export function RootView({
|
|
|
284
331
|
: undefined
|
|
285
332
|
|
|
286
333
|
const activeTab = tabs.find((tab) => tab.id === activeTabId)
|
|
287
|
-
const activeTree =
|
|
334
|
+
const activeTree =
|
|
335
|
+
activeTabId != null && activeTabId !== ''
|
|
336
|
+
? getTreeForTab(layoutTrees, tabGroupMap, activeTabId)
|
|
337
|
+
: null
|
|
288
338
|
const createSessionFields = getCreateSessionFields(modal)
|
|
289
339
|
const snippetEditorFields = getSnippetEditorFields(modal)
|
|
290
340
|
const splitChrome = PANE_BORDER * 2
|
|
@@ -310,7 +360,9 @@ export function RootView({
|
|
|
310
360
|
snippetEditorFields,
|
|
311
361
|
snippets,
|
|
312
362
|
themeId,
|
|
363
|
+
worktreeDivergence,
|
|
313
364
|
})}
|
|
365
|
+
<ToastViewport />
|
|
314
366
|
</box>
|
|
315
367
|
)
|
|
316
368
|
}
|
|
@@ -322,7 +374,7 @@ export function RootView({
|
|
|
322
374
|
height="100%"
|
|
323
375
|
backgroundColor={editorBg}
|
|
324
376
|
onMouseDrag={(event) => {
|
|
325
|
-
if (onSeparatorDrag?.(event)) {
|
|
377
|
+
if (onSeparatorDrag?.(event) === true) {
|
|
326
378
|
event.preventDefault()
|
|
327
379
|
event.stopPropagation()
|
|
328
380
|
}
|
|
@@ -424,7 +476,9 @@ export function RootView({
|
|
|
424
476
|
snippetEditorFields,
|
|
425
477
|
snippets,
|
|
426
478
|
themeId,
|
|
479
|
+
worktreeDivergence,
|
|
427
480
|
})}
|
|
481
|
+
<ToastViewport />
|
|
428
482
|
</box>
|
|
429
483
|
)
|
|
430
484
|
}
|
|
@@ -5,7 +5,7 @@ import type { SessionRecord } from '../state/types'
|
|
|
5
5
|
* with any missing `order` falling back to `createdAt` ascending.
|
|
6
6
|
*/
|
|
7
7
|
export function orderSessionsForDisplay(sessions: SessionRecord[]): SessionRecord[] {
|
|
8
|
-
return sessions.
|
|
8
|
+
return [...sessions].sort((a, b) => {
|
|
9
9
|
const ao = a.order ?? Number.MAX_SAFE_INTEGER
|
|
10
10
|
const bo = b.order ?? Number.MAX_SAFE_INTEGER
|
|
11
11
|
if (ao !== bo) return ao - bo
|
|
@@ -27,7 +27,7 @@ export function moveIdToIdPosition(
|
|
|
27
27
|
const from = ids.indexOf(moveId)
|
|
28
28
|
const to = ids.indexOf(intoPositionOfId)
|
|
29
29
|
if (from < 0 || to < 0) return ids
|
|
30
|
-
const next = ids
|
|
30
|
+
const next = [...ids]
|
|
31
31
|
next.splice(from, 1)
|
|
32
32
|
next.splice(to, 0, moveId)
|
|
33
33
|
return next
|
|
@@ -3,6 +3,7 @@ import type { ModeId, ResolvedKeymapConfig } from '@brimveyn/aimux-config'
|
|
|
3
3
|
import type { AppState, TabSession } from '../state/types'
|
|
4
4
|
|
|
5
5
|
import { describeBindings } from '../input/keymap/describe-bindings'
|
|
6
|
+
import { getSessionProjectPath } from '../state/session-worktrees'
|
|
6
7
|
import { buildHintText } from './keymap-context'
|
|
7
8
|
import { abbreviatePath } from './path-format'
|
|
8
9
|
|
|
@@ -58,13 +59,16 @@ export function getStatusBarModel(
|
|
|
58
59
|
activeTab: TabSession | undefined,
|
|
59
60
|
config: ResolvedKeymapConfig
|
|
60
61
|
): StatusBarModel {
|
|
61
|
-
const currentSession =
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
const currentSession =
|
|
63
|
+
state.currentSessionId != null && state.currentSessionId !== ''
|
|
64
|
+
? state.sessions.find((session) => session.id === state.currentSessionId)
|
|
65
|
+
: undefined
|
|
64
66
|
const sessionName = currentSession?.name ?? 'no workspace'
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const sessionPath = getSessionProjectPath(currentSession)
|
|
68
|
+
const sessionLabel =
|
|
69
|
+
sessionPath != null && sessionPath !== ''
|
|
70
|
+
? `${sessionName} (${abbreviatePath(sessionPath)})`
|
|
71
|
+
: sessionName
|
|
68
72
|
|
|
69
73
|
// \u{f0b1} = nf-fa-briefcase (session icon)
|
|
70
74
|
const sessionIcon = '\u{f0b1}'
|
|
@@ -87,9 +91,10 @@ export function getStatusBarModel(
|
|
|
87
91
|
case 'git': {
|
|
88
92
|
const headOffset = state.gitMode.headOffset
|
|
89
93
|
const offsetTag = headOffset > 0 ? ` HEAD~${headOffset}` : ''
|
|
94
|
+
const reviewTag = state.gitMode.reviewBase ? ' vs base' : ''
|
|
90
95
|
return {
|
|
91
96
|
help: helpHintForMode(config, 'git-mode'),
|
|
92
|
-
left: `${sessionIcon} ${sessionLabel}${offsetTag}`,
|
|
97
|
+
left: `${sessionIcon} ${sessionLabel}${offsetTag}${reviewTag}`,
|
|
93
98
|
right: hintForGitMode(config, headOffset),
|
|
94
99
|
}
|
|
95
100
|
}
|
|
@@ -127,6 +132,8 @@ function deriveModalModeId(modalType: AppState['modal']['type']): ModeId | null
|
|
|
127
132
|
return 'modal.theme-picker.filtering'
|
|
128
133
|
case 'update-available':
|
|
129
134
|
return 'modal.update-available'
|
|
135
|
+
case 'worktree-move':
|
|
136
|
+
return 'modal.worktree-move'
|
|
130
137
|
default:
|
|
131
138
|
return null
|
|
132
139
|
}
|
|
@@ -11,7 +11,7 @@ let cached: GraphicsProtocol | null = null
|
|
|
11
11
|
export function detectGraphicsProtocol(renderer: CliRenderer): GraphicsProtocol {
|
|
12
12
|
if (cached !== null) return cached
|
|
13
13
|
const caps = (renderer as RendererWithCapabilities).capabilities
|
|
14
|
-
if (caps?.kitty_graphics) {
|
|
14
|
+
if (caps?.kitty_graphics === true) {
|
|
15
15
|
cached = 'kitty'
|
|
16
16
|
return cached
|
|
17
17
|
}
|
|
@@ -24,10 +24,8 @@ async function tryConverter(
|
|
|
24
24
|
stdout: 'pipe',
|
|
25
25
|
})
|
|
26
26
|
const writer = proc.stdin
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
await writer.end()
|
|
30
|
-
}
|
|
27
|
+
void writer.write(bytes)
|
|
28
|
+
await writer.end()
|
|
31
29
|
const timer = setTimeout(() => proc.kill(), timeoutMs)
|
|
32
30
|
const [out, code] = await Promise.all([new Response(proc.stdout).bytes(), proc.exited])
|
|
33
31
|
clearTimeout(timer)
|
|
@@ -85,11 +83,11 @@ export async function convertToPng(bytes: Uint8Array, mime: string): Promise<Con
|
|
|
85
83
|
const png = await renderSvgToPng(bytes)
|
|
86
84
|
result = png ? { kind: 'ok', png } : { kind: 'error', reason: 'failed to render SVG' }
|
|
87
85
|
} else {
|
|
88
|
-
const attempts:
|
|
89
|
-
() => tryConverter(['magick', '-', 'png:-'], bytes, 1500),
|
|
90
|
-
() => tryConverter(['convert', '-', 'png:-'], bytes, 1500),
|
|
91
|
-
() => trySips(bytes),
|
|
92
|
-
() =>
|
|
86
|
+
const attempts: (() => Promise<Uint8Array | null>)[] = [
|
|
87
|
+
async () => tryConverter(['magick', '-', 'png:-'], bytes, 1500),
|
|
88
|
+
async () => tryConverter(['convert', '-', 'png:-'], bytes, 1500),
|
|
89
|
+
async () => trySips(bytes),
|
|
90
|
+
async () =>
|
|
93
91
|
tryConverter(
|
|
94
92
|
[
|
|
95
93
|
'ffmpeg',
|
|
@@ -53,13 +53,8 @@ export function uploadPngEscape(pngBytes: Uint8Array, id: number): string {
|
|
|
53
53
|
for (let i = 0; i < chunks.length; i++) {
|
|
54
54
|
const isLast = i === chunks.length - 1
|
|
55
55
|
const m = isLast ? 0 : 1
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// a=t (transmit), t=d (direct), f=100 (PNG), q=2 (quiet).
|
|
59
|
-
header = `q=2,a=t,t=d,f=100,i=${id},m=${m}`
|
|
60
|
-
} else {
|
|
61
|
-
header = `m=${m},q=2`
|
|
62
|
-
}
|
|
56
|
+
// a=t (transmit), t=d (direct), f=100 (PNG), q=2 (quiet).
|
|
57
|
+
const header = i === 0 ? `q=2,a=t,t=d,f=100,i=${id},m=${m}` : `m=${m},q=2`
|
|
63
58
|
parts.push(`${ESC}_G${header};${chunks[i]}${ST}`)
|
|
64
59
|
}
|
|
65
60
|
return wrapForTmux(parts.join(''))
|
|
@@ -9,7 +9,7 @@ export async function getCurrentPackageVersion(): Promise<string> {
|
|
|
9
9
|
|
|
10
10
|
export async function fetchLatestNpmVersion(packageName: string): Promise<string | null> {
|
|
11
11
|
const debugOverride = process.env.AIMUX_DEBUG_UPDATE_LATEST
|
|
12
|
-
if (debugOverride) {
|
|
12
|
+
if (debugOverride != null && debugOverride !== '') {
|
|
13
13
|
return debugOverride
|
|
14
14
|
}
|
|
15
15
|
|