@brimveyn/aimux 1.12.2 → 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/pty-write.ts +52 -0
- 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 +598 -102
- package/src/app-runtime/snippet-actions.ts +25 -11
- 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 +143 -8
- package/src/app.tsx +25 -9
- 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 +13 -1
- package/src/input/raw-input-handler.ts +34 -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 +18 -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 +8 -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 +112 -0
- package/src/snippets/run-shell-var.ts +93 -0
- package/src/snippets/trigger-detector.ts +105 -0
- 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 +385 -24
- 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/snippet-catalog.ts +65 -4
- package/src/state/store.ts +1 -0
- package/src/state/toast-store.ts +95 -0
- package/src/state/types.ts +108 -16
- package/src/state/validation.ts +47 -3
- 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-editor-modal.tsx +10 -6
- package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +25 -6
- 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 +65 -14
- 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
package/src/state/types.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { ModeId } from '@brimveyn/aimux-config'
|
|
1
|
+
import type { ModeId, SnippetVar } from '@brimveyn/aimux-config'
|
|
2
2
|
import type { ThemedToken } from 'shiki'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import type { LayoutNode, SplitDirection } from './layout-tree'
|
|
5
|
+
|
|
6
|
+
export type BuiltinAssistantId = 'claude' | 'codex' | 'opencode' | 'terminal' | 'antigravity'
|
|
5
7
|
|
|
6
8
|
export type AssistantId = BuiltinAssistantId | (string & {})
|
|
7
9
|
|
|
@@ -43,6 +45,7 @@ export type ModalType =
|
|
|
43
45
|
| 'git-commit'
|
|
44
46
|
| 'update-available'
|
|
45
47
|
| 'ai-usage'
|
|
48
|
+
| 'worktree-move'
|
|
46
49
|
| null
|
|
47
50
|
|
|
48
51
|
export interface TerminalSpan {
|
|
@@ -97,6 +100,7 @@ export interface PersistedTabSnapshot {
|
|
|
97
100
|
scrollIntent?: ScrollIntent
|
|
98
101
|
errorMessage?: string
|
|
99
102
|
exitCode?: number
|
|
103
|
+
worktreeId?: string
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
export interface WorkspaceSnapshotV1 {
|
|
@@ -108,11 +112,28 @@ export interface WorkspaceSnapshotV1 {
|
|
|
108
112
|
width: number
|
|
109
113
|
}
|
|
110
114
|
tabs: PersistedTabSnapshot[]
|
|
111
|
-
layoutTree?:
|
|
112
|
-
layoutTrees?: Record<string,
|
|
115
|
+
layoutTree?: LayoutNode
|
|
116
|
+
layoutTrees?: Record<string, LayoutNode>
|
|
113
117
|
tabGroupMap?: Record<string, string>
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
export type WorktreeSource = 'primary' | 'aimux-temp' | 'external'
|
|
121
|
+
|
|
122
|
+
export interface WorktreeRecord {
|
|
123
|
+
id: string
|
|
124
|
+
name: string
|
|
125
|
+
path: string
|
|
126
|
+
repoRoot: string
|
|
127
|
+
branch?: string
|
|
128
|
+
baseRef?: string
|
|
129
|
+
commitSha?: string
|
|
130
|
+
source: WorktreeSource
|
|
131
|
+
createdByAimux: boolean
|
|
132
|
+
color?: string
|
|
133
|
+
createdAt: string
|
|
134
|
+
updatedAt: string
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
export interface SessionRecord {
|
|
117
138
|
id: string
|
|
118
139
|
name: string
|
|
@@ -122,6 +143,8 @@ export interface SessionRecord {
|
|
|
122
143
|
lastOpenedAt: string
|
|
123
144
|
order?: number
|
|
124
145
|
workspaceSnapshot?: WorkspaceSnapshotV1
|
|
146
|
+
worktrees?: WorktreeRecord[]
|
|
147
|
+
activeWorktreeId?: string
|
|
125
148
|
}
|
|
126
149
|
|
|
127
150
|
export type SessionBarPosition = 'top' | 'bottom'
|
|
@@ -144,6 +167,7 @@ export interface TabSession {
|
|
|
144
167
|
command: string
|
|
145
168
|
errorMessage?: string
|
|
146
169
|
exitCode?: number
|
|
170
|
+
worktreeId?: string
|
|
147
171
|
}
|
|
148
172
|
|
|
149
173
|
export interface SidebarState {
|
|
@@ -160,7 +184,9 @@ export type GitPanePathConfig =
|
|
|
160
184
|
| { enabled: false }
|
|
161
185
|
| { enabled: true; pathFn?: (path: string) => string }
|
|
162
186
|
|
|
163
|
-
export
|
|
187
|
+
export interface GitPaneDiffCountConfig {
|
|
188
|
+
enabled: boolean
|
|
189
|
+
}
|
|
164
190
|
|
|
165
191
|
export interface GitPaneState {
|
|
166
192
|
visible: boolean
|
|
@@ -271,6 +297,8 @@ export interface GitModeState {
|
|
|
271
297
|
folds: Record<string, Record<string, FoldState>>
|
|
272
298
|
/** Working-tree-vs-HEAD~N offset. 0 = working tree vs HEAD (default). */
|
|
273
299
|
headOffset: number
|
|
300
|
+
/** When true, diff the active worktree's working tree against its fork point. */
|
|
301
|
+
reviewBase: boolean
|
|
274
302
|
}
|
|
275
303
|
|
|
276
304
|
interface ModalBase {
|
|
@@ -289,6 +317,16 @@ export interface ModalClosed extends ModalBase {
|
|
|
289
317
|
export interface ModalNewTab extends ModalBase {
|
|
290
318
|
type: 'new-tab'
|
|
291
319
|
editingCommand: AssistantId | null
|
|
320
|
+
activeField: 'assistant' | 'branch-name' | 'target-worktree' | 'worktree-name'
|
|
321
|
+
branchError: string | null
|
|
322
|
+
branchName: string
|
|
323
|
+
createWorktree: boolean
|
|
324
|
+
selectedAssistantId: AssistantId | null
|
|
325
|
+
step: 'assistant' | 'worktree' | 'worktree-create'
|
|
326
|
+
targetWorktreeIndex: number
|
|
327
|
+
worktreeDeleteConfirmId: string | null
|
|
328
|
+
worktreeDeleteMessage: string | null
|
|
329
|
+
worktreeName: string
|
|
292
330
|
}
|
|
293
331
|
|
|
294
332
|
export interface ModalSessionPicker extends ModalBase {
|
|
@@ -306,6 +344,11 @@ export interface ModalRenameTab extends ModalBase {
|
|
|
306
344
|
|
|
307
345
|
export interface ModalSnippetPicker extends ModalBase {
|
|
308
346
|
type: 'snippet-picker'
|
|
347
|
+
/**
|
|
348
|
+
* Transient status line shown at the bottom of the picker (e.g. error from
|
|
349
|
+
* an open-in-editor attempt). Cleared automatically when the modal closes.
|
|
350
|
+
*/
|
|
351
|
+
actionMessage?: string | null
|
|
309
352
|
}
|
|
310
353
|
|
|
311
354
|
export interface ModalThemePicker extends ModalBase {
|
|
@@ -321,7 +364,7 @@ export interface ModalHelp extends ModalBase {
|
|
|
321
364
|
|
|
322
365
|
export interface ModalSplitPicker extends ModalBase {
|
|
323
366
|
type: 'split-picker'
|
|
324
|
-
splitDirection:
|
|
367
|
+
splitDirection: SplitDirection
|
|
325
368
|
}
|
|
326
369
|
|
|
327
370
|
export interface ModalGitCommit extends ModalBase {
|
|
@@ -342,7 +385,12 @@ export interface ModalCreateSession extends ModalBase {
|
|
|
342
385
|
|
|
343
386
|
export interface ModalSnippetEditor extends ModalBase {
|
|
344
387
|
type: 'snippet-editor'
|
|
345
|
-
activeField: 'name' | 'content'
|
|
388
|
+
activeField: 'name' | 'trigger' | 'content'
|
|
389
|
+
/** Persisted value of the name field when it is not the active editor. */
|
|
390
|
+
nameBuffer: string
|
|
391
|
+
/** Persisted value of the trigger field when it is not the active editor. */
|
|
392
|
+
triggerBuffer: string
|
|
393
|
+
/** Persisted value of the content field when it is not the active editor. */
|
|
346
394
|
contentBuffer: string
|
|
347
395
|
}
|
|
348
396
|
|
|
@@ -356,6 +404,13 @@ export interface ModalAIUsage extends ModalBase {
|
|
|
356
404
|
type: 'ai-usage'
|
|
357
405
|
}
|
|
358
406
|
|
|
407
|
+
export interface ModalWorktreeMove extends ModalBase {
|
|
408
|
+
type: 'worktree-move'
|
|
409
|
+
/** The worktree being moved (may differ from the active one, e.g. a tab menu). */
|
|
410
|
+
sourceWorktreeId: string
|
|
411
|
+
deleteSource: boolean
|
|
412
|
+
}
|
|
413
|
+
|
|
359
414
|
export type DirectoryResultType = 'git-repo' | 'worktree' | 'workspace'
|
|
360
415
|
|
|
361
416
|
export interface DirectoryResult {
|
|
@@ -378,6 +433,7 @@ export type ModalState =
|
|
|
378
433
|
| ModalGitCommit
|
|
379
434
|
| ModalUpdateAvailable
|
|
380
435
|
| ModalAIUsage
|
|
436
|
+
| ModalWorktreeMove
|
|
381
437
|
|
|
382
438
|
export interface LayoutState {
|
|
383
439
|
terminalCols: number
|
|
@@ -388,6 +444,8 @@ export interface SnippetRecord {
|
|
|
388
444
|
id: string
|
|
389
445
|
name: string
|
|
390
446
|
content: string
|
|
447
|
+
trigger?: string
|
|
448
|
+
vars?: Record<string, SnippetVar>
|
|
391
449
|
}
|
|
392
450
|
|
|
393
451
|
export interface DiscoveredRepo {
|
|
@@ -411,7 +469,7 @@ export const EMPTY_MULTI_REPO_STATE: MultiRepoState = { prefixes: {}, repos: []
|
|
|
411
469
|
export interface AppState {
|
|
412
470
|
tabs: TabSession[]
|
|
413
471
|
activeTabId: string | null
|
|
414
|
-
layoutTrees: Record<string,
|
|
472
|
+
layoutTrees: Record<string, LayoutNode>
|
|
415
473
|
tabGroupMap: Record<string, string>
|
|
416
474
|
sessions: SessionRecord[]
|
|
417
475
|
currentSessionId: string | null
|
|
@@ -428,6 +486,11 @@ export interface AppState {
|
|
|
428
486
|
gitMode: GitModeState
|
|
429
487
|
autoCommit: AutoCommitState
|
|
430
488
|
multiRepo: MultiRepoState
|
|
489
|
+
/**
|
|
490
|
+
* Commits each worktree's branch is ahead/behind the ref it forked from,
|
|
491
|
+
* keyed by worktree id. Ephemeral (polled); not persisted to the catalog.
|
|
492
|
+
*/
|
|
493
|
+
worktreeDivergence: Record<string, BranchDivergence>
|
|
431
494
|
/** Chord prefix the sequence resolver is currently waiting on, or null when idle. */
|
|
432
495
|
pendingChords: string[] | null
|
|
433
496
|
}
|
|
@@ -436,9 +499,18 @@ export interface AppState {
|
|
|
436
499
|
export type ModalAction =
|
|
437
500
|
| { type: 'move-modal-cursor'; delta?: number; to?: 'home' | 'end' }
|
|
438
501
|
| { type: 'open-new-tab-modal' }
|
|
502
|
+
| { type: 'set-new-tab-branch-error'; message: string | null }
|
|
503
|
+
| {
|
|
504
|
+
type: 'set-new-tab-worktree-delete-state'
|
|
505
|
+
confirmWorktreeId?: string | null
|
|
506
|
+
message: string | null
|
|
507
|
+
}
|
|
508
|
+
| { type: 'enter-new-tab-worktree-create' }
|
|
509
|
+
| { type: 'select-new-tab-assistant'; assistantId?: AssistantId }
|
|
510
|
+
| { type: 'toggle-new-tab-worktree'; assistantId?: AssistantId }
|
|
439
511
|
| { type: 'open-edit-custom-command'; assistantId: AssistantId }
|
|
440
512
|
| { type: 'open-help-modal'; scope?: ModeId }
|
|
441
|
-
| { type: 'open-split-picker'; direction:
|
|
513
|
+
| { type: 'open-split-picker'; direction: SplitDirection }
|
|
442
514
|
| { type: 'open-session-picker' }
|
|
443
515
|
| {
|
|
444
516
|
type: 'open-session-name-modal'
|
|
@@ -463,6 +535,8 @@ export type ModalAction =
|
|
|
463
535
|
| { type: 'open-update-available-modal'; currentVersion: string; latestVersion: string }
|
|
464
536
|
| { type: 'set-modal-selection-index'; index: number }
|
|
465
537
|
| { type: 'open-ai-usage-modal' }
|
|
538
|
+
| { type: 'open-worktree-move-modal'; sourceWorktreeId: string }
|
|
539
|
+
| { type: 'toggle-worktree-move-delete' }
|
|
466
540
|
|
|
467
541
|
// -- Session actions --
|
|
468
542
|
export type SessionAction =
|
|
@@ -473,6 +547,15 @@ export type SessionAction =
|
|
|
473
547
|
| { type: 'delete-session-record'; sessionId: string; openSessionPicker?: boolean }
|
|
474
548
|
| { type: 'reorder-sessions'; orderedIds: string[] }
|
|
475
549
|
| { type: 'set-session-status'; sessionId: string; status: SessionStatus }
|
|
550
|
+
| { type: 'add-worktree-record'; sessionId: string; worktree: WorktreeRecord; activate?: boolean }
|
|
551
|
+
| { type: 'remove-worktree-record'; sessionId: string; worktreeId: string }
|
|
552
|
+
| { type: 'set-active-worktree'; sessionId: string; worktreeId: string }
|
|
553
|
+
| {
|
|
554
|
+
type: 'update-worktree-record'
|
|
555
|
+
sessionId: string
|
|
556
|
+
worktreeId: string
|
|
557
|
+
patch: Partial<WorktreeRecord>
|
|
558
|
+
}
|
|
476
559
|
|
|
477
560
|
// -- Tab actions --
|
|
478
561
|
export type TabAction =
|
|
@@ -481,8 +564,8 @@ export type TabAction =
|
|
|
481
564
|
type: 'hydrate-workspace'
|
|
482
565
|
tabs: TabSession[]
|
|
483
566
|
activeTabId: string | null
|
|
484
|
-
layoutTree?:
|
|
485
|
-
layoutTrees?: Record<string,
|
|
567
|
+
layoutTree?: LayoutNode | null
|
|
568
|
+
layoutTrees?: Record<string, LayoutNode>
|
|
486
569
|
tabGroupMap?: Record<string, string>
|
|
487
570
|
}
|
|
488
571
|
| { type: 'close-tab'; tabId: string }
|
|
@@ -508,7 +591,7 @@ export type TabAction =
|
|
|
508
591
|
export type LayoutAction =
|
|
509
592
|
| {
|
|
510
593
|
type: 'split-pane'
|
|
511
|
-
direction:
|
|
594
|
+
direction: SplitDirection
|
|
512
595
|
newTab: TabSession
|
|
513
596
|
}
|
|
514
597
|
| { type: 'close-pane'; tabId: string }
|
|
@@ -520,13 +603,13 @@ export type LayoutAction =
|
|
|
520
603
|
type: 'resize-pane'
|
|
521
604
|
tabId: string
|
|
522
605
|
delta: number
|
|
523
|
-
axis?:
|
|
606
|
+
axis?: SplitDirection
|
|
524
607
|
}
|
|
525
608
|
| {
|
|
526
609
|
type: 'set-split-ratio'
|
|
527
610
|
tabId: string
|
|
528
611
|
ratio: number
|
|
529
|
-
axis?:
|
|
612
|
+
axis?: SplitDirection
|
|
530
613
|
}
|
|
531
614
|
|
|
532
615
|
// -- UI actions --
|
|
@@ -554,10 +637,17 @@ export interface GitRefreshPayload {
|
|
|
554
637
|
files: GitFileEntry[]
|
|
555
638
|
}
|
|
556
639
|
|
|
640
|
+
/** Commits a worktree branch is ahead/behind the ref it forked from. */
|
|
641
|
+
export interface BranchDivergence {
|
|
642
|
+
ahead: number
|
|
643
|
+
behind: number
|
|
644
|
+
}
|
|
645
|
+
|
|
557
646
|
export type GitPanelAction =
|
|
558
647
|
| { type: 'git-refresh-success'; payload: GitRefreshPayload }
|
|
559
648
|
| { type: 'git-refresh-error'; kind: GitPanelError }
|
|
560
649
|
| { type: 'git-panel-reset' }
|
|
650
|
+
| { type: 'set-worktree-divergence'; divergence: Record<string, BranchDivergence> }
|
|
561
651
|
|
|
562
652
|
// -- Auto-commit state & actions --
|
|
563
653
|
export type AutoCommitSuggestion =
|
|
@@ -635,15 +725,17 @@ export type GitModeAction =
|
|
|
635
725
|
key: string
|
|
636
726
|
hash: string
|
|
637
727
|
themeId: string
|
|
638
|
-
add:
|
|
639
|
-
del:
|
|
728
|
+
add: { start: number; tokens: unknown }[]
|
|
729
|
+
del: { start: number; tokens: unknown }[]
|
|
640
730
|
}
|
|
641
731
|
| { type: 'git-mode-invalidate-diffs'; paths: string[] }
|
|
642
732
|
| { type: 'git-mode-set-loading'; key: string; loading: boolean }
|
|
643
733
|
| { type: 'git-mode-set-pending-delete'; path: string | null }
|
|
644
734
|
| { type: 'git-mode-clear-diff-cache'; path: string }
|
|
645
735
|
| { type: 'git-mode-set-message'; message: string | null }
|
|
736
|
+
| { type: 'snippet-picker-set-message'; message: string | null }
|
|
646
737
|
| { type: 'git-mode-toggle-diff-view' }
|
|
738
|
+
| { type: 'git-mode-toggle-review-base' }
|
|
647
739
|
| { type: 'git-mode-shift-head-offset'; delta: number }
|
|
648
740
|
| { type: 'git-mode-set-head-offset'; offset: number }
|
|
649
741
|
| {
|
package/src/state/validation.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
TerminalModeState,
|
|
6
6
|
TerminalSnapshot,
|
|
7
7
|
WorkspaceSnapshotV1,
|
|
8
|
+
WorktreeRecord,
|
|
8
9
|
} from './types'
|
|
9
10
|
|
|
10
11
|
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -105,6 +106,24 @@ function isStringRecord(value: unknown): boolean {
|
|
|
105
106
|
return Object.values(value).every(isString)
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
function isWorktreeRecord(value: unknown): value is WorktreeRecord {
|
|
110
|
+
return (
|
|
111
|
+
isObjectRecord(value) &&
|
|
112
|
+
isString(value.id) &&
|
|
113
|
+
isString(value.name) &&
|
|
114
|
+
isString(value.path) &&
|
|
115
|
+
isString(value.repoRoot) &&
|
|
116
|
+
(value.branch === undefined || isString(value.branch)) &&
|
|
117
|
+
(value.baseRef === undefined || isString(value.baseRef)) &&
|
|
118
|
+
(value.commitSha === undefined || isString(value.commitSha)) &&
|
|
119
|
+
(value.source === 'primary' || value.source === 'aimux-temp' || value.source === 'external') &&
|
|
120
|
+
isBoolean(value.createdByAimux) &&
|
|
121
|
+
(value.color === undefined || isString(value.color)) &&
|
|
122
|
+
isString(value.createdAt) &&
|
|
123
|
+
isString(value.updatedAt)
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
108
127
|
export function isWorkspaceSnapshotV1(value: unknown): value is WorkspaceSnapshotV1 {
|
|
109
128
|
return (
|
|
110
129
|
isObjectRecord(value) &&
|
|
@@ -132,7 +151,8 @@ export function isWorkspaceSnapshotV1(value: unknown): value is WorkspaceSnapsho
|
|
|
132
151
|
(tab.viewport === undefined || isTerminalSnapshot(tab.viewport)) &&
|
|
133
152
|
(tab.scrollIntent === undefined || isScrollIntent(tab.scrollIntent)) &&
|
|
134
153
|
(tab.errorMessage === undefined || isString(tab.errorMessage)) &&
|
|
135
|
-
(tab.exitCode === undefined || isFiniteNumber(tab.exitCode))
|
|
154
|
+
(tab.exitCode === undefined || isFiniteNumber(tab.exitCode)) &&
|
|
155
|
+
(tab.worktreeId === undefined || isString(tab.worktreeId))
|
|
136
156
|
) &&
|
|
137
157
|
(value.layoutTree === undefined || isLayoutNode(value.layoutTree)) &&
|
|
138
158
|
(value.layoutTrees === undefined || isLayoutTreesMap(value.layoutTrees)) &&
|
|
@@ -151,12 +171,36 @@ export function isSessionRecord(value: unknown): value is SessionRecord {
|
|
|
151
171
|
isString(value.lastOpenedAt) &&
|
|
152
172
|
(value.order === undefined ||
|
|
153
173
|
(typeof value.order === 'number' && Number.isFinite(value.order))) &&
|
|
154
|
-
(value.workspaceSnapshot === undefined || isWorkspaceSnapshotV1(value.workspaceSnapshot))
|
|
174
|
+
(value.workspaceSnapshot === undefined || isWorkspaceSnapshotV1(value.workspaceSnapshot)) &&
|
|
175
|
+
(value.worktrees === undefined ||
|
|
176
|
+
(Array.isArray(value.worktrees) && value.worktrees.every(isWorktreeRecord))) &&
|
|
177
|
+
(value.activeWorktreeId === undefined || isString(value.activeWorktreeId))
|
|
155
178
|
)
|
|
156
179
|
}
|
|
157
180
|
|
|
181
|
+
function isSnippetVar(value: unknown): boolean {
|
|
182
|
+
if (!isObjectRecord(value)) return false
|
|
183
|
+
if (!isString(value.sh)) return false
|
|
184
|
+
if (value.timeout !== undefined && !isFiniteNumber(value.timeout)) return false
|
|
185
|
+
if (value.trim !== undefined && !isBoolean(value.trim)) return false
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isSnippetVarRecord(value: unknown): boolean {
|
|
190
|
+
if (!isObjectRecord(value)) return false
|
|
191
|
+
for (const entry of Object.values(value)) {
|
|
192
|
+
if (!isSnippetVar(entry)) return false
|
|
193
|
+
}
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
|
|
158
197
|
export function isSnippetRecord(value: unknown): value is SnippetRecord {
|
|
159
198
|
return (
|
|
160
|
-
isObjectRecord(value) &&
|
|
199
|
+
isObjectRecord(value) &&
|
|
200
|
+
isString(value.id) &&
|
|
201
|
+
isString(value.name) &&
|
|
202
|
+
isString(value.content) &&
|
|
203
|
+
(value.trigger === undefined || isString(value.trigger)) &&
|
|
204
|
+
(value.vars === undefined || isSnippetVarRecord(value.vars))
|
|
161
205
|
)
|
|
162
206
|
}
|
|
@@ -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 {
|
|
5
5
|
ScrollIntent,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
parseManagerMessage,
|
|
24
24
|
} from '../ipc/manager-protocol'
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
interface ManagerClientEvents {
|
|
27
27
|
render: [
|
|
28
28
|
sessionId: string,
|
|
29
29
|
tabId: string,
|
|
@@ -84,7 +84,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
84
84
|
return this.socket
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
private send(request: ManagerRequest): Promise<ManagerResponse> {
|
|
87
|
+
private async send(request: ManagerRequest): Promise<ManagerResponse> {
|
|
88
88
|
const socket = this.getConnectedSocket()
|
|
89
89
|
logDebug('managerClient.send', { id: request.id, type: request.type })
|
|
90
90
|
return new Promise((resolve, reject) => {
|
|
@@ -286,7 +286,9 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
286
286
|
return response.payload
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
createTab(
|
|
289
|
+
async createTab(
|
|
290
|
+
options: Extract<ManagerRequest, { type: 'createTab' }>['payload']
|
|
291
|
+
): Promise<void> {
|
|
290
292
|
logDebug('managerClient.createTab', {
|
|
291
293
|
command: options.command,
|
|
292
294
|
sessionId: options.sessionId,
|
|
@@ -296,7 +298,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
296
298
|
return this.sendExpectOk({ id: crypto.randomUUID(), payload: options, type: 'createTab' })
|
|
297
299
|
}
|
|
298
300
|
|
|
299
|
-
write(sessionId: string, tabId: string, data: string): Promise<void> {
|
|
301
|
+
async write(sessionId: string, tabId: string, data: string): Promise<void> {
|
|
300
302
|
return this.sendExpectOk({
|
|
301
303
|
id: crypto.randomUUID(),
|
|
302
304
|
payload: { data, sessionId, tabId },
|
|
@@ -304,7 +306,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
304
306
|
})
|
|
305
307
|
}
|
|
306
308
|
|
|
307
|
-
resize(
|
|
309
|
+
async resize(
|
|
308
310
|
sessionId: string,
|
|
309
311
|
cols: number,
|
|
310
312
|
rows: number,
|
|
@@ -317,7 +319,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
317
319
|
})
|
|
318
320
|
}
|
|
319
321
|
|
|
320
|
-
resizeTab(
|
|
322
|
+
async resizeTab(
|
|
321
323
|
sessionId: string,
|
|
322
324
|
tabId: string,
|
|
323
325
|
cols: number,
|
|
@@ -331,7 +333,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
331
333
|
})
|
|
332
334
|
}
|
|
333
335
|
|
|
334
|
-
scroll(sessionId: string, tabId: string, deltaLines: number): Promise<void> {
|
|
336
|
+
async scroll(sessionId: string, tabId: string, deltaLines: number): Promise<void> {
|
|
335
337
|
return this.sendExpectOk({
|
|
336
338
|
id: crypto.randomUUID(),
|
|
337
339
|
payload: { deltaLines, sessionId, tabId },
|
|
@@ -339,7 +341,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
339
341
|
})
|
|
340
342
|
}
|
|
341
343
|
|
|
342
|
-
scrollToBottom(sessionId: string, tabId: string): Promise<void> {
|
|
344
|
+
async scrollToBottom(sessionId: string, tabId: string): Promise<void> {
|
|
343
345
|
return this.sendExpectOk({
|
|
344
346
|
id: crypto.randomUUID(),
|
|
345
347
|
payload: { sessionId, tabId },
|
|
@@ -347,7 +349,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
347
349
|
})
|
|
348
350
|
}
|
|
349
351
|
|
|
350
|
-
reapplyScrollIntent(sessionId: string, tabId: string, intent: ScrollIntent): Promise<void> {
|
|
352
|
+
async reapplyScrollIntent(sessionId: string, tabId: string, intent: ScrollIntent): Promise<void> {
|
|
351
353
|
return this.sendExpectOk({
|
|
352
354
|
id: crypto.randomUUID(),
|
|
353
355
|
payload: { intent, sessionId, tabId },
|
|
@@ -355,7 +357,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
355
357
|
})
|
|
356
358
|
}
|
|
357
359
|
|
|
358
|
-
setActiveTab(sessionId: string, tabId: string | null): Promise<void> {
|
|
360
|
+
async setActiveTab(sessionId: string, tabId: string | null): Promise<void> {
|
|
359
361
|
return this.sendExpectOk({
|
|
360
362
|
id: crypto.randomUUID(),
|
|
361
363
|
payload: { sessionId, tabId },
|
|
@@ -363,7 +365,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
363
365
|
})
|
|
364
366
|
}
|
|
365
367
|
|
|
366
|
-
closeTab(sessionId: string, tabId: string): Promise<void> {
|
|
368
|
+
async closeTab(sessionId: string, tabId: string): Promise<void> {
|
|
367
369
|
return this.sendExpectOk({
|
|
368
370
|
id: crypto.randomUUID(),
|
|
369
371
|
payload: { sessionId, tabId },
|
|
@@ -371,7 +373,7 @@ export class TerminalManagerClient extends EventEmitter<ManagerClientEvents> {
|
|
|
371
373
|
})
|
|
372
374
|
}
|
|
373
375
|
|
|
374
|
-
disposeSession(sessionId: string): Promise<void> {
|
|
376
|
+
async disposeSession(sessionId: string): Promise<void> {
|
|
375
377
|
return this.sendExpectOk({
|
|
376
378
|
id: crypto.randomUUID(),
|
|
377
379
|
payload: { sessionId },
|
|
@@ -3,11 +3,11 @@ import type { ThemedToken } from 'shiki'
|
|
|
3
3
|
|
|
4
4
|
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react'
|
|
5
5
|
|
|
6
|
+
import type { FoldState } from '../../../../state/types'
|
|
6
7
|
import type { ThemeId } from '../../../themes'
|
|
7
8
|
|
|
8
9
|
import { useAppStore } from '../../../../state/app-store'
|
|
9
10
|
import { dispatchGlobal } from '../../../../state/dispatch-ref'
|
|
10
|
-
import { type FoldState } from '../../../../state/types'
|
|
11
11
|
import { useTheme } from '../../../theme'
|
|
12
12
|
import { buildDiffSegments, firstChangeSegmentOffset, gutterWidth } from './build-rows'
|
|
13
13
|
import { SplitView, type SplitViewHandle } from './split-view'
|
|
@@ -22,14 +22,14 @@ interface PrepareOptions {
|
|
|
22
22
|
signal?: AbortSignal
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function yieldToEventLoop(): Promise<void> {
|
|
25
|
+
async function yieldToEventLoop(): Promise<void> {
|
|
26
26
|
return new Promise((resolve) => {
|
|
27
27
|
setImmediate(resolve)
|
|
28
28
|
})
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function throwIfAborted(signal?: AbortSignal): void {
|
|
32
|
-
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError')
|
|
32
|
+
if (signal?.aborted === true) throw new DOMException('Aborted', 'AbortError')
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Lifts diff parsing off the synchronous render tree and returns a segmented
|
|
@@ -230,7 +230,9 @@ function HunkHeaderRow({ row }: { row: Extract<SplitRowOrHeader, { type: 'hunk-h
|
|
|
230
230
|
return (
|
|
231
231
|
<box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
|
|
232
232
|
<text fg={t.text}>{row.spec}</text>
|
|
233
|
-
{row.context
|
|
233
|
+
{row.context != null && row.context !== '' ? (
|
|
234
|
+
<text fg={t.textMuted}> {row.context}</text>
|
|
235
|
+
) : null}
|
|
234
236
|
</box>
|
|
235
237
|
)
|
|
236
238
|
}
|
|
@@ -285,9 +287,9 @@ function LineContent({ content, tokens }: { content: string; tokens: ThemedToken
|
|
|
285
287
|
{tokens.map((tok, i) => {
|
|
286
288
|
const s = tokenToSpan(tok)
|
|
287
289
|
let attributes = 0
|
|
288
|
-
if (s.bold) attributes |= TextAttributes.BOLD
|
|
289
|
-
if (s.italic) attributes |= TextAttributes.ITALIC
|
|
290
|
-
if (s.underline) attributes |= TextAttributes.UNDERLINE
|
|
290
|
+
if (s.bold === true) attributes |= TextAttributes.BOLD
|
|
291
|
+
if (s.italic === true) attributes |= TextAttributes.ITALIC
|
|
292
|
+
if (s.underline === true) attributes |= TextAttributes.UNDERLINE
|
|
291
293
|
return (
|
|
292
294
|
<span key={i} fg={s.fg ?? t.text} attributes={attributes}>
|
|
293
295
|
{s.text}
|
|
@@ -186,7 +186,9 @@ function UnifiedRowRender({
|
|
|
186
186
|
return (
|
|
187
187
|
<box flexDirection="row" backgroundColor={headerBg} paddingLeft={1} paddingRight={1}>
|
|
188
188
|
<text fg={t.text}>{row.spec}</text>
|
|
189
|
-
{row.context
|
|
189
|
+
{row.context != null && row.context !== '' ? (
|
|
190
|
+
<text fg={t.textMuted}> {row.context}</text>
|
|
191
|
+
) : null}
|
|
190
192
|
</box>
|
|
191
193
|
)
|
|
192
194
|
}
|
|
@@ -230,9 +232,9 @@ function LineContent({ content, tokens }: { content: string; tokens: ThemedToken
|
|
|
230
232
|
{tokens.map((tok, i) => {
|
|
231
233
|
const s = tokenToSpan(tok)
|
|
232
234
|
let attributes = 0
|
|
233
|
-
if (s.bold) attributes |= TextAttributes.BOLD
|
|
234
|
-
if (s.italic) attributes |= TextAttributes.ITALIC
|
|
235
|
-
if (s.underline) attributes |= TextAttributes.UNDERLINE
|
|
235
|
+
if (s.bold === true) attributes |= TextAttributes.BOLD
|
|
236
|
+
if (s.italic === true) attributes |= TextAttributes.ITALIC
|
|
237
|
+
if (s.underline === true) attributes |= TextAttributes.UNDERLINE
|
|
236
238
|
return (
|
|
237
239
|
<span key={i} fg={s.fg ?? t.text} attributes={attributes}>
|
|
238
240
|
{s.text}
|
|
@@ -13,6 +13,7 @@ interface PrefetchOptions {
|
|
|
13
13
|
projectPath: string | undefined
|
|
14
14
|
themeId: string
|
|
15
15
|
headOffset: number
|
|
16
|
+
compareRef?: string
|
|
16
17
|
enabled: boolean
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -94,15 +95,20 @@ export function useDiffPrefetch(
|
|
|
94
95
|
|
|
95
96
|
const queueRef = useRef<PrefetchQueue | null>(null)
|
|
96
97
|
|
|
97
|
-
const { enabled, headOffset, projectPath } = opts
|
|
98
|
+
const { compareRef, enabled, headOffset, projectPath } = opts
|
|
98
99
|
const runTaskRef = useRef<(task: Task) => Promise<void>>(async () => {})
|
|
99
100
|
|
|
100
101
|
// Keep a ref to the execute function so the queue keeps the latest closure
|
|
101
102
|
// without needing to recreate the queue itself.
|
|
102
103
|
runTaskRef.current = async (task: Task) => {
|
|
103
|
-
if (!projectPath) return
|
|
104
|
+
if (!(projectPath != null && projectPath !== '')) return
|
|
104
105
|
try {
|
|
105
|
-
const diff = await fetchDiff(
|
|
106
|
+
const diff = await fetchDiff(
|
|
107
|
+
task.file.repoPath ?? projectPath,
|
|
108
|
+
task.file,
|
|
109
|
+
headOffset,
|
|
110
|
+
compareRef
|
|
111
|
+
)
|
|
106
112
|
if (task.controller.signal.aborted) return
|
|
107
113
|
const hash = diffHash(diff.rawDiff)
|
|
108
114
|
dispatchGlobal({ diff, hash, key: task.key, type: 'git-mode-set-diff' })
|
|
@@ -122,13 +128,19 @@ export function useDiffPrefetch(
|
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
if (!queueRef.current) {
|
|
125
|
-
queueRef.current = new PrefetchQueue((task) => runTaskRef.current(task), MAX_CONCURRENCY)
|
|
131
|
+
queueRef.current = new PrefetchQueue(async (task) => runTaskRef.current(task), MAX_CONCURRENCY)
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
useEffect(() => {
|
|
129
135
|
const queue = queueRef.current
|
|
130
136
|
if (!queue) return
|
|
131
|
-
if (
|
|
137
|
+
if (
|
|
138
|
+
!enabled ||
|
|
139
|
+
radius <= 0 ||
|
|
140
|
+
projectPath == null ||
|
|
141
|
+
projectPath === '' ||
|
|
142
|
+
!(selectedEntryKey != null && selectedEntryKey !== '')
|
|
143
|
+
) {
|
|
132
144
|
queue.cancelAll()
|
|
133
145
|
return
|
|
134
146
|
}
|
|
@@ -149,7 +161,7 @@ export function useDiffPrefetch(
|
|
|
149
161
|
const key = row.key
|
|
150
162
|
if (diffs[key]) continue
|
|
151
163
|
if (parsed[key]) continue
|
|
152
|
-
if (loading[key]) continue
|
|
164
|
+
if (loading[key] === true) continue
|
|
153
165
|
tasks.push({
|
|
154
166
|
controller: new AbortController(),
|
|
155
167
|
distance: Math.abs(i - selectedIdx),
|
|
@@ -171,6 +183,7 @@ export function useDiffPrefetch(
|
|
|
171
183
|
parsed,
|
|
172
184
|
loading,
|
|
173
185
|
headOffset,
|
|
186
|
+
compareRef,
|
|
174
187
|
])
|
|
175
188
|
|
|
176
189
|
useEffect(() => {
|
|
@@ -163,11 +163,11 @@ export function useDiffPreparation(
|
|
|
163
163
|
(segments: readonly DiffSegment[]) => {
|
|
164
164
|
const file = parsed.file
|
|
165
165
|
const filetype = parsed.filetype
|
|
166
|
-
if (!file || !filetype) return
|
|
166
|
+
if (!file || !(filetype != null && filetype !== '')) return
|
|
167
167
|
const owner = requestOwner
|
|
168
168
|
const version = requestVersionRef.current
|
|
169
169
|
const activeHash = cachedParsed?.hash ?? (localOwnerMatches ? localHash : null)
|
|
170
|
-
if (!activeHash) return
|
|
170
|
+
if (!(activeHash != null && activeHash !== '')) return
|
|
171
171
|
const next = segments.filter(
|
|
172
172
|
(segment) =>
|
|
173
173
|
(segment.kind === 'context' || segment.kind === 'change') &&
|