@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.
Files changed (57) hide show
  1. package/package.json +3 -4
  2. package/src/app-runtime/pty-write.ts +8 -3
  3. package/src/app-runtime/side-effects.ts +10 -2
  4. package/src/app-runtime/use-renderer-bindings.ts +2 -1
  5. package/src/app.tsx +5 -8
  6. package/src/config.ts +2 -10
  7. package/src/input/modes/bridge.ts +1 -1
  8. package/src/input/modes/types.ts +6 -1
  9. package/src/input/raw-input-handler.ts +12 -3
  10. package/src/ipc/manager-protocol.ts +2 -2
  11. package/src/ipc/protocol.ts +2 -2
  12. package/src/pty/terminal-snapshot.ts +4 -4
  13. package/src/session-backend/bootstrap.ts +21 -1
  14. package/src/state/reducers/modal-state.ts +3 -2
  15. package/src/state/reducers/session-state.ts +0 -7
  16. package/src/state/types.ts +7 -1
  17. package/src/ui/components/bare-input.tsx +5 -5
  18. package/src/ui/components/context-menu-box.tsx +21 -0
  19. package/src/ui/components/context-menu-overlay.tsx +114 -0
  20. package/src/ui/components/create-session-modal.tsx +12 -41
  21. package/src/ui/components/diff-renderer/fold-strip.tsx +7 -7
  22. package/src/ui/components/diff-renderer/highlight.ts +6 -10
  23. package/src/ui/components/diff-renderer/pierre-diff.tsx +3 -3
  24. package/src/ui/components/diff-renderer/prepare-diff.ts +2 -3
  25. package/src/ui/components/diff-renderer/split-view.tsx +22 -29
  26. package/src/ui/components/diff-renderer/stacked-view.tsx +18 -31
  27. package/src/ui/components/diff-renderer/use-diff-prefetch.ts +0 -1
  28. package/src/ui/components/diff-renderer/use-diff-preparation.ts +1 -1
  29. package/src/ui/components/git-commit-modal.tsx +4 -16
  30. package/src/ui/components/git-panel.tsx +37 -73
  31. package/src/ui/components/git-view.tsx +17 -19
  32. package/src/ui/components/help-modal.tsx +5 -13
  33. package/src/ui/components/input-field.tsx +5 -7
  34. package/src/ui/components/list-item.tsx +3 -11
  35. package/src/ui/components/modal-keybinds-overlay.tsx +4 -4
  36. package/src/ui/components/modal-shell.tsx +6 -11
  37. package/src/ui/components/new-tab-modal.tsx +7 -15
  38. package/src/ui/components/pending-chord-overlay.tsx +5 -5
  39. package/src/ui/components/picker.tsx +6 -6
  40. package/src/ui/components/session-bar.tsx +34 -20
  41. package/src/ui/components/session-picker-modal.tsx +7 -20
  42. package/src/ui/components/sidebar.tsx +36 -35
  43. package/src/ui/components/snippet-editor-modal.tsx +4 -18
  44. package/src/ui/components/snippet-picker-modal.tsx +5 -9
  45. package/src/ui/components/split-layout.tsx +3 -3
  46. package/src/ui/components/status-bar.tsx +12 -13
  47. package/src/ui/components/surface.tsx +8 -11
  48. package/src/ui/components/tab-item.tsx +54 -29
  49. package/src/ui/components/terminal-pane.tsx +59 -25
  50. package/src/ui/components/theme-picker-modal.tsx +7 -21
  51. package/src/ui/components/update-available-modal.tsx +3 -13
  52. package/src/ui/context-menu/controller.ts +34 -0
  53. package/src/ui/root.tsx +5 -2
  54. package/src/ui/shiki.ts +29 -21
  55. package/src/ui/theme-store.ts +110 -21
  56. package/src/ui/theme.ts +6 -3
  57. 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 { getCurrentTheme, getTransparent, useTheme, useTransparent } from '../theme'
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 = getCurrentTheme()
47
+ const t = getCurrentTokens()
48
48
  switch (status) {
49
49
  case '?':
50
50
  case 'A':
51
- return t.colors['gitDecoration.addedResourceForeground']
51
+ return t.palette.success
52
52
  case 'C':
53
53
  case 'R':
54
- return t.colors['textLink.foreground']
54
+ return t.palette.primary
55
55
  case 'D':
56
56
  case 'U':
57
- return t.colors['editorError.foreground']
57
+ return t.palette.error
58
58
  case 'M':
59
- return t.colors['editorWarning.foreground']
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={getCurrentTheme().colors['editor.foreground']}>{basename}</span>
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={getCurrentTheme().colors['descriptionForeground']}>{renamed.basename}</span>
118
- {showDir && renamedDir ? (
119
- <span fg={getCurrentTheme().colors['descriptionForeground']}> {renamedDir}</span>
120
- ) : null}
121
- <span fg={getCurrentTheme().colors['descriptionForeground']}> </span>
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={getCurrentTheme().colors['descriptionForeground']} bg={bg} flexShrink={0}>
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
- fg={getCurrentTheme().colors['gitDecoration.addedResourceForeground']}
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 bg =
163
- isSelected && !getTransparent()
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={getCurrentTheme().colors['descriptionForeground']} bg={bg}>
166
+ <text fg={t.muted} bg={bg}>
177
167
  {row.isCollapsed ? '▸' : '▾'}
178
168
  </text>
179
- <text fg={getCurrentTheme().colors['textLink.foreground']} bg={bg}>
169
+ <text fg={t.palette.primary} bg={bg}>
180
170
  {row.isCollapsed ? '\uf07b' : '\uf07c'}
181
171
  </text>
182
- <text fg={getCurrentTheme().colors['textLink.foreground']} bg={bg} wrapMode="none">
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={getCurrentTheme().colors['terminal.ansiMagenta']}>
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
- fg={
256
- fileListMode === 'tree'
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: getCurrentTheme().colors['editorError.foreground'] }
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 theme = useTheme()
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={theme.colors['descriptionForeground']}>
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, useTheme } from '../theme'
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 theme = useTheme()
50
+ const t = useTokens()
51
51
  if (loading && !diff) {
52
52
  return (
53
53
  <box flexGrow={1} padding={1}>
54
- <text fg={theme.colors['descriptionForeground']}>Loading diff…</text>
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={theme.colors['descriptionForeground']}>Select a file.</text>
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={theme.colors['editorError.foreground']}>{diff.errorMessage}</text>
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={theme.colors['descriptionForeground']}>{placeholder}</text>
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={theme.colors['descriptionForeground']}>
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 theme = useTheme()
109
- const sidebarBg = useBg('sideBar.background')
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={theme.colors['editorWarning.foreground']}>
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={theme.colors['textLink.foreground']}>
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={theme.colors['textLink.foreground']}>
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={theme.colors['textLink.foreground']}>{'\u{e702}'} </text>
224
- <text fg={theme.colors['descriptionForeground']}>{gitPanel.branch}</text>
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={theme.colors['editorWarning.foreground']}>
229
+ <text fg={t.palette.warning}>
230
230
  <strong>HEAD~{gitMode.headOffset}</strong>
231
231
  </text>
232
- <text fg={theme.colors['descriptionForeground']}>[ newer · ] older</text>
232
+ <text fg={t.muted}>[ newer · ] older</text>
233
233
  </box>
234
234
  ) : null}
235
- <text fg={theme.colors['editor.lineHighlightBackground']}>
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 { useTheme } from '../theme'
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 theme = useTheme()
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={theme.colors['descriptionForeground']}>
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 { useTheme } from '../theme'
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 theme = useTheme()
13
- const fg = active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
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 ? theme.colors['editorLineNumber.foreground'] : fg}>
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={theme.colors['editor.foreground']} fg={theme.colors['editor.background']}>
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 { useTheme } from '../theme'
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 theme = useTheme()
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 { useTheme } from '../theme'
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 theme = useTheme()
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={theme.colors['terminal.ansiMagenta']}>{binding.keysDisplay}</text>
32
+ <text fg={t.accent}>{binding.keysDisplay}</text>
33
33
  </box>
34
- <text fg={theme.colors['descriptionForeground']}>{binding.description ?? ''}</text>
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 { useTheme, useTransparent } from '../theme'
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 theme = useTheme()
60
+ const t = useTokens()
66
61
  const transparent = useTransparent()
67
- const bg = transparent ? 'transparent' : theme.colors['sideBar.background']
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={theme.colors['focusBorder']}
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={theme.colors['terminal.ansiMagenta']}>{title}</text>
89
- {subtitle ? <text fg={theme.colors['descriptionForeground']}>{subtitle}</text> : null}
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 { useTheme } from '../theme'
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 theme = useTheme()
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={theme.colors['descriptionForeground']}>
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={theme.colors['descriptionForeground']}>{option.description}</text>
69
- {customCmd ? <text fg={theme.colors['textLink.foreground']}>{customCmd}</text> : null}
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={theme.colors['descriptionForeground']}>No matching assistants.</text>}
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 { useTheme } from '../theme'
5
+ import { useTokens } from '../theme'
6
6
  import { Surface } from './surface'
7
7
 
8
8
  export function PendingChordOverlay() {
9
- const theme = useTheme()
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={theme.colors['descriptionForeground']}>pending: </text>
24
- <text fg={theme.colors['textLink.foreground']}>{display}</text>
25
- <text fg={theme.colors['descriptionForeground']}> …</text>
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>