@brimveyn/aimux 1.4.1 → 1.5.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 +142 -176
- package/package.json +5 -2
- package/src/app-runtime/use-terminal-resize.ts +12 -2
- package/src/app.tsx +20 -3
- package/src/config.ts +53 -17
- package/src/input/keymap/build-handlers.ts +1 -0
- package/src/input/keymap/help-entries.ts +44 -0
- package/src/input/keymap/keymap-ref.ts +11 -0
- package/src/input/keymap/sequence-resolver.ts +35 -2
- package/src/input/keymap/trie.ts +1 -0
- package/src/input/modes/bridge.ts +1 -1
- package/src/input/modes/handlers/shared.ts +0 -16
- package/src/input/modes/transitions.ts +4 -4
- package/src/input/modes/types.ts +1 -1
- package/src/platform/daemon-control.ts +0 -8
- package/src/state/reducers/git-panel-state.ts +35 -8
- package/src/state/reducers/modal-state.ts +48 -3
- package/src/state/selectors.ts +1 -5
- package/src/state/session-persistence.ts +1 -20
- package/src/state/store.ts +38 -5
- package/src/state/types.ts +27 -13
- package/src/state/validation.ts +0 -2
- package/src/state/workspace-save.ts +6 -2
- package/src/ui/components/create-session-modal.tsx +5 -3
- package/src/ui/components/git-commit-modal.tsx +1 -3
- package/src/ui/components/git-pane-widget.tsx +46 -0
- package/src/ui/components/git-panel.tsx +72 -25
- package/src/ui/components/help-modal.tsx +156 -42
- package/src/ui/components/modal-keybinds-overlay.tsx +39 -0
- package/src/ui/components/modal-shell.tsx +15 -3
- package/src/ui/components/new-tab-modal.tsx +9 -10
- package/src/ui/components/pending-chord-overlay.tsx +2 -1
- package/src/ui/components/session-name-modal.tsx +1 -3
- package/src/ui/components/session-picker-modal.tsx +1 -3
- package/src/ui/components/sidebar.tsx +24 -50
- package/src/ui/components/snippet-editor-modal.tsx +1 -3
- package/src/ui/components/snippet-picker-modal.tsx +1 -3
- package/src/ui/components/status-bar.tsx +0 -4
- package/src/ui/components/theme-picker-modal.tsx +6 -3
- package/src/ui/components/update-available-modal.tsx +7 -4
- package/src/ui/keymap-context.ts +1 -6
- package/src/ui/root.tsx +29 -1
- package/src/ui/status-bar-model.ts +0 -5
- package/src/ui/directory-search.ts +0 -1
|
@@ -2,12 +2,9 @@ import type { ScrollBoxRenderable } from '@opentui/core'
|
|
|
2
2
|
|
|
3
3
|
import { memo, useMemo, useRef } from 'react'
|
|
4
4
|
|
|
5
|
-
import type { GitPanelState } from '../../state/types'
|
|
6
|
-
|
|
7
|
-
import { useGitPanelPolling } from '../../git/git-poller'
|
|
8
5
|
import { useAppStore } from '../../state/app-store'
|
|
9
6
|
import { theme } from '../theme'
|
|
10
|
-
import {
|
|
7
|
+
import { GitPaneWidget } from './git-pane-widget'
|
|
11
8
|
import { buildTabGroupInfo } from './sidebar-group-metadata'
|
|
12
9
|
import { TabItem } from './tab-item'
|
|
13
10
|
import { useSidebarAutoScroll } from './use-sidebar-auto-scroll'
|
|
@@ -143,50 +140,29 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
143
140
|
)
|
|
144
141
|
})
|
|
145
142
|
|
|
146
|
-
const GitBody = memo(function GitBody() {
|
|
147
|
-
const gitPanel = useAppStore((s) => s.gitPanel)
|
|
148
|
-
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
149
|
-
const sessions = useAppStore((s) => s.sessions)
|
|
150
|
-
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
151
|
-
const gitPanelVisible = useAppStore((s) => s.sidebar.gitPanelVisible)
|
|
152
|
-
const currentSession = currentSessionId
|
|
153
|
-
? sessions.find((s) => s.id === currentSessionId)
|
|
154
|
-
: undefined
|
|
155
|
-
const projectPath = currentSession?.projectPath
|
|
156
|
-
|
|
157
|
-
useGitPanelPolling({
|
|
158
|
-
enabled: sidebarVisible && gitPanelVisible,
|
|
159
|
-
projectPath,
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
const lastGoodRef = useRef<GitPanelState | null>(null)
|
|
163
|
-
const prevProjectPathRef = useRef(projectPath)
|
|
164
|
-
if (prevProjectPathRef.current !== projectPath) {
|
|
165
|
-
prevProjectPathRef.current = projectPath
|
|
166
|
-
lastGoodRef.current = null
|
|
167
|
-
}
|
|
168
|
-
const isGood = gitPanel.error === null && gitPanel.branch !== null
|
|
169
|
-
if (isGood) {
|
|
170
|
-
lastGoodRef.current = gitPanel
|
|
171
|
-
}
|
|
172
|
-
const display = lastGoodRef.current ?? gitPanel
|
|
173
|
-
|
|
174
|
-
return <GitPanel gitPanel={display} projectPath={projectPath} />
|
|
175
|
-
})
|
|
176
|
-
|
|
177
143
|
export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
178
144
|
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
179
145
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
180
|
-
const
|
|
181
|
-
const gitPanelRatio = useAppStore((s) => s.sidebar.gitPanelRatio)
|
|
146
|
+
const gitPane = useAppStore((s) => s.gitPane)
|
|
182
147
|
|
|
183
148
|
if (!sidebarVisible) {
|
|
184
149
|
return null
|
|
185
150
|
}
|
|
186
151
|
|
|
152
|
+
const gitEmbedded = gitPane.mode === 'embedded' && gitPane.visible
|
|
153
|
+
const gitOnTop = gitEmbedded && gitPane.position === 'top'
|
|
154
|
+
const gitOnBottom = gitEmbedded && gitPane.position === 'bottom'
|
|
155
|
+
|
|
187
156
|
// flex-grow scaled by 100 (integer preferred); tabs gets (1-ratio), git gets ratio.
|
|
188
|
-
const tabsGrow = Math.max(1, Math.round((1 -
|
|
189
|
-
const gitGrow = Math.max(1, Math.round(
|
|
157
|
+
const tabsGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.ratio) * 100)) : 1
|
|
158
|
+
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.ratio * 100)) : 0
|
|
159
|
+
|
|
160
|
+
const separator = <text fg={theme.dim}>{'·'.repeat(Math.max(0, sidebarWidth - 2))}</text>
|
|
161
|
+
const gitBody = gitEmbedded ? (
|
|
162
|
+
<box flexDirection="column" flexGrow={gitGrow} flexShrink={1} flexBasis={0} overflow="hidden">
|
|
163
|
+
<GitPaneWidget pollingEnabled={gitPane.visible} />
|
|
164
|
+
</box>
|
|
165
|
+
) : null
|
|
190
166
|
|
|
191
167
|
return (
|
|
192
168
|
<box
|
|
@@ -197,6 +173,12 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
197
173
|
gap={0}
|
|
198
174
|
>
|
|
199
175
|
<SidebarTop />
|
|
176
|
+
{gitOnTop ? (
|
|
177
|
+
<>
|
|
178
|
+
{gitBody}
|
|
179
|
+
{separator}
|
|
180
|
+
</>
|
|
181
|
+
) : null}
|
|
200
182
|
<box
|
|
201
183
|
flexDirection="column"
|
|
202
184
|
flexGrow={tabsGrow}
|
|
@@ -206,18 +188,10 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
206
188
|
>
|
|
207
189
|
<TabsBody onTabActivate={onTabActivate} />
|
|
208
190
|
</box>
|
|
209
|
-
{
|
|
191
|
+
{gitOnBottom ? (
|
|
210
192
|
<>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
flexDirection="column"
|
|
214
|
-
flexGrow={gitGrow}
|
|
215
|
-
flexShrink={1}
|
|
216
|
-
flexBasis={0}
|
|
217
|
-
overflow="hidden"
|
|
218
|
-
>
|
|
219
|
-
<GitBody />
|
|
220
|
-
</box>
|
|
193
|
+
{separator}
|
|
194
|
+
{gitBody}
|
|
221
195
|
</>
|
|
222
196
|
) : null}
|
|
223
197
|
</box>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useModalHelp } from '../keymap-context'
|
|
2
1
|
import { theme } from '../theme'
|
|
3
2
|
import { uiTokens } from '../ui-tokens'
|
|
4
3
|
import { InputField } from './input-field'
|
|
@@ -19,12 +18,11 @@ export function SnippetEditorModal({
|
|
|
19
18
|
}: SnippetEditorModalProps) {
|
|
20
19
|
const nameActive = activeField === 'name'
|
|
21
20
|
const contentActive = activeField === 'content'
|
|
22
|
-
const help = useModalHelp('modal.snippet-editor')
|
|
23
21
|
|
|
24
22
|
return (
|
|
25
23
|
<ModalShell
|
|
26
24
|
title={isEditing ? 'Edit snippet' : 'Create snippet'}
|
|
27
|
-
|
|
25
|
+
keybindsModeId="modal.snippet-editor"
|
|
28
26
|
width={uiTokens.modalWidth.xl}
|
|
29
27
|
>
|
|
30
28
|
<box flexDirection="column">
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { SnippetRecord } from '../../state/types'
|
|
2
2
|
|
|
3
3
|
import { filterSnippets } from '../../state/selectors'
|
|
4
|
-
import { useModalHelp } from '../keymap-context'
|
|
5
4
|
import { theme } from '../theme'
|
|
6
5
|
import { uiTokens } from '../ui-tokens'
|
|
7
6
|
import { ListItem } from './list-item'
|
|
@@ -24,12 +23,11 @@ function truncateContent(content: string): string {
|
|
|
24
23
|
|
|
25
24
|
export function SnippetPickerModal({ filter, selectedIndex, snippets }: SnippetPickerModalProps) {
|
|
26
25
|
const filtered = filterSnippets(snippets, filter)
|
|
27
|
-
const help = useModalHelp('modal.snippet-picker')
|
|
28
26
|
|
|
29
27
|
return (
|
|
30
28
|
<ModalShell
|
|
31
29
|
title="Snippets"
|
|
32
|
-
|
|
30
|
+
keybindsModeId="modal.snippet-picker"
|
|
33
31
|
width={uiTokens.modalWidth.xl}
|
|
34
32
|
footer={<ModalFilterBar filter={filter} />}
|
|
35
33
|
>
|
|
@@ -10,8 +10,6 @@ function getModeColor(focusMode: AppState['focusMode']): string {
|
|
|
10
10
|
switch (focusMode) {
|
|
11
11
|
case 'terminal-input':
|
|
12
12
|
return theme.accent
|
|
13
|
-
case 'layout':
|
|
14
|
-
return theme.warning
|
|
15
13
|
case 'modal':
|
|
16
14
|
return theme.warning
|
|
17
15
|
case 'command-edit':
|
|
@@ -28,8 +26,6 @@ function getModeLabel(focusMode: AppState['focusMode']): string {
|
|
|
28
26
|
switch (focusMode) {
|
|
29
27
|
case 'terminal-input':
|
|
30
28
|
return 'input'
|
|
31
|
-
case 'layout':
|
|
32
|
-
return 'layout'
|
|
33
29
|
case 'modal':
|
|
34
30
|
return 'modal'
|
|
35
31
|
case 'command-edit':
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useModalHelp } from '../keymap-context'
|
|
2
1
|
import { theme } from '../theme'
|
|
3
2
|
import { THEME_IDS, type ThemeId, THEMES } from '../themes'
|
|
4
3
|
import { uiTokens } from '../ui-tokens'
|
|
@@ -11,9 +10,13 @@ interface ThemePickerModalProps {
|
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
export function ThemePickerModal({ currentThemeId, selectedIndex }: ThemePickerModalProps) {
|
|
14
|
-
const help = useModalHelp('modal.theme-picker')
|
|
15
13
|
return (
|
|
16
|
-
<ModalShell
|
|
14
|
+
<ModalShell
|
|
15
|
+
title="Select theme"
|
|
16
|
+
keybindsModeId="modal.theme-picker"
|
|
17
|
+
width={uiTokens.modalWidth.md}
|
|
18
|
+
listGap={0}
|
|
19
|
+
>
|
|
17
20
|
{THEME_IDS.map((id, index) => {
|
|
18
21
|
const entry = THEMES[id]
|
|
19
22
|
const active = index === selectedIndex
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useModalHelp } from '../keymap-context'
|
|
2
1
|
import { theme } from '../theme'
|
|
3
2
|
import { uiTokens } from '../ui-tokens'
|
|
4
3
|
import { ListItem } from './list-item'
|
|
@@ -20,10 +19,14 @@ export function UpdateAvailableModal({
|
|
|
20
19
|
latestVersion,
|
|
21
20
|
selectedIndex,
|
|
22
21
|
}: UpdateAvailableModalProps) {
|
|
23
|
-
const modalHelp = useModalHelp('modal.update-available')
|
|
24
|
-
const help = `${currentVersion} → ${latestVersion} ${modalHelp}`
|
|
25
22
|
return (
|
|
26
|
-
<ModalShell
|
|
23
|
+
<ModalShell
|
|
24
|
+
title="Update available"
|
|
25
|
+
subtitle={`${currentVersion} → ${latestVersion}`}
|
|
26
|
+
keybindsModeId="modal.update-available"
|
|
27
|
+
width={uiTokens.modalWidth.md}
|
|
28
|
+
listGap={1}
|
|
29
|
+
>
|
|
27
30
|
<box flexDirection="row" gap={1} marginTop={1}>
|
|
28
31
|
{OPTIONS.map((option, index) => {
|
|
29
32
|
const active = index === selectedIndex
|
package/src/ui/keymap-context.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type ModeId,
|
|
4
4
|
type ResolvedKeymapConfig,
|
|
5
5
|
} from '@brimveyn/aimux-config'
|
|
6
|
-
import { createContext, useContext
|
|
6
|
+
import { createContext, useContext } from 'react'
|
|
7
7
|
|
|
8
8
|
import { describeBindings } from '../input/keymap/describe-bindings'
|
|
9
9
|
|
|
@@ -32,8 +32,3 @@ export function buildHintText(
|
|
|
32
32
|
const trimmed = bindings.slice(0, limit)
|
|
33
33
|
return trimmed.map((b) => `${b.keysDisplay} ${b.description ?? ''}`.trim()).join(HELP_JOINER)
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
export function useModalHelp(modeId: ModeId, limit?: number): string {
|
|
37
|
-
const config = useKeymap()
|
|
38
|
-
return useMemo(() => buildHintText(config, modeId, limit), [config, modeId, limit])
|
|
39
|
-
}
|
package/src/ui/root.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { useAppStore } from '../state/app-store'
|
|
|
8
8
|
import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
|
|
9
9
|
import { CreateSessionModal } from './components/create-session-modal'
|
|
10
10
|
import { GitCommitModal } from './components/git-commit-modal'
|
|
11
|
+
import { GitPaneWidget } from './components/git-pane-widget'
|
|
11
12
|
import { GitView } from './components/git-view'
|
|
12
13
|
import { HelpModal } from './components/help-modal'
|
|
13
14
|
import { NewTabModal } from './components/new-tab-modal'
|
|
@@ -144,7 +145,7 @@ function renderModal(
|
|
|
144
145
|
/>
|
|
145
146
|
)
|
|
146
147
|
case 'help':
|
|
147
|
-
return <HelpModal />
|
|
148
|
+
return <HelpModal filter={modal.editBuffer} selectedIndex={modal.selectedIndex} />
|
|
148
149
|
case 'git-commit': {
|
|
149
150
|
const titleText =
|
|
150
151
|
modal.activeField === 'title' ? (modal.editBuffer ?? '') : modal.contentBuffer
|
|
@@ -215,6 +216,10 @@ export function RootView({
|
|
|
215
216
|
const sessions = useAppStore((s) => s.sessions)
|
|
216
217
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
217
218
|
const sessionBarPosition = useAppStore((s) => s.sessionBar.position)
|
|
219
|
+
const gitPaneMode = useAppStore((s) => s.gitPane.mode)
|
|
220
|
+
const gitPaneVisible = useAppStore((s) => s.gitPane.visible)
|
|
221
|
+
const gitPanePosition = useAppStore((s) => s.gitPane.position)
|
|
222
|
+
const gitPaneRatio = useAppStore((s) => s.gitPane.ratio)
|
|
218
223
|
|
|
219
224
|
const activeTab = tabs.find((tab) => tab.id === activeTabId)
|
|
220
225
|
const activeTree = activeTabId ? getTreeForTab(layoutTrees, tabGroupMap, activeTabId) : null
|
|
@@ -248,6 +253,9 @@ export function RootView({
|
|
|
248
253
|
{sessionBarPosition === 'top' && <SessionBar />}
|
|
249
254
|
<box flexDirection="row" gap={0} padding={0} flexGrow={1}>
|
|
250
255
|
<Sidebar onTabActivate={onPaneActivate} />
|
|
256
|
+
{gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left' ? (
|
|
257
|
+
<GitPaneInPaneMode ratio={gitPaneRatio} />
|
|
258
|
+
) : null}
|
|
251
259
|
{activeTree && activeTree.type === 'split' ? (
|
|
252
260
|
<SplitLayout
|
|
253
261
|
node={activeTree}
|
|
@@ -292,6 +300,9 @@ export function RootView({
|
|
|
292
300
|
onPaneActivate={onPaneActivate}
|
|
293
301
|
/>
|
|
294
302
|
)}
|
|
303
|
+
{gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'right' ? (
|
|
304
|
+
<GitPaneInPaneMode ratio={gitPaneRatio} />
|
|
305
|
+
) : null}
|
|
295
306
|
</box>
|
|
296
307
|
{sessionBarPosition === 'bottom' && <SessionBar />}
|
|
297
308
|
<StatusBar />
|
|
@@ -309,3 +320,20 @@ export function RootView({
|
|
|
309
320
|
</box>
|
|
310
321
|
)
|
|
311
322
|
}
|
|
323
|
+
|
|
324
|
+
function GitPaneInPaneMode({ ratio }: { ratio: number }) {
|
|
325
|
+
// Ratio maps to a fixed column count (20..80), mirroring the reservation in
|
|
326
|
+
// use-terminal-resize so the terminal-content area stays in sync.
|
|
327
|
+
const width = Math.max(20, Math.min(80, Math.round(ratio * 80)))
|
|
328
|
+
return (
|
|
329
|
+
<box
|
|
330
|
+
flexDirection="column"
|
|
331
|
+
width={width}
|
|
332
|
+
flexShrink={0}
|
|
333
|
+
backgroundColor={theme.panel}
|
|
334
|
+
overflow="hidden"
|
|
335
|
+
>
|
|
336
|
+
<GitPaneWidget pollingEnabled />
|
|
337
|
+
</box>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
@@ -62,11 +62,6 @@ export function getStatusBarModel(
|
|
|
62
62
|
right: modalMode ? hintForMode(config, modalMode) : '',
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
case 'layout':
|
|
66
|
-
return {
|
|
67
|
-
left: `${getActiveTabLabel(activeTab)} ${sessionIcon} ${sessionLabel}`,
|
|
68
|
-
right: hintForMode(config, 'layout'),
|
|
69
|
-
}
|
|
70
65
|
case 'git':
|
|
71
66
|
return {
|
|
72
67
|
left: `${sessionIcon} ${sessionLabel}`,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { searchProjectDirectories as searchDirectories } from '../platform/project-search'
|