@brimveyn/aimux 1.5.4 → 1.6.0

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 (65) 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/input/modes/bridge.ts +8 -0
  13. package/src/input/modes/transitions.ts +2 -1
  14. package/src/input/modes/types.ts +4 -1
  15. package/src/pty/terminal-snapshot.ts +4 -4
  16. package/src/state/git-tree.ts +220 -0
  17. package/src/state/reducers/git-mode-state.ts +232 -35
  18. package/src/state/reducers/git-panel-state.ts +29 -3
  19. package/src/state/reducers/modal-state.ts +43 -10
  20. package/src/state/store.ts +5 -1
  21. package/src/state/types.ts +41 -5
  22. package/src/state/workspace-save.ts +2 -0
  23. package/src/ui/components/create-session-modal.tsx +23 -7
  24. package/src/ui/components/diff-renderer/build-rows.ts +349 -0
  25. package/src/ui/components/diff-renderer/filetype.ts +29 -0
  26. package/src/ui/components/diff-renderer/fold-strip.tsx +84 -0
  27. package/src/ui/components/diff-renderer/highlight.ts +48 -0
  28. package/src/ui/components/diff-renderer/index.ts +1 -0
  29. package/src/ui/components/diff-renderer/pierre-diff.tsx +170 -0
  30. package/src/ui/components/diff-renderer/split-view.tsx +207 -0
  31. package/src/ui/components/diff-renderer/stacked-view.tsx +166 -0
  32. package/src/ui/components/git-commit-modal.tsx +14 -2
  33. package/src/ui/components/git-pane-widget.tsx +5 -0
  34. package/src/ui/components/git-panel.tsx +176 -79
  35. package/src/ui/components/git-view.tsx +89 -87
  36. package/src/ui/components/help-modal.tsx +43 -11
  37. package/src/ui/components/input-field.tsx +2 -2
  38. package/src/ui/components/list-item.tsx +9 -1
  39. package/src/ui/components/modal-filter-bar.tsx +1 -1
  40. package/src/ui/components/modal-keybinds-overlay.tsx +2 -2
  41. package/src/ui/components/modal-shell.tsx +3 -3
  42. package/src/ui/components/new-tab-modal.tsx +15 -3
  43. package/src/ui/components/pending-chord-overlay.tsx +3 -3
  44. package/src/ui/components/session-bar.tsx +10 -5
  45. package/src/ui/components/session-picker-modal.tsx +26 -9
  46. package/src/ui/components/sidebar.tsx +22 -10
  47. package/src/ui/components/snippet-editor-modal.tsx +16 -2
  48. package/src/ui/components/snippet-picker-modal.tsx +11 -3
  49. package/src/ui/components/split-layout.tsx +1 -1
  50. package/src/ui/components/status-bar.tsx +12 -9
  51. package/src/ui/components/surface.tsx +5 -5
  52. package/src/ui/components/tab-item.tsx +22 -16
  53. package/src/ui/components/terminal-pane.tsx +25 -15
  54. package/src/ui/components/theme-picker-modal.tsx +111 -16
  55. package/src/ui/components/update-available-modal.tsx +11 -1
  56. package/src/ui/filter-themes.ts +10 -0
  57. package/src/ui/house-themes.ts +313 -0
  58. package/src/ui/keymap-context.ts +10 -2
  59. package/src/ui/root.tsx +26 -6
  60. package/src/ui/shiki.ts +49 -0
  61. package/src/ui/status-bar-model.ts +19 -1
  62. package/src/ui/theme.ts +22 -10
  63. package/src/ui/themes.generated.ts +59794 -0
  64. package/src/ui/themes.ts +68 -274
  65. package/src/ui/syntax.ts +0 -102
@@ -17,7 +17,13 @@ export function GitCommitModal({ activeField, body, cursorPos, title }: GitCommi
17
17
  return (
18
18
  <ModalShell title="Commit" keybindsModeId="modal.git-commit" width={uiTokens.modalWidth.xl}>
19
19
  <box flexDirection="column">
20
- <text fg={titleActive ? theme.text : theme.textMuted}>Title</text>
20
+ <text
21
+ fg={
22
+ titleActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
23
+ }
24
+ >
25
+ Title
26
+ </text>
21
27
  <InputField
22
28
  active={titleActive}
23
29
  cursorPos={titleActive ? cursorPos : undefined}
@@ -26,7 +32,13 @@ export function GitCommitModal({ activeField, body, cursorPos, title }: GitCommi
26
32
  </box>
27
33
 
28
34
  <box flexDirection="column">
29
- <text fg={bodyActive ? theme.text : theme.textMuted}>Body (optional)</text>
35
+ <text
36
+ fg={
37
+ bodyActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
38
+ }
39
+ >
40
+ Body (optional)
41
+ </text>
30
42
  <InputField
31
43
  active={bodyActive}
32
44
  cursorPos={bodyActive ? cursorPos : undefined}
@@ -12,6 +12,8 @@ interface GitPaneWidgetProps {
12
12
 
13
13
  export const GitPaneWidget = memo(function GitPaneWidget({ pollingEnabled }: GitPaneWidgetProps) {
14
14
  const gitPanel = useAppStore((s) => s.gitPanel)
15
+ const gitMode = useAppStore((s) => s.gitMode)
16
+ const gitFileListMode = useAppStore((s) => s.gitPane.fileListMode)
15
17
  const pathConfig = useAppStore((s) => s.gitPane.path)
16
18
  const diffCountConfig = useAppStore((s) => s.gitPane.diffCount)
17
19
  const currentSessionId = useAppStore((s) => s.currentSessionId)
@@ -37,10 +39,13 @@ export const GitPaneWidget = memo(function GitPaneWidget({ pollingEnabled }: Git
37
39
 
38
40
  return (
39
41
  <GitPanel
42
+ collapsedFolders={gitMode.collapsedFolders}
40
43
  diffCountConfig={diffCountConfig}
44
+ fileListMode={gitFileListMode}
41
45
  gitPanel={display}
42
46
  pathConfig={pathConfig}
43
47
  projectPath={projectPath}
48
+ selectedEntryKey={gitMode.selectedEntryKey}
44
49
  />
45
50
  )
46
51
  })
@@ -1,27 +1,30 @@
1
- import { memo, type ReactNode, useMemo } from 'react'
1
+ import type { ScrollBoxRenderable } from '@opentui/core'
2
+
3
+ import { memo, type ReactNode, useEffect, useMemo, useRef } from 'react'
2
4
 
3
5
  import type {
4
6
  GitFileEntry,
7
+ GitFileListMode,
5
8
  GitFileSection,
6
9
  GitPaneDiffCountConfig,
7
10
  GitPanelState,
8
11
  GitPanePathConfig,
9
12
  } from '../../state/types'
10
13
 
14
+ import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
15
+ import { buildGitTreeRows, type GitTreeFileRow, type GitTreeFolderRow } from '../../state/git-tree'
11
16
  import { theme } from '../theme'
12
17
 
13
18
  interface GitPanelProps {
19
+ collapsedFolders?: Record<string, true>
20
+ fileListMode?: GitFileListMode
14
21
  gitPanel: GitPanelState
15
22
  projectPath: string | undefined
16
- selectedFileKey?: string | null
23
+ selectedEntryKey?: string | null
17
24
  pathConfig?: GitPanePathConfig
18
25
  diffCountConfig?: GitPaneDiffCountConfig
19
26
  }
20
27
 
21
- export function fileKey(file: Pick<GitFileEntry, 'path' | 'section'>): string {
22
- return `${file.section}:${file.path}`
23
- }
24
-
25
28
  const SECTION_ORDER: { key: GitFileSection; title: string }[] = [
26
29
  { key: 'staged', title: 'Staged Changes' },
27
30
  { key: 'unstaged', title: 'Changes' },
@@ -29,13 +32,13 @@ const SECTION_ORDER: { key: GitFileSection; title: string }[] = [
29
32
  ]
30
33
 
31
34
  const STATUS_COLORS: Record<GitFileEntry['status'], string> = {
32
- '?': theme.success,
33
- 'A': theme.success,
34
- 'C': theme.accent,
35
- 'D': theme.danger,
36
- 'M': theme.warning,
37
- 'R': theme.accent,
38
- 'U': theme.danger,
35
+ '?': theme.colors['gitDecoration.addedResourceForeground'],
36
+ 'A': theme.colors['gitDecoration.addedResourceForeground'],
37
+ 'C': theme.colors['textLink.foreground'],
38
+ 'D': theme.colors['editorError.foreground'],
39
+ 'M': theme.colors['editorWarning.foreground'],
40
+ 'R': theme.colors['textLink.foreground'],
41
+ 'U': theme.colors['editorError.foreground'],
39
42
  }
40
43
 
41
44
  function displayStatus(file: GitFileEntry): string {
@@ -43,16 +46,6 @@ function displayStatus(file: GitFileEntry): string {
43
46
  return file.status
44
47
  }
45
48
 
46
- function groupBySection(files: GitFileEntry[]): Record<GitFileSection, GitFileEntry[]> {
47
- const groups: Record<GitFileSection, GitFileEntry[]> = {
48
- staged: [],
49
- unstaged: [],
50
- untracked: [],
51
- }
52
- for (const file of files) groups[file.section].push(file)
53
- return groups
54
- }
55
-
56
49
  function maxDigitWidth(files: GitFileEntry[]): { added: number; removed: number } {
57
50
  let addedMax = 1
58
51
  let removedMax = 1
@@ -67,7 +60,7 @@ function padRight(value: number | null, width: number): string {
67
60
  return String(value ?? 0).padStart(width, ' ')
68
61
  }
69
62
 
70
- export function splitPath(path: string): { prefix: string; basename: string } {
63
+ function splitPath(path: string): { prefix: string; basename: string } {
71
64
  const slash = path.lastIndexOf('/')
72
65
  if (slash < 0) return { basename: path, prefix: '' }
73
66
  return { basename: path.slice(slash + 1), prefix: path.slice(0, slash + 1) }
@@ -77,30 +70,36 @@ function stripTrailingSlash(prefix: string): string {
77
70
  return prefix.endsWith('/') ? prefix.slice(0, -1) : prefix
78
71
  }
79
72
 
80
- function renderPath(file: GitFileEntry, pathConfig: GitPanePathConfig): ReactNode {
81
- const showDir = pathConfig.enabled
73
+ function renderFileLabel(
74
+ file: GitFileEntry,
75
+ pathConfig: GitPanePathConfig,
76
+ fileListMode: GitFileListMode
77
+ ): ReactNode {
82
78
  const transform = pathConfig.enabled ? pathConfig.pathFn : undefined
83
79
  const displayPath = transform ? transform(file.path) : file.path
84
80
  const { basename, prefix } = splitPath(displayPath)
85
81
  const dir = stripTrailingSlash(prefix)
86
- if (file.renamedFrom) {
87
- const renamedDisplay = transform ? transform(file.renamedFrom) : file.renamedFrom
88
- const renamed = splitPath(renamedDisplay)
89
- const renamedDir = stripTrailingSlash(renamed.prefix)
82
+ const showDir = fileListMode === 'flat' && pathConfig.enabled && dir
83
+ if (!file.renamedFrom) {
90
84
  return (
91
85
  <text wrapMode="none">
92
- <span fg={theme.text}>{renamed.basename}</span>
93
- {showDir && renamedDir ? <span fg={theme.textMuted}> {renamedDir}</span> : null}
94
- <span fg={theme.textMuted}> → </span>
95
- <span fg={theme.text}>{basename}</span>
96
- {showDir && dir ? <span fg={theme.textMuted}> {dir}</span> : null}
86
+ <span fg={theme.colors['editor.foreground']}>{basename}</span>
87
+ {showDir ? <span fg={theme.colors['descriptionForeground']}> {dir}</span> : null}
97
88
  </text>
98
89
  )
99
90
  }
91
+ const renamedDisplay = transform ? transform(file.renamedFrom) : file.renamedFrom
92
+ const renamed = splitPath(renamedDisplay)
93
+ const renamedDir = stripTrailingSlash(renamed.prefix)
100
94
  return (
101
95
  <text wrapMode="none">
102
- <span fg={theme.text}>{basename}</span>
103
- {showDir && dir ? <span fg={theme.textMuted}> {dir}</span> : null}
96
+ <span fg={theme.colors['descriptionForeground']}>{renamed.basename}</span>
97
+ {showDir && renamedDir ? (
98
+ <span fg={theme.colors['descriptionForeground']}> {renamedDir}</span>
99
+ ) : null}
100
+ <span fg={theme.colors['descriptionForeground']}> → </span>
101
+ <span fg={theme.colors['editor.foreground']}>{basename}</span>
102
+ {showDir ? <span fg={theme.colors['descriptionForeground']}> {dir}</span> : null}
104
103
  </text>
105
104
  )
106
105
  }
@@ -116,74 +115,145 @@ function renderDiffCount(
116
115
  if (!diffCountConfig.enabled) return null
117
116
  if (!hasNumstat) {
118
117
  return (
119
- <text fg={theme.textMuted} bg={bg} flexShrink={0}>
118
+ <text fg={theme.colors['descriptionForeground']} bg={bg} flexShrink={0}>
120
119
 
121
120
  </text>
122
121
  )
123
122
  }
124
123
  return (
125
124
  <box flexDirection="row" flexShrink={0}>
126
- <text fg={theme.success} bg={bg}>{`+${padRight(file.added, addedW)}`}</text>
127
- <text fg={theme.dim} bg={bg}>
125
+ <text
126
+ fg={theme.colors['gitDecoration.addedResourceForeground']}
127
+ bg={bg}
128
+ >{`+${padRight(file.added, addedW)}`}</text>
129
+ <text fg={theme.colors['editor.lineHighlightBackground']} bg={bg}>
128
130
  {' '}
129
131
  </text>
130
- <text fg={theme.danger} bg={bg}>{`−${padRight(file.removed, removedW)}`}</text>
132
+ <text
133
+ fg={theme.colors['editorError.foreground']}
134
+ bg={bg}
135
+ >{`−${padRight(file.removed, removedW)}`}</text>
136
+ </box>
137
+ )
138
+ }
139
+
140
+ function renderFolderRow(row: GitTreeFolderRow, isSelected: boolean): ReactNode {
141
+ const bg = isSelected ? theme.colors['list.activeSelectionBackground'] : undefined
142
+ const onSelect = (): void => {
143
+ dispatchGlobal({ key: row.key, type: 'git-mode-select-entry-by-key' })
144
+ }
145
+ const onToggle = (): void => {
146
+ dispatchGlobal({ key: row.key, type: 'git-mode-toggle-folder' })
147
+ }
148
+ return (
149
+ <box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
150
+ <box flexGrow={1} overflow="hidden" paddingLeft={row.depth * 2}>
151
+ <box flexDirection="row" gap={1} onMouseDown={onToggle}>
152
+ <text fg={theme.colors['descriptionForeground']} bg={bg}>
153
+ {row.isCollapsed ? '▸' : '▾'}
154
+ </text>
155
+ <text fg={theme.colors['terminal.ansiMagenta']} bg={bg} wrapMode="none">
156
+ {row.name}
157
+ </text>
158
+ </box>
159
+ </box>
131
160
  </box>
132
161
  )
133
162
  }
134
163
 
135
164
  function renderFileRow(
136
- file: GitFileEntry,
137
- key: string,
165
+ row: GitTreeFileRow,
138
166
  addedW: number,
139
167
  removedW: number,
140
168
  isSelected: boolean,
169
+ fileListMode: GitFileListMode,
141
170
  pathConfig: GitPanePathConfig,
142
171
  diffCountConfig: GitPaneDiffCountConfig
143
172
  ): ReactNode {
173
+ const file = row.file
144
174
  const hasNumstat = file.added !== null || file.removed !== null
145
- const bg = isSelected ? theme.panelHighlight : undefined
175
+ const bg = isSelected ? theme.colors['list.activeSelectionBackground'] : undefined
176
+ const onSelect = (): void => {
177
+ dispatchGlobal({ key: row.key, type: 'git-mode-select-entry-by-key' })
178
+ }
146
179
  return (
147
- <box key={key} flexDirection="row" gap={1} paddingLeft={1} backgroundColor={bg}>
148
- <text fg={STATUS_COLORS[file.status]} bg={bg}>
149
- <strong>{displayStatus(file)}</strong>
150
- </text>
151
- <box flexGrow={1} overflow="hidden">
152
- {renderPath(file, pathConfig)}
180
+ <box key={row.key} flexDirection="row" gap={1} backgroundColor={bg} onMouseDown={onSelect}>
181
+ <box width={2} flexShrink={0} justifyContent="center">
182
+ <text fg={STATUS_COLORS[file.status]} bg={bg}>
183
+ <strong>{displayStatus(file)}</strong>
184
+ </text>
185
+ </box>
186
+ <box flexGrow={1} overflow="hidden" paddingLeft={fileListMode === 'tree' ? row.depth * 2 : 0}>
187
+ {renderFileLabel(file, pathConfig, fileListMode)}
153
188
  </box>
154
189
  {renderDiffCount(file, addedW, removedW, bg, diffCountConfig, hasNumstat)}
155
190
  </box>
156
191
  )
157
192
  }
158
193
 
159
- function renderSection(
194
+ function renderTreeSection(
160
195
  section: GitFileSection,
161
196
  title: string,
162
197
  files: GitFileEntry[],
198
+ rows: Array<GitTreeFolderRow | GitTreeFileRow>,
163
199
  addedW: number,
164
200
  removedW: number,
165
- selectedFileKey: string | null | undefined,
201
+ fileListMode: GitFileListMode,
202
+ selectedEntryKey: string | null | undefined,
203
+ showListModeToggle: boolean,
166
204
  pathConfig: GitPanePathConfig,
167
205
  diffCountConfig: GitPaneDiffCountConfig
168
206
  ): ReactNode {
169
207
  if (files.length === 0) return null
208
+ const nextFileListMode = fileListMode === 'tree' ? 'flat' : 'tree'
209
+ const toggleListMode = (): void => {
210
+ dispatchGlobal({ type: 'git-mode-toggle-file-list-mode' })
211
+ runSideEffectGlobal({ mode: nextFileListMode, type: 'persist-git-file-list-mode' })
212
+ }
170
213
  return (
171
214
  <box key={section} flexDirection="column">
172
- <text fg={theme.accentAlt}>
173
- <strong>
174
- {title} ({files.length})
175
- </strong>
176
- </text>
177
- {files.map((file, i) =>
178
- renderFileRow(
179
- file,
180
- `${section}-${i}`,
181
- addedW,
182
- removedW,
183
- !!selectedFileKey && fileKey(file) === selectedFileKey,
184
- pathConfig,
185
- diffCountConfig
186
- )
215
+ <box flexDirection="row" justifyContent="space-between">
216
+ <text fg={theme.colors['terminal.ansiMagenta']}>
217
+ <strong>
218
+ {title} ({files.length})
219
+ </strong>
220
+ </text>
221
+ {showListModeToggle ? (
222
+ <box flexDirection="row" gap={1} onMouseDown={toggleListMode}>
223
+ <text
224
+ fg={
225
+ fileListMode === 'tree'
226
+ ? theme.colors['textLink.foreground']
227
+ : theme.colors['descriptionForeground']
228
+ }
229
+ >
230
+ tree
231
+ </text>
232
+ <text fg={theme.colors['descriptionForeground']}>|</text>
233
+ <text
234
+ fg={
235
+ fileListMode === 'flat'
236
+ ? theme.colors['textLink.foreground']
237
+ : theme.colors['descriptionForeground']
238
+ }
239
+ >
240
+ flat
241
+ </text>
242
+ </box>
243
+ ) : null}
244
+ </box>
245
+ {rows.map((row) =>
246
+ row.kind === 'folder'
247
+ ? renderFolderRow(row, row.key === selectedEntryKey)
248
+ : renderFileRow(
249
+ row,
250
+ addedW,
251
+ removedW,
252
+ row.key === selectedEntryKey,
253
+ fileListMode,
254
+ pathConfig,
255
+ diffCountConfig
256
+ )
187
257
  )}
188
258
  </box>
189
259
  )
@@ -209,16 +279,16 @@ function computeStatusPlaceholder(
209
279
  hasProjectPath: boolean
210
280
  ): StatusPlaceholder | null {
211
281
  if (!hasProjectPath) {
212
- return { label: 'No active session', labelColor: theme.textMuted }
282
+ return { label: 'No active session', labelColor: theme.colors['descriptionForeground'] }
213
283
  }
214
284
  if (gitPanel.error === 'not-a-repo') {
215
- return { label: 'Not a git repository', labelColor: theme.textMuted }
285
+ return { label: 'Not a git repository', labelColor: theme.colors['descriptionForeground'] }
216
286
  }
217
287
  if (gitPanel.error === 'unknown') {
218
- return { label: 'Git error', labelColor: theme.danger }
288
+ return { label: 'Git error', labelColor: theme.colors['editorError.foreground'] }
219
289
  }
220
290
  if (gitPanel.files.length === 0) {
221
- return { label: 'Working tree clean', labelColor: theme.textMuted }
291
+ return { label: 'Working tree clean', labelColor: theme.colors['descriptionForeground'] }
222
292
  }
223
293
  return null
224
294
  }
@@ -227,13 +297,19 @@ const DEFAULT_PATH_CONFIG: GitPanePathConfig = { enabled: true }
227
297
  const DEFAULT_DIFF_COUNT_CONFIG: GitPaneDiffCountConfig = { enabled: true }
228
298
 
229
299
  export const GitPanel = memo(function GitPanel({
300
+ collapsedFolders = {},
230
301
  diffCountConfig = DEFAULT_DIFF_COUNT_CONFIG,
302
+ fileListMode = 'tree',
231
303
  gitPanel,
232
304
  pathConfig = DEFAULT_PATH_CONFIG,
233
305
  projectPath,
234
- selectedFileKey,
306
+ selectedEntryKey,
235
307
  }: GitPanelProps) {
236
- const groups = useMemo(() => groupBySection(gitPanel.files), [gitPanel.files])
308
+ const scrollRef = useRef<ScrollBoxRenderable | null>(null)
309
+ const tree = useMemo(
310
+ () => buildGitTreeRows(gitPanel.files, collapsedFolders, fileListMode),
311
+ [collapsedFolders, fileListMode, gitPanel.files]
312
+ )
237
313
  const { added: addedW, removed: removedW } = useMemo(
238
314
  () => maxDigitWidth(gitPanel.files),
239
315
  [gitPanel.files]
@@ -242,33 +318,54 @@ export const GitPanel = memo(function GitPanel({
242
318
  const statusNode = renderStatus(gitPanel, !!projectPath)
243
319
 
244
320
  const hasRemoteTracking = gitPanel.ahead > 0 || gitPanel.behind > 0
321
+ const toggleSection =
322
+ tree.sections.find((section) => section.section === 'unstaged' && section.files.length > 0)
323
+ ?.section ??
324
+ tree.sections.find((section) => section.files.length > 0)?.section ??
325
+ null
326
+
327
+ useEffect(() => {
328
+ const scrollbox = scrollRef.current
329
+ if (!scrollbox || !selectedEntryKey) return
330
+ const selectedIndex = tree.visibleRows.findIndex((row) => row.key === selectedEntryKey)
331
+ if (selectedIndex < 0) return
332
+ const viewportHeight = Math.max(1, scrollbox.viewport.height)
333
+ const target = Math.max(0, selectedIndex - Math.floor(viewportHeight / 2))
334
+ scrollbox.scrollTo({ x: 0, y: target })
335
+ }, [selectedEntryKey, tree.visibleRows])
245
336
 
246
337
  return (
247
338
  <box flexDirection="column" flexGrow={1} gap={0}>
248
339
  {hasRemoteTracking ? (
249
- <text fg={theme.textMuted}>
340
+ <text fg={theme.colors['descriptionForeground']}>
250
341
  ↑{gitPanel.ahead} ↓{gitPanel.behind}
251
342
  </text>
252
343
  ) : null}
253
344
  {statusNode ?? (
254
345
  <scrollbox
255
346
  flexGrow={1}
347
+ ref={scrollRef}
256
348
  scrollY
349
+ scrollbarOptions={{ visible: false }}
257
350
  viewportCulling
258
351
  contentOptions={{ flexDirection: 'column', gap: 0 }}
259
352
  >
260
- {SECTION_ORDER.map((s) =>
261
- renderSection(
353
+ {SECTION_ORDER.map((s) => {
354
+ const section = tree.sections.find((entry) => entry.section === s.key)
355
+ return renderTreeSection(
262
356
  s.key,
263
357
  s.title,
264
- groups[s.key],
358
+ section?.files ?? [],
359
+ section?.rows ?? [],
265
360
  addedW,
266
361
  removedW,
267
- selectedFileKey,
362
+ fileListMode,
363
+ selectedEntryKey,
364
+ s.key === toggleSection,
268
365
  pathConfig,
269
366
  diffCountConfig
270
367
  )
271
- )}
368
+ })}
272
369
  </scrollbox>
273
370
  )}
274
371
  </box>