@brimveyn/aimux 1.6.1 → 1.7.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 +2 -2
- package/src/app-runtime/backend-attach-runtime.ts +6 -0
- package/src/app-runtime/backend-runtime-events.ts +21 -21
- package/src/app-runtime/side-effects.ts +43 -12
- package/src/app-runtime/use-backend-runtime.ts +2 -20
- package/src/app-runtime/use-directory-search.ts +6 -1
- package/src/app.tsx +6 -1
- package/src/config.ts +28 -1
- package/src/daemon/daemon.ts +197 -15
- package/src/daemon/session-manager.ts +9 -0
- package/src/daemon/session-registry.ts +5 -5
- package/src/git/diff-hash.ts +10 -0
- package/src/index.tsx +10 -2
- package/src/input/keymap/help-entries.ts +5 -5
- package/src/input/modes/bridge.ts +5 -8
- package/src/input/modes/transitions.ts +15 -23
- package/src/input/modes/types.ts +3 -5
- package/src/ipc/protocol.ts +49 -5
- package/src/platform/project-search.ts +45 -12
- package/src/pty/assistant-status-detection-loop.ts +192 -0
- package/src/pty/assistant-status-detector.ts +226 -0
- package/src/pty/pty-manager.ts +3 -37
- package/src/session-backend/bootstrap.ts +4 -1
- package/src/session-backend/local-session-backend.ts +43 -56
- package/src/session-backend/remote-session-backend.ts +15 -0
- package/src/session-backend/types.ts +10 -1
- package/src/state/git-tree.ts +42 -16
- package/src/state/reducers/diff-cache.ts +64 -0
- package/src/state/reducers/git-mode-state.ts +91 -33
- package/src/state/reducers/git-panel-state.ts +33 -2
- package/src/state/reducers/modal-state.ts +91 -134
- package/src/state/reducers/session-state.ts +13 -6
- package/src/state/reducers/tab-state.ts +0 -10
- package/src/state/selectors.ts +12 -0
- package/src/state/session-persistence.ts +20 -15
- package/src/state/store.ts +13 -3
- package/src/state/types.ts +87 -14
- package/src/ui/breaking-update-screen.tsx +31 -0
- package/src/ui/components/bare-input.tsx +44 -0
- package/src/ui/components/create-session-modal.tsx +16 -8
- package/src/ui/components/diff-renderer/fold-strip.tsx +5 -13
- package/src/ui/components/diff-renderer/pierre-diff.tsx +9 -31
- package/src/ui/components/diff-renderer/prepare-diff.ts +66 -0
- package/src/ui/components/diff-renderer/split-view.tsx +35 -16
- package/src/ui/components/diff-renderer/stacked-view.tsx +30 -12
- package/src/ui/components/diff-renderer/use-diff-prefetch.ts +191 -0
- package/src/ui/components/diff-renderer/use-diff-preparation.ts +106 -0
- package/src/ui/components/git-pane-widget.tsx +2 -0
- package/src/ui/components/git-panel.tsx +27 -10
- package/src/ui/components/git-view.tsx +23 -9
- package/src/ui/components/help-modal.tsx +45 -160
- package/src/ui/components/input-field.tsx +6 -2
- package/src/ui/components/list-item.tsx +36 -28
- package/src/ui/components/modal-shell.tsx +51 -13
- package/src/ui/components/new-tab-modal.tsx +78 -45
- package/src/ui/components/picker.tsx +179 -0
- package/src/ui/components/session-bar.tsx +47 -21
- package/src/ui/components/session-picker-modal.tsx +58 -56
- package/src/ui/components/sidebar.tsx +49 -22
- package/src/ui/components/snippet-picker-modal.tsx +43 -34
- package/src/ui/components/status-bar.tsx +3 -2
- package/src/ui/components/surface.tsx +11 -8
- package/src/ui/components/tab-item.tsx +38 -22
- package/src/ui/components/terminal-pane.tsx +8 -8
- package/src/ui/components/theme-picker-modal.tsx +51 -91
- package/src/ui/root.tsx +14 -23
- package/src/ui/status-bar-model.ts +5 -5
- package/src/ui/theme-store.ts +26 -2
- package/src/ui/theme.ts +9 -1
- package/src/ui/components/modal-filter-bar.tsx +0 -19
package/README.md
CHANGED
|
@@ -202,6 +202,9 @@ not collide with a globally installed `aimux` instance.
|
|
|
202
202
|
and focusing on the first change rather than the top of the file.
|
|
203
203
|
- [shiki](https://shiki.style) — syntax highlighting for the git diff view
|
|
204
204
|
and the source of the bundled theme catalog.
|
|
205
|
+
- [herdr](https://github.com/ogulcancelik/herdr) by @ogulcancelik — the
|
|
206
|
+
per-CLI assistant status heuristics (working / waiting-input / idle)
|
|
207
|
+
used in the session bar are adapted from herdr's `detect.rs` rule tables.
|
|
205
208
|
|
|
206
209
|
## License
|
|
207
210
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brimveyn/aimux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
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",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"generate-themes": "bun run scripts/generate-themes.ts"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@brimveyn/aimux-config": "0.4.
|
|
64
|
+
"@brimveyn/aimux-config": "0.4.3",
|
|
65
65
|
"@opentui/core": "^0.1.90",
|
|
66
66
|
"@opentui/react": "^0.1.90",
|
|
67
67
|
"@xterm/headless": "^6.0.0",
|
|
@@ -48,6 +48,12 @@ function hydrateAttachedSession(
|
|
|
48
48
|
tabs: result.tabs,
|
|
49
49
|
type: 'hydrate-workspace',
|
|
50
50
|
})
|
|
51
|
+
// Dispatch the session-status snapshot *after* hydrate-workspace so
|
|
52
|
+
// sidebar chips reflect per-session state on attach without waiting
|
|
53
|
+
// for the next daemon-side status transition.
|
|
54
|
+
for (const entry of result.initialSessionStatuses) {
|
|
55
|
+
dispatch({ sessionId: entry.sessionId, status: entry.status, type: 'set-session-status' })
|
|
56
|
+
}
|
|
51
57
|
resizeSnapshotPanes(workspaceSnapshot, layoutRef, backend)
|
|
52
58
|
return
|
|
53
59
|
}
|
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import type { MutableRefObject } from 'react'
|
|
2
2
|
|
|
3
3
|
import type { SessionBackend } from '../session-backend/types'
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
AppAction,
|
|
6
|
+
SessionStatus,
|
|
7
|
+
TabActivity,
|
|
8
|
+
TabSession,
|
|
9
|
+
TerminalModeState,
|
|
10
|
+
} from '../state/types'
|
|
5
11
|
|
|
6
12
|
import { logInputDebug } from '../debug/input-log'
|
|
7
13
|
import { type TabRuntimeTimeouts } from './tab-runtime-timeouts'
|
|
8
14
|
|
|
9
|
-
const IDLE_ACTIVITY_TIMEOUT_MS = 2_000
|
|
10
|
-
|
|
11
15
|
interface BindBackendRuntimeEventsOptions {
|
|
12
16
|
backend: SessionBackend
|
|
13
17
|
dispatch: (action: AppAction) => void
|
|
14
18
|
resizingRef: MutableRefObject<boolean>
|
|
15
|
-
timeouts: Pick<
|
|
16
|
-
TabRuntimeTimeouts,
|
|
17
|
-
| 'clearIdleTimer'
|
|
18
|
-
| 'clearStartupGrace'
|
|
19
|
-
| 'isStartupGraceActive'
|
|
20
|
-
| 'scheduleIdle'
|
|
21
|
-
| 'clearAllTimers'
|
|
22
|
-
>
|
|
19
|
+
timeouts: Pick<TabRuntimeTimeouts, 'clearIdleTimer' | 'clearStartupGrace' | 'clearAllTimers'>
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
function clearTabRuntimeState(
|
|
@@ -58,19 +55,14 @@ export function bindBackendRuntimeEvents({
|
|
|
58
55
|
type: 'replace-tab-viewport',
|
|
59
56
|
viewport,
|
|
60
57
|
})
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
dispatch({ activity: 'busy', tabId, type: 'set-tab-activity' })
|
|
66
|
-
timeouts.scheduleIdle(tabId, IDLE_ACTIVITY_TIMEOUT_MS)
|
|
58
|
+
// Per-tab activity is driven by the backend's status-detection loop via
|
|
59
|
+
// the `tabActivity` event — no client-side idle timer needed.
|
|
67
60
|
}
|
|
68
61
|
|
|
69
62
|
const handleExit = (tabId: string, exitCode: number) => {
|
|
70
63
|
logInputDebug('app.backend.event.exit', { exitCode, tabId })
|
|
71
64
|
clearTabRuntimeState(timeouts, tabId)
|
|
72
|
-
dispatch({
|
|
73
|
-
dispatch({ activity: undefined, tabId, type: 'set-tab-activity' })
|
|
65
|
+
dispatch({ tabId, type: 'close-tab' })
|
|
74
66
|
}
|
|
75
67
|
|
|
76
68
|
const handleError = (tabId: string, message: string) => {
|
|
@@ -79,14 +71,21 @@ export function bindBackendRuntimeEvents({
|
|
|
79
71
|
dispatch({ message, tabId, type: 'set-tab-error' })
|
|
80
72
|
}
|
|
81
73
|
|
|
82
|
-
const handleSessionActivity = (sessionId: string,
|
|
83
|
-
|
|
74
|
+
const handleSessionActivity = (sessionId: string, status: SessionStatus) => {
|
|
75
|
+
logInputDebug('app.backend.event.sessionActivity', { sessionId, status })
|
|
76
|
+
dispatch({ sessionId, status, type: 'set-session-status' })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const handleTabActivity = (tabId: string, activity: TabActivity) => {
|
|
80
|
+
logInputDebug('app.backend.event.tabActivity', { activity, tabId })
|
|
81
|
+
dispatch({ activity, tabId, type: 'set-tab-activity' })
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
backend.on('render', handleRender)
|
|
87
85
|
backend.on('exit', handleExit)
|
|
88
86
|
backend.on('error', handleError)
|
|
89
87
|
backend.on('sessionActivity', handleSessionActivity)
|
|
88
|
+
backend.on('tabActivity', handleTabActivity)
|
|
90
89
|
|
|
91
90
|
return () => {
|
|
92
91
|
timeouts.clearAllTimers()
|
|
@@ -94,6 +93,7 @@ export function bindBackendRuntimeEvents({
|
|
|
94
93
|
backend.off('exit', handleExit)
|
|
95
94
|
backend.off('error', handleError)
|
|
96
95
|
backend.off('sessionActivity', handleSessionActivity)
|
|
96
|
+
backend.off('tabActivity', handleTabActivity)
|
|
97
97
|
void backend.destroy(true)
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
type SplitDirection,
|
|
25
25
|
splitNode,
|
|
26
26
|
} from '../state/layout-tree'
|
|
27
|
-
import { filterSessions, filterSnippets } from '../state/selectors'
|
|
27
|
+
import { filterAssistants, filterSessions, filterSnippets } from '../state/selectors'
|
|
28
28
|
import { createDefaultTerminalModes } from '../state/terminal-modes'
|
|
29
29
|
import {
|
|
30
30
|
type AppAction,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
import { saveCurrentWorkspace } from '../state/workspace-save'
|
|
37
37
|
import { filterThemeIds } from '../ui/filter-themes'
|
|
38
38
|
import { scrollGitDiff } from '../ui/git-view-controls'
|
|
39
|
-
import { applyTheme } from '../ui/theme'
|
|
39
|
+
import { applyTheme, getTransparent, setTransparent } from '../ui/theme'
|
|
40
40
|
import { type ThemeId } from '../ui/themes'
|
|
41
41
|
import {
|
|
42
42
|
handleCreateSessionEffect,
|
|
@@ -68,9 +68,10 @@ export interface SideEffectContext {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function getSelectedAssistantOption(state: AppState) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
71
|
+
const all = getAllAssistantOptions(state.customCommands)
|
|
72
|
+
const filter = state.modal.type === 'new-tab' ? state.modal.editBuffer : null
|
|
73
|
+
const list = filterAssistants(all, filter)
|
|
74
|
+
return list[state.modal.selectedIndex] ?? list[0] ?? getAssistantOption(0)
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function handleSessionSelection(ctx: SideEffectContext): void {
|
|
@@ -87,7 +88,7 @@ function handleSessionSelection(ctx: SideEffectContext): void {
|
|
|
87
88
|
return
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
dispatch({ type: 'open-create-session-modal' })
|
|
91
|
+
dispatch({ returnToSessionPicker: true, type: 'open-create-session-modal' })
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
function handleSelectedSessionDelete(ctx: SideEffectContext): void {
|
|
@@ -143,24 +144,28 @@ function pasteSnippetToActiveGroup(ctx: SideEffectContext): void {
|
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
function saveCustomCommandSelection(
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
147
|
+
function saveCustomCommandSelection(ctx: SideEffectContext): void {
|
|
148
|
+
const { dispatch, state } = ctx
|
|
149
|
+
if (state.modal.type !== 'new-tab' || state.modal.editingCommand === null) {
|
|
149
150
|
return
|
|
150
151
|
}
|
|
152
|
+
const assistantId = state.modal.editingCommand
|
|
153
|
+
if (state.modal.editBuffer === null) return
|
|
151
154
|
|
|
152
155
|
const trimmed = state.modal.editBuffer.trim()
|
|
153
156
|
const newCustomCommands = { ...state.customCommands }
|
|
154
157
|
if (trimmed) {
|
|
155
|
-
newCustomCommands[
|
|
158
|
+
newCustomCommands[assistantId] = trimmed
|
|
156
159
|
} else {
|
|
157
|
-
delete newCustomCommands[
|
|
160
|
+
delete newCustomCommands[assistantId]
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
saveConfig({
|
|
161
164
|
...loadConfig(),
|
|
162
165
|
customCommands: newCustomCommands,
|
|
163
166
|
})
|
|
167
|
+
dispatch({ customCommands: newCustomCommands, type: 'set-custom-commands' })
|
|
168
|
+
dispatch({ type: 'cancel-command-edit' })
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
function applyThemeEffect(
|
|
@@ -299,6 +304,7 @@ function launchAssistant(ctx: SideEffectContext, assistant: AssistantId): void {
|
|
|
299
304
|
tabId: tab.id,
|
|
300
305
|
})
|
|
301
306
|
dispatch({ tab, type: 'add-tab' })
|
|
307
|
+
dispatch({ focusMode: 'terminal-input', type: 'set-focus-mode' })
|
|
302
308
|
startTabSession(
|
|
303
309
|
backend,
|
|
304
310
|
dispatch,
|
|
@@ -435,7 +441,7 @@ export function executeSideEffect(effect: SideEffect, ctx: SideEffectContext): v
|
|
|
435
441
|
return
|
|
436
442
|
}
|
|
437
443
|
case 'save-custom-command': {
|
|
438
|
-
saveCustomCommandSelection(
|
|
444
|
+
saveCustomCommandSelection(ctx)
|
|
439
445
|
return
|
|
440
446
|
}
|
|
441
447
|
case 'apply-theme': {
|
|
@@ -471,6 +477,7 @@ export function executeSideEffect(effect: SideEffect, ctx: SideEffectContext): v
|
|
|
471
477
|
mode: persistedGitPane?.mode ?? 'embedded',
|
|
472
478
|
position: persistedGitPane?.position ?? 'bottom',
|
|
473
479
|
ratio: persistedGitPane?.ratio ?? 0.5,
|
|
480
|
+
treeCompaction: persistedGitPane?.treeCompaction,
|
|
474
481
|
visible: persistedGitPane?.visible ?? true,
|
|
475
482
|
},
|
|
476
483
|
})
|
|
@@ -487,6 +494,24 @@ export function executeSideEffect(effect: SideEffect, ctx: SideEffectContext): v
|
|
|
487
494
|
mode: persistedGitPane?.mode ?? 'embedded',
|
|
488
495
|
position: persistedGitPane?.position ?? 'bottom',
|
|
489
496
|
ratio: persistedGitPane?.ratio ?? 0.5,
|
|
497
|
+
treeCompaction: persistedGitPane?.treeCompaction,
|
|
498
|
+
visible: persistedGitPane?.visible ?? true,
|
|
499
|
+
},
|
|
500
|
+
})
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
case 'persist-git-tree-compaction': {
|
|
504
|
+
const config = loadConfig()
|
|
505
|
+
const persistedGitPane = config.gitPane
|
|
506
|
+
saveConfig({
|
|
507
|
+
...config,
|
|
508
|
+
gitPane: {
|
|
509
|
+
diffModeRatio: persistedGitPane?.diffModeRatio,
|
|
510
|
+
fileListMode: persistedGitPane?.fileListMode,
|
|
511
|
+
mode: persistedGitPane?.mode ?? 'embedded',
|
|
512
|
+
position: persistedGitPane?.position ?? 'bottom',
|
|
513
|
+
ratio: persistedGitPane?.ratio ?? 0.5,
|
|
514
|
+
treeCompaction: effect.enabled,
|
|
490
515
|
visible: persistedGitPane?.visible ?? true,
|
|
491
516
|
},
|
|
492
517
|
})
|
|
@@ -527,6 +552,12 @@ export function executeSideEffect(effect: SideEffect, ctx: SideEffectContext): v
|
|
|
527
552
|
handleSwitchSessionByIndex(ctx, effect.index)
|
|
528
553
|
return
|
|
529
554
|
}
|
|
555
|
+
case 'toggle-transparent': {
|
|
556
|
+
const next = !getTransparent()
|
|
557
|
+
setTransparent(next)
|
|
558
|
+
saveConfig({ ...loadConfig(), themeTransparent: next })
|
|
559
|
+
return
|
|
560
|
+
}
|
|
530
561
|
default:
|
|
531
562
|
effect satisfies never
|
|
532
563
|
}
|
|
@@ -36,14 +36,7 @@ export function useBackendRuntime({
|
|
|
36
36
|
}: BackendRuntimeOptions): TabRuntimeControls {
|
|
37
37
|
const attachRequestIdRef = useRef(0)
|
|
38
38
|
const timeouts = useTabRuntimeTimeouts(dispatch)
|
|
39
|
-
const {
|
|
40
|
-
clearAllTimers,
|
|
41
|
-
clearIdleTimer,
|
|
42
|
-
clearStartupGrace,
|
|
43
|
-
isStartupGraceActive,
|
|
44
|
-
scheduleIdle,
|
|
45
|
-
startStartupGrace,
|
|
46
|
-
} = timeouts
|
|
39
|
+
const { clearAllTimers, clearIdleTimer, clearStartupGrace, startStartupGrace } = timeouts
|
|
47
40
|
|
|
48
41
|
useEffect(() => {
|
|
49
42
|
if (!currentSessionId) {
|
|
@@ -81,20 +74,9 @@ export function useBackendRuntime({
|
|
|
81
74
|
clearAllTimers,
|
|
82
75
|
clearIdleTimer,
|
|
83
76
|
clearStartupGrace,
|
|
84
|
-
isStartupGraceActive,
|
|
85
|
-
scheduleIdle,
|
|
86
77
|
},
|
|
87
78
|
})
|
|
88
|
-
}, [
|
|
89
|
-
backend,
|
|
90
|
-
clearAllTimers,
|
|
91
|
-
clearIdleTimer,
|
|
92
|
-
clearStartupGrace,
|
|
93
|
-
dispatch,
|
|
94
|
-
isStartupGraceActive,
|
|
95
|
-
resizingRef,
|
|
96
|
-
scheduleIdle,
|
|
97
|
-
])
|
|
79
|
+
}, [backend, clearAllTimers, clearIdleTimer, clearStartupGrace, dispatch, resizingRef])
|
|
98
80
|
|
|
99
81
|
return {
|
|
100
82
|
clearIdleTimer,
|
|
@@ -2,7 +2,7 @@ import { useEffect } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import type { AppAction, ModalState } from '../state/types'
|
|
4
4
|
|
|
5
|
-
import { searchProjectDirectories } from '../platform/project-search'
|
|
5
|
+
import { searchProjectDirectories, warmDirectoryCache } from '../platform/project-search'
|
|
6
6
|
|
|
7
7
|
const DEFAULT_DIRECTORY_SEARCH_DEBOUNCE_MS = 200
|
|
8
8
|
|
|
@@ -25,6 +25,11 @@ export function useDirectorySearch(
|
|
|
25
25
|
): void {
|
|
26
26
|
const directoryQuery = getDirectoryQuery(modal)
|
|
27
27
|
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (modal.type !== 'create-session') return
|
|
30
|
+
void warmDirectoryCache()
|
|
31
|
+
}, [modal.type])
|
|
32
|
+
|
|
28
33
|
useEffect(() => {
|
|
29
34
|
if (modal.type !== 'create-session') {
|
|
30
35
|
return
|
package/src/app.tsx
CHANGED
|
@@ -37,7 +37,7 @@ import { loadSnippetCatalog } from './state/snippet-catalog'
|
|
|
37
37
|
import { appReducer, createInitialState } from './state/store'
|
|
38
38
|
import { KeymapContext } from './ui/keymap-context'
|
|
39
39
|
import { RootView } from './ui/root'
|
|
40
|
-
import { applyTheme } from './ui/theme'
|
|
40
|
+
import { applyTheme, setTransparent } from './ui/theme'
|
|
41
41
|
import { isKnownThemeId, registerUserThemes, type ThemeId } from './ui/themes'
|
|
42
42
|
import {
|
|
43
43
|
fetchLatestNpmVersion,
|
|
@@ -75,6 +75,7 @@ export function App({
|
|
|
75
75
|
: undefined
|
|
76
76
|
const initial = persisted ?? fromConfig ?? 'aimux'
|
|
77
77
|
applyTheme(initial)
|
|
78
|
+
setTransparent(config.themeTransparent ?? false)
|
|
78
79
|
return initial
|
|
79
80
|
})
|
|
80
81
|
const [state, dispatch] = useReducer(appReducer, undefined, () => {
|
|
@@ -88,10 +89,14 @@ export function App({
|
|
|
88
89
|
const userGitPane = resolvedConfig.gitPane
|
|
89
90
|
const fileListMode = userGitPane?.fileListMode ?? json.gitPane?.fileListMode ?? 'tree'
|
|
90
91
|
const diffModeRatio = userGitPane?.diffModeRatio ?? json.gitPane?.diffModeRatio ?? 0.35
|
|
92
|
+
const treeCompaction = userGitPane?.treeCompaction ?? json.gitPane?.treeCompaction ?? true
|
|
93
|
+
const prefetchRadius = userGitPane?.prefetchRadius ?? json.gitPane?.prefetchRadius ?? 5
|
|
91
94
|
const gitPaneOverrides = {
|
|
92
95
|
...json.gitPane,
|
|
93
96
|
diffModeRatio,
|
|
94
97
|
fileListMode,
|
|
98
|
+
prefetchRadius,
|
|
99
|
+
treeCompaction,
|
|
95
100
|
...(userGitPane?.visible !== undefined ? { visible: userGitPane.visible } : {}),
|
|
96
101
|
...(userGitPane?.mode !== undefined ? { mode: userGitPane.mode } : {}),
|
|
97
102
|
...(userGitPane?.position !== undefined ? { position: userGitPane.position } : {}),
|
package/src/config.ts
CHANGED
|
@@ -25,6 +25,8 @@ export const CONFIG_PATH = `${getProfileConfigDir()}/aimux.json`
|
|
|
25
25
|
export interface PersistedGitPane {
|
|
26
26
|
diffModeRatio?: number
|
|
27
27
|
fileListMode?: GitFileListMode
|
|
28
|
+
treeCompaction?: boolean
|
|
29
|
+
prefetchRadius?: number
|
|
28
30
|
visible: boolean
|
|
29
31
|
mode: 'embedded' | 'pane'
|
|
30
32
|
position: 'top' | 'bottom' | 'left' | 'right'
|
|
@@ -35,6 +37,7 @@ export interface AimuxConfig {
|
|
|
35
37
|
version: 2
|
|
36
38
|
customCommands: Record<string, string>
|
|
37
39
|
themeId?: ThemeId
|
|
40
|
+
themeTransparent?: boolean
|
|
38
41
|
gitPane?: PersistedGitPane
|
|
39
42
|
sessionBarVisible?: boolean
|
|
40
43
|
sessionBarPosition?: SessionBarPosition
|
|
@@ -62,7 +65,23 @@ function isPersistedGitPane(value: unknown): value is PersistedGitPane {
|
|
|
62
65
|
const visibleOk = typeof v.visible === 'boolean'
|
|
63
66
|
const fileListModeOk =
|
|
64
67
|
v.fileListMode === undefined || v.fileListMode === 'tree' || v.fileListMode === 'flat'
|
|
65
|
-
|
|
68
|
+
const treeCompactionOk = v.treeCompaction === undefined || typeof v.treeCompaction === 'boolean'
|
|
69
|
+
const prefetchRadiusOk =
|
|
70
|
+
v.prefetchRadius === undefined ||
|
|
71
|
+
(typeof v.prefetchRadius === 'number' &&
|
|
72
|
+
Number.isFinite(v.prefetchRadius) &&
|
|
73
|
+
v.prefetchRadius >= 0 &&
|
|
74
|
+
v.prefetchRadius <= 50)
|
|
75
|
+
if (
|
|
76
|
+
!modeOk ||
|
|
77
|
+
!positionOk ||
|
|
78
|
+
!ratioOk ||
|
|
79
|
+
!diffModeRatioOk ||
|
|
80
|
+
!visibleOk ||
|
|
81
|
+
!fileListModeOk ||
|
|
82
|
+
!treeCompactionOk ||
|
|
83
|
+
!prefetchRadiusOk
|
|
84
|
+
) {
|
|
66
85
|
return false
|
|
67
86
|
}
|
|
68
87
|
// cross-field coherence: embedded => top|bottom; pane => left|right
|
|
@@ -108,6 +127,7 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
108
127
|
version?: number
|
|
109
128
|
customCommands?: unknown
|
|
110
129
|
themeId?: unknown
|
|
130
|
+
themeTransparent?: unknown
|
|
111
131
|
gitPane?: unknown
|
|
112
132
|
gitPanelVisible?: unknown
|
|
113
133
|
gitPanelRatio?: unknown
|
|
@@ -131,6 +151,12 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
131
151
|
issues.push('ignored invalid themeId')
|
|
132
152
|
}
|
|
133
153
|
|
|
154
|
+
const validThemeTransparent =
|
|
155
|
+
typeof parsed.themeTransparent === 'boolean' ? parsed.themeTransparent : undefined
|
|
156
|
+
if (parsed.themeTransparent !== undefined && validThemeTransparent === undefined) {
|
|
157
|
+
issues.push('ignored invalid themeTransparent')
|
|
158
|
+
}
|
|
159
|
+
|
|
134
160
|
let validGitPane = isPersistedGitPane(parsed.gitPane) ? parsed.gitPane : undefined
|
|
135
161
|
if (parsed.gitPane !== undefined && validGitPane === undefined) {
|
|
136
162
|
issues.push('ignored invalid gitPane')
|
|
@@ -200,6 +226,7 @@ export function loadConfigResult(): ConfigLoadResult {
|
|
|
200
226
|
sessionBarVisible: validSessionBarVisible,
|
|
201
227
|
skippedUpdateVersion: validSkippedUpdateVersion,
|
|
202
228
|
themeId: migrateThemeId(parsed.themeId),
|
|
229
|
+
themeTransparent: validThemeTransparent,
|
|
203
230
|
version: 2,
|
|
204
231
|
workspaceSnapshot: isWorkspaceSnapshotV1(parsed.workspaceSnapshot)
|
|
205
232
|
? parsed.workspaceSnapshot
|