@brimveyn/aimux 1.7.0 → 1.7.2
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 +3 -4
- package/src/app-runtime/pty-write.ts +8 -3
- package/src/app-runtime/side-effects.ts +10 -2
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app.tsx +5 -8
- package/src/config.ts +2 -10
- package/src/input/modes/bridge.ts +1 -1
- package/src/input/modes/types.ts +6 -1
- package/src/input/raw-input-handler.ts +12 -3
- package/src/ipc/manager-protocol.ts +2 -2
- package/src/ipc/protocol.ts +2 -2
- package/src/pty/terminal-snapshot.ts +4 -4
- package/src/session-backend/bootstrap.ts +21 -1
- package/src/state/reducers/modal-state.ts +3 -2
- package/src/state/reducers/session-state.ts +0 -7
- package/src/state/types.ts +7 -1
- package/src/ui/components/bare-input.tsx +5 -5
- package/src/ui/components/context-menu-box.tsx +21 -0
- package/src/ui/components/context-menu-overlay.tsx +114 -0
- package/src/ui/components/create-session-modal.tsx +12 -41
- package/src/ui/components/diff-renderer/fold-strip.tsx +7 -7
- package/src/ui/components/diff-renderer/highlight.ts +6 -10
- package/src/ui/components/diff-renderer/pierre-diff.tsx +3 -3
- package/src/ui/components/diff-renderer/prepare-diff.ts +2 -3
- package/src/ui/components/diff-renderer/split-view.tsx +22 -29
- package/src/ui/components/diff-renderer/stacked-view.tsx +18 -31
- package/src/ui/components/diff-renderer/use-diff-prefetch.ts +0 -1
- package/src/ui/components/diff-renderer/use-diff-preparation.ts +1 -1
- package/src/ui/components/git-commit-modal.tsx +4 -16
- package/src/ui/components/git-panel.tsx +37 -73
- package/src/ui/components/git-view.tsx +17 -19
- package/src/ui/components/help-modal.tsx +5 -13
- package/src/ui/components/input-field.tsx +5 -7
- package/src/ui/components/list-item.tsx +3 -11
- package/src/ui/components/modal-keybinds-overlay.tsx +4 -4
- package/src/ui/components/modal-shell.tsx +6 -11
- package/src/ui/components/new-tab-modal.tsx +7 -15
- package/src/ui/components/pending-chord-overlay.tsx +5 -5
- package/src/ui/components/picker.tsx +6 -6
- package/src/ui/components/session-bar.tsx +34 -20
- package/src/ui/components/session-picker-modal.tsx +7 -20
- package/src/ui/components/sidebar.tsx +36 -35
- package/src/ui/components/snippet-editor-modal.tsx +4 -18
- package/src/ui/components/snippet-picker-modal.tsx +5 -9
- package/src/ui/components/split-layout.tsx +3 -3
- package/src/ui/components/status-bar.tsx +12 -13
- package/src/ui/components/surface.tsx +8 -11
- package/src/ui/components/tab-item.tsx +54 -29
- package/src/ui/components/terminal-pane.tsx +59 -25
- package/src/ui/components/theme-picker-modal.tsx +7 -21
- package/src/ui/components/update-available-modal.tsx +3 -13
- package/src/ui/context-menu/controller.ts +34 -0
- package/src/ui/root.tsx +5 -2
- package/src/ui/shiki.ts +29 -21
- package/src/ui/theme-store.ts +110 -21
- package/src/ui/theme.ts +6 -3
- package/src/ui/themes.ts +18 -13
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
15
15
|
import { buildGitTreeRows, type GitTreeFileRow, type GitTreeFolderRow } from '../../state/git-tree'
|
|
16
|
-
import {
|
|
16
|
+
import { getCurrentTokens, getTransparent, useTokens, useTransparent } from '../theme'
|
|
17
17
|
|
|
18
18
|
interface GitPanelProps {
|
|
19
19
|
collapsedFolders?: Record<string, true>
|
|
@@ -44,19 +44,19 @@ const BASE_SECTION_ORDER: GitFileSection[] = ['staged', 'unstaged', 'untracked']
|
|
|
44
44
|
const HISTORICAL_SECTION_ORDER: GitFileSection[] = ['historical', 'untracked']
|
|
45
45
|
|
|
46
46
|
function statusColor(status: GitFileEntry['status']): string {
|
|
47
|
-
const t =
|
|
47
|
+
const t = getCurrentTokens()
|
|
48
48
|
switch (status) {
|
|
49
49
|
case '?':
|
|
50
50
|
case 'A':
|
|
51
|
-
return t.
|
|
51
|
+
return t.palette.success
|
|
52
52
|
case 'C':
|
|
53
53
|
case 'R':
|
|
54
|
-
return t.
|
|
54
|
+
return t.palette.primary
|
|
55
55
|
case 'D':
|
|
56
56
|
case 'U':
|
|
57
|
-
return t.
|
|
57
|
+
return t.palette.error
|
|
58
58
|
case 'M':
|
|
59
|
-
return t.
|
|
59
|
+
return t.palette.warning
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -94,6 +94,7 @@ function renderFileLabel(
|
|
|
94
94
|
pathConfig: GitPanePathConfig,
|
|
95
95
|
fileListMode: GitFileListMode
|
|
96
96
|
): ReactNode {
|
|
97
|
+
const t = getCurrentTokens()
|
|
97
98
|
const transform = pathConfig.enabled ? pathConfig.pathFn : undefined
|
|
98
99
|
const displayPath = transform ? transform(file.path) : file.path
|
|
99
100
|
const { basename, prefix } = splitPath(displayPath)
|
|
@@ -102,10 +103,8 @@ function renderFileLabel(
|
|
|
102
103
|
if (!file.renamedFrom) {
|
|
103
104
|
return (
|
|
104
105
|
<text wrapMode="none">
|
|
105
|
-
<span fg={
|
|
106
|
-
{showDir ?
|
|
107
|
-
<span fg={getCurrentTheme().colors['descriptionForeground']}> {dir}</span>
|
|
108
|
-
) : null}
|
|
106
|
+
<span fg={t.palette.ink}>{basename}</span>
|
|
107
|
+
{showDir ? <span fg={t.muted}> {dir}</span> : null}
|
|
109
108
|
</text>
|
|
110
109
|
)
|
|
111
110
|
}
|
|
@@ -114,13 +113,11 @@ function renderFileLabel(
|
|
|
114
113
|
const renamedDir = stripTrailingSlash(renamed.prefix)
|
|
115
114
|
return (
|
|
116
115
|
<text wrapMode="none">
|
|
117
|
-
<span fg={
|
|
118
|
-
{showDir && renamedDir ?
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<span fg={
|
|
122
|
-
<span fg={getCurrentTheme().colors['editor.foreground']}>{basename}</span>
|
|
123
|
-
{showDir ? <span fg={getCurrentTheme().colors['descriptionForeground']}> {dir}</span> : null}
|
|
116
|
+
<span fg={t.muted}>{renamed.basename}</span>
|
|
117
|
+
{showDir && renamedDir ? <span fg={t.muted}> {renamedDir}</span> : null}
|
|
118
|
+
<span fg={t.muted}> → </span>
|
|
119
|
+
<span fg={t.palette.ink}>{basename}</span>
|
|
120
|
+
{showDir ? <span fg={t.muted}> {dir}</span> : null}
|
|
124
121
|
</text>
|
|
125
122
|
)
|
|
126
123
|
}
|
|
@@ -134,35 +131,28 @@ function renderDiffCount(
|
|
|
134
131
|
hasNumstat: boolean
|
|
135
132
|
): ReactNode {
|
|
136
133
|
if (!diffCountConfig.enabled) return null
|
|
134
|
+
const t = getCurrentTokens()
|
|
137
135
|
if (!hasNumstat) {
|
|
138
136
|
return (
|
|
139
|
-
<text fg={
|
|
137
|
+
<text fg={t.muted} bg={bg} flexShrink={0}>
|
|
140
138
|
—
|
|
141
139
|
</text>
|
|
142
140
|
)
|
|
143
141
|
}
|
|
144
142
|
return (
|
|
145
143
|
<box flexDirection="row" flexShrink={0}>
|
|
146
|
-
<text
|
|
147
|
-
|
|
148
|
-
bg={bg}
|
|
149
|
-
>{`+${padRight(file.added, addedW)}`}</text>
|
|
150
|
-
<text fg={getCurrentTheme().colors['editor.lineHighlightBackground']} bg={bg}>
|
|
144
|
+
<text fg={t.palette.success} bg={bg}>{`+${padRight(file.added, addedW)}`}</text>
|
|
145
|
+
<text fg={t.hover} bg={bg}>
|
|
151
146
|
{' '}
|
|
152
147
|
</text>
|
|
153
|
-
<text
|
|
154
|
-
fg={getCurrentTheme().colors['editorError.foreground']}
|
|
155
|
-
bg={bg}
|
|
156
|
-
>{`−${padRight(file.removed, removedW)}`}</text>
|
|
148
|
+
<text fg={t.palette.error} bg={bg}>{`−${padRight(file.removed, removedW)}`}</text>
|
|
157
149
|
</box>
|
|
158
150
|
)
|
|
159
151
|
}
|
|
160
152
|
|
|
161
153
|
function renderFolderRow(row: GitTreeFolderRow, isSelected: boolean): ReactNode {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
? getCurrentTheme().colors['list.activeSelectionBackground']
|
|
165
|
-
: undefined
|
|
154
|
+
const t = getCurrentTokens()
|
|
155
|
+
const bg = isSelected && !getTransparent() ? t.selected : undefined
|
|
166
156
|
const onSelect = (): void => {
|
|
167
157
|
dispatchGlobal({ key: row.key, type: 'git-mode-select-entry-by-key' })
|
|
168
158
|
}
|
|
@@ -173,13 +163,13 @@ function renderFolderRow(row: GitTreeFolderRow, isSelected: boolean): ReactNode
|
|
|
173
163
|
<box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
|
|
174
164
|
<box flexGrow={1} overflow="hidden" paddingLeft={row.depth * 2}>
|
|
175
165
|
<box flexDirection="row" gap={1} onMouseDown={onToggle}>
|
|
176
|
-
<text fg={
|
|
166
|
+
<text fg={t.muted} bg={bg}>
|
|
177
167
|
{row.isCollapsed ? '▸' : '▾'}
|
|
178
168
|
</text>
|
|
179
|
-
<text fg={
|
|
169
|
+
<text fg={t.palette.primary} bg={bg}>
|
|
180
170
|
{row.isCollapsed ? '\uf07b' : '\uf07c'}
|
|
181
171
|
</text>
|
|
182
|
-
<text fg={
|
|
172
|
+
<text fg={t.palette.primary} bg={bg} wrapMode="none">
|
|
183
173
|
{row.name}
|
|
184
174
|
</text>
|
|
185
175
|
</box>
|
|
@@ -199,10 +189,7 @@ function renderFileRow(
|
|
|
199
189
|
): ReactNode {
|
|
200
190
|
const file = row.file
|
|
201
191
|
const hasNumstat = file.added !== null || file.removed !== null
|
|
202
|
-
const bg =
|
|
203
|
-
isSelected && !getTransparent()
|
|
204
|
-
? getCurrentTheme().colors['list.activeSelectionBackground']
|
|
205
|
-
: undefined
|
|
192
|
+
const bg = isSelected && !getTransparent() ? getCurrentTokens().selected : undefined
|
|
206
193
|
const onSelect = (): void => {
|
|
207
194
|
dispatchGlobal({ key: row.key, type: 'git-mode-select-entry-by-key' })
|
|
208
195
|
}
|
|
@@ -236,6 +223,7 @@ function renderTreeSection(
|
|
|
236
223
|
marginTop: number
|
|
237
224
|
): ReactNode {
|
|
238
225
|
if (files.length === 0) return null
|
|
226
|
+
const t = getCurrentTokens()
|
|
239
227
|
const nextFileListMode = fileListMode === 'tree' ? 'flat' : 'tree'
|
|
240
228
|
const toggleListMode = (): void => {
|
|
241
229
|
dispatchGlobal({ type: 'git-mode-toggle-file-list-mode' })
|
|
@@ -244,32 +232,16 @@ function renderTreeSection(
|
|
|
244
232
|
return (
|
|
245
233
|
<box key={section} flexDirection="column" marginTop={marginTop}>
|
|
246
234
|
<box flexDirection="row" justifyContent="space-between">
|
|
247
|
-
<text fg={
|
|
235
|
+
<text fg={t.accent}>
|
|
248
236
|
<strong>
|
|
249
237
|
{title} ({files.length})
|
|
250
238
|
</strong>
|
|
251
239
|
</text>
|
|
252
240
|
{showListModeToggle ? (
|
|
253
241
|
<box flexDirection="row" gap={1} onMouseDown={toggleListMode}>
|
|
254
|
-
<text
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
? getCurrentTheme().colors['textLink.foreground']
|
|
258
|
-
: getCurrentTheme().colors['descriptionForeground']
|
|
259
|
-
}
|
|
260
|
-
>
|
|
261
|
-
tree
|
|
262
|
-
</text>
|
|
263
|
-
<text fg={getCurrentTheme().colors['descriptionForeground']}>|</text>
|
|
264
|
-
<text
|
|
265
|
-
fg={
|
|
266
|
-
fileListMode === 'flat'
|
|
267
|
-
? getCurrentTheme().colors['textLink.foreground']
|
|
268
|
-
: getCurrentTheme().colors['descriptionForeground']
|
|
269
|
-
}
|
|
270
|
-
>
|
|
271
|
-
flat
|
|
272
|
-
</text>
|
|
242
|
+
<text fg={fileListMode === 'tree' ? t.palette.primary : t.muted}>tree</text>
|
|
243
|
+
<text fg={t.muted}>|</text>
|
|
244
|
+
<text fg={fileListMode === 'flat' ? t.palette.primary : t.muted}>flat</text>
|
|
273
245
|
</box>
|
|
274
246
|
) : null}
|
|
275
247
|
</box>
|
|
@@ -309,26 +281,18 @@ function computeStatusPlaceholder(
|
|
|
309
281
|
gitPanel: GitPanelState,
|
|
310
282
|
hasProjectPath: boolean
|
|
311
283
|
): StatusPlaceholder | null {
|
|
284
|
+
const t = getCurrentTokens()
|
|
312
285
|
if (!hasProjectPath) {
|
|
313
|
-
return {
|
|
314
|
-
label: 'No active session',
|
|
315
|
-
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
316
|
-
}
|
|
286
|
+
return { label: 'No active session', labelColor: t.muted }
|
|
317
287
|
}
|
|
318
288
|
if (gitPanel.error === 'not-a-repo') {
|
|
319
|
-
return {
|
|
320
|
-
label: 'Not a git repository',
|
|
321
|
-
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
322
|
-
}
|
|
289
|
+
return { label: 'Not a git repository', labelColor: t.muted }
|
|
323
290
|
}
|
|
324
291
|
if (gitPanel.error === 'unknown') {
|
|
325
|
-
return { label: 'Git error', labelColor:
|
|
292
|
+
return { label: 'Git error', labelColor: t.palette.error }
|
|
326
293
|
}
|
|
327
294
|
if (gitPanel.files.length === 0) {
|
|
328
|
-
return {
|
|
329
|
-
label: 'Working tree clean',
|
|
330
|
-
labelColor: getCurrentTheme().colors['descriptionForeground'],
|
|
331
|
-
}
|
|
295
|
+
return { label: 'Working tree clean', labelColor: t.muted }
|
|
332
296
|
}
|
|
333
297
|
return null
|
|
334
298
|
}
|
|
@@ -347,7 +311,7 @@ export const GitPanel = memo(function GitPanel({
|
|
|
347
311
|
projectPath,
|
|
348
312
|
selectedEntryKey,
|
|
349
313
|
}: GitPanelProps) {
|
|
350
|
-
const
|
|
314
|
+
const t = useTokens()
|
|
351
315
|
useTransparent()
|
|
352
316
|
const sectionOrder = headOffset > 0 ? HISTORICAL_SECTION_ORDER : BASE_SECTION_ORDER
|
|
353
317
|
const scrollRef = useRef<ScrollBoxRenderable | null>(null)
|
|
@@ -382,7 +346,7 @@ export const GitPanel = memo(function GitPanel({
|
|
|
382
346
|
return (
|
|
383
347
|
<box flexDirection="column" flexGrow={1} gap={0}>
|
|
384
348
|
{hasRemoteTracking ? (
|
|
385
|
-
<text fg={
|
|
349
|
+
<text fg={t.muted}>
|
|
386
350
|
↑{gitPanel.ahead} ↓{gitPanel.behind}
|
|
387
351
|
</text>
|
|
388
352
|
) : null}
|
|
@@ -11,7 +11,7 @@ import { useAppStore } from '../../state/app-store'
|
|
|
11
11
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
12
12
|
import { getSelectedGitFile, gitFileKey } from '../../state/git-tree'
|
|
13
13
|
import { setGitDiffScroller } from '../git-view-controls'
|
|
14
|
-
import { useBg,
|
|
14
|
+
import { useBg, useTokens } from '../theme'
|
|
15
15
|
import { PierreDiff, type PierreDiffHandle } from './diff-renderer'
|
|
16
16
|
import { useDiffPrefetch } from './diff-renderer/use-diff-prefetch'
|
|
17
17
|
import { GitPanel } from './git-panel'
|
|
@@ -47,25 +47,25 @@ const DiffStage = memo(function DiffStage({
|
|
|
47
47
|
themeId,
|
|
48
48
|
view,
|
|
49
49
|
}: DiffStageProps) {
|
|
50
|
-
const
|
|
50
|
+
const t = useTokens()
|
|
51
51
|
if (loading && !diff) {
|
|
52
52
|
return (
|
|
53
53
|
<box flexGrow={1} padding={1}>
|
|
54
|
-
<text fg={
|
|
54
|
+
<text fg={t.muted}>Loading diff…</text>
|
|
55
55
|
</box>
|
|
56
56
|
)
|
|
57
57
|
}
|
|
58
58
|
if (!diff) {
|
|
59
59
|
return (
|
|
60
60
|
<box flexGrow={1} padding={1}>
|
|
61
|
-
<text fg={
|
|
61
|
+
<text fg={t.muted}>Select a file.</text>
|
|
62
62
|
</box>
|
|
63
63
|
)
|
|
64
64
|
}
|
|
65
65
|
if (diff.errorMessage) {
|
|
66
66
|
return (
|
|
67
67
|
<box flexGrow={1} padding={1}>
|
|
68
|
-
<text fg={
|
|
68
|
+
<text fg={t.palette.error}>{diff.errorMessage}</text>
|
|
69
69
|
</box>
|
|
70
70
|
)
|
|
71
71
|
}
|
|
@@ -74,7 +74,7 @@ const DiffStage = memo(function DiffStage({
|
|
|
74
74
|
if (placeholder) {
|
|
75
75
|
return (
|
|
76
76
|
<box flexGrow={1} padding={1}>
|
|
77
|
-
<text fg={
|
|
77
|
+
<text fg={t.muted}>{placeholder}</text>
|
|
78
78
|
</box>
|
|
79
79
|
)
|
|
80
80
|
}
|
|
@@ -83,7 +83,7 @@ const DiffStage = memo(function DiffStage({
|
|
|
83
83
|
<box flexDirection="column" flexGrow={1} overflow="hidden">
|
|
84
84
|
{diff.oldPath ? (
|
|
85
85
|
<box paddingLeft={1} paddingRight={1}>
|
|
86
|
-
<text fg={
|
|
86
|
+
<text fg={t.muted}>
|
|
87
87
|
renamed: {diff.oldPath} → {diff.path}
|
|
88
88
|
</text>
|
|
89
89
|
</box>
|
|
@@ -105,8 +105,8 @@ interface GitViewProps {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export const GitView = memo(function GitView({ themeId }: GitViewProps) {
|
|
108
|
-
const
|
|
109
|
-
const sidebarBg = useBg('
|
|
108
|
+
const t = useTokens()
|
|
109
|
+
const sidebarBg = useBg('elevated')
|
|
110
110
|
const dimensions = useTerminalDimensions()
|
|
111
111
|
const gitPane = useAppStore((s) => s.gitPane)
|
|
112
112
|
const gitPanel = useAppStore((s) => s.gitPanel)
|
|
@@ -193,13 +193,13 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
|
|
|
193
193
|
let footerNode: React.ReactNode = null
|
|
194
194
|
if (pendingHint) {
|
|
195
195
|
footerNode = (
|
|
196
|
-
<text fg={
|
|
196
|
+
<text fg={t.palette.warning}>
|
|
197
197
|
<strong>{pendingHint}</strong>
|
|
198
198
|
</text>
|
|
199
199
|
)
|
|
200
200
|
} else if (actionMessage) {
|
|
201
201
|
footerNode = actionMessage.split('\n').map((line, idx) => (
|
|
202
|
-
<text key={idx} fg={
|
|
202
|
+
<text key={idx} fg={t.palette.primary}>
|
|
203
203
|
{line}
|
|
204
204
|
</text>
|
|
205
205
|
))
|
|
@@ -215,26 +215,24 @@ export const GitView = memo(function GitView({ themeId }: GitViewProps) {
|
|
|
215
215
|
padding={0}
|
|
216
216
|
gap={0}
|
|
217
217
|
>
|
|
218
|
-
<text fg={
|
|
218
|
+
<text fg={t.palette.primary}>
|
|
219
219
|
<strong>aimux · git</strong>
|
|
220
220
|
</text>
|
|
221
221
|
{gitPanel.branch ? (
|
|
222
222
|
<box flexDirection="row">
|
|
223
|
-
<text fg={
|
|
224
|
-
<text fg={
|
|
223
|
+
<text fg={t.palette.primary}>{'\u{e702}'} </text>
|
|
224
|
+
<text fg={t.muted}>{gitPanel.branch}</text>
|
|
225
225
|
</box>
|
|
226
226
|
) : null}
|
|
227
227
|
{gitMode.headOffset > 0 ? (
|
|
228
228
|
<box flexDirection="row" gap={1}>
|
|
229
|
-
<text fg={
|
|
229
|
+
<text fg={t.palette.warning}>
|
|
230
230
|
<strong>HEAD~{gitMode.headOffset}</strong>
|
|
231
231
|
</text>
|
|
232
|
-
<text fg={
|
|
232
|
+
<text fg={t.muted}>[ newer · ] older</text>
|
|
233
233
|
</box>
|
|
234
234
|
) : null}
|
|
235
|
-
<text fg={
|
|
236
|
-
{'·'.repeat(Math.max(0, fileBarWidth - 2))}
|
|
237
|
-
</text>
|
|
235
|
+
<text fg={t.hover}>{'·'.repeat(Math.max(0, fileBarWidth - 2))}</text>
|
|
238
236
|
<GitPanel
|
|
239
237
|
collapsedFolders={gitMode.collapsedFolders}
|
|
240
238
|
compact={gitPane.treeCompaction}
|
|
@@ -5,7 +5,7 @@ import { useLayoutEffect, useMemo } from 'react'
|
|
|
5
5
|
import { collectHelpEntries, HELP_MODE_LABELS } from '../../input/keymap/help-entries'
|
|
6
6
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
7
7
|
import { useKeymap } from '../keymap-context'
|
|
8
|
-
import {
|
|
8
|
+
import { useTokens } from '../theme'
|
|
9
9
|
import { uiTokens } from '../ui-tokens'
|
|
10
10
|
import { Picker, type PickerItem } from './picker'
|
|
11
11
|
|
|
@@ -23,7 +23,7 @@ function matchesFilter(needle: string, ...fields: (string | undefined)[]): boole
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function HelpModal({ cursorPos, filter, scope, selectedIndex }: HelpModalProps) {
|
|
26
|
-
const
|
|
26
|
+
const t = useTokens()
|
|
27
27
|
const config = useKeymap()
|
|
28
28
|
const allEntries = useMemo(() => collectHelpEntries(config), [config])
|
|
29
29
|
const scoped = useMemo(
|
|
@@ -54,18 +54,12 @@ export function HelpModal({ cursorPos, filter, scope, selectedIndex }: HelpModal
|
|
|
54
54
|
group: entry.modeLabel,
|
|
55
55
|
key: `${entry.mode}::${entry.keysDisplay}::${entry.description ?? ''}::${index}`,
|
|
56
56
|
title: (
|
|
57
|
-
<text
|
|
58
|
-
fg={active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']}
|
|
59
|
-
wrapMode="none"
|
|
60
|
-
>
|
|
57
|
+
<text fg={active ? t.palette.ink : t.muted} wrapMode="none">
|
|
61
58
|
{entry.description ?? ''}
|
|
62
59
|
</text>
|
|
63
60
|
),
|
|
64
61
|
trailing: (
|
|
65
|
-
<text
|
|
66
|
-
fg={active ? theme.colors['textLink.foreground'] : theme.colors['terminal.ansiMagenta']}
|
|
67
|
-
wrapMode="none"
|
|
68
|
-
>
|
|
62
|
+
<text fg={active ? t.palette.primary : t.accent} wrapMode="none">
|
|
69
63
|
{entry.keysDisplay}
|
|
70
64
|
</text>
|
|
71
65
|
),
|
|
@@ -82,9 +76,7 @@ export function HelpModal({ cursorPos, filter, scope, selectedIndex }: HelpModal
|
|
|
82
76
|
items={items}
|
|
83
77
|
selectedIndex={selectedIndex}
|
|
84
78
|
emptyState={
|
|
85
|
-
<text fg={
|
|
86
|
-
{filter ? 'No matching bindings.' : 'No bindings registered.'}
|
|
87
|
-
</text>
|
|
79
|
+
<text fg={t.muted}>{filter ? 'No matching bindings.' : 'No bindings registered.'}</text>
|
|
88
80
|
}
|
|
89
81
|
onHover={(index) => dispatchGlobal({ index, type: 'set-modal-selection-index' })}
|
|
90
82
|
/>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTokens } from '../theme'
|
|
2
2
|
import { Surface } from './surface'
|
|
3
3
|
|
|
4
4
|
interface InputFieldProps {
|
|
@@ -9,15 +9,13 @@ interface InputFieldProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function InputField({ active, cursorPos, placeholder, value }: InputFieldProps) {
|
|
12
|
-
const
|
|
13
|
-
const fg = active ?
|
|
12
|
+
const t = useTokens()
|
|
13
|
+
const fg = active ? t.palette.ink : t.muted
|
|
14
14
|
if (!active) {
|
|
15
15
|
const showPlaceholder = !value && !!placeholder
|
|
16
16
|
return (
|
|
17
17
|
<Surface tone="input" padding={1}>
|
|
18
|
-
<text fg={showPlaceholder ?
|
|
19
|
-
{showPlaceholder ? placeholder : value}
|
|
20
|
-
</text>
|
|
18
|
+
<text fg={showPlaceholder ? t.faint : fg}>{showPlaceholder ? placeholder : value}</text>
|
|
21
19
|
</Surface>
|
|
22
20
|
)
|
|
23
21
|
}
|
|
@@ -34,7 +32,7 @@ export function InputField({ active, cursorPos, placeholder, value }: InputField
|
|
|
34
32
|
<Surface tone="inputActive" padding={1}>
|
|
35
33
|
<text fg={fg}>
|
|
36
34
|
{before}
|
|
37
|
-
<span bg={
|
|
35
|
+
<span bg={t.palette.ink} fg={t.palette.neutral}>
|
|
38
36
|
{cursorDisplay}
|
|
39
37
|
</span>
|
|
40
38
|
{trailing}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useTokens } from '../theme'
|
|
4
4
|
import { Surface } from './surface'
|
|
5
5
|
|
|
6
6
|
export type ListItemDirection = 'row' | 'column'
|
|
@@ -28,7 +28,7 @@ export function ListItem({
|
|
|
28
28
|
title,
|
|
29
29
|
trailing,
|
|
30
30
|
}: ListItemProps) {
|
|
31
|
-
const
|
|
31
|
+
const t = useTokens()
|
|
32
32
|
const isRow = direction === 'row'
|
|
33
33
|
return (
|
|
34
34
|
<box id={id} onMouseOver={onHover} onMouseDown={onClick}>
|
|
@@ -41,15 +41,7 @@ export function ListItem({
|
|
|
41
41
|
<box flexDirection="row">
|
|
42
42
|
{isRow ? null : (
|
|
43
43
|
<>
|
|
44
|
-
<text
|
|
45
|
-
fg={
|
|
46
|
-
active
|
|
47
|
-
? theme.colors['textLink.foreground']
|
|
48
|
-
: theme.colors['editor.lineHighlightBackground']
|
|
49
|
-
}
|
|
50
|
-
>
|
|
51
|
-
{active ? '›' : '·'}
|
|
52
|
-
</text>
|
|
44
|
+
<text fg={active ? t.palette.primary : t.hover}>{active ? '›' : '·'}</text>
|
|
53
45
|
<text> </text>
|
|
54
46
|
</>
|
|
55
47
|
)}
|
|
@@ -2,7 +2,7 @@ import type { ModeId } from '@brimveyn/aimux-config'
|
|
|
2
2
|
|
|
3
3
|
import { describeBindings } from '../../input/keymap/describe-bindings'
|
|
4
4
|
import { useKeymap } from '../keymap-context'
|
|
5
|
-
import {
|
|
5
|
+
import { useTokens } from '../theme'
|
|
6
6
|
import { Surface } from './surface'
|
|
7
7
|
|
|
8
8
|
const KEYS_COLUMN_WIDTH = 12
|
|
@@ -13,7 +13,7 @@ interface ModalKeybindsOverlayProps {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function ModalKeybindsOverlay({ limit, modeId }: ModalKeybindsOverlayProps) {
|
|
16
|
-
const
|
|
16
|
+
const t = useTokens()
|
|
17
17
|
const config = useKeymap()
|
|
18
18
|
const bindings = describeBindings(config, modeId, {
|
|
19
19
|
dedupeByDescription: true,
|
|
@@ -29,9 +29,9 @@ export function ModalKeybindsOverlay({ limit, modeId }: ModalKeybindsOverlayProp
|
|
|
29
29
|
{entries.map((binding) => (
|
|
30
30
|
<box key={`${binding.keys}-${binding.description}`} flexDirection="row">
|
|
31
31
|
<box width={KEYS_COLUMN_WIDTH}>
|
|
32
|
-
<text fg={
|
|
32
|
+
<text fg={t.accent}>{binding.keysDisplay}</text>
|
|
33
33
|
</box>
|
|
34
|
-
<text fg={
|
|
34
|
+
<text fg={t.muted}>{binding.description ?? ''}</text>
|
|
35
35
|
</box>
|
|
36
36
|
))}
|
|
37
37
|
</Surface>
|
|
@@ -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 {
|
|
6
|
+
import { useTokens, useTransparent } from '../theme'
|
|
7
7
|
import { ModalKeybindsOverlay } from './modal-keybinds-overlay'
|
|
8
8
|
|
|
9
9
|
interface ModalShellProps {
|
|
@@ -32,16 +32,11 @@ const TRANSPARENT_RGBA = RGBA.fromValues(0, 0, 0, 0)
|
|
|
32
32
|
//
|
|
33
33
|
// `setCell` (→ `bufferSetCell` → zig `buffer.set`) bypasses both: it writes
|
|
34
34
|
// the cell unconditionally, no alpha check, no blending, no char preservation.
|
|
35
|
-
// That overwrites each chrome char with a space while keeping the cell bg
|
|
36
|
-
// transparent, so the terminal emulator's own (blurred) window bg still shows
|
|
37
|
-
// through. Children (modal content) then render on top as normal.
|
|
38
35
|
function fillModalInteriorWithSpaces(this: BoxRenderable, buffer: OptimizedBuffer): void {
|
|
39
36
|
const x0 = this.screenX
|
|
40
37
|
const y0 = this.screenY
|
|
41
38
|
const w = this.width
|
|
42
39
|
const h = this.height
|
|
43
|
-
// Inset by 1 on each side so we don't erase the border that renderSelf just
|
|
44
|
-
// drew. The modal always has a single-cell border.
|
|
45
40
|
const startX = x0 + 1
|
|
46
41
|
const startY = y0 + 1
|
|
47
42
|
const endX = x0 + w - 1
|
|
@@ -62,9 +57,9 @@ export function ModalShell({
|
|
|
62
57
|
title,
|
|
63
58
|
width,
|
|
64
59
|
}: ModalShellProps) {
|
|
65
|
-
const
|
|
60
|
+
const t = useTokens()
|
|
66
61
|
const transparent = useTransparent()
|
|
67
|
-
const bg = transparent ? 'transparent' :
|
|
62
|
+
const bg = transparent ? 'transparent' : t.elevated
|
|
68
63
|
return (
|
|
69
64
|
<box
|
|
70
65
|
position="absolute"
|
|
@@ -77,7 +72,7 @@ export function ModalShell({
|
|
|
77
72
|
>
|
|
78
73
|
<box
|
|
79
74
|
border
|
|
80
|
-
borderColor={
|
|
75
|
+
borderColor={t.palette.primary}
|
|
81
76
|
backgroundColor={bg}
|
|
82
77
|
padding={1}
|
|
83
78
|
width={width}
|
|
@@ -85,8 +80,8 @@ export function ModalShell({
|
|
|
85
80
|
>
|
|
86
81
|
<box width="100%" flexDirection="column" gap={listGap}>
|
|
87
82
|
<box flexDirection="column">
|
|
88
|
-
<text fg={
|
|
89
|
-
{subtitle ? <text fg={
|
|
83
|
+
<text fg={t.accent}>{title}</text>
|
|
84
|
+
{subtitle ? <text fg={t.muted}>{subtitle}</text> : null}
|
|
90
85
|
</box>
|
|
91
86
|
{children}
|
|
92
87
|
{footer ? <box>{footer}</box> : null}
|
|
@@ -3,7 +3,7 @@ import type { AssistantId } from '../../state/types'
|
|
|
3
3
|
import { getAllAssistantOptions, getAssistantOption } from '../../pty/command-registry'
|
|
4
4
|
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
5
5
|
import { filterAssistants } from '../../state/selectors'
|
|
6
|
-
import {
|
|
6
|
+
import { useTokens } from '../theme'
|
|
7
7
|
import { uiTokens } from '../ui-tokens'
|
|
8
8
|
import { InputField } from './input-field'
|
|
9
9
|
import { ModalShell } from './modal-shell'
|
|
@@ -26,7 +26,7 @@ export function NewTabModal({
|
|
|
26
26
|
filter,
|
|
27
27
|
selectedIndex,
|
|
28
28
|
}: NewTabModalProps) {
|
|
29
|
-
const
|
|
29
|
+
const t = useTokens()
|
|
30
30
|
|
|
31
31
|
if (editingCommand !== null) {
|
|
32
32
|
const option =
|
|
@@ -39,9 +39,7 @@ export function NewTabModal({
|
|
|
39
39
|
width={uiTokens.modalWidth.md}
|
|
40
40
|
>
|
|
41
41
|
<box flexDirection="column">
|
|
42
|
-
<text fg={
|
|
43
|
-
Custom command (blank to reset to default: {option.command})
|
|
44
|
-
</text>
|
|
42
|
+
<text fg={t.muted}>Custom command (blank to reset to default: {option.command})</text>
|
|
45
43
|
<InputField
|
|
46
44
|
active
|
|
47
45
|
value={editBuffer}
|
|
@@ -65,17 +63,11 @@ export function NewTabModal({
|
|
|
65
63
|
onEdit: () => dispatchGlobal({ assistantId: option.id, type: 'open-edit-custom-command' }),
|
|
66
64
|
subtitle: (
|
|
67
65
|
<box flexDirection="column">
|
|
68
|
-
<text fg={
|
|
69
|
-
{customCmd ? <text fg={
|
|
66
|
+
<text fg={t.muted}>{option.description}</text>
|
|
67
|
+
{customCmd ? <text fg={t.palette.primary}>{customCmd}</text> : null}
|
|
70
68
|
</box>
|
|
71
69
|
),
|
|
72
|
-
title:
|
|
73
|
-
<text
|
|
74
|
-
fg={active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']}
|
|
75
|
-
>
|
|
76
|
-
{option.label}
|
|
77
|
-
</text>
|
|
78
|
-
),
|
|
70
|
+
title: <text fg={active ? t.palette.ink : t.muted}>{option.label}</text>,
|
|
79
71
|
}
|
|
80
72
|
})
|
|
81
73
|
|
|
@@ -89,7 +81,7 @@ export function NewTabModal({
|
|
|
89
81
|
cursorPos={cursorPos}
|
|
90
82
|
items={items}
|
|
91
83
|
selectedIndex={selectedIndex}
|
|
92
|
-
emptyState={<text fg={
|
|
84
|
+
emptyState={<text fg={t.muted}>No matching assistants.</text>}
|
|
93
85
|
onHover={(index) => dispatchGlobal({ index, type: 'set-modal-selection-index' })}
|
|
94
86
|
/>
|
|
95
87
|
)
|
|
@@ -2,11 +2,11 @@ import { parseKeyNotation } from '../../input/keymap/key-chord'
|
|
|
2
2
|
import { formatChord } from '../../input/keymap/key-format'
|
|
3
3
|
import { useAppStore } from '../../state/app-store'
|
|
4
4
|
import { useKeymap } from '../keymap-context'
|
|
5
|
-
import {
|
|
5
|
+
import { useTokens } from '../theme'
|
|
6
6
|
import { Surface } from './surface'
|
|
7
7
|
|
|
8
8
|
export function PendingChordOverlay() {
|
|
9
|
-
const
|
|
9
|
+
const t = useTokens()
|
|
10
10
|
const pendingChords = useAppStore((s) => s.pendingChords)
|
|
11
11
|
const modalOpen = useAppStore((s) => s.modal.type !== null)
|
|
12
12
|
const config = useKeymap()
|
|
@@ -20,9 +20,9 @@ export function PendingChordOverlay() {
|
|
|
20
20
|
<box position="absolute" bottom={modalOpen ? 10 : 2} right={1}>
|
|
21
21
|
<Surface tone="elevated" paddingLeft={1} paddingRight={1}>
|
|
22
22
|
<box flexDirection="row">
|
|
23
|
-
<text fg={
|
|
24
|
-
<text fg={
|
|
25
|
-
<text fg={
|
|
23
|
+
<text fg={t.muted}>pending: </text>
|
|
24
|
+
<text fg={t.palette.primary}>{display}</text>
|
|
25
|
+
<text fg={t.muted}> …</text>
|
|
26
26
|
</box>
|
|
27
27
|
</Surface>
|
|
28
28
|
</box>
|