@brimveyn/aimux 1.7.1 → 1.7.3

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 (55) hide show
  1. package/package.json +3 -4
  2. package/src/app-runtime/pty-write.ts +15 -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/daemon/daemon.ts +7 -2
  8. package/src/input/modes/bridge.ts +1 -1
  9. package/src/input/modes/types.ts +6 -1
  10. package/src/input/raw-input-handler.ts +12 -3
  11. package/src/pty/terminal-snapshot.ts +4 -4
  12. package/src/state/reducers/modal-state.ts +3 -2
  13. package/src/state/reducers/session-state.ts +0 -7
  14. package/src/state/types.ts +7 -1
  15. package/src/ui/components/bare-input.tsx +5 -5
  16. package/src/ui/components/context-menu-box.tsx +21 -0
  17. package/src/ui/components/context-menu-overlay.tsx +114 -0
  18. package/src/ui/components/create-session-modal.tsx +12 -41
  19. package/src/ui/components/diff-renderer/fold-strip.tsx +7 -7
  20. package/src/ui/components/diff-renderer/highlight.ts +6 -10
  21. package/src/ui/components/diff-renderer/pierre-diff.tsx +3 -3
  22. package/src/ui/components/diff-renderer/prepare-diff.ts +2 -3
  23. package/src/ui/components/diff-renderer/split-view.tsx +22 -29
  24. package/src/ui/components/diff-renderer/stacked-view.tsx +18 -31
  25. package/src/ui/components/diff-renderer/use-diff-prefetch.ts +0 -1
  26. package/src/ui/components/diff-renderer/use-diff-preparation.ts +1 -1
  27. package/src/ui/components/git-commit-modal.tsx +4 -16
  28. package/src/ui/components/git-panel.tsx +37 -73
  29. package/src/ui/components/git-view.tsx +17 -19
  30. package/src/ui/components/help-modal.tsx +5 -13
  31. package/src/ui/components/input-field.tsx +5 -7
  32. package/src/ui/components/list-item.tsx +3 -11
  33. package/src/ui/components/modal-keybinds-overlay.tsx +4 -4
  34. package/src/ui/components/modal-shell.tsx +6 -11
  35. package/src/ui/components/new-tab-modal.tsx +7 -15
  36. package/src/ui/components/pending-chord-overlay.tsx +5 -5
  37. package/src/ui/components/picker.tsx +6 -6
  38. package/src/ui/components/session-bar.tsx +34 -20
  39. package/src/ui/components/session-picker-modal.tsx +7 -20
  40. package/src/ui/components/sidebar.tsx +36 -35
  41. package/src/ui/components/snippet-editor-modal.tsx +4 -18
  42. package/src/ui/components/snippet-picker-modal.tsx +5 -9
  43. package/src/ui/components/split-layout.tsx +3 -3
  44. package/src/ui/components/status-bar.tsx +12 -13
  45. package/src/ui/components/surface.tsx +8 -11
  46. package/src/ui/components/tab-item.tsx +54 -29
  47. package/src/ui/components/terminal-pane.tsx +59 -25
  48. package/src/ui/components/theme-picker-modal.tsx +7 -21
  49. package/src/ui/components/update-available-modal.tsx +3 -13
  50. package/src/ui/context-menu/controller.ts +34 -0
  51. package/src/ui/root.tsx +5 -2
  52. package/src/ui/shiki.ts +29 -21
  53. package/src/ui/theme-store.ts +110 -21
  54. package/src/ui/theme.ts +6 -3
  55. package/src/ui/themes.ts +18 -13
@@ -1,6 +1,6 @@
1
1
  import type { ThemedToken } from 'shiki'
2
2
 
3
- import { ensureShikiLang, ensureShikiTheme, getShikiHighlighter } from '../../shiki'
3
+ import { ensureActiveShikiTheme, ensureShikiLang, getShikiHighlighter } from '../../shiki'
4
4
 
5
5
  export interface HighlightSpan {
6
6
  bold?: boolean
@@ -22,24 +22,20 @@ export function tokenToSpan(token: ThemedToken): HighlightSpan {
22
22
  }
23
23
  }
24
24
 
25
- export async function tokenizeSide(
26
- lines: string[],
27
- lang: string,
28
- theme: string
29
- ): Promise<ThemedToken[][]> {
25
+ export async function tokenizeSide(lines: string[], lang: string): Promise<ThemedToken[][]> {
30
26
  if (lines.length === 0) return []
31
27
  const highlighter = await getShikiHighlighter()
32
- const [langOk, themeOk] = await Promise.all([
28
+ const [langOk, themeName] = await Promise.all([
33
29
  ensureShikiLang(highlighter, lang),
34
- ensureShikiTheme(highlighter, theme),
30
+ ensureActiveShikiTheme(highlighter),
35
31
  ])
36
- if (!langOk || !themeOk) return []
32
+ if (!langOk) return []
37
33
  try {
38
34
  const result = highlighter.codeToTokens(lines.join(''), {
39
35
  // eslint-disable-next-line typescript/no-explicit-any
40
36
  lang: lang as any,
41
37
  // eslint-disable-next-line typescript/no-explicit-any
42
- theme: theme as any,
38
+ theme: themeName as any,
43
39
  })
44
40
  return result.tokens
45
41
  } catch {
@@ -8,7 +8,7 @@ import type { ThemeId } from '../../themes'
8
8
  import { useAppStore } from '../../../state/app-store'
9
9
  import { dispatchGlobal } from '../../../state/dispatch-ref'
10
10
  import { type FoldState } from '../../../state/types'
11
- import { useTheme } from '../../theme'
11
+ import { useTokens } from '../../theme'
12
12
  import { buildSplitRows, buildUnifiedRows, firstChangeRowOffset, gutterWidth } from './build-rows'
13
13
  import { SplitView, type SplitViewHandle } from './split-view'
14
14
  import { StackedView, type StackedViewHandle } from './stacked-view'
@@ -45,7 +45,7 @@ export const PierreDiff = forwardRef<PierreDiffHandle, Props>(function PierreDif
45
45
  { cacheKey, diff, path, themeId, view },
46
46
  ref
47
47
  ) {
48
- const theme = useTheme()
48
+ const t = useTokens()
49
49
  const preparation = useDiffPreparation(cacheKey, diff, path, themeId)
50
50
  const file = preparation.file ?? undefined
51
51
  const highlights: DiffHighlights = preparation.highlights
@@ -117,7 +117,7 @@ export const PierreDiff = forwardRef<PierreDiffHandle, Props>(function PierreDif
117
117
  if (!file) {
118
118
  return (
119
119
  <box flexGrow={1} padding={1}>
120
- <text fg={theme.colors['descriptionForeground']}>
120
+ <text fg={t.muted}>
121
121
  {preparation.preparing ? 'Preparing diff…' : '(could not parse diff)'}
122
122
  </text>
123
123
  </box>
@@ -14,7 +14,6 @@ export interface PreparedDiff {
14
14
 
15
15
  interface PrepareOptions {
16
16
  signal?: AbortSignal
17
- themeId: string
18
17
  /** Skip Shiki for big files to keep prepare under a few tens of ms. */
19
18
  skipHighlightThreshold?: number
20
19
  }
@@ -55,11 +54,11 @@ export async function prepareDiff(
55
54
 
56
55
  await yieldToEventLoop()
57
56
  throwIfAborted(opts.signal)
58
- const add = await tokenizeSide(file.additionLines, filetype, opts.themeId)
57
+ const add = await tokenizeSide(file.additionLines, filetype)
59
58
  throwIfAborted(opts.signal)
60
59
  await yieldToEventLoop()
61
60
  throwIfAborted(opts.signal)
62
- const del = await tokenizeSide(file.deletionLines, filetype, opts.themeId)
61
+ const del = await tokenizeSide(file.deletionLines, filetype)
63
62
  throwIfAborted(opts.signal)
64
63
 
65
64
  return { file, filetype, hash, highlights: { add, del } }
@@ -13,14 +13,11 @@ import type { DiffHighlights, FoldDispatch } from './pierre-diff'
13
13
 
14
14
  import { getScrollViewportDelta } from '../../../app-runtime/terminal-mouse-adapter'
15
15
  import { scrollGitDiff } from '../../git-view-controls'
16
- import { useBg, useTheme, useTransparent } from '../../theme'
16
+ import { useBg, useTokens, useTransparent } from '../../theme'
17
17
  import { buildSplitRows, gutterWidth, type SplitCell, type SplitRowOrHeader } from './build-rows'
18
18
  import { FoldStrip } from './fold-strip'
19
19
  import { tokenToSpan } from './highlight'
20
20
 
21
- // Cap DOM size for extreme diffs. Rows beyond this are hidden behind a banner;
22
- // users scroll within the visible window. Shiki highlighting is already skipped
23
- // upstream in prepare-diff for similarly large diffs.
24
21
  const LARGE_ROW_CAP = 5000
25
22
 
26
23
  export interface SplitViewHandle {
@@ -48,7 +45,7 @@ export const SplitView = forwardRef<SplitViewHandle, Props>(function SplitView(
48
45
  { contentWidth, file, foldDispatch, folds, highlights },
49
46
  ref
50
47
  ) {
51
- const separatorBg = useBg('editor.background')
48
+ const separatorBg = useBg('base')
52
49
  const leftRef = useRef<ScrollBoxRenderable | null>(null)
53
50
  const rightRef = useRef<ScrollBoxRenderable | null>(null)
54
51
 
@@ -121,13 +118,11 @@ export const SplitView = forwardRef<SplitViewHandle, Props>(function SplitView(
121
118
  })
122
119
 
123
120
  function TruncationNotice({ hidden }: { hidden: number }) {
124
- const theme = useTheme()
125
- const headerBg = useBg('sideBarSectionHeader.background')
121
+ const t = useTokens()
122
+ const headerBg = useBg('elevated')
126
123
  return (
127
124
  <box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
128
- <text fg={theme.colors['editorWarning.foreground']}>
129
- …diff truncated — {hidden} more rows hidden
130
- </text>
125
+ <text fg={t.palette.warning}>…diff truncated — {hidden} more rows hidden</text>
131
126
  </box>
132
127
  )
133
128
  }
@@ -154,14 +149,12 @@ function SideRow({
154
149
  }
155
150
 
156
151
  function HunkHeaderRow({ row }: { row: Extract<SplitRowOrHeader, { type: 'hunk-header' }> }) {
157
- const theme = useTheme()
158
- const headerBg = useBg('sideBarSectionHeader.background')
152
+ const t = useTokens()
153
+ const headerBg = useBg('elevated')
159
154
  return (
160
155
  <box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
161
- <text fg={theme.colors['descriptionForeground']}>{row.spec}</text>
162
- {row.context ? (
163
- <text fg={theme.colors['editor.lineHighlightBackground']}> {row.context}</text>
164
- ) : null}
156
+ <text fg={t.muted}>{row.spec}</text>
157
+ {row.context ? <text fg={t.hover}> {row.context}</text> : null}
165
158
  </box>
166
159
  )
167
160
  }
@@ -177,29 +170,29 @@ function HalfRow({
177
170
  height: number
178
171
  tokens: ThemedToken[][]
179
172
  }) {
180
- const theme = useTheme()
181
- const headerBg = useBg('sideBarSectionHeader.background')
173
+ const t = useTokens()
174
+ const headerBg = useBg('elevated')
182
175
  const transparent = useTransparent()
183
176
  if (cell.type === 'filler') {
184
177
  return <box backgroundColor={headerBg} height={height} />
185
178
  }
186
179
  let bg: string | undefined
187
180
  let sign = ' '
188
- let signColor = theme.colors['descriptionForeground']
181
+ let signColor = t.muted
189
182
  if (cell.type === 'addition') {
190
- bg = theme.colors['diffEditor.insertedLineBackground']
183
+ bg = t.diffAddBg
191
184
  sign = '+'
192
- signColor = theme.colors['gitDecoration.addedResourceForeground']
185
+ signColor = t.palette.success
193
186
  } else if (cell.type === 'deletion') {
194
- bg = theme.colors['diffEditor.removedLineBackground']
187
+ bg = t.diffDeleteBg
195
188
  sign = '-'
196
- signColor = theme.colors['editorError.foreground']
189
+ signColor = t.palette.error
197
190
  }
198
191
  const num = String(cell.lineNumber).padStart(gw, ' ')
199
192
  const lineTokens = tokens[cell.lineIdx]
200
193
  return (
201
194
  <box flexDirection="row" backgroundColor={transparent ? undefined : bg} height={height}>
202
- <text fg={theme.colors['descriptionForeground']}>{` ${num} `}</text>
195
+ <text fg={t.muted}>{` ${num} `}</text>
203
196
  <text fg={signColor}>{`${sign} `}</text>
204
197
  <LineContent content={cell.content} tokens={lineTokens} />
205
198
  </box>
@@ -207,20 +200,20 @@ function HalfRow({
207
200
  }
208
201
 
209
202
  function LineContent({ content, tokens }: { content: string; tokens: ThemedToken[] | undefined }) {
210
- const theme = useTheme()
203
+ const t = useTokens()
211
204
  if (!tokens || tokens.length === 0) {
212
- return <text fg={theme.colors['editor.foreground']}>{content}</text>
205
+ return <text fg={t.palette.ink}>{content}</text>
213
206
  }
214
207
  return (
215
208
  <text>
216
- {tokens.map((t, i) => {
217
- const s = tokenToSpan(t)
209
+ {tokens.map((tok, i) => {
210
+ const s = tokenToSpan(tok)
218
211
  let attributes = 0
219
212
  if (s.bold) attributes |= TextAttributes.BOLD
220
213
  if (s.italic) attributes |= TextAttributes.ITALIC
221
214
  if (s.underline) attributes |= TextAttributes.UNDERLINE
222
215
  return (
223
- <span key={i} fg={s.fg ?? theme.colors['editor.foreground']} attributes={attributes}>
216
+ <span key={i} fg={s.fg ?? t.palette.ink} attributes={attributes}>
224
217
  {s.text}
225
218
  </span>
226
219
  )
@@ -13,12 +13,11 @@ import type { DiffHighlights, FoldDispatch } from './pierre-diff'
13
13
 
14
14
  import { getScrollViewportDelta } from '../../../app-runtime/terminal-mouse-adapter'
15
15
  import { scrollGitDiff } from '../../git-view-controls'
16
- import { useBg, useTheme, useTransparent } from '../../theme'
16
+ import { useBg, useTokens, useTransparent } from '../../theme'
17
17
  import { buildUnifiedRows, gutterWidth, type UnifiedRowOrHeader } from './build-rows'
18
18
  import { FoldStrip } from './fold-strip'
19
19
  import { tokenToSpan } from './highlight'
20
20
 
21
- // Same cap as split-view — keeps the React child count bounded.
22
21
  const LARGE_ROW_CAP = 5000
23
22
 
24
23
  export interface StackedViewHandle {
@@ -89,13 +88,11 @@ export const StackedView = forwardRef<StackedViewHandle, Props>(function Stacked
89
88
  })
90
89
 
91
90
  function TruncationNotice({ hidden }: { hidden: number }) {
92
- const theme = useTheme()
93
- const headerBg = useBg('sideBarSectionHeader.background')
91
+ const t = useTokens()
92
+ const headerBg = useBg('elevated')
94
93
  return (
95
94
  <box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
96
- <text fg={theme.colors['editorWarning.foreground']}>
97
- …diff truncated — {hidden} more rows hidden
98
- </text>
95
+ <text fg={t.palette.warning}>…diff truncated — {hidden} more rows hidden</text>
99
96
  </box>
100
97
  )
101
98
  }
@@ -111,16 +108,14 @@ function UnifiedRowRender({
111
108
  highlights: DiffHighlights
112
109
  row: UnifiedRowOrHeader
113
110
  }) {
114
- const theme = useTheme()
115
- const headerBg = useBg('sideBarSectionHeader.background')
111
+ const t = useTokens()
112
+ const headerBg = useBg('elevated')
116
113
  const transparent = useTransparent()
117
114
  if (row.type === 'hunk-header') {
118
115
  return (
119
116
  <box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
120
- <text fg={theme.colors['descriptionForeground']}>{row.spec}</text>
121
- {row.context ? (
122
- <text fg={theme.colors['editor.lineHighlightBackground']}> {row.context}</text>
123
- ) : null}
117
+ <text fg={t.muted}>{row.spec}</text>
118
+ {row.context ? <text fg={t.hover}> {row.context}</text> : null}
124
119
  </box>
125
120
  )
126
121
  }
@@ -133,29 +128,21 @@ function UnifiedRowRender({
133
128
  const tokens = highlights.add[row.lineIdx]
134
129
  return (
135
130
  <box flexDirection="row" height={row.height}>
136
- <text
137
- fg={theme.colors['descriptionForeground']}
138
- >{` ${pad(row.delLineNumber)} ${pad(row.addLineNumber)} `}</text>
139
- <text fg={theme.colors['editor.foreground']}> </text>
131
+ <text fg={t.muted}>{` ${pad(row.delLineNumber)} ${pad(row.addLineNumber)} `}</text>
132
+ <text fg={t.palette.ink}> </text>
140
133
  <LineContent content={row.content} tokens={tokens} />
141
134
  </box>
142
135
  )
143
136
  }
144
- const bg =
145
- row.type === 'addition'
146
- ? theme.colors['diffEditor.insertedLineBackground']
147
- : theme.colors['diffEditor.removedLineBackground']
137
+ const bg = row.type === 'addition' ? t.diffAddBg : t.diffDeleteBg
148
138
  const sign = row.type === 'addition' ? '+' : '-'
149
- const signColor =
150
- row.type === 'addition'
151
- ? theme.colors['gitDecoration.addedResourceForeground']
152
- : theme.colors['editorError.foreground']
139
+ const signColor = row.type === 'addition' ? t.palette.success : t.palette.error
153
140
  const delNum = row.type === 'deletion' ? row.lineNumber : undefined
154
141
  const addNum = row.type === 'addition' ? row.lineNumber : undefined
155
142
  const tokens = row.type === 'addition' ? highlights.add[row.lineIdx] : highlights.del[row.lineIdx]
156
143
  return (
157
144
  <box flexDirection="row" backgroundColor={transparent ? undefined : bg} height={row.height}>
158
- <text fg={theme.colors['descriptionForeground']}>{` ${pad(delNum)} ${pad(addNum)} `}</text>
145
+ <text fg={t.muted}>{` ${pad(delNum)} ${pad(addNum)} `}</text>
159
146
  <text fg={signColor}>{`${sign} `}</text>
160
147
  <LineContent content={row.content} tokens={tokens} />
161
148
  </box>
@@ -163,20 +150,20 @@ function UnifiedRowRender({
163
150
  }
164
151
 
165
152
  function LineContent({ content, tokens }: { content: string; tokens: ThemedToken[] | undefined }) {
166
- const theme = useTheme()
153
+ const t = useTokens()
167
154
  if (!tokens || tokens.length === 0) {
168
- return <text fg={theme.colors['editor.foreground']}>{content}</text>
155
+ return <text fg={t.palette.ink}>{content}</text>
169
156
  }
170
157
  return (
171
158
  <text>
172
- {tokens.map((t, i) => {
173
- const s = tokenToSpan(t)
159
+ {tokens.map((tok, i) => {
160
+ const s = tokenToSpan(tok)
174
161
  let attributes = 0
175
162
  if (s.bold) attributes |= TextAttributes.BOLD
176
163
  if (s.italic) attributes |= TextAttributes.ITALIC
177
164
  if (s.underline) attributes |= TextAttributes.UNDERLINE
178
165
  return (
179
- <span key={i} fg={s.fg ?? theme.colors['editor.foreground']} attributes={attributes}>
166
+ <span key={i} fg={s.fg ?? t.palette.ink} attributes={attributes}>
180
167
  {s.text}
181
168
  </span>
182
169
  )
@@ -108,7 +108,6 @@ export function useDiffPrefetch(
108
108
  dispatchGlobal({ diff, hash, key: task.key, type: 'git-mode-set-diff' })
109
109
  const prep = await prepareDiff(diff.rawDiff, task.file.path, {
110
110
  signal: task.controller.signal,
111
- themeId,
112
111
  })
113
112
  if (task.controller.signal.aborted) return
114
113
  dispatchGlobal({
@@ -68,7 +68,7 @@ export function useDiffPreparation(
68
68
 
69
69
  const controller = new AbortController()
70
70
  setPreparing(true)
71
- void prepareDiff(diff, path, { signal: controller.signal, themeId })
71
+ void prepareDiff(diff, path, { signal: controller.signal })
72
72
  .then((result) => {
73
73
  if (controller.signal.aborted) return
74
74
  setLocalFile(result.file)
@@ -1,4 +1,4 @@
1
- import { useTheme } from '../theme'
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'
@@ -11,20 +11,14 @@ interface GitCommitModalProps {
11
11
  }
12
12
 
13
13
  export function GitCommitModal({ activeField, body, cursorPos, title }: GitCommitModalProps) {
14
- const theme = useTheme()
14
+ const t = useTokens()
15
15
  const titleActive = activeField === 'title'
16
16
  const bodyActive = activeField === 'body'
17
17
 
18
18
  return (
19
19
  <ModalShell title="Commit" keybindsModeId="modal.git-commit" width={uiTokens.modalWidth.xl}>
20
20
  <box flexDirection="column">
21
- <text
22
- fg={
23
- titleActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
24
- }
25
- >
26
- Title
27
- </text>
21
+ <text fg={titleActive ? t.palette.ink : t.muted}>Title</text>
28
22
  <InputField
29
23
  active={titleActive}
30
24
  cursorPos={titleActive ? cursorPos : undefined}
@@ -33,13 +27,7 @@ export function GitCommitModal({ activeField, body, cursorPos, title }: GitCommi
33
27
  </box>
34
28
 
35
29
  <box flexDirection="column">
36
- <text
37
- fg={
38
- bodyActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
39
- }
40
- >
41
- Body (optional)
42
- </text>
30
+ <text fg={bodyActive ? t.palette.ink : t.muted}>Body (optional)</text>
43
31
  <InputField
44
32
  active={bodyActive}
45
33
  cursorPos={bodyActive ? cursorPos : undefined}
@@ -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}