@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
|
@@ -4,7 +4,7 @@ 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 {
|
|
7
|
+
import { useTokens } from '../theme'
|
|
8
8
|
import { BareInput } from './bare-input'
|
|
9
9
|
import { ListItem } from './list-item'
|
|
10
10
|
import { ModalShell } from './modal-shell'
|
|
@@ -39,7 +39,7 @@ const VIEWPORT_HEIGHT_RATIO = 0.6
|
|
|
39
39
|
const MODAL_CHROME_ROWS = 6
|
|
40
40
|
|
|
41
41
|
function PickerItemCtas({ onDelete, onEdit }: { onEdit?: () => void; onDelete?: () => void }) {
|
|
42
|
-
const
|
|
42
|
+
const t = useTokens()
|
|
43
43
|
return (
|
|
44
44
|
<box flexDirection="row" gap={1}>
|
|
45
45
|
{onEdit ? (
|
|
@@ -49,7 +49,7 @@ function PickerItemCtas({ onDelete, onEdit }: { onEdit?: () => void; onDelete?:
|
|
|
49
49
|
onEdit()
|
|
50
50
|
}}
|
|
51
51
|
>
|
|
52
|
-
<text fg={
|
|
52
|
+
<text fg={t.palette.primary}>[edit]</text>
|
|
53
53
|
</box>
|
|
54
54
|
) : null}
|
|
55
55
|
{onDelete ? (
|
|
@@ -59,7 +59,7 @@ function PickerItemCtas({ onDelete, onEdit }: { onEdit?: () => void; onDelete?:
|
|
|
59
59
|
onDelete()
|
|
60
60
|
}}
|
|
61
61
|
>
|
|
62
|
-
<text fg={
|
|
62
|
+
<text fg={t.palette.error}>[del]</text>
|
|
63
63
|
</box>
|
|
64
64
|
) : null}
|
|
65
65
|
</box>
|
|
@@ -80,7 +80,7 @@ export function Picker({
|
|
|
80
80
|
title,
|
|
81
81
|
width,
|
|
82
82
|
}: PickerProps) {
|
|
83
|
-
const
|
|
83
|
+
const t = useTokens()
|
|
84
84
|
const dimensions = useTerminalDimensions()
|
|
85
85
|
const maxHeight = Math.max(6, Math.floor(dimensions.height * VIEWPORT_HEIGHT_RATIO))
|
|
86
86
|
const listHeight = Math.max(1, maxHeight - MODAL_CHROME_ROWS)
|
|
@@ -140,7 +140,7 @@ export function Picker({
|
|
|
140
140
|
if (item.group && item.group !== prevGroup) {
|
|
141
141
|
nodes.push(
|
|
142
142
|
<box key={`group::${item.group}`} paddingLeft={1} paddingTop={index === 0 ? 0 : 1}>
|
|
143
|
-
<text fg={
|
|
143
|
+
<text fg={t.palette.warning} wrapMode="none">
|
|
144
144
|
{item.group}
|
|
145
145
|
</text>
|
|
146
146
|
</box>
|
|
@@ -10,11 +10,12 @@ import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
|
10
10
|
import { IDLE_SESSION_STATUS } from '../../state/types'
|
|
11
11
|
import { useBusySpinner } from '../hooks/use-busy-spinner'
|
|
12
12
|
import { moveIdToIdPosition, orderSessionsForDisplay } from '../session-ordering'
|
|
13
|
-
import { useBg,
|
|
13
|
+
import { useBg, useTokens } from '../theme'
|
|
14
|
+
import { ContextMenuBox } from './context-menu-box'
|
|
14
15
|
|
|
15
16
|
export function SessionBar() {
|
|
16
|
-
const
|
|
17
|
-
const headerBg = useBg('
|
|
17
|
+
const t = useTokens()
|
|
18
|
+
const headerBg = useBg('elevated')
|
|
18
19
|
const sessions = useAppStore((s) => s.sessions)
|
|
19
20
|
const currentId = useAppStore((s) => s.currentSessionId)
|
|
20
21
|
const bar = useAppStore((s) => s.sessionBar)
|
|
@@ -22,11 +23,7 @@ export function SessionBar() {
|
|
|
22
23
|
|
|
23
24
|
const [draggingId, setDraggingId] = useState<string | null>(null)
|
|
24
25
|
const [dragOrder, setDragOrder] = useState<string[] | null>(null)
|
|
25
|
-
// Hysteresis: the id of the chip we most recently swapped with. While the
|
|
26
|
-
// cursor remains over that chip we refuse to swap back (prevents oscillation
|
|
27
|
-
// when a long chip's new bounds still cover the cursor after a swap).
|
|
28
26
|
const lastSwapWithRef = useRef<string | null>(null)
|
|
29
|
-
// Live bounds of each chip after render, keyed by session id.
|
|
30
27
|
const chipRefs = useRef(new Map<string, BoxRenderable>())
|
|
31
28
|
|
|
32
29
|
const ordered = useMemo(() => orderSessionsForDisplay(sessions), [sessions])
|
|
@@ -125,6 +122,22 @@ export function SessionBar() {
|
|
|
125
122
|
onMouseDrag={handleMouseDrag}
|
|
126
123
|
onMouseUp={commitDrop}
|
|
127
124
|
onMouseDragEnd={cancelDrag}
|
|
125
|
+
rightClickMenu={[
|
|
126
|
+
[
|
|
127
|
+
'Rename',
|
|
128
|
+
() =>
|
|
129
|
+
dispatchGlobal({
|
|
130
|
+
initialName: session.name,
|
|
131
|
+
returnToSessionPicker: false,
|
|
132
|
+
sessionTargetId: session.id,
|
|
133
|
+
type: 'open-session-name-modal',
|
|
134
|
+
}),
|
|
135
|
+
],
|
|
136
|
+
[
|
|
137
|
+
'Close',
|
|
138
|
+
() => runSideEffectGlobal({ sessionId: session.id, type: 'delete-session' }),
|
|
139
|
+
],
|
|
140
|
+
]}
|
|
128
141
|
/>
|
|
129
142
|
)
|
|
130
143
|
})}
|
|
@@ -133,13 +146,13 @@ export function SessionBar() {
|
|
|
133
146
|
flexDirection="row"
|
|
134
147
|
paddingLeft={1}
|
|
135
148
|
paddingRight={1}
|
|
136
|
-
backgroundColor={
|
|
149
|
+
backgroundColor={t.selected}
|
|
137
150
|
onMouseDown={(e) => {
|
|
138
151
|
e.stopPropagation()
|
|
139
152
|
dispatchGlobal({ returnToSessionPicker: false, type: 'open-create-session-modal' })
|
|
140
153
|
}}
|
|
141
154
|
>
|
|
142
|
-
<text fg={
|
|
155
|
+
<text fg={t.palette.ink} selectable={false}>
|
|
143
156
|
+ New
|
|
144
157
|
</text>
|
|
145
158
|
</box>
|
|
@@ -166,6 +179,7 @@ interface SessionChipProps {
|
|
|
166
179
|
onMouseDrag: (event: OtuiMouseEvent) => void
|
|
167
180
|
onMouseUp: (event: OtuiMouseEvent) => void
|
|
168
181
|
onMouseDragEnd: (event: OtuiMouseEvent) => void
|
|
182
|
+
rightClickMenu: Array<[string, () => void]>
|
|
169
183
|
}
|
|
170
184
|
|
|
171
185
|
function SessionChip({
|
|
@@ -177,29 +191,29 @@ function SessionChip({
|
|
|
177
191
|
onMouseDragEnd,
|
|
178
192
|
onMouseUp,
|
|
179
193
|
onRef,
|
|
194
|
+
rightClickMenu,
|
|
180
195
|
session,
|
|
181
196
|
status,
|
|
182
197
|
}: SessionChipProps) {
|
|
183
|
-
const
|
|
184
|
-
const selectionBg = useBg('
|
|
198
|
+
const t = useTokens()
|
|
199
|
+
const selectionBg = useBg('selected')
|
|
185
200
|
const showSpinner = status.working
|
|
186
201
|
const showWaiting = status.waiting
|
|
187
202
|
const spinner = useBusySpinner(showSpinner)
|
|
188
|
-
const labelColor = active
|
|
189
|
-
? theme.colors['editor.foreground']
|
|
190
|
-
: theme.colors['descriptionForeground']
|
|
203
|
+
const labelColor = active ? t.palette.ink : t.muted
|
|
191
204
|
const bgColor = dragging || active ? selectionBg : undefined
|
|
192
|
-
const idleColor =
|
|
193
|
-
const workingColor =
|
|
194
|
-
const waitingColor =
|
|
205
|
+
const idleColor = t.palette.success
|
|
206
|
+
const workingColor = t.palette.primary
|
|
207
|
+
const waitingColor = t.palette.warning
|
|
195
208
|
|
|
196
209
|
return (
|
|
197
|
-
<
|
|
210
|
+
<ContextMenuBox
|
|
198
211
|
ref={onRef}
|
|
199
212
|
flexDirection="row"
|
|
200
213
|
paddingLeft={1}
|
|
201
214
|
paddingRight={1}
|
|
202
215
|
backgroundColor={bgColor}
|
|
216
|
+
rightClickMenu={rightClickMenu}
|
|
203
217
|
onMouseDown={(e) => {
|
|
204
218
|
e.preventDefault()
|
|
205
219
|
onMouseDown(e)
|
|
@@ -233,9 +247,9 @@ function SessionChip({
|
|
|
233
247
|
<text fg={labelColor} selectable={false}>
|
|
234
248
|
[{index}] {session.name}
|
|
235
249
|
</text>
|
|
236
|
-
<text fg={
|
|
250
|
+
<text fg={t.hover} selectable={false}>
|
|
237
251
|
{' '}
|
|
238
252
|
</text>
|
|
239
|
-
</
|
|
253
|
+
</ContextMenuBox>
|
|
240
254
|
)
|
|
241
255
|
}
|
|
@@ -4,7 +4,7 @@ import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
|
4
4
|
import { filterSessions } from '../../state/selectors'
|
|
5
5
|
import { abbreviatePath } from '../path-format'
|
|
6
6
|
import { orderSessionsForDisplay } from '../session-ordering'
|
|
7
|
-
import {
|
|
7
|
+
import { useTokens } from '../theme'
|
|
8
8
|
import { uiTokens } from '../ui-tokens'
|
|
9
9
|
import { Picker, type PickerItem } from './picker'
|
|
10
10
|
|
|
@@ -31,10 +31,7 @@ function formatSessionLine(
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function getEmptyStateMessage(hasFilter: boolean): string {
|
|
34
|
-
if (hasFilter)
|
|
35
|
-
return 'No matching sessions.'
|
|
36
|
-
}
|
|
37
|
-
|
|
34
|
+
if (hasFilter) return 'No matching sessions.'
|
|
38
35
|
return 'No sessions yet. Press Enter or n to create your first session.'
|
|
39
36
|
}
|
|
40
37
|
|
|
@@ -46,7 +43,7 @@ export function SessionPickerModal({
|
|
|
46
43
|
selectedIndex,
|
|
47
44
|
sessions,
|
|
48
45
|
}: SessionPickerModalProps) {
|
|
49
|
-
const
|
|
46
|
+
const t = useTokens()
|
|
50
47
|
const ordered = orderSessionsForDisplay(sessions)
|
|
51
48
|
const baselineOrder = ordered.map((s) => s.id)
|
|
52
49
|
const filtered = filterSessions(ordered, filter)
|
|
@@ -60,14 +57,10 @@ export function SessionPickerModal({
|
|
|
60
57
|
onClick: () => runSideEffectGlobal({ type: 'confirm-selected-session' }),
|
|
61
58
|
onDelete: () => runSideEffectGlobal({ type: 'delete-selected-session' }),
|
|
62
59
|
subtitle: session.projectPath ? (
|
|
63
|
-
<text fg={
|
|
64
|
-
{abbreviatePath(session.projectPath)}
|
|
65
|
-
</text>
|
|
60
|
+
<text fg={t.muted}>{abbreviatePath(session.projectPath)}</text>
|
|
66
61
|
) : undefined,
|
|
67
62
|
title: (
|
|
68
|
-
<text
|
|
69
|
-
fg={active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']}
|
|
70
|
-
>
|
|
63
|
+
<text fg={active ? t.palette.ink : t.muted}>
|
|
71
64
|
{formatSessionLine(session, currentSessionId, currentTabCount, displayIndex)}
|
|
72
65
|
</text>
|
|
73
66
|
),
|
|
@@ -78,13 +71,7 @@ export function SessionPickerModal({
|
|
|
78
71
|
key: '__create-new__',
|
|
79
72
|
onClick: () => runSideEffectGlobal({ type: 'confirm-selected-session' }),
|
|
80
73
|
title: (
|
|
81
|
-
<text
|
|
82
|
-
fg={
|
|
83
|
-
selectedIndex === filtered.length
|
|
84
|
-
? theme.colors['editor.foreground']
|
|
85
|
-
: theme.colors['descriptionForeground']
|
|
86
|
-
}
|
|
87
|
-
>
|
|
74
|
+
<text fg={selectedIndex === filtered.length ? t.palette.ink : t.muted}>
|
|
88
75
|
Create new session
|
|
89
76
|
</text>
|
|
90
77
|
),
|
|
@@ -104,7 +91,7 @@ export function SessionPickerModal({
|
|
|
104
91
|
selectedIndex={selectedIndex}
|
|
105
92
|
emptyState={
|
|
106
93
|
filtered.length === 0 ? (
|
|
107
|
-
<text fg={
|
|
94
|
+
<text fg={t.muted}>{getEmptyStateMessage(hasFilter)}</text>
|
|
108
95
|
) : undefined
|
|
109
96
|
}
|
|
110
97
|
onHover={(index) => dispatchGlobal({ index, type: 'set-modal-selection-index' })}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { Theme } from '@brimveyn/aimux-config'
|
|
2
|
-
|
|
3
1
|
import { type ScrollBoxRenderable } from '@opentui/core'
|
|
4
2
|
import { memo, useMemo, useRef } from 'react'
|
|
5
3
|
|
|
6
4
|
import { useAppStore } from '../../state/app-store'
|
|
7
5
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
8
|
-
import {
|
|
6
|
+
import { getCurrentTokens, type ThemeTokens, useBg, useTokens } from '../theme'
|
|
7
|
+
import { ContextMenuBox } from './context-menu-box'
|
|
9
8
|
import { GitPaneWidget } from './git-pane-widget'
|
|
10
9
|
import { buildTabGroupInfo } from './sidebar-group-metadata'
|
|
11
10
|
import { TabItem } from './tab-item'
|
|
@@ -24,19 +23,19 @@ const GUTTER_PAD = '│'
|
|
|
24
23
|
function getRowBackground({
|
|
25
24
|
alternate,
|
|
26
25
|
isActive,
|
|
27
|
-
|
|
26
|
+
tokens,
|
|
28
27
|
}: {
|
|
29
28
|
isActive: boolean
|
|
30
29
|
alternate: boolean
|
|
31
|
-
|
|
30
|
+
tokens: ThemeTokens
|
|
32
31
|
}): string | undefined {
|
|
33
|
-
if (isActive) return
|
|
34
|
-
if (alternate) return
|
|
32
|
+
if (isActive) return tokens.selected
|
|
33
|
+
if (alternate) return tokens.hover
|
|
35
34
|
return undefined
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
const SidebarTop = memo(function SidebarTop() {
|
|
39
|
-
const
|
|
38
|
+
const tokens = useTokens()
|
|
40
39
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
41
40
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
42
41
|
const sessions = useAppStore((s) => s.sessions)
|
|
@@ -48,22 +47,20 @@ const SidebarTop = memo(function SidebarTop() {
|
|
|
48
47
|
|
|
49
48
|
return (
|
|
50
49
|
<box flexDirection="column" flexShrink={0} gap={0}>
|
|
51
|
-
<text fg={
|
|
50
|
+
<text fg={tokens.palette.primary}>
|
|
52
51
|
<strong>aimux</strong>
|
|
53
52
|
</text>
|
|
54
|
-
<text fg={
|
|
55
|
-
{currentSession ? currentSession.name : 'No session selected'}
|
|
56
|
-
</text>
|
|
53
|
+
<text fg={tokens.accent}>{currentSession ? currentSession.name : 'No session selected'}</text>
|
|
57
54
|
{branch ? (
|
|
58
55
|
<box flexDirection="row">
|
|
59
|
-
<text fg={
|
|
60
|
-
<text fg={
|
|
56
|
+
<text fg={tokens.palette.primary}>{'\u{e702}'} </text>
|
|
57
|
+
<text fg={tokens.muted}>{branch}</text>
|
|
61
58
|
</box>
|
|
62
59
|
) : null}
|
|
63
60
|
<box
|
|
64
61
|
flexDirection="row"
|
|
65
62
|
paddingY={1}
|
|
66
|
-
backgroundColor={
|
|
63
|
+
backgroundColor={tokens.selected}
|
|
67
64
|
justifyContent="center"
|
|
68
65
|
marginTop={1}
|
|
69
66
|
onMouseDown={(e) => {
|
|
@@ -71,25 +68,22 @@ const SidebarTop = memo(function SidebarTop() {
|
|
|
71
68
|
dispatchGlobal({ type: 'open-new-tab-modal' })
|
|
72
69
|
}}
|
|
73
70
|
>
|
|
74
|
-
<text fg={
|
|
71
|
+
<text fg={tokens.palette.ink}>+ New assistant</text>
|
|
75
72
|
</box>
|
|
76
|
-
<text fg={
|
|
77
|
-
{'·'.repeat(Math.max(0, sidebarWidth - 2))}
|
|
78
|
-
</text>
|
|
73
|
+
<text fg={tokens.hover}>{'·'.repeat(Math.max(0, sidebarWidth - 2))}</text>
|
|
79
74
|
</box>
|
|
80
75
|
)
|
|
81
76
|
})
|
|
82
77
|
|
|
83
78
|
function renderGroupGutter(isGroupStart: boolean, isGroupMiddle: boolean, isGroupEnd: boolean) {
|
|
79
|
+
const t = getCurrentTokens()
|
|
84
80
|
return (
|
|
85
81
|
<box flexDirection="column" width={1} overflow="hidden">
|
|
86
|
-
<text fg={
|
|
82
|
+
<text fg={t.accent}>
|
|
87
83
|
{/* oxlint-disable-next-line no-nested-ternary */}
|
|
88
84
|
{isGroupStart ? GUTTER_START : isGroupMiddle ? GUTTER_MIDDLE : GUTTER_PAD}
|
|
89
85
|
</text>
|
|
90
|
-
<text fg={
|
|
91
|
-
{isGroupEnd ? GUTTER_END : GUTTER_PAD}
|
|
92
|
-
</text>
|
|
86
|
+
<text fg={t.accent}>{isGroupEnd ? GUTTER_END : GUTTER_PAD}</text>
|
|
93
87
|
</box>
|
|
94
88
|
)
|
|
95
89
|
}
|
|
@@ -99,7 +93,7 @@ interface TabsBodyProps {
|
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
102
|
-
const
|
|
96
|
+
const tokens = useTokens()
|
|
103
97
|
const tabs = useAppStore((s) => s.tabs)
|
|
104
98
|
const activeTabId = useAppStore((s) => s.activeTabId)
|
|
105
99
|
const focusMode = useAppStore((s) => s.focusMode)
|
|
@@ -129,7 +123,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
129
123
|
>
|
|
130
124
|
{tabs.length === 0 ? (
|
|
131
125
|
<box paddingTop={1}>
|
|
132
|
-
<text fg={
|
|
126
|
+
<text fg={tokens.muted}>No tabs yet. Press Ctrl+n.</text>
|
|
133
127
|
</box>
|
|
134
128
|
) : (
|
|
135
129
|
tabs.map((tab, index) => {
|
|
@@ -145,7 +139,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
145
139
|
return (
|
|
146
140
|
<box
|
|
147
141
|
key={tab.id}
|
|
148
|
-
backgroundColor={getRowBackground({ alternate, isActive,
|
|
142
|
+
backgroundColor={getRowBackground({ alternate, isActive, tokens })}
|
|
149
143
|
flexDirection="row"
|
|
150
144
|
onMouseDown={(event) => {
|
|
151
145
|
event.stopPropagation()
|
|
@@ -171,8 +165,8 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
171
165
|
})
|
|
172
166
|
|
|
173
167
|
export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
174
|
-
const
|
|
175
|
-
const sidebarBg = useBg('
|
|
168
|
+
const tokens = useTokens()
|
|
169
|
+
const sidebarBg = useBg('elevated')
|
|
176
170
|
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
177
171
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
178
172
|
const gitPane = useAppStore((s) => s.gitPane)
|
|
@@ -190,11 +184,7 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
190
184
|
const tabsGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.ratio) * 100)) : 1
|
|
191
185
|
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.ratio * 100)) : 0
|
|
192
186
|
|
|
193
|
-
const separator = (
|
|
194
|
-
<text fg={theme.colors['editor.lineHighlightBackground']}>
|
|
195
|
-
{'·'.repeat(Math.max(0, sidebarWidth - 2))}
|
|
196
|
-
</text>
|
|
197
|
-
)
|
|
187
|
+
const separator = <text fg={tokens.hover}>{'·'.repeat(Math.max(0, sidebarWidth - 2))}</text>
|
|
198
188
|
const gitBody = gitEmbedded ? (
|
|
199
189
|
<box flexDirection="column" flexGrow={gitGrow} flexShrink={1} flexBasis={0} overflow="hidden">
|
|
200
190
|
<GitPaneWidget pollingEnabled={gitPane.visible} />
|
|
@@ -202,12 +192,23 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
202
192
|
) : null
|
|
203
193
|
|
|
204
194
|
return (
|
|
205
|
-
<
|
|
195
|
+
<ContextMenuBox
|
|
206
196
|
width={sidebarWidth}
|
|
207
197
|
padding={0}
|
|
208
198
|
flexDirection="column"
|
|
209
199
|
backgroundColor={sidebarBg}
|
|
210
200
|
gap={0}
|
|
201
|
+
rightClickMenu={[
|
|
202
|
+
['Hide sidebar', () => dispatchGlobal({ type: 'toggle-sidebar' })],
|
|
203
|
+
['Toggle git pane', () => dispatchGlobal({ type: 'toggle-git-pane' })],
|
|
204
|
+
[
|
|
205
|
+
'Toggle diff mode',
|
|
206
|
+
() => {
|
|
207
|
+
dispatchGlobal({ type: 'enter-git-mode' })
|
|
208
|
+
dispatchGlobal({ type: 'git-mode-toggle-diff-view' })
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
]}
|
|
211
212
|
onMouseDown={() => {
|
|
212
213
|
if (focusMode === 'terminal-input') {
|
|
213
214
|
dispatchGlobal({ focusMode: 'navigation', type: 'set-focus-mode' })
|
|
@@ -236,6 +237,6 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
236
237
|
{gitBody}
|
|
237
238
|
</>
|
|
238
239
|
) : null}
|
|
239
|
-
</
|
|
240
|
+
</ContextMenuBox>
|
|
240
241
|
)
|
|
241
242
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTokens } from '../theme'
|
|
2
2
|
import { uiTokens } from '../ui-tokens'
|
|
3
3
|
import { InputField } from './input-field'
|
|
4
4
|
import { ModalShell } from './modal-shell'
|
|
@@ -16,7 +16,7 @@ export function SnippetEditorModal({
|
|
|
16
16
|
snippetContent,
|
|
17
17
|
snippetName,
|
|
18
18
|
}: SnippetEditorModalProps) {
|
|
19
|
-
const
|
|
19
|
+
const t = useTokens()
|
|
20
20
|
const nameActive = activeField === 'name'
|
|
21
21
|
const contentActive = activeField === 'content'
|
|
22
22
|
|
|
@@ -27,26 +27,12 @@ export function SnippetEditorModal({
|
|
|
27
27
|
width={uiTokens.modalWidth.xl}
|
|
28
28
|
>
|
|
29
29
|
<box flexDirection="column">
|
|
30
|
-
<text
|
|
31
|
-
fg={
|
|
32
|
-
nameActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
33
|
-
}
|
|
34
|
-
>
|
|
35
|
-
Name
|
|
36
|
-
</text>
|
|
30
|
+
<text fg={nameActive ? t.palette.ink : t.muted}>Name</text>
|
|
37
31
|
<InputField active={nameActive} value={snippetName} />
|
|
38
32
|
</box>
|
|
39
33
|
|
|
40
34
|
<box flexDirection="column">
|
|
41
|
-
<text
|
|
42
|
-
fg={
|
|
43
|
-
contentActive
|
|
44
|
-
? theme.colors['editor.foreground']
|
|
45
|
-
: theme.colors['descriptionForeground']
|
|
46
|
-
}
|
|
47
|
-
>
|
|
48
|
-
Content
|
|
49
|
-
</text>
|
|
35
|
+
<text fg={contentActive ? t.palette.ink : t.muted}>Content</text>
|
|
50
36
|
<InputField active={contentActive} value={snippetContent} />
|
|
51
37
|
</box>
|
|
52
38
|
</ModalShell>
|
|
@@ -2,7 +2,7 @@ import type { SnippetRecord } from '../../state/types'
|
|
|
2
2
|
|
|
3
3
|
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
4
4
|
import { filterSnippets } from '../../state/selectors'
|
|
5
|
-
import {
|
|
5
|
+
import { useTokens } from '../theme'
|
|
6
6
|
import { uiTokens } from '../ui-tokens'
|
|
7
7
|
import { Picker, type PickerItem } from './picker'
|
|
8
8
|
|
|
@@ -27,7 +27,7 @@ export function SnippetPickerModal({
|
|
|
27
27
|
selectedIndex,
|
|
28
28
|
snippets,
|
|
29
29
|
}: SnippetPickerModalProps) {
|
|
30
|
-
const
|
|
30
|
+
const t = useTokens()
|
|
31
31
|
const filtered = filterSnippets(snippets, filter)
|
|
32
32
|
|
|
33
33
|
const items: PickerItem[] = filtered.map((snippet, index) => {
|
|
@@ -40,13 +40,9 @@ export function SnippetPickerModal({
|
|
|
40
40
|
},
|
|
41
41
|
onDelete: () => runSideEffectGlobal({ type: 'delete-selected-snippet' }),
|
|
42
42
|
onEdit: () => runSideEffectGlobal({ type: 'edit-selected-snippet' }),
|
|
43
|
-
subtitle: (
|
|
44
|
-
<text fg={theme.colors['descriptionForeground']}>{truncateContent(snippet.content)}</text>
|
|
45
|
-
),
|
|
43
|
+
subtitle: <text fg={t.muted}>{truncateContent(snippet.content)}</text>,
|
|
46
44
|
title: (
|
|
47
|
-
<text
|
|
48
|
-
fg={active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']}
|
|
49
|
-
>
|
|
45
|
+
<text fg={active ? t.palette.ink : t.muted}>
|
|
50
46
|
<strong>{snippet.name}</strong>
|
|
51
47
|
</text>
|
|
52
48
|
),
|
|
@@ -64,7 +60,7 @@ export function SnippetPickerModal({
|
|
|
64
60
|
items={items}
|
|
65
61
|
selectedIndex={selectedIndex}
|
|
66
62
|
emptyState={
|
|
67
|
-
<text fg={
|
|
63
|
+
<text fg={t.muted}>
|
|
68
64
|
{filter ? 'No matching snippets.' : 'No snippets yet. Press n to create one.'}
|
|
69
65
|
</text>
|
|
70
66
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type PaneRect,
|
|
12
12
|
type SplitDirection,
|
|
13
13
|
} from '../../state/layout-tree'
|
|
14
|
-
import {
|
|
14
|
+
import { useTokens } from '../theme'
|
|
15
15
|
import { TerminalPane } from './terminal-pane'
|
|
16
16
|
|
|
17
17
|
const PANE_CHROME = PANE_BORDER
|
|
@@ -58,7 +58,7 @@ export function SplitLayout({
|
|
|
58
58
|
onTerminalScrollEvent,
|
|
59
59
|
tabs,
|
|
60
60
|
}: SplitLayoutProps) {
|
|
61
|
-
const
|
|
61
|
+
const t = useTokens()
|
|
62
62
|
if (node.type === 'leaf') {
|
|
63
63
|
const tab = tabs.find((t) => t.id === node.tabId)
|
|
64
64
|
const isActive = node.tabId === activeTabId
|
|
@@ -135,7 +135,7 @@ export function SplitLayout({
|
|
|
135
135
|
<box
|
|
136
136
|
minWidth={node.direction === 'vertical' ? 1 : undefined}
|
|
137
137
|
minHeight={node.direction === 'horizontal' ? 1 : undefined}
|
|
138
|
-
backgroundColor={
|
|
138
|
+
backgroundColor={t.border}
|
|
139
139
|
onMouseDown={(e: OtuiMouseEvent) => {
|
|
140
140
|
e.preventDefault()
|
|
141
141
|
if (!onSeparatorDragStart) return
|
|
@@ -4,22 +4,21 @@ import { version as APP_VERSION } from '../../../package.json'
|
|
|
4
4
|
import { useAppStore } from '../../state/app-store'
|
|
5
5
|
import { useKeymap } from '../keymap-context'
|
|
6
6
|
import { getStatusBarModel } from '../status-bar-model'
|
|
7
|
-
import {
|
|
7
|
+
import { getCurrentTokens, useBg, useTokens } from '../theme'
|
|
8
8
|
|
|
9
9
|
function getModeColor(focusMode: AppState['focusMode']): string {
|
|
10
|
-
const t =
|
|
10
|
+
const t = getCurrentTokens()
|
|
11
11
|
switch (focusMode) {
|
|
12
12
|
case 'terminal-input':
|
|
13
|
-
return t.
|
|
13
|
+
return t.palette.primary
|
|
14
14
|
case 'modal':
|
|
15
|
-
return t.colors['editorWarning.foreground']
|
|
16
15
|
case 'command-edit':
|
|
17
|
-
return t.
|
|
16
|
+
return t.palette.warning
|
|
18
17
|
case 'git':
|
|
19
|
-
return t.
|
|
18
|
+
return t.palette.success
|
|
20
19
|
case 'navigation':
|
|
21
20
|
default:
|
|
22
|
-
return t.
|
|
21
|
+
return t.accent
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -40,8 +39,8 @@ function getModeLabel(focusMode: AppState['focusMode']): string {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
export function StatusBar() {
|
|
43
|
-
const
|
|
44
|
-
const headerBg = useBg('
|
|
42
|
+
const t = useTokens()
|
|
43
|
+
const headerBg = useBg('elevated')
|
|
45
44
|
const state = useAppStore((s) => s)
|
|
46
45
|
const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId)
|
|
47
46
|
const config = useKeymap()
|
|
@@ -60,13 +59,13 @@ export function StatusBar() {
|
|
|
60
59
|
<box width="100%" flexDirection="row">
|
|
61
60
|
<text fg={getModeColor(state.focusMode)}>[{getModeLabel(state.focusMode)}]</text>
|
|
62
61
|
<text> </text>
|
|
63
|
-
<text fg={
|
|
62
|
+
<text fg={t.palette.ink}>{model.left}</text>
|
|
64
63
|
</box>
|
|
65
64
|
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
66
|
-
<text fg={
|
|
65
|
+
<text fg={t.muted}>{model.right}</text>
|
|
67
66
|
<box flexDirection="row" gap={2}>
|
|
68
|
-
{model.help ? <text fg={
|
|
69
|
-
<text fg={
|
|
67
|
+
{model.help ? <text fg={t.muted}>{model.help}</text> : null}
|
|
68
|
+
<text fg={t.hover}>v{APP_VERSION}</text>
|
|
70
69
|
</box>
|
|
71
70
|
</box>
|
|
72
71
|
</box>
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
import type
|
|
4
|
-
|
|
5
|
-
import { useBg } from '../theme'
|
|
3
|
+
import { type SurfaceToken, useBg } from '../theme'
|
|
6
4
|
|
|
7
5
|
type SurfaceTone = 'muted' | 'elevated' | 'selected' | 'input' | 'inputActive'
|
|
8
6
|
|
|
9
|
-
function
|
|
7
|
+
function toneToToken(tone: SurfaceTone): SurfaceToken {
|
|
10
8
|
switch (tone) {
|
|
11
9
|
case 'elevated':
|
|
12
|
-
return '
|
|
10
|
+
return 'elevated'
|
|
13
11
|
case 'selected':
|
|
14
|
-
return 'list.activeSelectionBackground'
|
|
15
|
-
case 'input':
|
|
16
|
-
return 'editor.background'
|
|
17
12
|
case 'inputActive':
|
|
18
|
-
return '
|
|
13
|
+
return 'selected'
|
|
14
|
+
case 'input':
|
|
15
|
+
return 'base'
|
|
19
16
|
case 'muted':
|
|
20
17
|
default:
|
|
21
|
-
return '
|
|
18
|
+
return 'elevated'
|
|
22
19
|
}
|
|
23
20
|
}
|
|
24
21
|
|
|
@@ -47,7 +44,7 @@ export function Surface({
|
|
|
47
44
|
tone = 'muted',
|
|
48
45
|
width,
|
|
49
46
|
}: SurfaceProps) {
|
|
50
|
-
const bg = useBg(
|
|
47
|
+
const bg = useBg(toneToToken(tone))
|
|
51
48
|
return (
|
|
52
49
|
<box
|
|
53
50
|
backgroundColor={bg}
|