@brimveyn/aimux 1.9.3 → 1.9.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brimveyn/aimux",
3
- "version": "1.9.3",
3
+ "version": "1.9.5",
4
4
  "description": "A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode side-by-side with tabbed navigation, split panes, and persistent sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -60,7 +60,7 @@
60
60
  "bump": "bun run scripts/bump.ts"
61
61
  },
62
62
  "dependencies": {
63
- "@brimveyn/aimux-config": "0.5.3",
63
+ "@brimveyn/aimux-config": "0.5.5",
64
64
  "@opentui/core": "^0.1.90",
65
65
  "@opentui/react": "^0.1.90",
66
66
  "@xterm/headless": "^6.0.0",
@@ -548,6 +548,16 @@ export function executeSideEffect(effect: SideEffect, ctx: SideEffectContext): v
548
548
  )
549
549
  return
550
550
  }
551
+ case 'git-stage-all': {
552
+ const paths = ctx.state.gitPanel.files.map((f) => f.path)
553
+ void enqueueGitOp(() => runGitActionAll(ctx, ['add', '-A'], paths))
554
+ return
555
+ }
556
+ case 'git-unstage-all': {
557
+ const paths = ctx.state.gitPanel.files.map((f) => f.path)
558
+ void enqueueGitOp(() => runGitActionAll(ctx, ['reset'], paths))
559
+ return
560
+ }
551
561
  case 'git-restore': {
552
562
  void enqueueGitOp(() => runGitAction(ctx, ['restore', '--', effect.path], effect.path))
553
563
  return
@@ -670,6 +680,25 @@ async function runGitAction(
670
680
  }
671
681
  }
672
682
 
683
+ async function runGitActionAll(
684
+ ctx: SideEffectContext,
685
+ args: string[],
686
+ pathsToInvalidate: string[]
687
+ ): Promise<void> {
688
+ const cwd = ctx.getCurrentSessionProjectPath()
689
+ if (!cwd) return
690
+ const result = await $`git -C ${cwd} ${args}`.quiet().nothrow()
691
+ if (result.exitCode !== 0) {
692
+ const stderr = result.stderr.toString().trim()
693
+ ctx.dispatch({ message: stderr || 'git action failed', type: 'git-mode-set-message' })
694
+ return
695
+ }
696
+ ctx.dispatch({ message: null, type: 'git-mode-set-message' })
697
+ if (pathsToInvalidate.length > 0) {
698
+ ctx.dispatch({ paths: pathsToInvalidate, type: 'git-mode-invalidate-diffs' })
699
+ }
700
+ }
701
+
673
702
  async function runGitRm(ctx: SideEffectContext, path: string): Promise<void> {
674
703
  const repoPath = ctx.state.gitPanel.files.find((f) => f.path === path)?.repoPath
675
704
  const cwd = repoPath ?? ctx.getCurrentSessionProjectPath()
@@ -55,6 +55,8 @@ export type SideEffect =
55
55
  | { type: 'persist-git-tree-compaction'; enabled: boolean }
56
56
  | { type: 'git-stage'; path: string }
57
57
  | { type: 'git-unstage'; path: string }
58
+ | { type: 'git-stage-all' }
59
+ | { type: 'git-unstage-all' }
58
60
  | { type: 'git-restore'; path: string }
59
61
  | { type: 'git-rm'; path: string }
60
62
  | { type: 'git-commit'; title: string; body: string }
@@ -1,5 +1,6 @@
1
- import type { AppAction, AppState, FoldState, GitFileEntry, GitModeState } from '../types'
1
+ import type { PreparedParsedDiff } from '../../ui/components/git/diff-renderer/prepare-diff'
2
2
 
3
+ import { collectFoldables } from '../../ui/components/git/diff-renderer/build-rows'
3
4
  import {
4
5
  getSelectedGitRow,
5
6
  gitFileKey,
@@ -7,6 +8,14 @@ import {
7
8
  moveGitSelection,
8
9
  reconcileSelectedGitEntryKey,
9
10
  } from '../git-tree'
11
+ import {
12
+ type AppAction,
13
+ type AppState,
14
+ type FoldState,
15
+ getParsedPayload,
16
+ type GitFileEntry,
17
+ type GitModeState,
18
+ } from '../types'
10
19
  import { clearDiffCacheForPath, clearDiffCacheForPaths } from './diff-cache'
11
20
  import { sortFilesBySection } from './git-panel-state'
12
21
 
@@ -439,6 +448,29 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
439
448
  const bottom = Math.max(0, action.bottom)
440
449
  return applyFold(state, action.key, action.foldId, { bottom, top })
441
450
  }
451
+ case 'git-mode-fold-toggle-all': {
452
+ const parsed = getParsedPayload<PreparedParsedDiff>(state.gitMode.parsedFiles[action.key])
453
+ const file = parsed?.file
454
+ if (!file || !Array.isArray(file.hunks)) return state
455
+ const foldables = collectFoldables(file)
456
+ if (foldables.length === 0) return state
457
+ const current = state.gitMode.folds[action.key] ?? {}
458
+ const allExpanded = foldables.every(({ foldId, middle }) => {
459
+ const fs = current[foldId] ?? { bottom: 0, top: 0 }
460
+ return fs.top + fs.bottom >= middle
461
+ })
462
+ const nextFolds = { ...state.gitMode.folds }
463
+ if (allExpanded) {
464
+ delete nextFolds[action.key]
465
+ } else {
466
+ const perPath: Record<string, FoldState> = {}
467
+ for (const { foldId, middle } of foldables) {
468
+ perPath[foldId] = { bottom: 0, top: middle }
469
+ }
470
+ nextFolds[action.key] = perPath
471
+ }
472
+ return { ...state, gitMode: { ...state.gitMode, folds: nextFolds } }
473
+ }
442
474
  case 'git-mode-optimistic-move': {
443
475
  const currentKey = state.gitMode.selectedEntryKey
444
476
  const files = state.gitPanel.files
@@ -650,6 +650,7 @@ export type GitModeAction =
650
650
  delta: number
651
651
  }
652
652
  | { type: 'git-mode-fold-set'; key: string; foldId: string; top: number; bottom: number }
653
+ | { type: 'git-mode-fold-toggle-all'; key: string }
653
654
  | {
654
655
  type: 'git-mode-optimistic-move'
655
656
  path: string
@@ -181,6 +181,27 @@ function contextNeighbors(
181
181
  return { hasChangeAfter, hasChangeBefore }
182
182
  }
183
183
 
184
+ export interface Foldable {
185
+ foldId: string
186
+ middle: number
187
+ }
188
+
189
+ export function collectFoldables(file: FileDiffMetadata): Foldable[] {
190
+ const result: Foldable[] = []
191
+ for (const [hIdx, hunk] of file.hunks.entries()) {
192
+ for (const [cIdx, content] of hunk.hunkContent.entries()) {
193
+ if (content.type !== 'context') continue
194
+ const { hasChangeAfter, hasChangeBefore } = contextNeighbors(hunk.hunkContent, cIdx)
195
+ const preKeep = hasChangeBefore ? Math.min(KEEP_CONTEXT, content.lines) : 0
196
+ const postKeep = hasChangeAfter ? Math.min(KEEP_CONTEXT, content.lines - preKeep) : 0
197
+ const middle = content.lines - preKeep - postKeep
198
+ if (middle < MIN_FOLD_SIZE) continue
199
+ result.push({ foldId: `${hIdx}:${cIdx}`, middle })
200
+ }
201
+ }
202
+ return result
203
+ }
204
+
184
205
  export function buildDiffSegments(file: FileDiffMetadata, folds: FoldMap = {}): DiffSegmentBuild {
185
206
  const segments: DiffSegment[] = []
186
207
  let offset = 0