@brimveyn/aimux 1.9.0 → 1.9.1
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/package.json +2 -2
- package/src/app-runtime/side-effects.ts +6 -1
- package/src/app.tsx +20 -16
- package/src/input/keymap/describe-bindings.ts +63 -2
- package/src/pty/terminal-snapshot.ts +7 -5
- package/src/state/reducers/git-mode-state.ts +58 -0
- package/src/state/types.ts +11 -6
- package/src/ui/breaking-update-screen.tsx +2 -2
- package/src/ui/components/git/diff-renderer/build-rows.ts +467 -0
- package/src/ui/components/{diff-renderer → git/diff-renderer}/fold-strip.tsx +1 -1
- package/src/ui/components/git/diff-renderer/highlight.ts +102 -0
- package/src/ui/components/{diff-renderer → git/diff-renderer}/pierre-diff.tsx +23 -15
- package/src/ui/components/git/diff-renderer/prepare-diff.ts +62 -0
- package/src/ui/components/{diff-renderer → git/diff-renderer}/split-view.tsx +122 -47
- package/src/ui/components/{diff-renderer → git/diff-renderer}/stacked-view.tsx +106 -35
- package/src/ui/components/{diff-renderer → git/diff-renderer}/use-diff-prefetch.ts +8 -17
- package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +240 -0
- package/src/ui/components/git/diff-renderer/use-segment-virtualization.ts +95 -0
- package/src/ui/components/{git-panel.tsx → git/git-panel.tsx} +9 -5
- package/src/ui/components/{git-view.tsx → git/git-view.tsx} +70 -37
- package/src/ui/components/{git-pane-context-menu.ts → git/pane/git-pane-context-menu.ts} +2 -2
- package/src/ui/components/{git-pane-widget.tsx → git/pane/git-pane-widget.tsx} +4 -4
- package/src/ui/components/{session-bar.tsx → layout/session-bar.tsx} +36 -13
- package/src/ui/components/{sidebar-group-metadata.ts → layout/sidebar/sidebar-group-metadata.ts} +2 -2
- package/src/ui/components/{sidebar.tsx → layout/sidebar/sidebar.tsx} +6 -6
- package/src/ui/components/{tab-item.tsx → layout/sidebar/tab-item.tsx} +5 -5
- package/src/ui/components/{use-sidebar-branch.ts → layout/sidebar/use-sidebar-branch.ts} +1 -1
- package/src/ui/components/{split-layout.tsx → layout/split-layout.tsx} +5 -5
- package/src/ui/components/{status-bar.tsx → layout/status-bar.tsx} +7 -7
- package/src/ui/components/{terminal-pane.tsx → layout/terminal-pane.tsx} +8 -8
- package/src/ui/components/{ai-usage-modal.tsx → modals/app/ai-usage-modal.tsx} +10 -6
- package/src/ui/components/{help-modal.tsx → modals/app/help-modal.tsx} +6 -6
- package/src/ui/components/{update-available-modal.tsx → modals/app/update-available-modal.tsx} +4 -4
- package/src/ui/components/{git-commit-modal.tsx → modals/git/git-commit-modal.tsx} +8 -8
- package/src/ui/components/modals/sessions/create-session-modal.tsx +79 -0
- package/src/ui/components/modals/sessions/session-name-modal.tsx +10 -0
- package/src/ui/components/{session-picker-modal.tsx → modals/sessions/session-picker-modal.tsx} +8 -8
- package/src/ui/components/modals/shared/form.tsx +164 -0
- package/src/ui/components/{modal-keybinds-overlay.tsx → modals/shared/modal-keybinds-overlay.tsx} +7 -7
- package/src/ui/components/{modal-shell.tsx → modals/shared/modal-shell.tsx} +1 -1
- package/src/ui/components/{picker.tsx → modals/shared/picker.tsx} +3 -3
- package/src/ui/components/modals/snippets/snippet-editor-modal.tsx +30 -0
- package/src/ui/components/{snippet-picker-modal.tsx → modals/snippets/snippet-picker-modal.tsx} +6 -6
- package/src/ui/components/{new-tab-modal.tsx → modals/tabs/new-tab-modal.tsx} +17 -20
- package/src/ui/components/{theme-picker-modal.tsx → modals/themes/theme-picker-modal.tsx} +6 -6
- package/src/ui/components/{ai-usage-indicator.tsx → overlays/ai-usage/ai-usage-indicator.tsx} +3 -3
- package/src/ui/components/{context-menu-box.tsx → overlays/context-menu/context-menu-box.tsx} +1 -1
- package/src/ui/components/{context-menu-overlay.tsx → overlays/context-menu/context-menu-overlay.tsx} +3 -3
- package/src/ui/components/{pending-chord-overlay.tsx → overlays/pending-chord-overlay.tsx} +6 -6
- package/src/ui/components/{bare-input.tsx → primitives/bare-input.tsx} +2 -2
- package/src/ui/components/{input-field.tsx → primitives/input-field.tsx} +2 -2
- package/src/ui/components/{list-item.tsx → primitives/list-item.tsx} +1 -1
- package/src/ui/components/{surface.tsx → primitives/surface.tsx} +1 -1
- package/src/ui/root.tsx +24 -22
- package/src/ui/theme-store.ts +30 -26
- package/src/ui/themes.ts +1 -0
- package/src/ui/components/create-session-modal.tsx +0 -96
- package/src/ui/components/diff-renderer/build-rows.ts +0 -349
- package/src/ui/components/diff-renderer/highlight.ts +0 -44
- package/src/ui/components/diff-renderer/prepare-diff.ts +0 -65
- package/src/ui/components/diff-renderer/use-diff-preparation.ts +0 -106
- package/src/ui/components/session-name-modal.tsx +0 -11
- package/src/ui/components/snippet-editor-modal.tsx +0 -40
- /package/src/ui/components/{diff-renderer → git/diff-renderer}/filetype.ts +0 -0
- /package/src/ui/components/{diff-renderer → git/diff-renderer}/index.ts +0 -0
- /package/src/ui/components/{sidebar-scroll.ts → layout/sidebar/sidebar-scroll.ts} +0 -0
- /package/src/ui/components/{use-sidebar-auto-scroll.ts → layout/sidebar/use-sidebar-auto-scroll.ts} +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { MouseEvent as OtuiMouseEvent } from '@opentui/core'
|
|
2
2
|
|
|
3
|
-
import type { TerminalContentOrigin } from '
|
|
4
|
-
import type { FocusMode, TabSession } from '
|
|
3
|
+
import type { TerminalContentOrigin } from '../../../input/raw-input-handler'
|
|
4
|
+
import type { FocusMode, TabSession } from '../../../state/types'
|
|
5
5
|
|
|
6
|
-
import { logInputDebug } from '
|
|
6
|
+
import { logInputDebug } from '../../../debug/input-log'
|
|
7
7
|
import {
|
|
8
8
|
computePaneRects,
|
|
9
9
|
type LayoutNode,
|
|
10
10
|
PANE_BORDER,
|
|
11
11
|
type PaneRect,
|
|
12
12
|
type SplitDirection,
|
|
13
|
-
} from '
|
|
14
|
-
import { useTokens } from '
|
|
13
|
+
} from '../../../state/layout-tree'
|
|
14
|
+
import { useTokens } from '../../theme'
|
|
15
15
|
import { TerminalPane } from './terminal-pane'
|
|
16
16
|
|
|
17
17
|
const PANE_CHROME = PANE_BORDER
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { AppState } from '
|
|
1
|
+
import type { AppState } from '../../../state/types'
|
|
2
2
|
|
|
3
|
-
import { version as APP_VERSION } from '
|
|
4
|
-
import { useAppStore } from '
|
|
5
|
-
import { useKeymap } from '
|
|
6
|
-
import { getStatusBarModel } from '
|
|
7
|
-
import { getCurrentTokens, useBg, useTokens } from '
|
|
8
|
-
import { AIUsageIndicator } from '
|
|
3
|
+
import { version as APP_VERSION } from '../../../../package.json'
|
|
4
|
+
import { useAppStore } from '../../../state/app-store'
|
|
5
|
+
import { useKeymap } from '../../keymap-context'
|
|
6
|
+
import { getStatusBarModel } from '../../status-bar-model'
|
|
7
|
+
import { getCurrentTokens, useBg, useTokens } from '../../theme'
|
|
8
|
+
import { AIUsageIndicator } from '../overlays/ai-usage/ai-usage-indicator'
|
|
9
9
|
|
|
10
10
|
function getModeColor(focusMode: AppState['focusMode']): string {
|
|
11
11
|
const t = getCurrentTokens()
|
|
@@ -2,19 +2,19 @@ import type { MouseEvent as OtuiMouseEvent } from '@opentui/core'
|
|
|
2
2
|
|
|
3
3
|
import { memo, type ReactNode } from 'react'
|
|
4
4
|
|
|
5
|
-
import type { TerminalContentOrigin } from '
|
|
6
|
-
import type { TabSession, TerminalSnapshot, TerminalSpan } from '
|
|
5
|
+
import type { TerminalContentOrigin } from '../../../input/raw-input-handler'
|
|
6
|
+
import type { TabSession, TerminalSnapshot, TerminalSpan } from '../../../state/types'
|
|
7
7
|
|
|
8
|
-
import { logInputDebug } from '
|
|
9
|
-
import { dispatchGlobal, runSideEffectGlobal } from '
|
|
10
|
-
import { type ContextMenuItem, openContextMenu } from '
|
|
11
|
-
import { getCurrentTokens, useBg, useTokens } from '
|
|
12
|
-
import { ContextMenuBox } from '
|
|
8
|
+
import { logInputDebug } from '../../../debug/input-log'
|
|
9
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../../state/dispatch-ref'
|
|
10
|
+
import { type ContextMenuItem, openContextMenu } from '../../context-menu/controller'
|
|
11
|
+
import { getCurrentTokens, useBg, useTokens } from '../../theme'
|
|
12
|
+
import { ContextMenuBox } from '../overlays/context-menu/context-menu-box'
|
|
13
13
|
|
|
14
14
|
interface TerminalPaneProps {
|
|
15
15
|
tab?: TabSession
|
|
16
16
|
tabId?: string
|
|
17
|
-
focusMode: import('
|
|
17
|
+
focusMode: import('../../../state/types').FocusMode
|
|
18
18
|
isActive?: boolean
|
|
19
19
|
contentOrigin: TerminalContentOrigin
|
|
20
20
|
mouseForwardingEnabled: boolean
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { AIUsageTool } from '@brimveyn/aimux-config'
|
|
2
2
|
import type { ReactNode } from 'react'
|
|
3
3
|
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import type {
|
|
5
|
+
UsagePaceStage,
|
|
6
|
+
UsageSnapshot,
|
|
7
|
+
UsageWindow,
|
|
8
|
+
} from '../../../../services/ai-usage/types'
|
|
9
|
+
|
|
10
|
+
import { useAIUsageStore } from '../../../../state/ai-usage-store'
|
|
11
|
+
import { useTokens } from '../../../theme'
|
|
12
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
13
|
+
import { ModalShell } from '../shared/modal-shell'
|
|
10
14
|
|
|
11
15
|
const TOOL_TITLE: Record<AIUsageTool, string> = {
|
|
12
16
|
claude: 'Claude',
|
|
@@ -2,12 +2,12 @@ import type { ModeId } from '@brimveyn/aimux-config'
|
|
|
2
2
|
|
|
3
3
|
import { useLayoutEffect, useMemo } from 'react'
|
|
4
4
|
|
|
5
|
-
import { collectHelpEntries, HELP_MODE_LABELS } from '
|
|
6
|
-
import { dispatchGlobal } from '
|
|
7
|
-
import { useKeymap } from '
|
|
8
|
-
import { useTokens } from '
|
|
9
|
-
import { uiTokens } from '
|
|
10
|
-
import { Picker, type PickerItem } from '
|
|
5
|
+
import { collectHelpEntries, HELP_MODE_LABELS } from '../../../../input/keymap/help-entries'
|
|
6
|
+
import { dispatchGlobal } from '../../../../state/dispatch-ref'
|
|
7
|
+
import { useKeymap } from '../../../keymap-context'
|
|
8
|
+
import { useTokens } from '../../../theme'
|
|
9
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
10
|
+
import { Picker, type PickerItem } from '../shared/picker'
|
|
11
11
|
|
|
12
12
|
interface HelpModalProps {
|
|
13
13
|
filter: string | null
|
package/src/ui/components/{update-available-modal.tsx → modals/app/update-available-modal.tsx}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useTokens } from '
|
|
2
|
-
import { uiTokens } from '
|
|
3
|
-
import { ListItem } from '
|
|
4
|
-
import { ModalShell } from '
|
|
1
|
+
import { useTokens } from '../../../theme'
|
|
2
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
3
|
+
import { ListItem } from '../../primitives/list-item'
|
|
4
|
+
import { ModalShell } from '../shared/modal-shell'
|
|
5
5
|
|
|
6
6
|
interface UpdateAvailableModalProps {
|
|
7
7
|
currentVersion: string
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { isAutoCommitEnabled } from '@brimveyn/aimux-config'
|
|
2
2
|
import { useEffect, useState } from 'react'
|
|
3
3
|
|
|
4
|
-
import type { ModeId } from '
|
|
5
|
-
|
|
6
|
-
import { useAppStore } from '
|
|
7
|
-
import { dispatchGlobal, runSideEffectGlobal } from '
|
|
8
|
-
import { useTokens } from '
|
|
9
|
-
import { uiTokens } from '
|
|
10
|
-
import { InputField } from '
|
|
11
|
-
import { ModalShell } from '
|
|
4
|
+
import type { ModeId } from '../../../../input/modes/types'
|
|
5
|
+
|
|
6
|
+
import { useAppStore } from '../../../../state/app-store'
|
|
7
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
8
|
+
import { useTokens } from '../../../theme'
|
|
9
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
10
|
+
import { InputField } from '../../primitives/input-field'
|
|
11
|
+
import { ModalShell } from '../shared/modal-shell'
|
|
12
12
|
|
|
13
13
|
interface GitCommitModalProps {
|
|
14
14
|
activeField: 'title' | 'body'
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { DirectoryResult } from '../../../../state/types'
|
|
2
|
+
|
|
3
|
+
import { abbreviatePath } from '../../../path-format'
|
|
4
|
+
import { getCurrentTokens, useTokens } from '../../../theme'
|
|
5
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
6
|
+
import { AutoComplete, Form, type FormOptionItem, TextField } from '../shared/form'
|
|
7
|
+
|
|
8
|
+
const VISIBLE_ROWS = 8
|
|
9
|
+
|
|
10
|
+
function getDirectoryResultIcon(result: DirectoryResult): string {
|
|
11
|
+
if (result.type === 'worktree') return '\u{e728}'
|
|
12
|
+
if (result.type === 'workspace') return '\u{f07c}'
|
|
13
|
+
return '\u{e702}'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getDirectoryResultColor(result: DirectoryResult): string {
|
|
17
|
+
const t = getCurrentTokens()
|
|
18
|
+
if (result.type === 'worktree') return t.palette.warning
|
|
19
|
+
if (result.type === 'workspace') return t.accent
|
|
20
|
+
return t.palette.primary
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CreateSessionModalProps {
|
|
24
|
+
activeField: 'directory' | 'name'
|
|
25
|
+
directoryQuery: string
|
|
26
|
+
sessionName: string
|
|
27
|
+
results: DirectoryResult[]
|
|
28
|
+
selectedIndex: number
|
|
29
|
+
pendingProjectPath: string | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function CreateSessionModal({
|
|
33
|
+
activeField,
|
|
34
|
+
directoryQuery,
|
|
35
|
+
pendingProjectPath,
|
|
36
|
+
results,
|
|
37
|
+
selectedIndex,
|
|
38
|
+
sessionName,
|
|
39
|
+
}: CreateSessionModalProps) {
|
|
40
|
+
const t = useTokens()
|
|
41
|
+
const dirActive = activeField === 'directory'
|
|
42
|
+
const nameActive = activeField === 'name'
|
|
43
|
+
|
|
44
|
+
const items: FormOptionItem[] = results.map((result) => {
|
|
45
|
+
return {
|
|
46
|
+
key: result.path,
|
|
47
|
+
leading: <text fg={getDirectoryResultColor(result)}>{getDirectoryResultIcon(result)}</text>,
|
|
48
|
+
title: (active) => (
|
|
49
|
+
<text fg={active ? t.palette.ink : t.muted}>{abbreviatePath(result.path)}</text>
|
|
50
|
+
),
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Form
|
|
56
|
+
title="Create workspace"
|
|
57
|
+
keybindsModeId="modal.create-session"
|
|
58
|
+
width={uiTokens.modalWidth.xl}
|
|
59
|
+
>
|
|
60
|
+
<AutoComplete
|
|
61
|
+
active={dirActive}
|
|
62
|
+
label="Search projects"
|
|
63
|
+
placeholder="Type a project name..."
|
|
64
|
+
value={directoryQuery}
|
|
65
|
+
displayValue={pendingProjectPath ? abbreviatePath(pendingProjectPath) : directoryQuery}
|
|
66
|
+
items={items}
|
|
67
|
+
selectedIndex={selectedIndex}
|
|
68
|
+
maxVisibleRows={VISIBLE_ROWS}
|
|
69
|
+
emptyState={
|
|
70
|
+
<text fg={t.muted}>
|
|
71
|
+
{directoryQuery.length > 0 ? 'No matches' : 'Type a project name to search...'}
|
|
72
|
+
</text>
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<TextField active={nameActive} label="Workspace name" value={sessionName} />
|
|
77
|
+
</Form>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
2
|
+
import { Form, TextField } from '../shared/form'
|
|
3
|
+
|
|
4
|
+
export function SessionNameModal({ title, value }: { title: string; value: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<Form title={title} keybindsModeId="modal.session-name" width={uiTokens.modalWidth.md}>
|
|
7
|
+
<TextField active value={value} />
|
|
8
|
+
</Form>
|
|
9
|
+
)
|
|
10
|
+
}
|
package/src/ui/components/{session-picker-modal.tsx → modals/sessions/session-picker-modal.tsx}
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { SessionRecord } from '
|
|
1
|
+
import type { SessionRecord } from '../../../../state/types'
|
|
2
2
|
|
|
3
|
-
import { dispatchGlobal, runSideEffectGlobal } from '
|
|
4
|
-
import { filterSessions } from '
|
|
5
|
-
import { abbreviatePath } from '
|
|
6
|
-
import { orderSessionsForDisplay } from '
|
|
7
|
-
import { useTokens } from '
|
|
8
|
-
import { uiTokens } from '
|
|
9
|
-
import { Picker, type PickerItem } from '
|
|
3
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
4
|
+
import { filterSessions } from '../../../../state/selectors'
|
|
5
|
+
import { abbreviatePath } from '../../../path-format'
|
|
6
|
+
import { orderSessionsForDisplay } from '../../../session-ordering'
|
|
7
|
+
import { useTokens } from '../../../theme'
|
|
8
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
9
|
+
import { Picker, type PickerItem } from '../shared/picker'
|
|
10
10
|
|
|
11
11
|
interface SessionPickerModalProps {
|
|
12
12
|
sessions: SessionRecord[]
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { ModeId } from '@brimveyn/aimux-config'
|
|
2
|
+
|
|
3
|
+
import { type ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
import { useTokens } from '../../../theme'
|
|
6
|
+
import { InputField } from '../../primitives/input-field'
|
|
7
|
+
import { ListItem } from '../../primitives/list-item'
|
|
8
|
+
import { ModalShell } from './modal-shell'
|
|
9
|
+
|
|
10
|
+
interface FormProps {
|
|
11
|
+
children: ReactNode
|
|
12
|
+
footer?: ReactNode
|
|
13
|
+
keybindsModeId?: ModeId
|
|
14
|
+
listGap?: number
|
|
15
|
+
subtitle?: string
|
|
16
|
+
title: string
|
|
17
|
+
width: number | `${number}%`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface FieldLabelProps {
|
|
21
|
+
active?: boolean
|
|
22
|
+
children?: ReactNode
|
|
23
|
+
description?: ReactNode
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface TextFieldProps {
|
|
27
|
+
active: boolean
|
|
28
|
+
cursorPos?: number
|
|
29
|
+
description?: ReactNode
|
|
30
|
+
label?: ReactNode
|
|
31
|
+
placeholder?: string
|
|
32
|
+
value: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type FormOptionContent = ReactNode | ((active: boolean) => ReactNode)
|
|
36
|
+
|
|
37
|
+
export interface FormOptionItem {
|
|
38
|
+
key: string
|
|
39
|
+
leading?: FormOptionContent
|
|
40
|
+
subtitle?: FormOptionContent
|
|
41
|
+
title: FormOptionContent
|
|
42
|
+
trailing?: FormOptionContent
|
|
43
|
+
onClick?: () => void
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface AutoCompleteProps {
|
|
47
|
+
active: boolean
|
|
48
|
+
cursorPos?: number
|
|
49
|
+
displayValue?: string
|
|
50
|
+
emptyState?: ReactNode
|
|
51
|
+
items: FormOptionItem[]
|
|
52
|
+
label: ReactNode
|
|
53
|
+
maxVisibleRows?: number
|
|
54
|
+
onHover?: (index: number) => void
|
|
55
|
+
placeholder?: string
|
|
56
|
+
selectedIndex: number
|
|
57
|
+
value: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function Form({
|
|
61
|
+
children,
|
|
62
|
+
footer,
|
|
63
|
+
keybindsModeId,
|
|
64
|
+
listGap,
|
|
65
|
+
subtitle,
|
|
66
|
+
title,
|
|
67
|
+
width,
|
|
68
|
+
}: FormProps) {
|
|
69
|
+
return (
|
|
70
|
+
<ModalShell
|
|
71
|
+
title={title}
|
|
72
|
+
subtitle={subtitle}
|
|
73
|
+
width={width}
|
|
74
|
+
keybindsModeId={keybindsModeId}
|
|
75
|
+
listGap={listGap}
|
|
76
|
+
footer={footer}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</ModalShell>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function FieldLabel({ active = false, children, description }: FieldLabelProps) {
|
|
84
|
+
const t = useTokens()
|
|
85
|
+
return (
|
|
86
|
+
<box flexDirection="column">
|
|
87
|
+
{children ? <text fg={active ? t.palette.ink : t.muted}>{children}</text> : null}
|
|
88
|
+
{description ? <text fg={t.muted}>{description}</text> : null}
|
|
89
|
+
</box>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function TextField({
|
|
94
|
+
active,
|
|
95
|
+
cursorPos,
|
|
96
|
+
description,
|
|
97
|
+
label,
|
|
98
|
+
placeholder,
|
|
99
|
+
value,
|
|
100
|
+
}: TextFieldProps) {
|
|
101
|
+
return (
|
|
102
|
+
<box flexDirection="column">
|
|
103
|
+
{label || description ? (
|
|
104
|
+
<FieldLabel active={active} description={description}>
|
|
105
|
+
{label}
|
|
106
|
+
</FieldLabel>
|
|
107
|
+
) : null}
|
|
108
|
+
<InputField active={active} value={value} cursorPos={cursorPos} placeholder={placeholder} />
|
|
109
|
+
</box>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resolveItemContent(content: FormOptionContent | undefined, active: boolean): ReactNode {
|
|
114
|
+
return typeof content === 'function' ? content(active) : content
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function AutoComplete({
|
|
118
|
+
active,
|
|
119
|
+
cursorPos,
|
|
120
|
+
displayValue,
|
|
121
|
+
emptyState,
|
|
122
|
+
items,
|
|
123
|
+
label,
|
|
124
|
+
maxVisibleRows = 8,
|
|
125
|
+
onHover,
|
|
126
|
+
placeholder,
|
|
127
|
+
selectedIndex,
|
|
128
|
+
value,
|
|
129
|
+
}: AutoCompleteProps) {
|
|
130
|
+
const scrollOffset = Math.max(0, selectedIndex - maxVisibleRows + 1)
|
|
131
|
+
const visibleItems = items.slice(scrollOffset, scrollOffset + maxVisibleRows)
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<box flexDirection="column" gap={1}>
|
|
135
|
+
<TextField
|
|
136
|
+
active={active}
|
|
137
|
+
label={label}
|
|
138
|
+
value={active ? value : (displayValue ?? value)}
|
|
139
|
+
cursorPos={cursorPos}
|
|
140
|
+
placeholder={placeholder}
|
|
141
|
+
/>
|
|
142
|
+
<box flexDirection="column" height={maxVisibleRows}>
|
|
143
|
+
{items.length === 0
|
|
144
|
+
? (emptyState ?? null)
|
|
145
|
+
: visibleItems.map((item, index) => {
|
|
146
|
+
const optionIndex = scrollOffset + index
|
|
147
|
+
const optionActive = active && optionIndex === selectedIndex
|
|
148
|
+
return (
|
|
149
|
+
<ListItem
|
|
150
|
+
key={item.key}
|
|
151
|
+
active={optionActive}
|
|
152
|
+
leading={resolveItemContent(item.leading, optionActive)}
|
|
153
|
+
title={resolveItemContent(item.title, optionActive)}
|
|
154
|
+
subtitle={resolveItemContent(item.subtitle, optionActive)}
|
|
155
|
+
trailing={resolveItemContent(item.trailing, optionActive)}
|
|
156
|
+
onHover={onHover ? () => onHover(optionIndex) : undefined}
|
|
157
|
+
onClick={item.onClick}
|
|
158
|
+
/>
|
|
159
|
+
)
|
|
160
|
+
})}
|
|
161
|
+
</box>
|
|
162
|
+
</box>
|
|
163
|
+
)
|
|
164
|
+
}
|
package/src/ui/components/{modal-keybinds-overlay.tsx → modals/shared/modal-keybinds-overlay.tsx}
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ModeId } from '@brimveyn/aimux-config'
|
|
2
2
|
|
|
3
|
-
import { describeBindings } from '
|
|
4
|
-
import { useKeymap } from '
|
|
5
|
-
import { useTokens } from '
|
|
6
|
-
import { Surface } from '
|
|
3
|
+
import { describeBindings } from '../../../../input/keymap/describe-bindings'
|
|
4
|
+
import { useKeymap } from '../../../keymap-context'
|
|
5
|
+
import { useTokens } from '../../../theme'
|
|
6
|
+
import { Surface } from '../../primitives/surface'
|
|
7
7
|
|
|
8
8
|
const KEYS_COLUMN_WIDTH = 12
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ export function ModalKeybindsOverlay({ limit, modeId }: ModalKeybindsOverlayProp
|
|
|
16
16
|
const t = useTokens()
|
|
17
17
|
const config = useKeymap()
|
|
18
18
|
const bindings = describeBindings(config, modeId, {
|
|
19
|
-
|
|
19
|
+
mergeAlternativesByDescription: true,
|
|
20
20
|
withDescriptionOnly: true,
|
|
21
21
|
})
|
|
22
22
|
|
|
@@ -24,10 +24,10 @@ export function ModalKeybindsOverlay({ limit, modeId }: ModalKeybindsOverlayProp
|
|
|
24
24
|
const entries = typeof limit === 'number' ? bindings.slice(0, limit) : bindings
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<box position="absolute" bottom={
|
|
27
|
+
<box position="absolute" bottom={0} right={0}>
|
|
28
28
|
<Surface tone="elevated" paddingLeft={2} paddingRight={2} paddingTop={1} paddingBottom={1}>
|
|
29
29
|
{entries.map((binding) => (
|
|
30
|
-
<box key={
|
|
30
|
+
<box key={binding.description ?? binding.keys} flexDirection="row">
|
|
31
31
|
<box width={KEYS_COLUMN_WIDTH}>
|
|
32
32
|
<text fg={t.accent}>{binding.keysDisplay}</text>
|
|
33
33
|
</box>
|
|
@@ -3,7 +3,7 @@ import type { ModeId } from '@brimveyn/aimux-config'
|
|
|
3
3
|
import { type BoxRenderable, type OptimizedBuffer, RGBA } from '@opentui/core'
|
|
4
4
|
import { type ReactNode } from 'react'
|
|
5
5
|
|
|
6
|
-
import { useTokens, useTransparent } from '
|
|
6
|
+
import { useTokens, useTransparent } from '../../../theme'
|
|
7
7
|
import { ModalKeybindsOverlay } from './modal-keybinds-overlay'
|
|
8
8
|
|
|
9
9
|
interface ModalShellProps {
|
|
@@ -4,9 +4,9 @@ import type { ScrollBoxRenderable } from '@opentui/core'
|
|
|
4
4
|
import { useTerminalDimensions } from '@opentui/react'
|
|
5
5
|
import { type ReactNode, useLayoutEffect, useRef } from 'react'
|
|
6
6
|
|
|
7
|
-
import { useTokens } from '
|
|
8
|
-
import { BareInput } from '
|
|
9
|
-
import { ListItem } from '
|
|
7
|
+
import { useTokens } from '../../../theme'
|
|
8
|
+
import { BareInput } from '../../primitives/bare-input'
|
|
9
|
+
import { ListItem } from '../../primitives/list-item'
|
|
10
10
|
import { ModalShell } from './modal-shell'
|
|
11
11
|
|
|
12
12
|
export interface PickerItem {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
2
|
+
import { Form, TextField } from '../shared/form'
|
|
3
|
+
|
|
4
|
+
interface SnippetEditorModalProps {
|
|
5
|
+
activeField: 'name' | 'content'
|
|
6
|
+
snippetName: string
|
|
7
|
+
snippetContent: string
|
|
8
|
+
isEditing: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function SnippetEditorModal({
|
|
12
|
+
activeField,
|
|
13
|
+
isEditing,
|
|
14
|
+
snippetContent,
|
|
15
|
+
snippetName,
|
|
16
|
+
}: SnippetEditorModalProps) {
|
|
17
|
+
const nameActive = activeField === 'name'
|
|
18
|
+
const contentActive = activeField === 'content'
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Form
|
|
22
|
+
title={isEditing ? 'Edit snippet' : 'Create snippet'}
|
|
23
|
+
keybindsModeId="modal.snippet-editor"
|
|
24
|
+
width={uiTokens.modalWidth.xl}
|
|
25
|
+
>
|
|
26
|
+
<TextField active={nameActive} label="Name" value={snippetName} />
|
|
27
|
+
<TextField active={contentActive} label="Content" value={snippetContent} />
|
|
28
|
+
</Form>
|
|
29
|
+
)
|
|
30
|
+
}
|
package/src/ui/components/{snippet-picker-modal.tsx → modals/snippets/snippet-picker-modal.tsx}
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { SnippetRecord } from '
|
|
1
|
+
import type { SnippetRecord } from '../../../../state/types'
|
|
2
2
|
|
|
3
|
-
import { dispatchGlobal, runSideEffectGlobal } from '
|
|
4
|
-
import { filterSnippets } from '
|
|
5
|
-
import { useTokens } from '
|
|
6
|
-
import { uiTokens } from '
|
|
7
|
-
import { Picker, type PickerItem } from '
|
|
3
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
4
|
+
import { filterSnippets } from '../../../../state/selectors'
|
|
5
|
+
import { useTokens } from '../../../theme'
|
|
6
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
7
|
+
import { Picker, type PickerItem } from '../shared/picker'
|
|
8
8
|
|
|
9
9
|
interface SnippetPickerModalProps {
|
|
10
10
|
snippets: SnippetRecord[]
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type { AssistantId } from '
|
|
1
|
+
import type { AssistantId } from '../../../../state/types'
|
|
2
2
|
|
|
3
|
-
import { getAllAssistantOptions, getAssistantOption } from '
|
|
4
|
-
import { dispatchGlobal, runSideEffectGlobal } from '
|
|
5
|
-
import { filterAssistants } from '
|
|
6
|
-
import { useTokens } from '
|
|
7
|
-
import { uiTokens } from '
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { Picker, type PickerItem } from './picker'
|
|
3
|
+
import { getAllAssistantOptions, getAssistantOption } from '../../../../pty/command-registry'
|
|
4
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
5
|
+
import { filterAssistants } from '../../../../state/selectors'
|
|
6
|
+
import { useTokens } from '../../../theme'
|
|
7
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
8
|
+
import { Form, TextField } from '../shared/form'
|
|
9
|
+
import { Picker, type PickerItem } from '../shared/picker'
|
|
11
10
|
|
|
12
11
|
interface NewTabModalProps {
|
|
13
12
|
selectedIndex: number
|
|
@@ -33,21 +32,19 @@ export function NewTabModal({
|
|
|
33
32
|
getAllAssistantOptions(customCommands).find((o) => o.id === editingCommand) ??
|
|
34
33
|
getAssistantOption(0)
|
|
35
34
|
return (
|
|
36
|
-
<
|
|
35
|
+
<Form
|
|
37
36
|
title={`Edit command — ${option.label}`}
|
|
38
37
|
keybindsModeId="modal.new-tab.editing-command"
|
|
39
38
|
width={uiTokens.modalWidth.md}
|
|
40
39
|
>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</box>
|
|
50
|
-
</ModalShell>
|
|
40
|
+
<TextField
|
|
41
|
+
active
|
|
42
|
+
description={<>blank to reset to default: {option.command}</>}
|
|
43
|
+
value={editBuffer}
|
|
44
|
+
cursorPos={cursorPos}
|
|
45
|
+
placeholder={option.command}
|
|
46
|
+
/>
|
|
47
|
+
</Form>
|
|
51
48
|
)
|
|
52
49
|
}
|
|
53
50
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useLayoutEffect, useMemo } from 'react'
|
|
2
2
|
|
|
3
|
-
import { dispatchGlobal, runSideEffectGlobal } from '
|
|
4
|
-
import { filterThemeIds } from '
|
|
5
|
-
import { useTokens, useTransparent } from '
|
|
6
|
-
import { type ThemeId, THEMES } from '
|
|
7
|
-
import { uiTokens } from '
|
|
8
|
-
import { Picker, type PickerItem } from '
|
|
3
|
+
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
4
|
+
import { filterThemeIds } from '../../../filter-themes'
|
|
5
|
+
import { useTokens, useTransparent } from '../../../theme'
|
|
6
|
+
import { type ThemeId, THEMES } from '../../../themes'
|
|
7
|
+
import { uiTokens } from '../../../ui-tokens'
|
|
8
|
+
import { Picker, type PickerItem } from '../shared/picker'
|
|
9
9
|
|
|
10
10
|
interface ThemePickerModalProps {
|
|
11
11
|
currentThemeId: ThemeId
|
package/src/ui/components/{ai-usage-indicator.tsx → overlays/ai-usage/ai-usage-indicator.tsx}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { AIUsageTool } from '@brimveyn/aimux-config'
|
|
2
2
|
|
|
3
|
-
import { useAIUsageStore } from '
|
|
4
|
-
import { dispatchGlobal } from '
|
|
5
|
-
import { useBg, useTokens } from '
|
|
3
|
+
import { useAIUsageStore } from '../../../../state/ai-usage-store'
|
|
4
|
+
import { dispatchGlobal } from '../../../../state/dispatch-ref'
|
|
5
|
+
import { useBg, useTokens } from '../../../theme'
|
|
6
6
|
|
|
7
7
|
const TOOL_ICON: Record<AIUsageTool, string> = {
|
|
8
8
|
claude: 'CC',
|
package/src/ui/components/{context-menu-box.tsx → overlays/context-menu/context-menu-box.tsx}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BoxRenderable, MouseEvent as OtuiMouseEvent } from '@opentui/core'
|
|
2
2
|
import type { BoxProps } from '@opentui/react'
|
|
3
3
|
|
|
4
|
-
import { type ContextMenuItem, openContextMenu } from '
|
|
4
|
+
import { type ContextMenuItem, openContextMenu } from '../../../context-menu/controller'
|
|
5
5
|
|
|
6
6
|
interface ContextMenuBoxProps extends BoxProps {
|
|
7
7
|
rightClickMenu?: ContextMenuItem[]
|