@brimveyn/aimux 1.5.4 → 1.6.1

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 (67) hide show
  1. package/README.md +14 -2
  2. package/package.json +5 -3
  3. package/src/app-runtime/side-effects.ts +41 -6
  4. package/src/app.tsx +17 -5
  5. package/src/config.ts +29 -4
  6. package/src/diff-parser/clean-last-newline.ts +5 -0
  7. package/src/diff-parser/constants.ts +13 -0
  8. package/src/diff-parser/index.ts +12 -0
  9. package/src/diff-parser/parse-line-type.ts +29 -0
  10. package/src/diff-parser/parse-patch-files.ts +369 -0
  11. package/src/diff-parser/types.ts +75 -0
  12. package/src/git/git-diff.ts +24 -12
  13. package/src/git/git-poller.ts +11 -3
  14. package/src/git/git-status.ts +98 -17
  15. package/src/input/modes/bridge.ts +8 -0
  16. package/src/input/modes/transitions.ts +2 -1
  17. package/src/input/modes/types.ts +4 -1
  18. package/src/pty/terminal-snapshot.ts +5 -5
  19. package/src/state/git-tree.ts +220 -0
  20. package/src/state/reducers/git-mode-state.ts +276 -36
  21. package/src/state/reducers/git-panel-state.ts +35 -4
  22. package/src/state/reducers/modal-state.ts +43 -10
  23. package/src/state/store.ts +5 -1
  24. package/src/state/types.ts +46 -6
  25. package/src/state/workspace-save.ts +2 -0
  26. package/src/ui/components/create-session-modal.tsx +25 -8
  27. package/src/ui/components/diff-renderer/build-rows.ts +349 -0
  28. package/src/ui/components/diff-renderer/filetype.ts +29 -0
  29. package/src/ui/components/diff-renderer/fold-strip.tsx +86 -0
  30. package/src/ui/components/diff-renderer/highlight.ts +48 -0
  31. package/src/ui/components/diff-renderer/index.ts +1 -0
  32. package/src/ui/components/diff-renderer/pierre-diff.tsx +171 -0
  33. package/src/ui/components/diff-renderer/split-view.tsx +211 -0
  34. package/src/ui/components/diff-renderer/stacked-view.tsx +168 -0
  35. package/src/ui/components/git-commit-modal.tsx +16 -3
  36. package/src/ui/components/git-pane-widget.tsx +6 -1
  37. package/src/ui/components/git-panel.tsx +215 -86
  38. package/src/ui/components/git-view.tsx +103 -90
  39. package/src/ui/components/help-modal.tsx +45 -12
  40. package/src/ui/components/input-field.tsx +4 -3
  41. package/src/ui/components/list-item.tsx +11 -2
  42. package/src/ui/components/modal-filter-bar.tsx +3 -2
  43. package/src/ui/components/modal-keybinds-overlay.tsx +4 -3
  44. package/src/ui/components/modal-shell.tsx +5 -4
  45. package/src/ui/components/new-tab-modal.tsx +17 -4
  46. package/src/ui/components/pending-chord-overlay.tsx +5 -4
  47. package/src/ui/components/session-bar.tsx +13 -6
  48. package/src/ui/components/session-picker-modal.tsx +28 -10
  49. package/src/ui/components/sidebar.tsx +26 -11
  50. package/src/ui/components/snippet-editor-modal.tsx +18 -3
  51. package/src/ui/components/snippet-picker-modal.tsx +13 -4
  52. package/src/ui/components/split-layout.tsx +3 -2
  53. package/src/ui/components/status-bar.tsx +15 -10
  54. package/src/ui/components/surface.tsx +6 -6
  55. package/src/ui/components/tab-item.tsx +28 -17
  56. package/src/ui/components/terminal-pane.tsx +28 -16
  57. package/src/ui/components/theme-picker-modal.tsx +113 -17
  58. package/src/ui/components/update-available-modal.tsx +13 -2
  59. package/src/ui/filter-themes.ts +15 -0
  60. package/src/ui/keymap-context.ts +10 -2
  61. package/src/ui/root.tsx +29 -7
  62. package/src/ui/shiki.ts +49 -0
  63. package/src/ui/status-bar-model.ts +32 -4
  64. package/src/ui/theme-store.ts +36 -0
  65. package/src/ui/theme.ts +4 -17
  66. package/src/ui/themes.ts +23 -277
  67. package/src/ui/syntax.ts +0 -102
@@ -8,9 +8,10 @@ import { useAppStore } from '../../state/app-store'
8
8
  import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
9
9
  import { useBusySpinner } from '../hooks/use-busy-spinner'
10
10
  import { moveIdToIdPosition, orderSessionsForDisplay } from '../session-ordering'
11
- import { theme } from '../theme'
11
+ import { useTheme } from '../theme'
12
12
 
13
13
  export function SessionBar() {
14
+ const theme = useTheme()
14
15
  const sessions = useAppStore((s) => s.sessions)
15
16
  const currentId = useAppStore((s) => s.currentSessionId)
16
17
  const bar = useAppStore((s) => s.sessionBar)
@@ -108,7 +109,7 @@ export function SessionBar() {
108
109
  flexDirection="row"
109
110
  paddingLeft={1}
110
111
  paddingRight={1}
111
- backgroundColor={theme.panelMuted}
112
+ backgroundColor={theme.colors['sideBarSectionHeader.background']}
112
113
  >
113
114
  {visibleSessions.map((session) => {
114
115
  const displayIndex = baselineOrder.indexOf(session.id) + 1
@@ -165,12 +166,18 @@ function SessionChip({
165
166
  onRef,
166
167
  session,
167
168
  }: SessionChipProps) {
169
+ const theme = useTheme()
168
170
  const showSpinner = busy && !active
169
171
  const spinner = useBusySpinner(showSpinner)
170
172
  const indicator = showSpinner ? spinner : '●'
171
- const indicatorColor = active || showSpinner ? theme.accent : theme.success
172
- const labelColor = active ? theme.text : theme.textMuted
173
- const bgColor = dragging || active ? theme.panelHighlight : undefined
173
+ const indicatorColor =
174
+ active || showSpinner
175
+ ? theme.colors['textLink.foreground']
176
+ : theme.colors['gitDecoration.addedResourceForeground']
177
+ const labelColor = active
178
+ ? theme.colors['editor.foreground']
179
+ : theme.colors['descriptionForeground']
180
+ const bgColor = dragging || active ? theme.colors['list.activeSelectionBackground'] : undefined
174
181
 
175
182
  return (
176
183
  <box
@@ -200,7 +207,7 @@ function SessionChip({
200
207
  <text fg={labelColor} selectable={false}>
201
208
  [{index}] {session.name}
202
209
  </text>
203
- <text fg={theme.dim} selectable={false}>
210
+ <text fg={theme.colors['editor.lineHighlightBackground']} selectable={false}>
204
211
  {' '}
205
212
  </text>
206
213
  </box>
@@ -2,7 +2,8 @@ import type { SessionRecord } from '../../state/types'
2
2
 
3
3
  import { filterSessions } from '../../state/selectors'
4
4
  import { abbreviatePath } from '../path-format'
5
- import { theme } from '../theme'
5
+ import { orderSessionsForDisplay } from '../session-ordering'
6
+ import { useTheme } from '../theme'
6
7
  import { uiTokens } from '../ui-tokens'
7
8
  import { ListItem } from './list-item'
8
9
  import { ModalFilterBar } from './modal-filter-bar'
@@ -19,13 +20,14 @@ interface SessionPickerModalProps {
19
20
  function formatSessionLine(
20
21
  session: SessionRecord,
21
22
  currentSessionId: string | null,
22
- currentTabCount: number
23
+ currentTabCount: number,
24
+ displayIndex: number
23
25
  ): string {
24
26
  const tabCount =
25
27
  session.id === currentSessionId
26
28
  ? currentTabCount
27
29
  : (session.workspaceSnapshot?.tabs.length ?? 0)
28
- return `${session.name} (${tabCount} tab${tabCount === 1 ? '' : 's'})`
30
+ return `[${displayIndex}] ${session.name} (${tabCount} tab${tabCount === 1 ? '' : 's'})`
29
31
  }
30
32
 
31
33
  function getEmptyStateMessage(hasFilter: boolean): string {
@@ -43,7 +45,10 @@ export function SessionPickerModal({
43
45
  selectedIndex,
44
46
  sessions,
45
47
  }: SessionPickerModalProps) {
46
- const filtered = filterSessions(sessions, filter)
48
+ const theme = useTheme()
49
+ const ordered = orderSessionsForDisplay(sessions)
50
+ const baselineOrder = ordered.map((s) => s.id)
51
+ const filtered = filterSessions(ordered, filter)
47
52
  const hasFilter = !!filter
48
53
  const showFilteredEmptyState = filtered.length === 0 && sessions.length > 0
49
54
  const showInitialEmptyState = filtered.length === 0 && sessions.length === 0
@@ -56,25 +61,32 @@ export function SessionPickerModal({
56
61
  footer={<ModalFilterBar filter={filter} />}
57
62
  >
58
63
  {showFilteredEmptyState ? (
59
- <text fg={theme.textMuted}>{getEmptyStateMessage(hasFilter)}</text>
64
+ <text fg={theme.colors['descriptionForeground']}>{getEmptyStateMessage(hasFilter)}</text>
60
65
  ) : null}
61
66
  {showInitialEmptyState ? (
62
- <text fg={theme.textMuted}>{getEmptyStateMessage(false)}</text>
67
+ <text fg={theme.colors['descriptionForeground']}>{getEmptyStateMessage(false)}</text>
63
68
  ) : null}
64
69
  {filtered.map((session, index) => {
65
70
  const active = index === selectedIndex
71
+ const displayIndex = baselineOrder.indexOf(session.id) + 1
66
72
  return (
67
73
  <ListItem
68
74
  key={session.id}
69
75
  active={active}
70
76
  title={
71
- <text fg={active ? theme.text : theme.textMuted}>
72
- {formatSessionLine(session, currentSessionId, currentTabCount)}
77
+ <text
78
+ fg={
79
+ active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
80
+ }
81
+ >
82
+ {formatSessionLine(session, currentSessionId, currentTabCount, displayIndex)}
73
83
  </text>
74
84
  }
75
85
  subtitle={
76
86
  session.projectPath ? (
77
- <text fg={theme.textMuted}>{abbreviatePath(session.projectPath)}</text>
87
+ <text fg={theme.colors['descriptionForeground']}>
88
+ {abbreviatePath(session.projectPath)}
89
+ </text>
78
90
  ) : undefined
79
91
  }
80
92
  />
@@ -83,7 +95,13 @@ export function SessionPickerModal({
83
95
  <ListItem
84
96
  active={selectedIndex === filtered.length}
85
97
  title={
86
- <text fg={selectedIndex === filtered.length ? theme.text : theme.textMuted}>
98
+ <text
99
+ fg={
100
+ selectedIndex === filtered.length
101
+ ? theme.colors['editor.foreground']
102
+ : theme.colors['descriptionForeground']
103
+ }
104
+ >
87
105
  Create new session
88
106
  </text>
89
107
  }
@@ -3,7 +3,7 @@ import type { ScrollBoxRenderable } from '@opentui/core'
3
3
  import { memo, useMemo, useRef } from 'react'
4
4
 
5
5
  import { useAppStore } from '../../state/app-store'
6
- import { theme } from '../theme'
6
+ import { getCurrentTheme, useTheme } from '../theme'
7
7
  import { GitPaneWidget } from './git-pane-widget'
8
8
  import { buildTabGroupInfo } from './sidebar-group-metadata'
9
9
  import { TabItem } from './tab-item'
@@ -20,6 +20,7 @@ const GUTTER_END = '╰'
20
20
  const GUTTER_PAD = '│'
21
21
 
22
22
  const SidebarTop = memo(function SidebarTop() {
23
+ const theme = useTheme()
23
24
  const sidebarWidth = useAppStore((s) => s.sidebar.width)
24
25
  const currentSessionId = useAppStore((s) => s.currentSessionId)
25
26
  const sessions = useAppStore((s) => s.sessions)
@@ -31,19 +32,21 @@ const SidebarTop = memo(function SidebarTop() {
31
32
 
32
33
  return (
33
34
  <box flexDirection="column" flexShrink={0} gap={0}>
34
- <text fg={theme.accent}>
35
+ <text fg={theme.colors['textLink.foreground']}>
35
36
  <strong>aimux</strong>
36
37
  </text>
37
- <text fg={theme.accentAlt}>
38
+ <text fg={theme.colors['terminal.ansiMagenta']}>
38
39
  {currentSession ? currentSession.name : 'No session selected'}
39
40
  </text>
40
41
  {branch ? (
41
42
  <box flexDirection="row">
42
- <text fg={theme.accent}>{'\u{e702}'} </text>
43
- <text fg={theme.textMuted}>{branch}</text>
43
+ <text fg={theme.colors['textLink.foreground']}>{'\u{e702}'} </text>
44
+ <text fg={theme.colors['descriptionForeground']}>{branch}</text>
44
45
  </box>
45
46
  ) : null}
46
- <text fg={theme.dim}>{'·'.repeat(Math.max(0, sidebarWidth - 2))}</text>
47
+ <text fg={theme.colors['editor.lineHighlightBackground']}>
48
+ {'·'.repeat(Math.max(0, sidebarWidth - 2))}
49
+ </text>
47
50
  </box>
48
51
  )
49
52
  })
@@ -56,11 +59,17 @@ function renderGroupGutter(
56
59
  ) {
57
60
  return (
58
61
  <box flexDirection="column" width={1} overflow="hidden">
59
- <text fg={theme.accentAlt} bg={isActive ? theme.panelHighlight : undefined}>
62
+ <text
63
+ fg={getCurrentTheme().colors['terminal.ansiMagenta']}
64
+ bg={isActive ? getCurrentTheme().colors['list.activeSelectionBackground'] : undefined}
65
+ >
60
66
  {/* oxlint-disable-next-line no-nested-ternary */}
61
67
  {isGroupStart ? GUTTER_START : isGroupMiddle ? GUTTER_MIDDLE : GUTTER_PAD}
62
68
  </text>
63
- <text fg={theme.accentAlt} bg={isActive ? theme.panelHighlight : undefined}>
69
+ <text
70
+ fg={getCurrentTheme().colors['terminal.ansiMagenta']}
71
+ bg={isActive ? getCurrentTheme().colors['list.activeSelectionBackground'] : undefined}
72
+ >
64
73
  {isGroupEnd ? GUTTER_END : GUTTER_PAD}
65
74
  </text>
66
75
  </box>
@@ -72,6 +81,7 @@ interface TabsBodyProps {
72
81
  }
73
82
 
74
83
  const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
84
+ const theme = useTheme()
75
85
  const tabs = useAppStore((s) => s.tabs)
76
86
  const activeTabId = useAppStore((s) => s.activeTabId)
77
87
  const focusMode = useAppStore((s) => s.focusMode)
@@ -101,7 +111,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
101
111
  >
102
112
  {tabs.length === 0 ? (
103
113
  <box paddingTop={1}>
104
- <text fg={theme.textMuted}>No tabs yet. Press Ctrl+n.</text>
114
+ <text fg={theme.colors['descriptionForeground']}>No tabs yet. Press Ctrl+n.</text>
105
115
  </box>
106
116
  ) : (
107
117
  tabs.map((tab, index) => {
@@ -141,6 +151,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
141
151
  })
142
152
 
143
153
  export function Sidebar({ onTabActivate }: SidebarProps) {
154
+ const theme = useTheme()
144
155
  const sidebarVisible = useAppStore((s) => s.sidebar.visible)
145
156
  const sidebarWidth = useAppStore((s) => s.sidebar.width)
146
157
  const gitPane = useAppStore((s) => s.gitPane)
@@ -157,7 +168,11 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
157
168
  const tabsGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.ratio) * 100)) : 1
158
169
  const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.ratio * 100)) : 0
159
170
 
160
- const separator = <text fg={theme.dim}>{'·'.repeat(Math.max(0, sidebarWidth - 2))}</text>
171
+ const separator = (
172
+ <text fg={theme.colors['editor.lineHighlightBackground']}>
173
+ {'·'.repeat(Math.max(0, sidebarWidth - 2))}
174
+ </text>
175
+ )
161
176
  const gitBody = gitEmbedded ? (
162
177
  <box flexDirection="column" flexGrow={gitGrow} flexShrink={1} flexBasis={0} overflow="hidden">
163
178
  <GitPaneWidget pollingEnabled={gitPane.visible} />
@@ -169,7 +184,7 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
169
184
  width={sidebarWidth}
170
185
  padding={0}
171
186
  flexDirection="column"
172
- backgroundColor={theme.panel}
187
+ backgroundColor={theme.colors['sideBar.background']}
173
188
  gap={0}
174
189
  >
175
190
  <SidebarTop />
@@ -1,4 +1,4 @@
1
- import { theme } from '../theme'
1
+ import { useTheme } from '../theme'
2
2
  import { uiTokens } from '../ui-tokens'
3
3
  import { InputField } from './input-field'
4
4
  import { ModalShell } from './modal-shell'
@@ -16,6 +16,7 @@ export function SnippetEditorModal({
16
16
  snippetContent,
17
17
  snippetName,
18
18
  }: SnippetEditorModalProps) {
19
+ const theme = useTheme()
19
20
  const nameActive = activeField === 'name'
20
21
  const contentActive = activeField === 'content'
21
22
 
@@ -26,12 +27,26 @@ export function SnippetEditorModal({
26
27
  width={uiTokens.modalWidth.xl}
27
28
  >
28
29
  <box flexDirection="column">
29
- <text fg={nameActive ? theme.text : theme.textMuted}>Name</text>
30
+ <text
31
+ fg={
32
+ nameActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
33
+ }
34
+ >
35
+ Name
36
+ </text>
30
37
  <InputField active={nameActive} value={snippetName} />
31
38
  </box>
32
39
 
33
40
  <box flexDirection="column">
34
- <text fg={contentActive ? theme.text : theme.textMuted}>Content</text>
41
+ <text
42
+ fg={
43
+ contentActive
44
+ ? theme.colors['editor.foreground']
45
+ : theme.colors['descriptionForeground']
46
+ }
47
+ >
48
+ Content
49
+ </text>
35
50
  <InputField active={contentActive} value={snippetContent} />
36
51
  </box>
37
52
  </ModalShell>
@@ -1,7 +1,7 @@
1
1
  import type { SnippetRecord } from '../../state/types'
2
2
 
3
3
  import { filterSnippets } from '../../state/selectors'
4
- import { theme } from '../theme'
4
+ import { useTheme } from '../theme'
5
5
  import { uiTokens } from '../ui-tokens'
6
6
  import { ListItem } from './list-item'
7
7
  import { ModalFilterBar } from './modal-filter-bar'
@@ -22,6 +22,7 @@ function truncateContent(content: string): string {
22
22
  }
23
23
 
24
24
  export function SnippetPickerModal({ filter, selectedIndex, snippets }: SnippetPickerModalProps) {
25
+ const theme = useTheme()
25
26
  const filtered = filterSnippets(snippets, filter)
26
27
 
27
28
  return (
@@ -32,7 +33,7 @@ export function SnippetPickerModal({ filter, selectedIndex, snippets }: SnippetP
32
33
  footer={<ModalFilterBar filter={filter} />}
33
34
  >
34
35
  {filtered.length === 0 ? (
35
- <text fg={theme.textMuted}>
36
+ <text fg={theme.colors['descriptionForeground']}>
36
37
  {filter ? 'No matching snippets.' : 'No snippets yet. Press n to create one.'}
37
38
  </text>
38
39
  ) : null}
@@ -43,11 +44,19 @@ export function SnippetPickerModal({ filter, selectedIndex, snippets }: SnippetP
43
44
  key={snippet.id}
44
45
  active={active}
45
46
  title={
46
- <text fg={active ? theme.text : theme.textMuted}>
47
+ <text
48
+ fg={
49
+ active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
50
+ }
51
+ >
47
52
  <strong>{snippet.name}</strong>
48
53
  </text>
49
54
  }
50
- subtitle={<text fg={theme.textMuted}>{truncateContent(snippet.content)}</text>}
55
+ subtitle={
56
+ <text fg={theme.colors['descriptionForeground']}>
57
+ {truncateContent(snippet.content)}
58
+ </text>
59
+ }
51
60
  />
52
61
  )
53
62
  })}
@@ -11,7 +11,7 @@ import {
11
11
  type PaneRect,
12
12
  type SplitDirection,
13
13
  } from '../../state/layout-tree'
14
- import { theme } from '../theme'
14
+ import { useTheme } from '../theme'
15
15
  import { TerminalPane } from './terminal-pane'
16
16
 
17
17
  const PANE_CHROME = PANE_BORDER
@@ -58,6 +58,7 @@ export function SplitLayout({
58
58
  onTerminalScrollEvent,
59
59
  tabs,
60
60
  }: SplitLayoutProps) {
61
+ const theme = useTheme()
61
62
  if (node.type === 'leaf') {
62
63
  const tab = tabs.find((t) => t.id === node.tabId)
63
64
  const isActive = node.tabId === activeTabId
@@ -134,7 +135,7 @@ export function SplitLayout({
134
135
  <box
135
136
  minWidth={node.direction === 'vertical' ? 1 : undefined}
136
137
  minHeight={node.direction === 'horizontal' ? 1 : undefined}
137
- backgroundColor={theme.border}
138
+ backgroundColor={theme.colors['editorGroup.border']}
138
139
  onMouseDown={(e: OtuiMouseEvent) => {
139
140
  e.preventDefault()
140
141
  if (!onSeparatorDragStart) return
@@ -4,21 +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 { theme } from '../theme'
7
+ import { getCurrentTheme, useTheme } from '../theme'
8
8
 
9
9
  function getModeColor(focusMode: AppState['focusMode']): string {
10
+ const t = getCurrentTheme()
10
11
  switch (focusMode) {
11
12
  case 'terminal-input':
12
- return theme.accent
13
+ return t.colors['textLink.foreground']
13
14
  case 'modal':
14
- return theme.warning
15
+ return t.colors['editorWarning.foreground']
15
16
  case 'command-edit':
16
- return theme.warning
17
+ return t.colors['editorWarning.foreground']
17
18
  case 'git':
18
- return theme.success
19
+ return t.colors['gitDecoration.addedResourceForeground']
19
20
  case 'navigation':
20
21
  default:
21
- return theme.accentAlt
22
+ return t.colors['terminal.ansiMagenta']
22
23
  }
23
24
  }
24
25
 
@@ -39,6 +40,7 @@ function getModeLabel(focusMode: AppState['focusMode']): string {
39
40
  }
40
41
 
41
42
  export function StatusBar() {
43
+ const theme = useTheme()
42
44
  const state = useAppStore((s) => s)
43
45
  const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId)
44
46
  const config = useKeymap()
@@ -52,16 +54,19 @@ export function StatusBar() {
52
54
  paddingTop={0}
53
55
  paddingBottom={0}
54
56
  flexDirection="column"
55
- backgroundColor={theme.panelMuted}
57
+ backgroundColor={theme.colors['sideBarSectionHeader.background']}
56
58
  >
57
59
  <box width="100%" flexDirection="row">
58
60
  <text fg={getModeColor(state.focusMode)}>[{getModeLabel(state.focusMode)}]</text>
59
61
  <text> </text>
60
- <text fg={theme.text}>{model.left}</text>
62
+ <text fg={theme.colors['editor.foreground']}>{model.left}</text>
61
63
  </box>
62
64
  <box width="100%" flexDirection="row" justifyContent="space-between">
63
- <text fg={theme.textMuted}>{model.right}</text>
64
- <text fg={theme.dim}>v{APP_VERSION}</text>
65
+ <text fg={theme.colors['descriptionForeground']}>{model.right}</text>
66
+ <box flexDirection="row" gap={2}>
67
+ {model.help ? <text fg={theme.colors['descriptionForeground']}>{model.help}</text> : null}
68
+ <text fg={theme.colors['editor.lineHighlightBackground']}>v{APP_VERSION}</text>
69
+ </box>
65
70
  </box>
66
71
  </box>
67
72
  )
@@ -1,22 +1,22 @@
1
1
  import type { ReactNode } from 'react'
2
2
 
3
- import { theme } from '../theme'
3
+ import { getCurrentTheme } from '../theme'
4
4
 
5
5
  type SurfaceTone = 'muted' | 'elevated' | 'selected' | 'input' | 'inputActive'
6
6
 
7
7
  function getSurfaceColor(tone: SurfaceTone): string {
8
8
  switch (tone) {
9
9
  case 'elevated':
10
- return theme.panel
10
+ return getCurrentTheme().colors['sideBar.background']
11
11
  case 'selected':
12
- return theme.panelHighlight
12
+ return getCurrentTheme().colors['list.activeSelectionBackground']
13
13
  case 'input':
14
- return theme.background
14
+ return getCurrentTheme().colors['editor.background']
15
15
  case 'inputActive':
16
- return theme.panelHighlight
16
+ return getCurrentTheme().colors['list.activeSelectionBackground']
17
17
  case 'muted':
18
18
  default:
19
- return theme.panelMuted
19
+ return getCurrentTheme().colors['sideBarSectionHeader.background']
20
20
  }
21
21
  }
22
22
 
@@ -1,7 +1,7 @@
1
1
  import type { TabSession } from '../../state/types'
2
2
 
3
3
  import { useBusySpinner } from '../hooks/use-busy-spinner'
4
- import { theme } from '../theme'
4
+ import { getCurrentTheme, useTheme } from '../theme'
5
5
 
6
6
  interface TabItemProps {
7
7
  id?: string
@@ -15,15 +15,15 @@ interface TabItemProps {
15
15
  function getStatusColor(status: TabSession['status']): string {
16
16
  switch (status) {
17
17
  case 'running':
18
- return theme.success
18
+ return getCurrentTheme().colors['gitDecoration.addedResourceForeground']
19
19
  case 'disconnected':
20
- return theme.warning
20
+ return getCurrentTheme().colors['editorWarning.foreground']
21
21
  case 'error':
22
- return theme.danger
22
+ return getCurrentTheme().colors['editorError.foreground']
23
23
  case 'exited':
24
- return theme.warning
24
+ return getCurrentTheme().colors['editorWarning.foreground']
25
25
  default:
26
- return theme.textMuted
26
+ return getCurrentTheme().colors['descriptionForeground']
27
27
  }
28
28
  }
29
29
 
@@ -37,32 +37,38 @@ function getIndicator(active: boolean, focused: boolean, inLayout: boolean): str
37
37
 
38
38
  function getIndicatorColor(active: boolean, focused: boolean, inLayout: boolean): string {
39
39
  if (active) {
40
- return focused ? theme.accent : theme.accentAlt
40
+ return focused
41
+ ? getCurrentTheme().colors['textLink.foreground']
42
+ : getCurrentTheme().colors['terminal.ansiMagenta']
41
43
  }
42
44
 
43
- return inLayout ? theme.textMuted : theme.dim
45
+ return inLayout
46
+ ? getCurrentTheme().colors['descriptionForeground']
47
+ : getCurrentTheme().colors['editor.lineHighlightBackground']
44
48
  }
45
49
 
46
50
  function BusyIndicator() {
51
+ const theme = useTheme()
47
52
  const frame = useBusySpinner()
48
- return <text fg={theme.accent}>{frame} busy</text>
53
+ return <text fg={theme.colors['textLink.foreground']}>{frame} busy</text>
49
54
  }
50
55
 
51
56
  function ActivityIndicator({ isFocusedInput, tab }: { tab: TabSession; isFocusedInput: boolean }) {
57
+ const theme = useTheme()
52
58
  if (tab.status === 'error') {
53
- return <text fg={theme.danger}>✗ error</text>
59
+ return <text fg={theme.colors['editorError.foreground']}>✗ error</text>
54
60
  }
55
61
 
56
62
  if (tab.status === 'disconnected') {
57
- return <text fg={theme.warning}>⏸ restore</text>
63
+ return <text fg={theme.colors['editorWarning.foreground']}>⏸ restore</text>
58
64
  }
59
65
 
60
66
  if (tab.status === 'exited') {
61
- return <text fg={theme.warning}>⏹ exited</text>
67
+ return <text fg={theme.colors['editorWarning.foreground']}>⏹ exited</text>
62
68
  }
63
69
 
64
70
  if (isFocusedInput) {
65
- return <text fg={theme.borderActive}>▸ focused</text>
71
+ return <text fg={theme.colors['focusBorder']}>▸ focused</text>
66
72
  }
67
73
 
68
74
  if (tab.activity === 'busy') {
@@ -70,13 +76,14 @@ function ActivityIndicator({ isFocusedInput, tab }: { tab: TabSession; isFocused
70
76
  }
71
77
 
72
78
  if (tab.activity === 'idle') {
73
- return <text fg={theme.success}>● idle</text>
79
+ return <text fg={theme.colors['gitDecoration.addedResourceForeground']}>● idle</text>
74
80
  }
75
81
 
76
82
  return <text fg={getStatusColor(tab.status)}>{tab.status}</text>
77
83
  }
78
84
 
79
85
  export function TabItem({ active, focused, id, inLayout, isFocusedInput, tab }: TabItemProps) {
86
+ const theme = useTheme()
80
87
  const label = tab.command.split(' ')[0]
81
88
  const isInLayout = inLayout ?? false
82
89
  const indicator = getIndicator(active, focused, isInLayout)
@@ -89,16 +96,20 @@ export function TabItem({ active, focused, id, inLayout, isFocusedInput, tab }:
89
96
  paddingRight={1}
90
97
  paddingTop={0}
91
98
  paddingBottom={0}
92
- backgroundColor={active ? theme.panelHighlight : undefined}
99
+ backgroundColor={active ? theme.colors['list.activeSelectionBackground'] : undefined}
93
100
  flexDirection="column"
94
101
  gap={0}
95
102
  >
96
103
  <box flexDirection="row">
97
104
  <text fg={indicatorColor}>{indicator} </text>
98
- <text fg={active ? theme.text : theme.textMuted}>{tab.title}</text>
105
+ <text
106
+ fg={active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']}
107
+ >
108
+ {tab.title}
109
+ </text>
99
110
  </box>
100
111
  <box flexDirection="row">
101
- <text fg={theme.textMuted}> {label} </text>
112
+ <text fg={theme.colors['descriptionForeground']}> {label} </text>
102
113
  <ActivityIndicator tab={tab} isFocusedInput={isFocusedInput} />
103
114
  </box>
104
115
  </box>