@brimveyn/aimux 1.8.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/input/modes/bridge.ts +1 -0
- package/src/input/modes/transitions.ts +3 -1
- package/src/input/modes/types.ts +1 -0
- package/src/pty/terminal-snapshot.ts +7 -5
- package/src/services/ai-usage/adapters/claude.ts +102 -26
- package/src/services/ai-usage/adapters/codex.ts +83 -32
- package/src/services/ai-usage/cache.ts +60 -0
- package/src/services/ai-usage/pace.ts +79 -0
- package/src/services/ai-usage/provider.ts +43 -20
- package/src/services/ai-usage/types.ts +31 -0
- package/src/state/ai-usage-store.ts +10 -3
- package/src/state/reducers/git-mode-state.ts +58 -0
- package/src/state/reducers/modal-state.ts +13 -0
- package/src/state/types.ts +18 -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/modals/app/ai-usage-modal.tsx +190 -0
- 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} +56 -24
- 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 +26 -23
- package/src/ui/theme-store.ts +30 -26
- package/src/ui/themes.ts +1 -0
- package/src/ui/ai-usage/controller.ts +0 -35
- package/src/ui/components/ai-usage-popover.tsx +0 -152
- 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
|
@@ -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 {
|
|
5
|
-
import { 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',
|
|
@@ -19,7 +19,7 @@ function formatTokens(total: number): string {
|
|
|
19
19
|
return String(total)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function buildBar(percent: number): {
|
|
22
|
+
function buildBar(percent: number): { empty: string; filled: string } {
|
|
23
23
|
let filledCount = 0
|
|
24
24
|
for (let i = 0; i < BAR_SEGMENTS; i++) {
|
|
25
25
|
if (percent > i * (100 / BAR_SEGMENTS)) filledCount++
|
|
@@ -48,6 +48,7 @@ function formatResetIn(snap: {
|
|
|
48
48
|
|
|
49
49
|
export function AIUsageIndicator() {
|
|
50
50
|
const t = useTokens()
|
|
51
|
+
const bg = useBg('elevated')
|
|
51
52
|
const enabled = useAIUsageStore((s) => s.enabled)
|
|
52
53
|
const snapshots = useAIUsageStore((s) => s.snapshots)
|
|
53
54
|
|
|
@@ -58,35 +59,49 @@ export function AIUsageIndicator() {
|
|
|
58
59
|
.map((tool) => ({ snap: snapshots[tool], tool }))
|
|
59
60
|
.filter((entry) => entry.snap !== undefined)
|
|
60
61
|
|
|
62
|
+
const openModal = (e: { preventDefault: () => void; stopPropagation: () => void }) => {
|
|
63
|
+
e.preventDefault()
|
|
64
|
+
e.stopPropagation()
|
|
65
|
+
dispatchGlobal({ type: 'open-ai-usage-modal' })
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
if (entries.length === 0) {
|
|
62
69
|
return (
|
|
63
|
-
<box
|
|
70
|
+
<box
|
|
71
|
+
flexDirection="row"
|
|
72
|
+
paddingLeft={1}
|
|
73
|
+
paddingRight={1}
|
|
74
|
+
backgroundColor={bg}
|
|
75
|
+
onMouseDown={openModal}
|
|
76
|
+
>
|
|
64
77
|
<text fg={t.muted}>…</text>
|
|
65
78
|
</box>
|
|
66
79
|
)
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
return (
|
|
70
|
-
<box
|
|
71
|
-
flexDirection="row"
|
|
72
|
-
gap={2}
|
|
73
|
-
onMouseDown={(e) => {
|
|
74
|
-
e.preventDefault()
|
|
75
|
-
e.stopPropagation()
|
|
76
|
-
if (e.button !== 0) return
|
|
77
|
-
toggleAIUsagePopover(e.x, e.y)
|
|
78
|
-
}}
|
|
79
|
-
>
|
|
83
|
+
<box flexDirection="row" gap={1}>
|
|
80
84
|
{entries.map(({ snap, tool }) => {
|
|
81
85
|
if (!snap) return null
|
|
82
86
|
const icon = TOOL_ICON[tool]
|
|
83
|
-
|
|
87
|
+
|
|
88
|
+
if (snap.error && !snap.stale) {
|
|
84
89
|
return (
|
|
85
|
-
<
|
|
86
|
-
{
|
|
87
|
-
|
|
90
|
+
<box
|
|
91
|
+
key={tool}
|
|
92
|
+
flexDirection="row"
|
|
93
|
+
paddingLeft={1}
|
|
94
|
+
paddingRight={1}
|
|
95
|
+
backgroundColor={bg}
|
|
96
|
+
onMouseDown={openModal}
|
|
97
|
+
>
|
|
98
|
+
<text fg={t.palette.error} selectable={false}>
|
|
99
|
+
{`${icon} —`}
|
|
100
|
+
</text>
|
|
101
|
+
</box>
|
|
88
102
|
)
|
|
89
103
|
}
|
|
104
|
+
|
|
90
105
|
if (snap.percent !== null) {
|
|
91
106
|
const p = Math.round(snap.percent)
|
|
92
107
|
let color = t.palette.success
|
|
@@ -99,9 +114,16 @@ export function AIUsageIndicator() {
|
|
|
99
114
|
const reset = formatResetIn(snap)
|
|
100
115
|
const pctText = `${String(p).padStart(2, ' ')}%`
|
|
101
116
|
return (
|
|
102
|
-
<box
|
|
117
|
+
<box
|
|
118
|
+
key={tool}
|
|
119
|
+
flexDirection="row"
|
|
120
|
+
paddingLeft={1}
|
|
121
|
+
paddingRight={1}
|
|
122
|
+
backgroundColor={bg}
|
|
123
|
+
onMouseDown={openModal}
|
|
124
|
+
>
|
|
103
125
|
<text fg={color} selectable={false}>
|
|
104
|
-
{icon}
|
|
126
|
+
{`${icon} `}
|
|
105
127
|
</text>
|
|
106
128
|
<text fg={color} selectable={false}>
|
|
107
129
|
{filled}
|
|
@@ -120,10 +142,20 @@ export function AIUsageIndicator() {
|
|
|
120
142
|
</box>
|
|
121
143
|
)
|
|
122
144
|
}
|
|
145
|
+
|
|
123
146
|
return (
|
|
124
|
-
<
|
|
125
|
-
{
|
|
126
|
-
|
|
147
|
+
<box
|
|
148
|
+
key={tool}
|
|
149
|
+
flexDirection="row"
|
|
150
|
+
paddingLeft={1}
|
|
151
|
+
paddingRight={1}
|
|
152
|
+
backgroundColor={bg}
|
|
153
|
+
onMouseDown={openModal}
|
|
154
|
+
>
|
|
155
|
+
<text fg={t.muted} selectable={false}>
|
|
156
|
+
{`${icon} ${formatTokens(snap.tokens.total)}`}
|
|
157
|
+
</text>
|
|
158
|
+
</box>
|
|
127
159
|
)
|
|
128
160
|
})}
|
|
129
161
|
</box>
|
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[]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { useKeyboard } from '@opentui/react'
|
|
2
2
|
import { useEffect, useState } from 'react'
|
|
3
3
|
|
|
4
|
-
import { useAppStore } from '
|
|
4
|
+
import { useAppStore } from '../../../../state/app-store'
|
|
5
5
|
import {
|
|
6
6
|
closeContextMenu,
|
|
7
7
|
type ContextMenuState,
|
|
8
8
|
subscribeContextMenu,
|
|
9
|
-
} from '
|
|
10
|
-
import { useTokens } from '
|
|
9
|
+
} from '../../../context-menu/controller'
|
|
10
|
+
import { useTokens } from '../../../theme'
|
|
11
11
|
|
|
12
12
|
export function ContextMenuOverlay() {
|
|
13
13
|
const [menu, setMenu] = useState<ContextMenuState | null>(null)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { parseKeyNotation } from '
|
|
2
|
-
import { formatChord } from '
|
|
3
|
-
import { useAppStore } from '
|
|
4
|
-
import { useKeymap } from '
|
|
5
|
-
import { useTokens } from '
|
|
6
|
-
import { Surface } from '
|
|
1
|
+
import { parseKeyNotation } from '../../../input/keymap/key-chord'
|
|
2
|
+
import { formatChord } from '../../../input/keymap/key-format'
|
|
3
|
+
import { useAppStore } from '../../../state/app-store'
|
|
4
|
+
import { useKeymap } from '../../keymap-context'
|
|
5
|
+
import { useTokens } from '../../theme'
|
|
6
|
+
import { Surface } from '../primitives/surface'
|
|
7
7
|
|
|
8
8
|
export function PendingChordOverlay() {
|
|
9
9
|
const t = useTokens()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useTokens } from '
|
|
1
|
+
import { useTokens } from '../../theme'
|
|
2
2
|
|
|
3
3
|
interface BareInputProps {
|
|
4
4
|
value: string
|
|
@@ -9,7 +9,7 @@ interface BareInputProps {
|
|
|
9
9
|
export function BareInput({ cursorPos, placeholder = '', value }: BareInputProps) {
|
|
10
10
|
const t = useTokens()
|
|
11
11
|
const fg = t.palette.ink
|
|
12
|
-
const bg = t.
|
|
12
|
+
const bg = t.bg
|
|
13
13
|
const placeholderFg = t.faint
|
|
14
14
|
|
|
15
15
|
if (!value) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useTokens } from '
|
|
1
|
+
import { useTokens } from '../../theme'
|
|
2
2
|
import { Surface } from './surface'
|
|
3
3
|
|
|
4
4
|
interface InputFieldProps {
|
|
@@ -32,7 +32,7 @@ export function InputField({ active, cursorPos, placeholder, value }: InputField
|
|
|
32
32
|
<Surface tone="inputActive" padding={1}>
|
|
33
33
|
<text fg={fg}>
|
|
34
34
|
{before}
|
|
35
|
-
<span bg={t.palette.ink} fg={t.
|
|
35
|
+
<span bg={t.palette.ink} fg={t.bg}>
|
|
36
36
|
{cursorDisplay}
|
|
37
37
|
</span>
|
|
38
38
|
{trailing}
|
package/src/ui/root.tsx
CHANGED
|
@@ -8,28 +8,28 @@ import { useAppStore } from '../state/app-store'
|
|
|
8
8
|
import { dispatchGlobal } from '../state/dispatch-ref'
|
|
9
9
|
import { getGitPaneWidthFromRatio } from '../state/git-pane-sizing'
|
|
10
10
|
import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { SnippetEditorModal } from './components/snippet-editor-modal'
|
|
27
|
-
import { SnippetPickerModal } from './components/snippet-picker-modal'
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
11
|
+
import { GitView } from './components/git/git-view'
|
|
12
|
+
import { buildGitPaneContextMenu } from './components/git/pane/git-pane-context-menu'
|
|
13
|
+
import { GitPaneWidget } from './components/git/pane/git-pane-widget'
|
|
14
|
+
import { SessionBar } from './components/layout/session-bar'
|
|
15
|
+
import { Sidebar } from './components/layout/sidebar/sidebar'
|
|
16
|
+
import { SplitLayout } from './components/layout/split-layout'
|
|
17
|
+
import { StatusBar } from './components/layout/status-bar'
|
|
18
|
+
import { TerminalPane } from './components/layout/terminal-pane'
|
|
19
|
+
import { AIUsageModal } from './components/modals/app/ai-usage-modal'
|
|
20
|
+
import { HelpModal } from './components/modals/app/help-modal'
|
|
21
|
+
import { UpdateAvailableModal } from './components/modals/app/update-available-modal'
|
|
22
|
+
import { GitCommitModal } from './components/modals/git/git-commit-modal'
|
|
23
|
+
import { CreateSessionModal } from './components/modals/sessions/create-session-modal'
|
|
24
|
+
import { SessionNameModal } from './components/modals/sessions/session-name-modal'
|
|
25
|
+
import { SessionPickerModal } from './components/modals/sessions/session-picker-modal'
|
|
26
|
+
import { SnippetEditorModal } from './components/modals/snippets/snippet-editor-modal'
|
|
27
|
+
import { SnippetPickerModal } from './components/modals/snippets/snippet-picker-modal'
|
|
28
|
+
import { NewTabModal } from './components/modals/tabs/new-tab-modal'
|
|
29
|
+
import { ThemePickerModal } from './components/modals/themes/theme-picker-modal'
|
|
30
|
+
import { ContextMenuBox } from './components/overlays/context-menu/context-menu-box'
|
|
31
|
+
import { ContextMenuOverlay } from './components/overlays/context-menu/context-menu-overlay'
|
|
32
|
+
import { PendingChordOverlay } from './components/overlays/pending-chord-overlay'
|
|
33
33
|
import { useBg, useTokens } from './theme'
|
|
34
34
|
|
|
35
35
|
function getCreateSessionFields(modal: ModalState) {
|
|
@@ -172,6 +172,8 @@ function renderModal(
|
|
|
172
172
|
cursorPos={modal.cursorPos}
|
|
173
173
|
/>
|
|
174
174
|
)
|
|
175
|
+
case 'ai-usage':
|
|
176
|
+
return <AIUsageModal />
|
|
175
177
|
case 'git-commit': {
|
|
176
178
|
const titleText =
|
|
177
179
|
modal.activeField === 'title' ? (modal.editBuffer ?? '') : modal.contentBuffer
|
|
@@ -275,11 +277,12 @@ export function RootView({
|
|
|
275
277
|
if (inGitMode) {
|
|
276
278
|
return (
|
|
277
279
|
<box flexDirection="column" width="100%" height="100%" backgroundColor={editorBg}>
|
|
280
|
+
{sessionBarPosition === 'top' && <SessionBar forceVisible />}
|
|
278
281
|
<GitView themeId={themeId} />
|
|
282
|
+
{sessionBarPosition === 'bottom' && <SessionBar forceVisible />}
|
|
279
283
|
<StatusBar />
|
|
280
284
|
<PendingChordOverlay />
|
|
281
285
|
<ContextMenuOverlay />
|
|
282
|
-
<AIUsagePopover />
|
|
283
286
|
{renderModal(modal, {
|
|
284
287
|
activeAssistant: activeTab?.assistant,
|
|
285
288
|
createSessionFields,
|
package/src/ui/theme-store.ts
CHANGED
|
@@ -5,22 +5,20 @@ import {
|
|
|
5
5
|
accent,
|
|
6
6
|
type AimuxPalette,
|
|
7
7
|
type AimuxTheme,
|
|
8
|
-
|
|
8
|
+
computeSurfaces,
|
|
9
9
|
diffAddBg,
|
|
10
10
|
diffDeleteBg,
|
|
11
|
-
elevated,
|
|
12
11
|
extendPalette,
|
|
13
|
-
faint,
|
|
14
|
-
hover,
|
|
15
|
-
muted,
|
|
16
|
-
selected,
|
|
17
12
|
type ThemeId,
|
|
13
|
+
type ThemeMode,
|
|
18
14
|
THEMES,
|
|
19
15
|
} from './themes'
|
|
20
16
|
|
|
21
17
|
export interface ThemeTokens {
|
|
22
18
|
/** `accent` with `primary` fallback. */
|
|
23
19
|
accent: string
|
|
20
|
+
/** App-level background surface (derived from palette.neutral seed via OKLCH). */
|
|
21
|
+
bg: string
|
|
24
22
|
/** Subtle border between panels. */
|
|
25
23
|
border: string
|
|
26
24
|
/** Background tint for inserted diff lines. */
|
|
@@ -40,29 +38,33 @@ export interface ThemeTokens {
|
|
|
40
38
|
selected: string
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
function computeTokens(palette: AimuxPalette): ThemeTokens {
|
|
41
|
+
function computeTokens(palette: AimuxPalette, mode: ThemeMode): ThemeTokens {
|
|
42
|
+
const surfaces = computeSurfaces(palette, mode)
|
|
44
43
|
return {
|
|
45
44
|
accent: accent(palette),
|
|
46
|
-
|
|
45
|
+
bg: surfaces.bg,
|
|
46
|
+
border: surfaces.border,
|
|
47
47
|
diffAddBg: diffAddBg(palette),
|
|
48
48
|
diffDeleteBg: diffDeleteBg(palette),
|
|
49
|
-
elevated: elevated
|
|
50
|
-
faint: faint
|
|
51
|
-
hover: hover
|
|
52
|
-
muted: muted
|
|
49
|
+
elevated: surfaces.elevated,
|
|
50
|
+
faint: surfaces.faint,
|
|
51
|
+
hover: surfaces.hover,
|
|
52
|
+
muted: surfaces.muted,
|
|
53
53
|
palette,
|
|
54
|
-
selected: selected
|
|
54
|
+
selected: surfaces.selected,
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// Memoize by palette
|
|
58
|
+
// Memoize by (palette, mode) so `useStore` selectors return stable objects.
|
|
59
59
|
let cachedPalette: AimuxPalette | null = null
|
|
60
|
+
let cachedMode: ThemeMode | null = null
|
|
60
61
|
let cachedTokens: ThemeTokens | null = null
|
|
61
62
|
|
|
62
|
-
function deriveTokens(palette: AimuxPalette): ThemeTokens {
|
|
63
|
-
if (cachedPalette === palette && cachedTokens) return cachedTokens
|
|
63
|
+
function deriveTokens(palette: AimuxPalette, mode: ThemeMode): ThemeTokens {
|
|
64
|
+
if (cachedPalette === palette && cachedMode === mode && cachedTokens) return cachedTokens
|
|
64
65
|
cachedPalette = palette
|
|
65
|
-
|
|
66
|
+
cachedMode = mode
|
|
67
|
+
cachedTokens = computeTokens(palette, mode)
|
|
66
68
|
return cachedTokens
|
|
67
69
|
}
|
|
68
70
|
|
|
@@ -78,12 +80,13 @@ const themeStore = createStore<ThemeStore>(() => ({ theme: DEFAULT, transparent:
|
|
|
78
80
|
|
|
79
81
|
/** Subscribe to the active palette plus precomputed derived shades. */
|
|
80
82
|
export function useTokens(): ThemeTokens {
|
|
81
|
-
return useStore(themeStore, (s) => deriveTokens(s.theme.palette))
|
|
83
|
+
return useStore(themeStore, (s) => deriveTokens(s.theme.palette, s.theme.mode))
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
/** Synchronous tokens for non-React callers. */
|
|
85
87
|
export function getCurrentTokens(): ThemeTokens {
|
|
86
|
-
|
|
88
|
+
const s = themeStore.getState()
|
|
89
|
+
return deriveTokens(s.theme.palette, s.theme.mode)
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
/** Synchronous snapshot for non-React callers (reducers, side effects). */
|
|
@@ -121,18 +124,19 @@ export function setTransparent(value: boolean): void {
|
|
|
121
124
|
|
|
122
125
|
export type SurfaceToken = 'base' | 'elevated' | 'hover' | 'selected' | 'border'
|
|
123
126
|
|
|
124
|
-
function resolveSurface(palette: AimuxPalette, token: SurfaceToken): string {
|
|
127
|
+
function resolveSurface(palette: AimuxPalette, mode: ThemeMode, token: SurfaceToken): string {
|
|
128
|
+
const surfaces = computeSurfaces(palette, mode)
|
|
125
129
|
switch (token) {
|
|
126
130
|
case 'base':
|
|
127
|
-
return
|
|
131
|
+
return surfaces.bg
|
|
128
132
|
case 'elevated':
|
|
129
|
-
return elevated
|
|
133
|
+
return surfaces.elevated
|
|
130
134
|
case 'hover':
|
|
131
|
-
return hover
|
|
135
|
+
return surfaces.hover
|
|
132
136
|
case 'selected':
|
|
133
|
-
return selected
|
|
137
|
+
return surfaces.selected
|
|
134
138
|
case 'border':
|
|
135
|
-
return border
|
|
139
|
+
return surfaces.border
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
@@ -144,6 +148,6 @@ function resolveSurface(palette: AimuxPalette, token: SurfaceToken): string {
|
|
|
144
148
|
export function useBg(token: SurfaceToken): string | undefined {
|
|
145
149
|
return useStore(themeStore, (s) => {
|
|
146
150
|
if (s.transparent && token === 'base') return undefined
|
|
147
|
-
return resolveSurface(s.theme.palette, token)
|
|
151
|
+
return resolveSurface(s.theme.palette, s.theme.mode, token)
|
|
148
152
|
})
|
|
149
153
|
}
|
package/src/ui/themes.ts
CHANGED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export interface AIUsagePopoverState {
|
|
2
|
-
anchorX: number
|
|
3
|
-
anchorY: number
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
type Listener = (state: AIUsagePopoverState | null) => void
|
|
7
|
-
|
|
8
|
-
let current: AIUsagePopoverState | null = null
|
|
9
|
-
const listeners = new Set<Listener>()
|
|
10
|
-
|
|
11
|
-
export function openAIUsagePopover(anchorX: number, anchorY: number): void {
|
|
12
|
-
current = { anchorX, anchorY }
|
|
13
|
-
for (const l of listeners) l(current)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function closeAIUsagePopover(): void {
|
|
17
|
-
if (current === null) return
|
|
18
|
-
current = null
|
|
19
|
-
for (const l of listeners) l(null)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function toggleAIUsagePopover(anchorX: number, anchorY: number): void {
|
|
23
|
-
if (current) {
|
|
24
|
-
closeAIUsagePopover()
|
|
25
|
-
} else {
|
|
26
|
-
openAIUsagePopover(anchorX, anchorY)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function subscribeAIUsagePopover(listener: Listener): () => void {
|
|
31
|
-
listeners.add(listener)
|
|
32
|
-
return () => {
|
|
33
|
-
listeners.delete(listener)
|
|
34
|
-
}
|
|
35
|
-
}
|