@brimveyn/aimux 1.12.6 → 1.13.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.
- package/README.md +3 -0
- package/package.json +4 -3
- package/src/app-runtime/auto-commit-driver.ts +8 -4
- package/src/app-runtime/auto-commit-ref.ts +1 -1
- package/src/app-runtime/backend-attach-runtime.ts +18 -2
- package/src/app-runtime/backend-runtime-events.ts +1 -1
- package/src/app-runtime/selection-scroll.ts +2 -2
- package/src/app-runtime/session-actions.ts +16 -4
- package/src/app-runtime/side-effects.ts +538 -83
- package/src/app-runtime/snippet-actions.ts +3 -3
- package/src/app-runtime/use-auto-commit-driver.ts +7 -4
- package/src/app-runtime/use-backend-runtime.ts +3 -3
- package/src/app-runtime/use-directory-search.ts +7 -5
- package/src/app-runtime/use-mouse-handlers.ts +11 -6
- package/src/app-runtime/use-pane-size-report.ts +1 -1
- package/src/app-runtime/use-renderer-bindings.ts +6 -5
- package/src/app.tsx +12 -7
- package/src/auto-commit/headless-commands.ts +4 -6
- package/src/daemon/daemon.ts +3 -3
- package/src/daemon/runtime-paths.ts +1 -1
- package/src/daemon/session-manager.ts +1 -1
- package/src/daemon/session-registry.ts +10 -3
- package/src/diff-parser/parse-patch-files.ts +18 -18
- package/src/git/command-queue.ts +1 -1
- package/src/git/divergence.ts +46 -0
- package/src/git/git-diff.ts +12 -5
- package/src/git/git-poller.ts +33 -11
- package/src/git/git-status.ts +14 -3
- package/src/git/move-worktree.ts +96 -0
- package/src/git/use-repo-discovery.ts +2 -1
- package/src/git/worktree-divergence-poller.ts +59 -0
- package/src/git/worktree.ts +99 -0
- package/src/index.tsx +1 -1
- package/src/input/keymap/describe-bindings.ts +27 -9
- package/src/input/keymap/key-chord.ts +4 -4
- package/src/input/keymap/key-format.ts +2 -2
- package/src/input/keymap/sequence-resolver.ts +1 -1
- package/src/input/keymap/trie.ts +1 -1
- package/src/input/modes/bridge.ts +7 -0
- package/src/input/modes/transitions.ts +2 -1
- package/src/input/modes/types.ts +11 -1
- package/src/input/raw-input-handler.ts +1 -1
- package/src/input/terminal-text-extraction.ts +9 -5
- package/src/integrations/claude-syntax-overlay.ts +8 -8
- package/src/integrations/claude-theme-sync.ts +12 -12
- package/src/ipc/protocol.ts +3 -3
- package/src/platform/clipboard.ts +4 -2
- package/src/platform/worktree-deps.ts +22 -0
- package/src/platform/worktree-paths.ts +67 -0
- package/src/profile-paths.ts +3 -3
- package/src/pty/assistant-status-detection-loop.ts +2 -2
- package/src/pty/assistant-status-detector.ts +15 -6
- package/src/pty/command-registry.ts +2 -1
- package/src/pty/pty-manager.ts +1 -1
- package/src/services/ai-usage/adapters/claude.ts +5 -5
- package/src/services/ai-usage/adapters/codex.ts +8 -8
- package/src/services/ai-usage/cache.ts +2 -2
- package/src/services/ai-usage/pace.ts +3 -6
- package/src/services/ai-usage/provider.ts +1 -1
- package/src/session-backend/bootstrap.ts +2 -2
- package/src/session-backend/local-session-backend.ts +11 -11
- package/src/session-backend/remote-session-backend.ts +3 -3
- package/src/session-backend/types.ts +2 -2
- package/src/snippets/expand-variables.ts +7 -7
- package/src/snippets/run-shell-var.ts +1 -1
- package/src/snippets/trigger-detector.ts +1 -1
- package/src/state/ai-usage-store.ts +1 -1
- package/src/state/git-tree.ts +8 -4
- package/src/state/layout-tree.ts +8 -5
- package/src/state/reducers/git-mode-state.ts +44 -14
- package/src/state/reducers/git-panel-state.ts +38 -2
- package/src/state/reducers/modal-state.ts +355 -21
- package/src/state/reducers/session-state.ts +60 -1
- package/src/state/reducers/tab-state.ts +151 -85
- package/src/state/selectors.ts +6 -4
- package/src/state/session-catalog.ts +20 -5
- package/src/state/session-persistence.ts +8 -4
- package/src/state/session-worktrees.ts +227 -0
- package/src/state/store.ts +1 -0
- package/src/state/toast-store.ts +95 -0
- package/src/state/types.ts +92 -13
- package/src/state/validation.ts +25 -2
- package/src/terminal-manager/manager-client.ts +15 -13
- package/src/ui/components/git/diff-renderer/fold-strip.tsx +2 -1
- package/src/ui/components/git/diff-renderer/pierre-diff.tsx +1 -1
- package/src/ui/components/git/diff-renderer/prepare-diff.ts +2 -2
- package/src/ui/components/git/diff-renderer/split-view.tsx +6 -4
- package/src/ui/components/git/diff-renderer/stacked-view.tsx +6 -4
- package/src/ui/components/git/diff-renderer/use-diff-prefetch.ts +19 -6
- package/src/ui/components/git/diff-renderer/use-diff-preparation.ts +2 -2
- package/src/ui/components/git/git-panel.tsx +17 -10
- package/src/ui/components/git/git-view.tsx +79 -19
- package/src/ui/components/git/image-diff/image-diff-view.tsx +2 -2
- package/src/ui/components/git/image-diff/terminal-image-pane.tsx +5 -4
- package/src/ui/components/git/pane/git-pane-context-menu.ts +2 -2
- package/src/ui/components/git/pane/git-pane-widget.tsx +6 -4
- package/src/ui/components/layout/session-bar.tsx +3 -3
- package/src/ui/components/layout/sidebar/sidebar.tsx +162 -36
- package/src/ui/components/layout/sidebar/tab-item.tsx +17 -1
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +2 -2
- package/src/ui/components/layout/sidebar/use-sidebar-branch.ts +1 -1
- package/src/ui/components/layout/split-layout.tsx +1 -1
- package/src/ui/components/layout/terminal-pane.tsx +43 -40
- package/src/ui/components/modals/app/ai-usage-modal.tsx +8 -5
- package/src/ui/components/modals/app/help-modal.tsx +4 -2
- package/src/ui/components/modals/git/git-commit-modal.tsx +2 -2
- package/src/ui/components/modals/sessions/create-session-modal.tsx +5 -1
- package/src/ui/components/modals/sessions/session-picker-modal.tsx +5 -4
- package/src/ui/components/modals/shared/form.tsx +4 -5
- package/src/ui/components/modals/shared/modal-shell.tsx +3 -3
- package/src/ui/components/modals/shared/picker.tsx +28 -5
- package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +11 -3
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +173 -5
- package/src/ui/components/modals/worktree/worktree-move-modal.tsx +94 -0
- package/src/ui/components/overlays/ai-usage/ai-usage-indicator.tsx +3 -3
- package/src/ui/components/overlays/toast/toast-item.tsx +88 -0
- package/src/ui/components/overlays/toast/toast-position.ts +45 -0
- package/src/ui/components/overlays/toast/toast-stack.tsx +30 -0
- package/src/ui/components/overlays/toast/toast-viewport.tsx +24 -0
- package/src/ui/components/overlays/toast/use-toast-animation.ts +64 -0
- package/src/ui/components/primitives/input-field.tsx +1 -1
- package/src/ui/components/primitives/list-item.tsx +3 -3
- package/src/ui/filter-themes.ts +1 -1
- package/src/ui/root.tsx +57 -3
- package/src/ui/session-ordering.ts +2 -2
- package/src/ui/status-bar-model.ts +14 -7
- package/src/ui/terminal-graphics/capabilities.ts +1 -1
- package/src/ui/terminal-graphics/format-fallback.ts +7 -9
- package/src/ui/terminal-graphics/kitty.ts +2 -7
- package/src/update/version-check.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events'
|
|
2
|
-
import { connect, Socket } from 'node:net'
|
|
2
|
+
import { connect, type Socket } from 'node:net'
|
|
3
3
|
|
|
4
4
|
import type { AssistantId, ScrollIntent, WorkspaceSnapshotV1 } from '../state/types'
|
|
5
5
|
import type { SessionBackend, SessionBackendEvents } from './types'
|
|
@@ -96,7 +96,7 @@ export class RemoteSessionBackend
|
|
|
96
96
|
return this.socket
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
private send(request: ClientRequest): Promise<ServerResponse> {
|
|
99
|
+
private async send(request: ClientRequest): Promise<ServerResponse> {
|
|
100
100
|
const socket = this.getConnectedSocket()
|
|
101
101
|
logDebug('backend.remote.send', { id: request.id, type: request.type })
|
|
102
102
|
return new Promise((resolve, reject) => {
|
|
@@ -139,7 +139,7 @@ export class RemoteSessionBackend
|
|
|
139
139
|
private reportCommandError(context: string, error: unknown, tabId?: string): void {
|
|
140
140
|
const message = error instanceof Error ? error.message : String(error)
|
|
141
141
|
logDebug('backend.remote.commandError', { context, error: message, tabId })
|
|
142
|
-
if (tabId) {
|
|
142
|
+
if (tabId != null && tabId !== '') {
|
|
143
143
|
this.emit('error', tabId, message)
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
WorkspaceSnapshotV1,
|
|
11
11
|
} from '../state/types'
|
|
12
12
|
|
|
13
|
-
export
|
|
13
|
+
export interface SessionBackendEvents {
|
|
14
14
|
render: [tabId: string, viewport: TerminalSnapshot, terminalModes: TerminalModeState]
|
|
15
15
|
exit: [tabId: string, exitCode: number]
|
|
16
16
|
error: [tabId: string, message: string]
|
|
@@ -26,7 +26,7 @@ export interface BackendAttachResult {
|
|
|
26
26
|
* atomically with tab hydration to prevent an unknown-tab race on
|
|
27
27
|
* separate `sessionActivity` events.
|
|
28
28
|
*/
|
|
29
|
-
initialSessionStatuses:
|
|
29
|
+
initialSessionStatuses: { sessionId: string; status: SessionStatus }[]
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export interface SessionBackend extends EventEmitter<SessionBackendEvents> {
|
|
@@ -34,16 +34,16 @@ const VAR_RE = /\{\{([^{}]+)\}\}/g
|
|
|
34
34
|
const TWO = (n: number): string => String(n).padStart(2, '0')
|
|
35
35
|
|
|
36
36
|
function formatDate(date: Date, format?: string): string {
|
|
37
|
-
if (!format) {
|
|
37
|
+
if (!(format != null && format !== '')) {
|
|
38
38
|
return `${date.getFullYear()}-${TWO(date.getMonth() + 1)}-${TWO(date.getDate())}`
|
|
39
39
|
}
|
|
40
40
|
return format
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
.
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
.
|
|
41
|
+
.replaceAll('YYYY', String(date.getFullYear()))
|
|
42
|
+
.replaceAll('MM', TWO(date.getMonth() + 1))
|
|
43
|
+
.replaceAll('DD', TWO(date.getDate()))
|
|
44
|
+
.replaceAll('HH', TWO(date.getHours()))
|
|
45
|
+
.replaceAll('mm', TWO(date.getMinutes()))
|
|
46
|
+
.replaceAll('ss', TWO(date.getSeconds()))
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function resolveSyncVariable(name: string, ctx: SyncExpansionContext): string | null {
|
|
@@ -14,7 +14,7 @@ const MAX_TIMEOUT_MS = 30_000
|
|
|
14
14
|
*/
|
|
15
15
|
function buildShellArgv(cmd: string): string[] {
|
|
16
16
|
const userShell = process.env.SHELL
|
|
17
|
-
if (userShell && userShell.length > 0) {
|
|
17
|
+
if (userShell != null && userShell !== '' && userShell.length > 0) {
|
|
18
18
|
return [userShell, '-l', '-c', cmd]
|
|
19
19
|
}
|
|
20
20
|
return ['/bin/sh', '-c', cmd]
|
|
@@ -50,7 +50,7 @@ export function createTriggerDetector(opts: TriggerDetectorOptions): TriggerDete
|
|
|
50
50
|
function tryMatch(separator: string): TriggerMatch | null {
|
|
51
51
|
const triggerChar = opts.getTriggerChar()
|
|
52
52
|
for (const snippet of opts.getSnippets()) {
|
|
53
|
-
if (snippet.trigger && snippet.trigger === buffer) {
|
|
53
|
+
if (snippet.trigger != null && snippet.trigger !== '' && snippet.trigger === buffer) {
|
|
54
54
|
const triggerText = `${triggerChar}${buffer}${separator}`
|
|
55
55
|
reset()
|
|
56
56
|
return { snippet, triggerText }
|
|
@@ -23,7 +23,7 @@ export const aiUsageStore = createStore<AIUsageState>((set) => ({
|
|
|
23
23
|
const isFailure = Boolean(snap.error)
|
|
24
24
|
const hasPriorValue = prev && prev.percent !== null
|
|
25
25
|
const merged: UsageSnapshot =
|
|
26
|
-
isFailure && hasPriorValue && prev
|
|
26
|
+
isFailure && hasPriorValue === true && prev != null
|
|
27
27
|
? { ...prev, error: snap.error, lastUpdated: snap.lastUpdated, stale: true }
|
|
28
28
|
: snap
|
|
29
29
|
return { snapshots: { ...state.snapshots, [snap.tool]: merged } }
|
package/src/state/git-tree.ts
CHANGED
|
@@ -42,8 +42,12 @@ export interface GitTreeRows {
|
|
|
42
42
|
visibleRows: GitTreeRow[]
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function gitFileKey(
|
|
46
|
-
|
|
45
|
+
export function gitFileKey(
|
|
46
|
+
file: Pick<GitFileEntry, 'path' | 'section'> & { repoPath?: string }
|
|
47
|
+
): string {
|
|
48
|
+
return file.repoPath != null && file.repoPath !== ''
|
|
49
|
+
? `${file.section}:${file.repoPath}:${file.path}`
|
|
50
|
+
: `${file.section}:${file.path}`
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
export function gitFolderKey(section: GitFileSection, folderPath: string): string {
|
|
@@ -85,7 +89,7 @@ export function getSelectedGitRow(
|
|
|
85
89
|
compact?: boolean
|
|
86
90
|
}
|
|
87
91
|
): GitTreeRow | null {
|
|
88
|
-
if (!options.selectedEntryKey) return null
|
|
92
|
+
if (!(options.selectedEntryKey != null && options.selectedEntryKey !== '')) return null
|
|
89
93
|
const { visibleRows } = buildGitTreeRows(
|
|
90
94
|
files,
|
|
91
95
|
options.collapsedFolders,
|
|
@@ -181,7 +185,7 @@ function buildSectionTree(files: GitFileEntry[], section: GitFileSection): GitTr
|
|
|
181
185
|
let cursor = root
|
|
182
186
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
183
187
|
const name = parts[i]
|
|
184
|
-
if (!name) continue
|
|
188
|
+
if (!(name != null && name !== '')) continue
|
|
185
189
|
const nextPath = cursor.path ? `${cursor.path}/${name}` : name
|
|
186
190
|
let child = cursor.folders.get(name)
|
|
187
191
|
if (!child) {
|
package/src/state/layout-tree.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export type SplitDirection = 'horizontal' | 'vertical'
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
3
|
+
export interface LayoutLeaf {
|
|
4
|
+
type: 'leaf'
|
|
5
|
+
tabId: string
|
|
6
|
+
}
|
|
7
|
+
export interface LayoutSplit {
|
|
5
8
|
type: 'split'
|
|
6
9
|
direction: SplitDirection
|
|
7
10
|
ratio: number
|
|
@@ -42,7 +45,7 @@ export function getTreeForTab(
|
|
|
42
45
|
tabId: string
|
|
43
46
|
): LayoutNode | null {
|
|
44
47
|
const groupId = tabGroupMap[tabId]
|
|
45
|
-
if (!groupId) return null
|
|
48
|
+
if (!(groupId != null && groupId !== '')) return null
|
|
46
49
|
return layoutTrees[groupId] ?? null
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -303,14 +306,14 @@ export function getAdjacentLeaf(
|
|
|
303
306
|
if (inFirst && goingToSecond) {
|
|
304
307
|
// Try to recurse in first child first
|
|
305
308
|
const deeper = getAdjacentLeaf(tree.first, fromTabId, direction)
|
|
306
|
-
if (deeper) return deeper
|
|
309
|
+
if (deeper != null && deeper !== '') return deeper
|
|
307
310
|
// Cross boundary: go to first leaf of second child
|
|
308
311
|
return firstLeafId(tree.second)
|
|
309
312
|
}
|
|
310
313
|
|
|
311
314
|
if (inSecond && !goingToSecond) {
|
|
312
315
|
const deeper = getAdjacentLeaf(tree.second, fromTabId, direction)
|
|
313
|
-
if (deeper) return deeper
|
|
316
|
+
if (deeper != null && deeper !== '') return deeper
|
|
314
317
|
return lastLeafId(tree.first)
|
|
315
318
|
}
|
|
316
319
|
}
|
|
@@ -50,6 +50,7 @@ export function emptyGitMode(): GitModeState {
|
|
|
50
50
|
loading: {},
|
|
51
51
|
parsedFiles: {},
|
|
52
52
|
pendingDeletePath: null,
|
|
53
|
+
reviewBase: false,
|
|
53
54
|
selectedEntryKey: null,
|
|
54
55
|
}
|
|
55
56
|
}
|
|
@@ -90,6 +91,7 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
90
91
|
highlights: {},
|
|
91
92
|
loading: {},
|
|
92
93
|
parsedFiles: {},
|
|
94
|
+
reviewBase: false,
|
|
93
95
|
},
|
|
94
96
|
}
|
|
95
97
|
}
|
|
@@ -102,7 +104,7 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
102
104
|
action.delta,
|
|
103
105
|
state.gitPane.treeCompaction
|
|
104
106
|
)
|
|
105
|
-
if (
|
|
107
|
+
if (next == null || next === '' || next === state.gitMode.selectedEntryKey) return state
|
|
106
108
|
return {
|
|
107
109
|
...state,
|
|
108
110
|
gitMode: {
|
|
@@ -121,7 +123,7 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
121
123
|
action.delta,
|
|
122
124
|
state.gitPane.treeCompaction
|
|
123
125
|
)
|
|
124
|
-
if (
|
|
126
|
+
if (next == null || next === '' || next === state.gitMode.selectedEntryKey) return state
|
|
125
127
|
return {
|
|
126
128
|
...state,
|
|
127
129
|
gitMode: {
|
|
@@ -140,7 +142,13 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
140
142
|
[action.key],
|
|
141
143
|
state.gitPane.treeCompaction
|
|
142
144
|
)
|
|
143
|
-
if (
|
|
145
|
+
if (
|
|
146
|
+
next == null ||
|
|
147
|
+
next === '' ||
|
|
148
|
+
next !== action.key ||
|
|
149
|
+
next === state.gitMode.selectedEntryKey
|
|
150
|
+
)
|
|
151
|
+
return state
|
|
144
152
|
return {
|
|
145
153
|
...state,
|
|
146
154
|
gitMode: {
|
|
@@ -199,7 +207,12 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
199
207
|
},
|
|
200
208
|
}
|
|
201
209
|
}
|
|
202
|
-
if (
|
|
210
|
+
if (
|
|
211
|
+
row.parentKey == null ||
|
|
212
|
+
row.parentKey === '' ||
|
|
213
|
+
row.parentKey === state.gitMode.selectedEntryKey
|
|
214
|
+
)
|
|
215
|
+
return state
|
|
203
216
|
return {
|
|
204
217
|
...state,
|
|
205
218
|
gitMode: {
|
|
@@ -318,7 +331,7 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
318
331
|
const k = `${action.key}|${action.themeId}`
|
|
319
332
|
const existing = state.gitMode.highlights[k]
|
|
320
333
|
const sameContent = existing && existing.hash === action.hash
|
|
321
|
-
if (sameContent) {
|
|
334
|
+
if (sameContent === true) {
|
|
322
335
|
const existingAdd = existing.add as unknown[]
|
|
323
336
|
const existingDel = existing.del as unknown[]
|
|
324
337
|
let changesAnything = false
|
|
@@ -344,8 +357,8 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
344
357
|
}
|
|
345
358
|
if (!changesAnything) return state
|
|
346
359
|
}
|
|
347
|
-
const nextAdd = sameContent ? ([...(existing.add as unknown[])] as unknown[]) : []
|
|
348
|
-
const nextDel = sameContent ? ([...(existing.del as unknown[])] as unknown[]) : []
|
|
360
|
+
const nextAdd = sameContent === true ? ([...(existing.add as unknown[])] as unknown[]) : []
|
|
361
|
+
const nextDel = sameContent === true ? ([...(existing.del as unknown[])] as unknown[]) : []
|
|
349
362
|
for (const patch of action.add) {
|
|
350
363
|
const tokens = patch.tokens as unknown[]
|
|
351
364
|
for (let i = 0; i < tokens.length; i++) nextAdd[patch.start + i] = tokens[i]
|
|
@@ -398,6 +411,26 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
398
411
|
const next = state.gitMode.diffView === 'split' ? 'stacked' : 'split'
|
|
399
412
|
return { ...state, gitMode: { ...state.gitMode, diffView: next } }
|
|
400
413
|
}
|
|
414
|
+
case 'git-mode-toggle-review-base': {
|
|
415
|
+
// Base review and history walking are mutually exclusive views; entering
|
|
416
|
+
// either resets headOffset and drops cached diffs (computed vs old ref).
|
|
417
|
+
const next = !state.gitMode.reviewBase
|
|
418
|
+
return {
|
|
419
|
+
...state,
|
|
420
|
+
gitMode: {
|
|
421
|
+
...state.gitMode,
|
|
422
|
+
actionMessage: null,
|
|
423
|
+
diffs: {},
|
|
424
|
+
folds: {},
|
|
425
|
+
headOffset: 0,
|
|
426
|
+
highlights: {},
|
|
427
|
+
loading: {},
|
|
428
|
+
parsedFiles: {},
|
|
429
|
+
pendingDeletePath: null,
|
|
430
|
+
reviewBase: next,
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
}
|
|
401
434
|
case 'git-mode-shift-head-offset': {
|
|
402
435
|
const next = Math.max(0, state.gitMode.headOffset + action.delta)
|
|
403
436
|
if (next === state.gitMode.headOffset) return state
|
|
@@ -485,13 +518,10 @@ export function reduceGitModeState(state: AppState, action: AppAction): AppState
|
|
|
485
518
|
const duplicateIdx = files.findIndex(
|
|
486
519
|
(f, i) => i !== idx && f.path === action.path && f.section === toSection
|
|
487
520
|
)
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
i === idx ? ({ ...f, section: toSection } as GitFileEntry) : f
|
|
493
|
-
)
|
|
494
|
-
}
|
|
521
|
+
nextFiles =
|
|
522
|
+
duplicateIdx >= 0
|
|
523
|
+
? files.filter((_, i) => i !== idx)
|
|
524
|
+
: files.map((f, i) => (i === idx ? ({ ...f, section: toSection } as GitFileEntry) : f))
|
|
495
525
|
}
|
|
496
526
|
nextFiles = sortFilesBySection(nextFiles)
|
|
497
527
|
const movedCurrentKey = gitFileKey({ path: action.path, section: action.fromSection })
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AppAction,
|
|
3
|
+
AppState,
|
|
4
|
+
BranchDivergence,
|
|
5
|
+
GitFileEntry,
|
|
6
|
+
GitFileSection,
|
|
7
|
+
GitPanelState,
|
|
8
|
+
} from '../types'
|
|
2
9
|
|
|
3
10
|
import { clampGitPaneRatio } from '../git-pane-sizing'
|
|
4
11
|
import { reconcileSelectedGitEntryKey } from '../git-tree'
|
|
@@ -27,6 +34,20 @@ function clampRatio(value: number): number {
|
|
|
27
34
|
return clampGitPaneRatio(value)
|
|
28
35
|
}
|
|
29
36
|
|
|
37
|
+
function sameDivergence(
|
|
38
|
+
a: Record<string, BranchDivergence>,
|
|
39
|
+
b: Record<string, BranchDivergence>
|
|
40
|
+
): boolean {
|
|
41
|
+
const aKeys = Object.keys(a)
|
|
42
|
+
if (aKeys.length !== Object.keys(b).length) return false
|
|
43
|
+
for (const key of aKeys) {
|
|
44
|
+
const x = a[key]
|
|
45
|
+
const y = b[key]
|
|
46
|
+
if (x == null || y == null || x.ahead !== y.ahead || x.behind !== y.behind) return false
|
|
47
|
+
}
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
export function emptyGitPanel(): GitPanelState {
|
|
31
52
|
return {
|
|
32
53
|
ahead: 0,
|
|
@@ -188,6 +209,10 @@ export function reduceGitPanelState(state: AppState, action: AppAction): AppStat
|
|
|
188
209
|
gitPanel: { ...prev, error: action.kind, files: [] },
|
|
189
210
|
}
|
|
190
211
|
}
|
|
212
|
+
case 'set-worktree-divergence': {
|
|
213
|
+
if (sameDivergence(state.worktreeDivergence, action.divergence)) return state
|
|
214
|
+
return { ...state, worktreeDivergence: action.divergence }
|
|
215
|
+
}
|
|
191
216
|
case 'git-panel-reset': {
|
|
192
217
|
const prev = state.gitPanel
|
|
193
218
|
if (
|
|
@@ -201,7 +226,18 @@ export function reduceGitPanelState(state: AppState, action: AppAction): AppStat
|
|
|
201
226
|
}
|
|
202
227
|
return {
|
|
203
228
|
...state,
|
|
204
|
-
|
|
229
|
+
// A reset means the underlying worktree/ref changed, so cached diffs are
|
|
230
|
+
// stale — drop them (keyed by section:path, they'd otherwise serve a
|
|
231
|
+
// previous worktree's diff for a same-named file after a move/switch).
|
|
232
|
+
gitMode: {
|
|
233
|
+
...state.gitMode,
|
|
234
|
+
diffs: {},
|
|
235
|
+
highlights: {},
|
|
236
|
+
loading: {},
|
|
237
|
+
parsedFiles: {},
|
|
238
|
+
pendingDeletePath: null,
|
|
239
|
+
selectedEntryKey: null,
|
|
240
|
+
},
|
|
205
241
|
gitPanel: emptyGitPanel(),
|
|
206
242
|
}
|
|
207
243
|
}
|