@brimveyn/aimux 1.9.3 → 1.9.6

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 (46) hide show
  1. package/package.json +2 -2
  2. package/src/app-runtime/side-effects.ts +29 -0
  3. package/src/app.tsx +4 -5
  4. package/src/input/modes/types.ts +2 -0
  5. package/src/pty/terminal-snapshot.ts +7 -7
  6. package/src/state/reducers/git-mode-state.ts +33 -1
  7. package/src/state/types.ts +1 -0
  8. package/src/ui/components/git/diff-renderer/build-rows.ts +21 -0
  9. package/src/ui/components/git/diff-renderer/fold-strip.tsx +7 -7
  10. package/src/ui/components/git/diff-renderer/pierre-diff.tsx +3 -3
  11. package/src/ui/components/git/diff-renderer/split-view.tsx +18 -17
  12. package/src/ui/components/git/diff-renderer/stacked-view.tsx +13 -13
  13. package/src/ui/components/git/git-panel.tsx +42 -39
  14. package/src/ui/components/git/git-view.tsx +20 -20
  15. package/src/ui/components/layout/session-bar.tsx +13 -13
  16. package/src/ui/components/layout/sidebar/sidebar.tsx +26 -28
  17. package/src/ui/components/layout/sidebar/tab-item.tsx +21 -21
  18. package/src/ui/components/layout/split-layout.tsx +2 -2
  19. package/src/ui/components/layout/status-bar.tsx +12 -12
  20. package/src/ui/components/layout/terminal-pane.tsx +19 -31
  21. package/src/ui/components/modals/app/ai-usage-modal.tsx +21 -21
  22. package/src/ui/components/modals/app/help-modal.tsx +5 -6
  23. package/src/ui/components/modals/app/update-available-modal.tsx +3 -3
  24. package/src/ui/components/modals/git/git-commit-modal.tsx +17 -17
  25. package/src/ui/components/modals/sessions/create-session-modal.tsx +8 -8
  26. package/src/ui/components/modals/sessions/session-picker-modal.tsx +6 -6
  27. package/src/ui/components/modals/shared/form.tsx +4 -4
  28. package/src/ui/components/modals/shared/modal-keybinds-overlay.tsx +4 -4
  29. package/src/ui/components/modals/shared/modal-shell.tsx +6 -6
  30. package/src/ui/components/modals/shared/picker.tsx +6 -6
  31. package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +5 -5
  32. package/src/ui/components/modals/tabs/new-tab-modal.tsx +6 -6
  33. package/src/ui/components/modals/themes/theme-picker-modal.tsx +17 -20
  34. package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +12 -12
  35. package/src/ui/components/overlays/context-menu/context-menu-overlay.tsx +6 -6
  36. package/src/ui/components/overlays/pending-chord-overlay.tsx +5 -5
  37. package/src/ui/components/primitives/bare-input.tsx +5 -5
  38. package/src/ui/components/primitives/input-field.tsx +5 -5
  39. package/src/ui/components/primitives/list-item.tsx +27 -22
  40. package/src/ui/components/primitives/surface.tsx +12 -9
  41. package/src/ui/filter-themes.ts +9 -8
  42. package/src/ui/root.tsx +5 -4
  43. package/src/ui/shiki.ts +8 -11
  44. package/src/ui/theme-store.ts +48 -118
  45. package/src/ui/theme.ts +8 -8
  46. package/src/ui/themes.ts +9 -20
@@ -4,7 +4,7 @@ import type { TabSession } from '../../../../state/types'
4
4
 
5
5
  import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
6
6
  import { useBusySpinner } from '../../../hooks/use-busy-spinner'
7
- import { getCurrentTokens, useTokens } from '../../../theme'
7
+ import { getCurrentTheme, useTheme } from '../../../theme'
8
8
  import { ContextMenuBox } from '../../overlays/context-menu/context-menu-box'
9
9
 
10
10
  interface TabItemProps {
@@ -16,16 +16,16 @@ interface TabItemProps {
16
16
  }
17
17
 
18
18
  function getStatusColor(status: TabSession['status']): string {
19
- const t = getCurrentTokens()
19
+ const t = getCurrentTheme()
20
20
  switch (status) {
21
21
  case 'running':
22
- return t.palette.success
22
+ return t.success
23
23
  case 'disconnected':
24
- return t.palette.warning
24
+ return t.warning
25
25
  case 'error':
26
- return t.palette.error
26
+ return t.error
27
27
  default:
28
- return t.muted
28
+ return t.textMuted
29
29
  }
30
30
  }
31
31
 
@@ -38,33 +38,33 @@ function getIndicator(active: boolean, focused: boolean, inLayout: boolean): str
38
38
  }
39
39
 
40
40
  function getIndicatorColor(active: boolean, focused: boolean, inLayout: boolean): string {
41
- const t = getCurrentTokens()
41
+ const t = getCurrentTheme()
42
42
  if (active) {
43
- return focused ? t.palette.primary : t.accent
43
+ return focused ? t.primary : t.text
44
44
  }
45
45
 
46
- return inLayout ? t.muted : t.hover
46
+ return inLayout ? t.textMuted : t.textMuted
47
47
  }
48
48
 
49
49
  function BusyIndicator() {
50
- const t = useTokens()
50
+ const t = useTheme()
51
51
  const frame = useBusySpinner()
52
- return <text fg={t.palette.primary}>{frame} working</text>
52
+ return <text fg={t.primary}>{frame} working</text>
53
53
  }
54
54
 
55
55
  function WaitingIndicator() {
56
- const t = useTokens()
57
- return <text fg={t.palette.warning}>? waiting</text>
56
+ const t = useTheme()
57
+ return <text fg={t.warning}>? waiting</text>
58
58
  }
59
59
 
60
60
  function ActivityIndicator({ tab }: { tab: TabSession }) {
61
- const t = useTokens()
61
+ const t = useTheme()
62
62
  if (tab.status === 'error') {
63
- return <text fg={t.palette.error}>✗ error</text>
63
+ return <text fg={t.error}>✗ error</text>
64
64
  }
65
65
 
66
66
  if (tab.status === 'disconnected') {
67
- return <text fg={t.palette.warning}>⏸ restore</text>
67
+ return <text fg={t.warning}>⏸ restore</text>
68
68
  }
69
69
 
70
70
  if (tab.activity === 'working') {
@@ -76,14 +76,14 @@ function ActivityIndicator({ tab }: { tab: TabSession }) {
76
76
  }
77
77
 
78
78
  if (tab.activity === 'idle') {
79
- return <text fg={t.palette.success}>● idle</text>
79
+ return <text fg={t.success}>● idle</text>
80
80
  }
81
81
 
82
82
  return <text fg={getStatusColor(tab.status)}>{tab.status}</text>
83
83
  }
84
84
 
85
85
  export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
86
- const t = useTokens()
86
+ const t = useTheme()
87
87
  const label = tab.command.split(' ')[0]
88
88
  const isInLayout = inLayout ?? false
89
89
  const indicator = getIndicator(active, focused, isInLayout)
@@ -135,7 +135,7 @@ export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
135
135
  <box flexDirection="row" alignItems="center">
136
136
  <text fg={indicatorColor}>{indicator} </text>
137
137
  <box flexGrow={1}>
138
- <text fg={active ? t.palette.ink : t.muted}>{tab.title}</text>
138
+ <text fg={active ? t.text : t.textMuted}>{tab.title}</text>
139
139
  </box>
140
140
  {hovered ? (
141
141
  <box
@@ -145,12 +145,12 @@ export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
145
145
  runSideEffectGlobal({ tabId: tab.id, type: 'close-tab' })
146
146
  }}
147
147
  >
148
- <text fg={t.muted}>×</text>
148
+ <text fg={t.textMuted}>×</text>
149
149
  </box>
150
150
  ) : null}
151
151
  </box>
152
152
  <box flexDirection="row">
153
- <text fg={t.muted}> {label} </text>
153
+ <text fg={t.textMuted}> {label} </text>
154
154
  <ActivityIndicator tab={tab} />
155
155
  </box>
156
156
  </ContextMenuBox>
@@ -11,7 +11,7 @@ import {
11
11
  type PaneRect,
12
12
  type SplitDirection,
13
13
  } from '../../../state/layout-tree'
14
- import { useTokens } from '../../theme'
14
+ import { useTheme } 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 t = useTokens()
61
+ const t = useTheme()
62
62
  if (node.type === 'leaf') {
63
63
  const tab = tabs.find((t) => t.id === node.tabId)
64
64
  const isActive = node.tabId === activeTabId
@@ -4,22 +4,22 @@ 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 { getCurrentTokens, useBg, useTokens } from '../../theme'
7
+ import { getCurrentTheme, useTheme } from '../../theme'
8
8
  import { AIUsageIndicator } from '../overlays/ai-usage/ai-usage-indicator'
9
9
 
10
10
  function getModeColor(focusMode: AppState['focusMode']): string {
11
- const t = getCurrentTokens()
11
+ const t = getCurrentTheme()
12
12
  switch (focusMode) {
13
13
  case 'terminal-input':
14
- return t.palette.primary
14
+ return t.primary
15
15
  case 'modal':
16
16
  case 'command-edit':
17
- return t.palette.warning
17
+ return t.warning
18
18
  case 'git':
19
- return t.palette.success
19
+ return t.success
20
20
  case 'navigation':
21
21
  default:
22
- return t.accent
22
+ return t.text
23
23
  }
24
24
  }
25
25
 
@@ -40,8 +40,8 @@ function getModeLabel(focusMode: AppState['focusMode']): string {
40
40
  }
41
41
 
42
42
  export function StatusBar() {
43
- const t = useTokens()
44
- const headerBg = useBg('elevated')
43
+ const t = useTheme()
44
+ const headerBg = t.backgroundPanel
45
45
  const state = useAppStore((s) => s)
46
46
  const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId)
47
47
  const config = useKeymap()
@@ -60,14 +60,14 @@ export function StatusBar() {
60
60
  <box width="100%" flexDirection="row">
61
61
  <text fg={getModeColor(state.focusMode)}>[{getModeLabel(state.focusMode)}]</text>
62
62
  <text> </text>
63
- <text fg={t.palette.ink}>{model.left}</text>
63
+ <text fg={t.text}>{model.left}</text>
64
64
  </box>
65
65
  <box width="100%" flexDirection="row" justifyContent="space-between">
66
- <text fg={t.muted}>{model.right}</text>
66
+ <text fg={t.textMuted}>{model.right}</text>
67
67
  <box flexDirection="row" gap={2}>
68
- {model.help ? <text fg={t.muted}>{model.help}</text> : null}
68
+ {model.help ? <text fg={t.textMuted}>{model.help}</text> : null}
69
69
  <AIUsageIndicator />
70
- <text fg={t.hover}>v{APP_VERSION}</text>
70
+ <text fg={t.textMuted}>v{APP_VERSION}</text>
71
71
  </box>
72
72
  </box>
73
73
  </box>
@@ -8,7 +8,7 @@ import type { TabSession, TerminalSnapshot, TerminalSpan } from '../../../state/
8
8
  import { logInputDebug } from '../../../debug/input-log'
9
9
  import { dispatchGlobal, runSideEffectGlobal } from '../../../state/dispatch-ref'
10
10
  import { type ContextMenuItem, openContextMenu } from '../../context-menu/controller'
11
- import { getCurrentTokens, useBg, useTokens } from '../../theme'
11
+ import { getCurrentTheme, useTheme } from '../../theme'
12
12
  import { ContextMenuBox } from '../overlays/context-menu/context-menu-box'
13
13
 
14
14
  interface TerminalPaneProps {
@@ -47,17 +47,9 @@ function getTitle(
47
47
  return `${tab.title} · ${tab.status}`
48
48
  }
49
49
 
50
- function getBorderColor(isActive: boolean, focusMode: TerminalPaneProps['focusMode']): string {
51
- const t = getCurrentTokens()
52
- if (isActive && focusMode === 'terminal-input') {
53
- return t.palette.primary
54
- }
55
-
56
- if (isActive) {
57
- return t.accent
58
- }
59
-
60
- return t.hover
50
+ function getBorderColor(isActive: boolean, _focusMode: TerminalPaneProps['focusMode']): string {
51
+ const t = getCurrentTheme()
52
+ return isActive ? t.borderActive : t.border
61
53
  }
62
54
 
63
55
  function renderSpan(span: TerminalSpan, key: string): ReactNode {
@@ -76,7 +68,7 @@ function renderSpan(span: TerminalSpan, key: string): ReactNode {
76
68
  }
77
69
 
78
70
  return (
79
- <span key={key} fg={span.fg ?? getCurrentTokens().palette.ink} bg={span.bg}>
71
+ <span key={key} fg={span.fg ?? getCurrentTheme().text} bg={span.bg}>
80
72
  {node}
81
73
  </span>
82
74
  )
@@ -91,11 +83,11 @@ const TerminalViewport = memo(function TerminalViewport({
91
83
  buffer,
92
84
  viewport,
93
85
  }: TerminalViewportProps) {
94
- const t = useTokens()
86
+ const t = useTheme()
95
87
  if (viewport && viewport.lines.length > 0) {
96
88
  const lines = viewport.lines
97
89
  return (
98
- <text fg={t.palette.ink}>
90
+ <text fg={t.text}>
99
91
  {lines.map((line, lineIndex) => (
100
92
  <span key={`line-${lineIndex}`}>
101
93
  {line.spans.map((span, spanIndex) => renderSpan(span, `s-${spanIndex}`))}
@@ -106,9 +98,7 @@ const TerminalViewport = memo(function TerminalViewport({
106
98
  )
107
99
  }
108
100
 
109
- return (
110
- <text fg={t.palette.ink}>{buffer.length > 0 ? buffer : 'Waiting for workspace output...'}</text>
111
- )
101
+ return <text fg={t.text}>{buffer.length > 0 ? buffer : 'Waiting for workspace output...'}</text>
112
102
  })
113
103
 
114
104
  export function TerminalPane({
@@ -126,8 +116,8 @@ export function TerminalPane({
126
116
  tab,
127
117
  tabId,
128
118
  }: TerminalPaneProps) {
129
- const t = useTokens()
130
- const editorBg = useBg('base')
119
+ const t = useTheme()
120
+ const editorBg = t.background
131
121
  const paneIsActive = isActive ?? true
132
122
  const canForwardMouse = focusMode === 'terminal-input' && !!tab && mouseForwardingEnabled
133
123
  const canUseLocalScrollback = focusMode === 'terminal-input' && !!tab && localScrollbackEnabled
@@ -232,25 +222,25 @@ export function TerminalPane({
232
222
  >
233
223
  {!tab ? (
234
224
  <box flexGrow={1} justifyContent="center" alignItems="center" flexDirection="column">
235
- <text fg={t.hover}>· · ·</text>
236
- <text fg={t.muted}> </text>
225
+ <text fg={t.textMuted}>· · ·</text>
226
+ <text fg={t.textMuted}> </text>
237
227
  <box flexDirection="row">
238
- <text fg={t.muted}>Press </text>
239
- <text fg={t.palette.primary}>Ctrl+n</text>
240
- <text fg={t.muted}> to launch an assistant</text>
228
+ <text fg={t.textMuted}>Press </text>
229
+ <text fg={t.primary}>Ctrl+n</text>
230
+ <text fg={t.textMuted}> to launch an assistant</text>
241
231
  </box>
242
232
  <box
243
233
  flexDirection="row"
244
234
  justifyContent="center"
245
235
  marginTop={1}
246
236
  paddingX={2}
247
- backgroundColor={t.selected}
237
+ backgroundColor={t.backgroundPanel}
248
238
  onMouseDown={(event) => {
249
239
  event.stopPropagation()
250
240
  dispatchGlobal({ type: 'open-new-tab-modal' })
251
241
  }}
252
242
  >
253
- <text fg={t.palette.ink}>New assistant</text>
243
+ <text fg={t.text}>New assistant</text>
254
244
  </box>
255
245
  </box>
256
246
  ) : (
@@ -271,11 +261,9 @@ export function TerminalPane({
271
261
  )}
272
262
  </ContextMenuBox>
273
263
  {tab?.status === 'disconnected' ? (
274
- <text fg={t.palette.warning}>
275
- Restored snapshot. Press Ctrl+r to restart this workspace.
276
- </text>
264
+ <text fg={t.warning}>Restored snapshot. Press Ctrl+r to restart this workspace.</text>
277
265
  ) : null}
278
- {tab?.errorMessage ? <text fg={t.palette.error}>{tab.errorMessage}</text> : null}
266
+ {tab?.errorMessage ? <text fg={t.error}>{tab.errorMessage}</text> : null}
279
267
  </box>
280
268
  )
281
269
  }
@@ -8,7 +8,7 @@ import type {
8
8
  } from '../../../../services/ai-usage/types'
9
9
 
10
10
  import { useAIUsageStore } from '../../../../state/ai-usage-store'
11
- import { useTokens } from '../../../theme'
11
+ import { useTheme } from '../../../theme'
12
12
  import { uiTokens } from '../../../ui-tokens'
13
13
  import { ModalShell } from '../shared/modal-shell'
14
14
 
@@ -56,7 +56,7 @@ function paceStageIsBehind(stage: UsagePaceStage): boolean {
56
56
  }
57
57
 
58
58
  export function AIUsageModal() {
59
- const t = useTokens()
59
+ const t = useTheme()
60
60
  const snapshots = useAIUsageStore((s) => s.snapshots)
61
61
 
62
62
  const tools: AIUsageTool[] = ['claude', 'codex']
@@ -72,7 +72,7 @@ export function AIUsageModal() {
72
72
  listGap={1}
73
73
  >
74
74
  {sections.length === 0 ? (
75
- <text fg={t.muted} selectable={false}>
75
+ <text fg={t.textMuted} selectable={false}>
76
76
  no data yet — collecting…
77
77
  </text>
78
78
  ) : (
@@ -92,20 +92,20 @@ interface ToolSectionProps {
92
92
  }
93
93
 
94
94
  function ToolSection({ snap, tool }: ToolSectionProps) {
95
- const t = useTokens()
95
+ const t = useTheme()
96
96
  const isHardError = Boolean(snap.error) && !snap.stale
97
97
  const relative = formatRelative(snap.lastUpdated)
98
98
 
99
99
  let body: ReactNode
100
100
  if (isHardError) {
101
101
  body = (
102
- <text fg={t.palette.error} selectable={false}>
102
+ <text fg={t.error} selectable={false}>
103
103
  {`error: ${snap.error ?? ''}`}
104
104
  </text>
105
105
  )
106
106
  } else if (snap.windows.length === 0) {
107
107
  body = (
108
- <text fg={t.muted} selectable={false}>
108
+ <text fg={t.textMuted} selectable={false}>
109
109
  no window data
110
110
  </text>
111
111
  )
@@ -116,16 +116,16 @@ function ToolSection({ snap, tool }: ToolSectionProps) {
116
116
  return (
117
117
  <box flexDirection="column">
118
118
  <box flexDirection="row" justifyContent="space-between">
119
- <text fg={t.accent} selectable={false}>
119
+ <text fg={t.text} selectable={false}>
120
120
  {TOOL_TITLE[tool]}
121
121
  </text>
122
122
  {snap.planTier ? (
123
- <text fg={t.muted} selectable={false}>
123
+ <text fg={t.textMuted} selectable={false}>
124
124
  {snap.planTier}
125
125
  </text>
126
126
  ) : null}
127
127
  </box>
128
- <text fg={t.muted} selectable={false}>
128
+ <text fg={t.textMuted} selectable={false}>
129
129
  {`Updated ${relative}`}
130
130
  </text>
131
131
  {body}
@@ -134,14 +134,14 @@ function ToolSection({ snap, tool }: ToolSectionProps) {
134
134
  }
135
135
 
136
136
  function WindowRow({ window }: { window: UsageWindow }) {
137
- const t = useTokens()
137
+ const t = useTheme()
138
138
  const percent = window.percent
139
139
  const { empty, filled } = buildBar(percent)
140
140
 
141
- let barColor = t.palette.success
141
+ let barColor = t.success
142
142
  if (percent !== null) {
143
- if (percent >= 85) barColor = t.palette.error
144
- else if (percent >= 60) barColor = t.palette.warning
143
+ if (percent >= 85) barColor = t.error
144
+ else if (percent >= 60) barColor = t.warning
145
145
  }
146
146
 
147
147
  const pctText = percent === null ? '—' : `${Math.round(percent)}% used`
@@ -149,23 +149,23 @@ function WindowRow({ window }: { window: UsageWindow }) {
149
149
 
150
150
  return (
151
151
  <box flexDirection="column" paddingTop={1}>
152
- <text fg={t.palette.ink} selectable={false}>
152
+ <text fg={t.text} selectable={false}>
153
153
  {window.label}
154
154
  </text>
155
155
  <box flexDirection="row">
156
156
  <text fg={barColor} selectable={false}>
157
157
  {filled}
158
158
  </text>
159
- <text fg={t.muted} selectable={false}>
159
+ <text fg={t.textMuted} selectable={false}>
160
160
  {empty}
161
161
  </text>
162
162
  </box>
163
163
  <box flexDirection="row" justifyContent="space-between">
164
- <text fg={t.muted} selectable={false}>
164
+ <text fg={t.textMuted} selectable={false}>
165
165
  {pctText}
166
166
  </text>
167
167
  {resetText ? (
168
- <text fg={t.muted} selectable={false}>
168
+ <text fg={t.textMuted} selectable={false}>
169
169
  {resetText}
170
170
  </text>
171
171
  ) : null}
@@ -176,10 +176,10 @@ function WindowRow({ window }: { window: UsageWindow }) {
176
176
  }
177
177
 
178
178
  function PaceLine({ pace }: { pace: NonNullable<UsageWindow['pace']> }) {
179
- const t = useTokens()
180
- let color = t.muted
181
- if (paceStageIsBehind(pace.stage)) color = t.palette.warning
182
- else if (paceStageIsAhead(pace.stage)) color = t.palette.success
179
+ const t = useTheme()
180
+ let color = t.textMuted
181
+ if (paceStageIsBehind(pace.stage)) color = t.warning
182
+ else if (paceStageIsAhead(pace.stage)) color = t.success
183
183
 
184
184
  const suffix = pace.rightText ? ` · ${pace.rightText}` : ''
185
185
  return (
@@ -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 { useTokens } from '../../../theme'
8
+ import { useTheme } from '../../../theme'
9
9
  import { uiTokens } from '../../../ui-tokens'
10
10
  import { Picker, type PickerItem } from '../shared/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 t = useTokens()
26
+ const t = useTheme()
27
27
  const config = useKeymap()
28
28
  const allEntries = useMemo(() => collectHelpEntries(config), [config])
29
29
  const scoped = useMemo(
@@ -49,17 +49,16 @@ export function HelpModal({ cursorPos, filter, scope, selectedIndex }: HelpModal
49
49
  }, [filtered.length])
50
50
 
51
51
  const items: PickerItem[] = filtered.map((entry, index) => {
52
- const active = index === selectedIndex
53
52
  return {
54
53
  group: entry.modeLabel,
55
54
  key: `${entry.mode}::${entry.keysDisplay}::${entry.description ?? ''}::${index}`,
56
55
  title: (
57
- <text fg={active ? t.palette.ink : t.muted} wrapMode="none">
56
+ <text fg={t.textMuted} wrapMode="none">
58
57
  {entry.description ?? ''}
59
58
  </text>
60
59
  ),
61
60
  trailing: (
62
- <text fg={active ? t.palette.primary : t.accent} wrapMode="none">
61
+ <text fg={t.textMuted} wrapMode="none">
63
62
  {entry.keysDisplay}
64
63
  </text>
65
64
  ),
@@ -76,7 +75,7 @@ export function HelpModal({ cursorPos, filter, scope, selectedIndex }: HelpModal
76
75
  items={items}
77
76
  selectedIndex={selectedIndex}
78
77
  emptyState={
79
- <text fg={t.muted}>{filter ? 'No matching bindings.' : 'No bindings registered.'}</text>
78
+ <text fg={t.textMuted}>{filter ? 'No matching bindings.' : 'No bindings registered.'}</text>
80
79
  }
81
80
  onHover={(index) => dispatchGlobal({ index, type: 'set-modal-selection-index' })}
82
81
  />
@@ -1,4 +1,4 @@
1
- import { useTokens } from '../../../theme'
1
+ import { useTheme } from '../../../theme'
2
2
  import { uiTokens } from '../../../ui-tokens'
3
3
  import { ListItem } from '../../primitives/list-item'
4
4
  import { ModalShell } from '../shared/modal-shell'
@@ -19,7 +19,7 @@ export function UpdateAvailableModal({
19
19
  latestVersion,
20
20
  selectedIndex,
21
21
  }: UpdateAvailableModalProps) {
22
- const t = useTokens()
22
+ const t = useTheme()
23
23
  return (
24
24
  <ModalShell
25
25
  title="Update available"
@@ -36,7 +36,7 @@ export function UpdateAvailableModal({
36
36
  key={option.label}
37
37
  active={active}
38
38
  direction="row"
39
- title={<text fg={active ? t.palette.ink : t.muted}>{option.label}</text>}
39
+ title={<text fg={active ? t.text : t.textMuted}>{option.label}</text>}
40
40
  />
41
41
  )
42
42
  })}
@@ -5,7 +5,7 @@ import type { ModeId } from '../../../../input/modes/types'
5
5
 
6
6
  import { useAppStore } from '../../../../state/app-store'
7
7
  import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
8
- import { useTokens } from '../../../theme'
8
+ import { useTheme } from '../../../theme'
9
9
  import { uiTokens } from '../../../ui-tokens'
10
10
  import { InputField } from '../../primitives/input-field'
11
11
  import { ModalShell } from '../shared/modal-shell'
@@ -49,7 +49,7 @@ function pickShellTitle(stage: 'edit' | 'generating' | 'confirm'): string {
49
49
  }
50
50
 
51
51
  function GeneratingOverlay({ assistant, model }: { assistant?: string; model?: string }) {
52
- const t = useTokens()
52
+ const t = useTheme()
53
53
  const frame = useSpinnerFrame(true)
54
54
  const providerLabel = [assistant, model].filter(Boolean).join(' · ') || 'configured provider'
55
55
  return (
@@ -60,14 +60,14 @@ function GeneratingOverlay({ assistant, model }: { assistant?: string; model?: s
60
60
  paddingTop={2}
61
61
  paddingBottom={2}
62
62
  >
63
- <text fg={t.palette.primary}>🪄</text>
63
+ <text fg={t.primary}>🪄</text>
64
64
  <box marginTop={1} flexDirection="row" gap={1}>
65
- <text fg={t.palette.primary}>{frame}</text>
66
- <text fg={t.palette.ink}>Generating commit message</text>
65
+ <text fg={t.primary}>{frame}</text>
66
+ <text fg={t.text}>Generating commit message</text>
67
67
  </box>
68
- <text fg={t.muted}>via {providerLabel}</text>
68
+ <text fg={t.textMuted}>via {providerLabel}</text>
69
69
  <box marginTop={1}>
70
- <text fg={t.muted}>Esc to cancel</text>
70
+ <text fg={t.textMuted}>Esc to cancel</text>
71
71
  </box>
72
72
  </box>
73
73
  )
@@ -82,7 +82,7 @@ export function GitCommitModal({
82
82
  stage,
83
83
  title,
84
84
  }: GitCommitModalProps) {
85
- const t = useTokens()
85
+ const t = useTheme()
86
86
  const titleActive = activeField === 'title'
87
87
  const bodyActive = activeField === 'body'
88
88
  const isConfirm = stage === 'confirm'
@@ -125,22 +125,22 @@ export function GitCommitModal({
125
125
  {isConfirm ? (
126
126
  <box flexDirection="column">
127
127
  {stagedCount > 0 ? (
128
- <text fg={t.palette.warning}>
128
+ <text fg={t.warning}>
129
129
  Commit will include the <strong>{stagedCount} staged file(s)</strong> only.
130
130
  </text>
131
131
  ) : (
132
- <text fg={t.palette.warning}>
132
+ <text fg={t.warning}>
133
133
  <strong>git add -A</strong> will stage every change before committing.
134
134
  </text>
135
135
  )}
136
- <text fg={t.muted}>Enter to confirm · Esc to cancel · edits below still apply.</text>
136
+ <text fg={t.textMuted}>Enter to confirm · Esc to cancel · edits below still apply.</text>
137
137
  </box>
138
138
  ) : null}
139
139
 
140
140
  {isGenerating ? null : (
141
141
  <>
142
142
  <box flexDirection="column">
143
- <text fg={titleActive ? t.palette.ink : t.muted}>Title</text>
143
+ <text fg={titleActive ? t.text : t.textMuted}>Title</text>
144
144
  <InputField
145
145
  active={titleActive}
146
146
  cursorPos={titleActive ? cursorPos : undefined}
@@ -149,7 +149,7 @@ export function GitCommitModal({
149
149
  </box>
150
150
 
151
151
  <box flexDirection="column">
152
- <text fg={bodyActive ? t.palette.ink : t.muted}>Body (optional)</text>
152
+ <text fg={bodyActive ? t.text : t.textMuted}>Body (optional)</text>
153
153
  <InputField
154
154
  active={bodyActive}
155
155
  cursorPos={bodyActive ? cursorPos : undefined}
@@ -163,7 +163,7 @@ export function GitCommitModal({
163
163
  <box flexDirection="row" gap={1} marginTop={1} alignItems="center">
164
164
  <box
165
165
  border
166
- borderColor={t.palette.primary}
166
+ borderColor={t.border}
167
167
  paddingLeft={1}
168
168
  paddingRight={1}
169
169
  flexDirection="row"
@@ -175,12 +175,12 @@ export function GitCommitModal({
175
175
  onAutoCommitClick()
176
176
  }}
177
177
  >
178
- {showBgSpinner ? <text fg={t.palette.primary}>{bgSpinnerFrame}</text> : null}
179
- <text fg={t.palette.primary}>
178
+ {showBgSpinner ? <text fg={t.primary}>{bgSpinnerFrame}</text> : null}
179
+ <text fg={t.text}>
180
180
  <strong>Auto-commit</strong>
181
181
  </text>
182
182
  </box>
183
- <text fg={t.muted}>C-a · stages all changes (AI-suggests message if empty)</text>
183
+ <text fg={t.textMuted}>C-a · stages all changes (AI-suggests message if empty)</text>
184
184
  </box>
185
185
  )}
186
186
  </ModalShell>
@@ -1,7 +1,7 @@
1
1
  import type { DirectoryResult } from '../../../../state/types'
2
2
 
3
3
  import { abbreviatePath } from '../../../path-format'
4
- import { getCurrentTokens, useTokens } from '../../../theme'
4
+ import { getCurrentTheme, useTheme } from '../../../theme'
5
5
  import { uiTokens } from '../../../ui-tokens'
6
6
  import { AutoComplete, Form, type FormOptionItem, TextField } from '../shared/form'
7
7
 
@@ -14,10 +14,10 @@ function getDirectoryResultIcon(result: DirectoryResult): string {
14
14
  }
15
15
 
16
16
  function getDirectoryResultColor(result: DirectoryResult): string {
17
- const t = getCurrentTokens()
18
- if (result.type === 'worktree') return t.palette.warning
19
- if (result.type === 'workspace') return t.accent
20
- return t.palette.primary
17
+ const t = getCurrentTheme()
18
+ if (result.type === 'worktree') return t.warning
19
+ if (result.type === 'workspace') return t.info
20
+ return t.text
21
21
  }
22
22
 
23
23
  interface CreateSessionModalProps {
@@ -37,7 +37,7 @@ export function CreateSessionModal({
37
37
  selectedIndex,
38
38
  sessionName,
39
39
  }: CreateSessionModalProps) {
40
- const t = useTokens()
40
+ const t = useTheme()
41
41
  const dirActive = activeField === 'directory'
42
42
  const nameActive = activeField === 'name'
43
43
 
@@ -46,7 +46,7 @@ export function CreateSessionModal({
46
46
  key: result.path,
47
47
  leading: <text fg={getDirectoryResultColor(result)}>{getDirectoryResultIcon(result)}</text>,
48
48
  title: (active) => (
49
- <text fg={active ? t.palette.ink : t.muted}>{abbreviatePath(result.path)}</text>
49
+ <text fg={active ? t.text : t.textMuted}>{abbreviatePath(result.path)}</text>
50
50
  ),
51
51
  }
52
52
  })
@@ -67,7 +67,7 @@ export function CreateSessionModal({
67
67
  selectedIndex={selectedIndex}
68
68
  maxVisibleRows={VISIBLE_ROWS}
69
69
  emptyState={
70
- <text fg={t.muted}>
70
+ <text fg={t.textMuted}>
71
71
  {directoryQuery.length > 0 ? 'No matches' : 'Type a project name to search...'}
72
72
  </text>
73
73
  }