@brimveyn/aimux 1.20.4 → 1.22.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 +27 -25
- package/package.json +2 -2
- package/src/app-runtime/auto-commit-driver.ts +22 -27
- package/src/app-runtime/backend-attach-runtime.ts +43 -36
- package/src/app-runtime/backend-runtime-events.ts +107 -88
- package/src/app-runtime/editor-actions.ts +201 -0
- package/src/app-runtime/git-actions.ts +197 -0
- package/src/app-runtime/measure-ref.ts +19 -0
- package/src/app-runtime/navigation-actions.ts +213 -0
- package/src/app-runtime/project-actions.ts +188 -0
- package/src/app-runtime/prompt-injection.ts +123 -0
- package/src/app-runtime/selection.ts +29 -0
- package/src/app-runtime/settings-actions.ts +156 -0
- package/src/app-runtime/setup-actions.ts +237 -0
- package/src/app-runtime/side-effect-context.ts +33 -0
- package/src/app-runtime/side-effects.ts +305 -1589
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/split-drag-controller.ts +4 -8
- package/src/app-runtime/tab-actions.ts +218 -0
- package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
- package/src/app-runtime/use-auto-commit-driver.ts +14 -13
- package/src/app-runtime/use-backend-runtime.ts +12 -11
- package/src/app-runtime/use-directory-search.ts +5 -4
- package/src/app-runtime/use-mouse-handlers.ts +28 -42
- package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app-runtime/use-setup-runner.ts +96 -0
- package/src/app-runtime/use-terminal-resize.ts +28 -33
- package/src/app-runtime/workspace-actions.ts +366 -0
- package/src/app-runtime/workspace-activity.ts +138 -0
- package/src/app-runtime/workspace-naming.ts +90 -0
- package/src/app.tsx +102 -85
- package/src/assets/sounds/bell.wav +0 -0
- package/src/assets/sounds/ding.wav +0 -0
- package/src/assets/sounds/plane.wav +0 -0
- package/src/cli/client/daemon-client.ts +2 -2
- package/src/cli/client/project-resolver.ts +112 -0
- package/src/cli/commands/project/close.ts +32 -0
- package/src/cli/commands/project/create.ts +93 -0
- package/src/cli/commands/project/list.ts +25 -0
- package/src/cli/commands/project/show.ts +32 -0
- package/src/cli/commands/{workspace → project}/switch.ts +19 -19
- package/src/cli/commands/tab/await.ts +2 -2
- package/src/cli/commands/tab/close.ts +3 -3
- package/src/cli/commands/tab/create.ts +74 -73
- package/src/cli/commands/tab/focus.ts +3 -3
- package/src/cli/commands/tab/list.ts +6 -6
- package/src/cli/commands/tab/run.ts +2 -2
- package/src/cli/commands/tab/send.ts +2 -2
- package/src/cli/commands/tab/snapshot.ts +2 -2
- package/src/cli/commands/tab/tail.ts +2 -2
- package/src/cli/commands/tab/wait.ts +2 -2
- package/src/cli/commands/worker/await.ts +3 -3
- package/src/cli/commands/worker/doctor.ts +30 -30
- package/src/cli/commands/worker/list.ts +15 -15
- package/src/cli/commands/worker/prompt.ts +3 -3
- package/src/cli/commands/worker/run.ts +23 -23
- package/src/cli/commands/worker/shared.ts +53 -59
- package/src/cli/commands/worker/stop.ts +29 -29
- package/src/cli/commands/worker/submit.ts +3 -3
- package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
- package/src/cli/commands/workspace/create.ts +30 -68
- package/src/cli/commands/workspace/list.ts +52 -9
- package/src/cli/commands/workspace/remove.ts +81 -0
- package/src/cli/completion/plan.ts +2 -2
- package/src/cli/completion/sources.ts +18 -18
- package/src/cli/context.ts +13 -13
- package/src/cli/flags.ts +30 -10
- package/src/cli/index.ts +26 -16
- package/src/cli/output.ts +1 -1
- package/src/cli/registry.ts +12 -12
- package/src/config/loader.ts +16 -5
- package/src/config.ts +149 -101
- package/src/daemon/catalog-writer.ts +67 -67
- package/src/daemon/daemon.ts +205 -201
- package/src/daemon/session-manager.ts +39 -39
- package/src/daemon/session-registry.ts +14 -14
- package/src/git/divergence.ts +33 -2
- package/src/git/git-poller.ts +1 -1
- package/src/git/git-status.ts +2 -2
- package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
- package/src/git/pr-merge.ts +64 -0
- package/src/git/pr-status-poller.ts +57 -0
- package/src/git/pr-status.ts +227 -0
- package/src/git/repo-discovery.ts +1 -1
- package/src/git/use-repo-discovery.ts +1 -1
- package/src/git/workspace-branch-poller.ts +54 -0
- package/src/git/workspace-divergence-poller.ts +65 -0
- package/src/git/worktree.ts +29 -0
- package/src/index.tsx +11 -6
- package/src/input/keymap/help-entries.ts +7 -5
- package/src/input/modes/bridge.ts +13 -12
- package/src/input/modes/handlers/shared.ts +1 -1
- package/src/input/modes/transitions.ts +34 -21
- package/src/input/modes/types.ts +48 -31
- package/src/ipc/manager-protocol.ts +44 -44
- package/src/ipc/protocol.ts +192 -166
- package/src/platform/open-url.ts +39 -0
- package/src/platform/play-sound.ts +184 -0
- package/src/platform/project-search.ts +8 -8
- package/src/platform/worktree-paths.ts +8 -6
- package/src/pty/assistant-question-extractor.ts +1 -1
- package/src/pty/assistant-status-detection-loop.ts +79 -55
- package/src/pty/assistant-status-detector.ts +40 -15
- package/src/pty/command-registry.ts +81 -1
- package/src/restart-terminal-manager.ts +1 -1
- package/src/services/ai-usage/provider.ts +9 -2
- package/src/services/ai-usage/spawn.ts +3 -1
- package/src/session-backend/bootstrap.ts +4 -4
- package/src/session-backend/local-session-backend.ts +78 -75
- package/src/session-backend/remote-session-backend.ts +64 -52
- package/src/session-backend/types.ts +43 -32
- package/src/settings/live.ts +90 -0
- package/src/settings/search.ts +43 -0
- package/src/settings/sections/about.ts +49 -0
- package/src/settings/sections/appearance.ts +54 -0
- package/src/settings/sections/automation.ts +105 -0
- package/src/settings/sections/commands.ts +72 -0
- package/src/settings/sections/editor.ts +56 -0
- package/src/settings/sections/experimental.ts +57 -0
- package/src/settings/sections/git.ts +112 -0
- package/src/settings/sections/index.ts +95 -0
- package/src/settings/sections/integrations.ts +23 -0
- package/src/settings/sections/layout.ts +72 -0
- package/src/settings/sections/notifications.ts +99 -0
- package/src/settings/sections/setup.ts +54 -0
- package/src/settings/sections/status-bar.ts +63 -0
- package/src/settings/settings-store.ts +207 -0
- package/src/settings/types.ts +108 -0
- package/src/snippets/run-shell-var.ts +48 -22
- package/src/state/actions.ts +342 -0
- package/src/state/app-store.ts +2 -1
- package/src/state/bars.ts +75 -0
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/git-pane-sizing.ts +0 -9
- package/src/state/layout-resize.ts +2 -2
- package/src/state/pr-status-store.ts +39 -0
- package/src/state/project-catalog.ts +275 -0
- package/src/state/project-data.ts +143 -0
- package/src/state/{session-persistence.ts → project-persistence.ts} +77 -73
- package/src/state/project-save.ts +46 -0
- package/src/state/project-workspaces.ts +392 -0
- package/src/state/reducers/auto-commit-state.ts +11 -10
- package/src/state/reducers/git-commit-modal-state.ts +122 -0
- package/src/state/reducers/git-mode-state.ts +1 -1
- package/src/state/reducers/git-panel-state.ts +10 -53
- package/src/state/reducers/modal-state.ts +290 -681
- package/src/state/reducers/multi-repo-state.ts +3 -6
- package/src/state/reducers/project-state.ts +312 -0
- package/src/state/reducers/settings-state.ts +80 -0
- package/src/state/reducers/tab-state.ts +107 -60
- package/src/state/reducers/ui-state.ts +86 -15
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +83 -57
- package/src/state/types.ts +261 -417
- package/src/state/validation.ts +14 -12
- package/src/terminal-manager/manager-client.ts +32 -32
- package/src/terminal-manager/terminal-manager.ts +24 -24
- package/src/ui/components/git/diff-renderer/pierre-diff.tsx +2 -1
- package/src/ui/components/git/git-panel.tsx +2 -2
- package/src/ui/components/git/git-view.tsx +12 -12
- package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
- package/src/ui/components/git/pane/git-pane-header.tsx +105 -39
- package/src/ui/components/git/pane/git-pane-widget.tsx +37 -23
- package/src/ui/components/git/pane/pr-checks-panel.tsx +197 -0
- package/src/ui/components/git/pane/pr-state-row.tsx +103 -0
- package/src/ui/components/layout/bar.tsx +191 -0
- package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
- package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
- package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
- package/src/ui/components/layout/status-bar.tsx +6 -2
- package/src/ui/components/layout/terminal-pane.tsx +34 -29
- package/src/ui/components/layout/top-tab-bar.tsx +48 -37
- package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
- package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
- package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
- package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
- package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
- package/src/ui/components/modals/shared/form.tsx +34 -22
- package/src/ui/components/modals/shared/picker.tsx +10 -10
- package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
- package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
- package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
- package/src/ui/components/primitives/input-field.tsx +7 -3
- package/src/ui/components/primitives/surface.tsx +3 -0
- package/src/ui/components/settings/row-value.tsx +74 -0
- package/src/ui/components/settings/settings-row.tsx +78 -0
- package/src/ui/components/settings/settings-view.tsx +169 -0
- package/src/ui/components/setup/setup-widget.tsx +152 -0
- package/src/ui/flash/build-labels.ts +27 -27
- package/src/ui/hooks/use-activity-sprite.ts +73 -0
- package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
- package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
- package/src/ui/root.tsx +164 -230
- package/src/ui/status-bar-model.ts +48 -34
- package/src/ui/terminal-graphics/kitty.ts +28 -2
- package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
- package/src/ui/terminal-graphics/sprites.ts +246 -0
- package/src/ui/truncate.ts +7 -0
- package/src/ui/widgets/registry.tsx +21 -0
- package/src/ui/widgets/widget-context-menu.ts +85 -0
- package/src/update.ts +2 -2
- package/src/app-runtime/session-actions.ts +0 -187
- package/src/cli/client/workspace-resolver.ts +0 -110
- package/src/cli/commands/workspace/close.ts +0 -32
- package/src/cli/commands/workspace/show.ts +0 -32
- package/src/cli/commands/worktree/create.ts +0 -55
- package/src/cli/commands/worktree/list.ts +0 -68
- package/src/cli/commands/worktree/remove.ts +0 -81
- package/src/git/worktree-branch-poller.ts +0 -54
- package/src/git/worktree-divergence-poller.ts +0 -59
- package/src/state/reducers/session-state.ts +0 -262
- package/src/state/session-catalog.ts +0 -143
- package/src/state/session-worktrees.ts +0 -260
- package/src/state/workspace-save.ts +0 -45
- package/src/ui/components/git/pane/git-pane-context-menu.ts +0 -26
- package/src/ui/components/layout/sidebar/sidebar.tsx +0 -163
- package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
- package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
- package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
- /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { AppState, SessionRecord } from './types'
|
|
2
|
-
|
|
3
|
-
import { loadConfig, saveConfig } from '../config'
|
|
4
|
-
import { saveSessionCatalog } from './session-catalog'
|
|
5
|
-
import { serializeWorkspace } from './session-persistence'
|
|
6
|
-
|
|
7
|
-
export function buildSessionsWithCurrentSnapshot(
|
|
8
|
-
sessions: SessionRecord[],
|
|
9
|
-
currentSessionId: string | null,
|
|
10
|
-
state: AppState
|
|
11
|
-
): SessionRecord[] {
|
|
12
|
-
return sessions.map((session) =>
|
|
13
|
-
session.id === currentSessionId
|
|
14
|
-
? {
|
|
15
|
-
...session,
|
|
16
|
-
updatedAt: new Date().toISOString(),
|
|
17
|
-
workspaceSnapshot: serializeWorkspace(state),
|
|
18
|
-
}
|
|
19
|
-
: session
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function saveCurrentWorkspace(state: AppState): void {
|
|
24
|
-
saveConfig({
|
|
25
|
-
...loadConfig(),
|
|
26
|
-
customCommands: state.customCommands,
|
|
27
|
-
gitPane: {
|
|
28
|
-
diffModeRatio: state.gitPane.diffModeRatio,
|
|
29
|
-
embeddedRatio: state.gitPane.embeddedRatio,
|
|
30
|
-
fileListMode: state.gitPane.fileListMode,
|
|
31
|
-
mode: state.gitPane.mode,
|
|
32
|
-
paneRatio: state.gitPane.paneRatio,
|
|
33
|
-
position: state.gitPane.position,
|
|
34
|
-
visible: state.gitPane.visible,
|
|
35
|
-
},
|
|
36
|
-
sessionBarVisible: state.sessionBar.visible,
|
|
37
|
-
sidebar: {
|
|
38
|
-
visible: state.sidebar.visible,
|
|
39
|
-
width: state.sidebar.width,
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
saveSessionCatalog(
|
|
43
|
-
buildSessionsWithCurrentSnapshot(state.sessions, state.currentSessionId, state)
|
|
44
|
-
)
|
|
45
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { GitPaneMode, GitPanePosition, GitPaneState } from '../../../../state/types'
|
|
2
|
-
import type { ContextMenuItem } from '../../../context-menu/controller'
|
|
3
|
-
|
|
4
|
-
const GIT_PANE_MENU_OPTIONS: {
|
|
5
|
-
label: string
|
|
6
|
-
mode: GitPaneMode
|
|
7
|
-
position: GitPanePosition
|
|
8
|
-
}[] = [
|
|
9
|
-
{ label: 'Move to top', mode: 'embedded', position: 'top' },
|
|
10
|
-
{ label: 'Move to bottom', mode: 'embedded', position: 'bottom' },
|
|
11
|
-
{ label: 'Move to left', mode: 'pane', position: 'left' },
|
|
12
|
-
{ label: 'Move to right', mode: 'pane', position: 'right' },
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
export function buildGitPaneContextMenu(
|
|
16
|
-
gitPane: Pick<GitPaneState, 'mode' | 'position'>,
|
|
17
|
-
onToggle: () => void,
|
|
18
|
-
onMove: (mode: GitPaneMode, position: GitPanePosition) => void
|
|
19
|
-
): ContextMenuItem[] {
|
|
20
|
-
return [
|
|
21
|
-
['Toggle', onToggle],
|
|
22
|
-
...GIT_PANE_MENU_OPTIONS.filter(
|
|
23
|
-
({ mode, position }) => !(gitPane.mode === mode && gitPane.position === position)
|
|
24
|
-
).map(({ label, mode, position }) => [label, () => onMove(mode, position)] as ContextMenuItem),
|
|
25
|
-
]
|
|
26
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import type { BoxRenderable, MouseEvent as OtuiMouseEvent } from '@opentui/core'
|
|
2
|
-
|
|
3
|
-
import { useCallback, useMemo, useRef } from 'react'
|
|
4
|
-
|
|
5
|
-
import { useAppStore } from '../../../../state/app-store'
|
|
6
|
-
import { dispatchGlobal } from '../../../../state/dispatch-ref'
|
|
7
|
-
import { useTheme } from '../../../theme'
|
|
8
|
-
import { buildGitPaneContextMenu } from '../../git/pane/git-pane-context-menu'
|
|
9
|
-
import { GitPaneWidget } from '../../git/pane/git-pane-widget'
|
|
10
|
-
import { ContextMenuBox } from '../../overlays/context-menu/context-menu-box'
|
|
11
|
-
import { WorkspaceList } from './workspace-list'
|
|
12
|
-
|
|
13
|
-
interface SidebarProps {
|
|
14
|
-
onResizeDrag?: (event: OtuiMouseEvent) => boolean
|
|
15
|
-
onResizeDragEnd?: () => void
|
|
16
|
-
onEmbeddedGitResizeStart?: (info: {
|
|
17
|
-
containerStart: number
|
|
18
|
-
position: 'top' | 'bottom'
|
|
19
|
-
totalSize: number
|
|
20
|
-
}) => void
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const RESIZE_HANDLE = '─'
|
|
24
|
-
|
|
25
|
-
export function Sidebar({ onEmbeddedGitResizeStart, onResizeDrag, onResizeDragEnd }: SidebarProps) {
|
|
26
|
-
const t = useTheme()
|
|
27
|
-
const sidebarBg = t.background
|
|
28
|
-
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
29
|
-
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
30
|
-
const gitPane = useAppStore((s) => s.gitPane)
|
|
31
|
-
const focusMode = useAppStore((s) => s.focusMode)
|
|
32
|
-
const bodyRef = useRef<BoxRenderable | null>(null)
|
|
33
|
-
|
|
34
|
-
const handleSidebarMouseDown = useCallback(() => {
|
|
35
|
-
if (focusMode === 'terminal-input') {
|
|
36
|
-
dispatchGlobal({ focusMode: 'navigation', type: 'set-focus-mode' })
|
|
37
|
-
}
|
|
38
|
-
}, [focusMode])
|
|
39
|
-
const handleSidebarMouseDrag = useCallback(
|
|
40
|
-
(event: OtuiMouseEvent) => {
|
|
41
|
-
if (onResizeDrag?.(event) === true) {
|
|
42
|
-
event.preventDefault()
|
|
43
|
-
event.stopPropagation()
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
[onResizeDrag]
|
|
47
|
-
)
|
|
48
|
-
const handleSidebarMouseUp = useCallback(() => {
|
|
49
|
-
onResizeDragEnd?.()
|
|
50
|
-
}, [onResizeDragEnd])
|
|
51
|
-
const handleEmbeddedHandleMouseDown = useCallback(
|
|
52
|
-
(event: OtuiMouseEvent) => {
|
|
53
|
-
const body = bodyRef.current
|
|
54
|
-
if (!body) return
|
|
55
|
-
event.preventDefault()
|
|
56
|
-
event.stopPropagation()
|
|
57
|
-
onEmbeddedGitResizeStart?.({
|
|
58
|
-
containerStart: body.y,
|
|
59
|
-
position: gitPane.position === 'top' ? 'top' : 'bottom',
|
|
60
|
-
totalSize: Math.max(1, body.height),
|
|
61
|
-
})
|
|
62
|
-
},
|
|
63
|
-
[gitPane.position, onEmbeddedGitResizeStart]
|
|
64
|
-
)
|
|
65
|
-
const sidebarMenu = useMemo<[string, () => void][]>(
|
|
66
|
-
() => [
|
|
67
|
-
['Hide sidebar', () => dispatchGlobal({ type: 'toggle-sidebar' })],
|
|
68
|
-
['Toggle git pane', () => dispatchGlobal({ type: 'toggle-git-pane' })],
|
|
69
|
-
[
|
|
70
|
-
'Toggle diff mode',
|
|
71
|
-
() => {
|
|
72
|
-
dispatchGlobal({ type: 'enter-git-mode' })
|
|
73
|
-
dispatchGlobal({ type: 'git-mode-toggle-diff-view' })
|
|
74
|
-
},
|
|
75
|
-
],
|
|
76
|
-
],
|
|
77
|
-
[]
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
if (!sidebarVisible) {
|
|
81
|
-
return null
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const gitEmbedded = gitPane.mode === 'embedded' && gitPane.visible
|
|
85
|
-
const gitOnTop = gitEmbedded && gitPane.position === 'top'
|
|
86
|
-
const gitOnBottom = gitEmbedded && gitPane.position === 'bottom'
|
|
87
|
-
const contentWidth = Math.max(1, sidebarWidth - 1)
|
|
88
|
-
const gitPaneMenu = buildGitPaneContextMenu(
|
|
89
|
-
gitPane,
|
|
90
|
-
() => dispatchGlobal({ type: 'toggle-git-pane' }),
|
|
91
|
-
(mode, position) => {
|
|
92
|
-
dispatchGlobal({ mode, type: 'set-git-pane-mode' })
|
|
93
|
-
dispatchGlobal({ position, type: 'set-git-pane-position' })
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
// flex-grow scaled by 100 (integer preferred); workspace list gets (1-ratio), git gets ratio.
|
|
98
|
-
const listGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.embeddedRatio) * 100)) : 1
|
|
99
|
-
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.embeddedRatio * 100)) : 0
|
|
100
|
-
|
|
101
|
-
const gitBody = gitEmbedded ? (
|
|
102
|
-
<ContextMenuBox
|
|
103
|
-
flexDirection="column"
|
|
104
|
-
flexGrow={gitGrow}
|
|
105
|
-
flexShrink={1}
|
|
106
|
-
flexBasis={0}
|
|
107
|
-
overflow="hidden"
|
|
108
|
-
rightClickMenu={gitPaneMenu}
|
|
109
|
-
>
|
|
110
|
-
<GitPaneWidget pollingEnabled={gitPane.visible} />
|
|
111
|
-
</ContextMenuBox>
|
|
112
|
-
) : null
|
|
113
|
-
const embeddedHandle = gitEmbedded ? (
|
|
114
|
-
<box minHeight={1} flexShrink={0} onMouseDown={handleEmbeddedHandleMouseDown}>
|
|
115
|
-
<text fg={t.border} selectable={false}>
|
|
116
|
-
{RESIZE_HANDLE.repeat(Math.max(1, contentWidth))}
|
|
117
|
-
</text>
|
|
118
|
-
</box>
|
|
119
|
-
) : null
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<ContextMenuBox
|
|
123
|
-
width={sidebarWidth}
|
|
124
|
-
padding={0}
|
|
125
|
-
flexDirection="column"
|
|
126
|
-
backgroundColor={sidebarBg}
|
|
127
|
-
gap={0}
|
|
128
|
-
overflow="hidden"
|
|
129
|
-
rightClickMenu={sidebarMenu}
|
|
130
|
-
onMouseDown={handleSidebarMouseDown}
|
|
131
|
-
onMouseDrag={handleSidebarMouseDrag}
|
|
132
|
-
onMouseUp={handleSidebarMouseUp}
|
|
133
|
-
>
|
|
134
|
-
<box flexDirection="row" width={sidebarWidth} flexGrow={1} overflow="hidden">
|
|
135
|
-
<box width={contentWidth} flexGrow={1} flexDirection="column" overflow="hidden">
|
|
136
|
-
<box ref={bodyRef} flexDirection="column" flexGrow={1} overflow="hidden">
|
|
137
|
-
{gitOnTop ? (
|
|
138
|
-
<>
|
|
139
|
-
{gitBody}
|
|
140
|
-
{embeddedHandle}
|
|
141
|
-
</>
|
|
142
|
-
) : null}
|
|
143
|
-
<box
|
|
144
|
-
flexDirection="column"
|
|
145
|
-
flexGrow={listGrow}
|
|
146
|
-
flexShrink={1}
|
|
147
|
-
flexBasis={0}
|
|
148
|
-
overflow="hidden"
|
|
149
|
-
>
|
|
150
|
-
<WorkspaceList contentWidth={contentWidth} />
|
|
151
|
-
</box>
|
|
152
|
-
{gitOnBottom ? (
|
|
153
|
-
<>
|
|
154
|
-
{embeddedHandle}
|
|
155
|
-
{gitBody}
|
|
156
|
-
</>
|
|
157
|
-
) : null}
|
|
158
|
-
</box>
|
|
159
|
-
</box>
|
|
160
|
-
</box>
|
|
161
|
-
</ContextMenuBox>
|
|
162
|
-
)
|
|
163
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { ScrollBoxRenderable } from '@opentui/core'
|
|
2
|
-
|
|
3
|
-
import { useEffect } from 'react'
|
|
4
|
-
|
|
5
|
-
interface Options {
|
|
6
|
-
scrollRef: React.RefObject<ScrollBoxRenderable | null>
|
|
7
|
-
visible: boolean
|
|
8
|
-
activeTabId: string | null
|
|
9
|
-
idPrefix: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Scroll the active tab into view whenever the active tab id changes (and on
|
|
14
|
-
* first reveal). The top bar is a single horizontal row, so this is just a
|
|
15
|
-
* thin wrapper around `scrollChildIntoView` — no wrap-around/edge heuristics.
|
|
16
|
-
*/
|
|
17
|
-
export function useTopTabBarAutoScroll({
|
|
18
|
-
activeTabId,
|
|
19
|
-
idPrefix,
|
|
20
|
-
scrollRef,
|
|
21
|
-
visible,
|
|
22
|
-
}: Options): void {
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
if (!visible) return
|
|
25
|
-
if (activeTabId == null || activeTabId === '') return
|
|
26
|
-
const scrollbox = scrollRef.current
|
|
27
|
-
if (!scrollbox) return
|
|
28
|
-
scrollbox.scrollChildIntoView(`${idPrefix}${activeTabId}`)
|
|
29
|
-
}, [activeTabId, idPrefix, scrollRef, visible])
|
|
30
|
-
}
|
|
@@ -1,418 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BoxRenderable,
|
|
3
|
-
MouseEvent as OtuiMouseEvent,
|
|
4
|
-
ScrollBoxRenderable,
|
|
5
|
-
} from '@opentui/core'
|
|
6
|
-
|
|
7
|
-
import { memo, type ReactNode, useCallback, useMemo, useRef, useState } from 'react'
|
|
8
|
-
|
|
9
|
-
import type { SessionRecord, SessionStatus, WorktreeRecord } from '../../../../state/types'
|
|
10
|
-
|
|
11
|
-
import { useAppStore } from '../../../../state/app-store'
|
|
12
|
-
import { dispatchGlobal, runSideEffectGlobal } from '../../../../state/dispatch-ref'
|
|
13
|
-
import { formatDivergence } from '../../../../state/session-worktrees'
|
|
14
|
-
// eslint-disable-next-line no-duplicate-imports
|
|
15
|
-
import { IDLE_SESSION_STATUS } from '../../../../state/types'
|
|
16
|
-
import { useBusySpinner } from '../../../hooks/use-busy-spinner'
|
|
17
|
-
import { moveIdToIdPosition, orderSessionsForDisplay } from '../../../session-ordering'
|
|
18
|
-
import { useBaseTheme, useTheme } from '../../../theme'
|
|
19
|
-
import { FlashLabelBadge } from '../../flash/flash-label-badge'
|
|
20
|
-
import { ContextMenuBox } from '../../overlays/context-menu/context-menu-box'
|
|
21
|
-
import { useSidebarAutoScroll } from './use-sidebar-auto-scroll'
|
|
22
|
-
import { WorktreeRow } from './worktree-row'
|
|
23
|
-
|
|
24
|
-
interface WorkspaceListProps {
|
|
25
|
-
contentWidth: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const COLUMN_CONTENT_OPTIONS = { flexDirection: 'column' as const, gap: 0 }
|
|
29
|
-
|
|
30
|
-
function arraysEqual(a: string[], b: string[]): boolean {
|
|
31
|
-
if (a.length !== b.length) return false
|
|
32
|
-
for (let i = 0; i < a.length; i++) {
|
|
33
|
-
if (a[i] !== b[i]) return false
|
|
34
|
-
}
|
|
35
|
-
return true
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function truncate(label: string, max: number): string {
|
|
39
|
-
if (max <= 0) return ''
|
|
40
|
-
if (label.length <= max) return label
|
|
41
|
-
if (max === 1) return '…'
|
|
42
|
-
return `${label.slice(0, max - 1)}…`
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function WorkspaceList({ contentWidth }: WorkspaceListProps) {
|
|
46
|
-
const t = useTheme()
|
|
47
|
-
const sessions = useAppStore((s) => s.sessions)
|
|
48
|
-
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
49
|
-
const statusMap = useAppStore((s) => s.sessionStatuses)
|
|
50
|
-
|
|
51
|
-
const [draggingId, setDraggingId] = useState<string | null>(null)
|
|
52
|
-
const [dragOrder, setDragOrder] = useState<string[] | null>(null)
|
|
53
|
-
const lastSwapWithRef = useRef<string | null>(null)
|
|
54
|
-
const rowRefs = useRef(new Map<string, BoxRenderable>())
|
|
55
|
-
const scrollRef = useRef<ScrollBoxRenderable | null>(null)
|
|
56
|
-
|
|
57
|
-
const ordered = useMemo(() => orderSessionsForDisplay(sessions), [sessions])
|
|
58
|
-
const baselineOrder = useMemo(() => ordered.map((s) => s.id), [ordered])
|
|
59
|
-
|
|
60
|
-
const currentSession = useMemo(
|
|
61
|
-
() =>
|
|
62
|
-
currentSessionId != null && currentSessionId !== ''
|
|
63
|
-
? sessions.find((s) => s.id === currentSessionId)
|
|
64
|
-
: undefined,
|
|
65
|
-
[currentSessionId, sessions]
|
|
66
|
-
)
|
|
67
|
-
// The active row can be either a worktree row OR the workspace row
|
|
68
|
-
// (when the primary worktree is active). Both must scroll into view —
|
|
69
|
-
// otherwise the cursor visually "disappears" off-screen when crossing
|
|
70
|
-
// a workspace boundary on a key press.
|
|
71
|
-
const currentWorktrees = currentSession?.worktrees ?? []
|
|
72
|
-
const currentPrimary = currentWorktrees.find((w) => w.source === 'primary') ?? currentWorktrees[0]
|
|
73
|
-
const rawActiveWorktreeId = currentSession?.activeWorktreeId
|
|
74
|
-
const activeOnNonPrimary =
|
|
75
|
-
rawActiveWorktreeId != null &&
|
|
76
|
-
rawActiveWorktreeId !== '' &&
|
|
77
|
-
rawActiveWorktreeId !== currentPrimary?.id
|
|
78
|
-
let activeRowId: string | null = null
|
|
79
|
-
if (activeOnNonPrimary) {
|
|
80
|
-
activeRowId = `sidebar-wt-${rawActiveWorktreeId}`
|
|
81
|
-
} else if (currentSessionId != null && currentSessionId !== '') {
|
|
82
|
-
activeRowId = `sidebar-ws-${currentSessionId}`
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
useSidebarAutoScroll({
|
|
86
|
-
activeRowId,
|
|
87
|
-
scrollRef,
|
|
88
|
-
visible: true,
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const setRowRef = useCallback((id: string, ref: BoxRenderable | null): void => {
|
|
92
|
-
if (ref) rowRefs.current.set(id, ref)
|
|
93
|
-
else rowRefs.current.delete(id)
|
|
94
|
-
}, [])
|
|
95
|
-
|
|
96
|
-
const findRowAtY = useCallback((y: number): string | null => {
|
|
97
|
-
for (const [id, ref] of rowRefs.current) {
|
|
98
|
-
if (y >= ref.y && y < ref.y + ref.height) return id
|
|
99
|
-
}
|
|
100
|
-
return null
|
|
101
|
-
}, [])
|
|
102
|
-
|
|
103
|
-
const handleRowDragStart = useCallback(
|
|
104
|
-
(id: string) => {
|
|
105
|
-
setDraggingId(id)
|
|
106
|
-
setDragOrder(baselineOrder)
|
|
107
|
-
lastSwapWithRef.current = null
|
|
108
|
-
},
|
|
109
|
-
[baselineOrder]
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
const handleRowDrag = useCallback(
|
|
113
|
-
(event: OtuiMouseEvent) => {
|
|
114
|
-
if (!(draggingId != null && draggingId !== '')) return
|
|
115
|
-
const hit = findRowAtY(event.y)
|
|
116
|
-
if (hit === null) {
|
|
117
|
-
lastSwapWithRef.current = null
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
if (hit === draggingId) {
|
|
121
|
-
lastSwapWithRef.current = null
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
if (hit === lastSwapWithRef.current) return
|
|
125
|
-
setDragOrder((prev) => (prev ? moveIdToIdPosition(prev, draggingId, hit) : prev))
|
|
126
|
-
lastSwapWithRef.current = hit
|
|
127
|
-
},
|
|
128
|
-
[draggingId, findRowAtY]
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
const commitDrop = useCallback(() => {
|
|
132
|
-
const source = draggingId
|
|
133
|
-
const finalOrder = dragOrder
|
|
134
|
-
setDraggingId(null)
|
|
135
|
-
setDragOrder(null)
|
|
136
|
-
lastSwapWithRef.current = null
|
|
137
|
-
|
|
138
|
-
if (source == null || source === '' || !finalOrder) return
|
|
139
|
-
|
|
140
|
-
const changed = !arraysEqual(finalOrder, baselineOrder)
|
|
141
|
-
if (changed) {
|
|
142
|
-
dispatchGlobal({ orderedIds: finalOrder, type: 'reorder-sessions' })
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const idx = baselineOrder.indexOf(source)
|
|
147
|
-
if (idx >= 0) {
|
|
148
|
-
// The workspace row visually anchors the session's primary worktree
|
|
149
|
-
// (its branch line shows the primary's branch). Clicking it should
|
|
150
|
-
// land on the primary — same semantics as j/k cycling onto a workspace
|
|
151
|
-
// item — instead of preserving whatever non-primary worktree happened
|
|
152
|
-
// to be active last time we left this session.
|
|
153
|
-
const sourceSession = ordered.find((s) => s.id === source)
|
|
154
|
-
const sourceWorktrees = sourceSession?.worktrees ?? []
|
|
155
|
-
const sourcePrimaryId = (
|
|
156
|
-
sourceWorktrees.find((w) => w.source === 'primary') ?? sourceWorktrees[0]
|
|
157
|
-
)?.id
|
|
158
|
-
runSideEffectGlobal({
|
|
159
|
-
index: idx + 1,
|
|
160
|
-
type: 'switch-session-by-index',
|
|
161
|
-
worktreeId: sourcePrimaryId,
|
|
162
|
-
})
|
|
163
|
-
}
|
|
164
|
-
}, [baselineOrder, dragOrder, draggingId, ordered])
|
|
165
|
-
|
|
166
|
-
const cancelDrag = useCallback(() => {
|
|
167
|
-
setDraggingId(null)
|
|
168
|
-
setDragOrder(null)
|
|
169
|
-
lastSwapWithRef.current = null
|
|
170
|
-
}, [])
|
|
171
|
-
|
|
172
|
-
const handleNewSession = useCallback((e: OtuiMouseEvent) => {
|
|
173
|
-
e.stopPropagation()
|
|
174
|
-
e.preventDefault()
|
|
175
|
-
dispatchGlobal({ returnToSessionPicker: false, type: 'open-create-session-modal' })
|
|
176
|
-
}, [])
|
|
177
|
-
|
|
178
|
-
const visibleSessions =
|
|
179
|
-
dragOrder !== null
|
|
180
|
-
? dragOrder
|
|
181
|
-
.map((id) => ordered.find((s) => s.id === id))
|
|
182
|
-
.filter((s): s is SessionRecord => !!s)
|
|
183
|
-
: ordered
|
|
184
|
-
|
|
185
|
-
return (
|
|
186
|
-
<box flexDirection="column" flexGrow={1} flexShrink={1} overflow="hidden">
|
|
187
|
-
<scrollbox
|
|
188
|
-
ref={scrollRef}
|
|
189
|
-
scrollY
|
|
190
|
-
flexGrow={1}
|
|
191
|
-
flexShrink={1}
|
|
192
|
-
contentOptions={COLUMN_CONTENT_OPTIONS}
|
|
193
|
-
>
|
|
194
|
-
{(() => {
|
|
195
|
-
// Build a single flat list of items — workspace rows interleaved
|
|
196
|
-
// with their non-primary worktrees. One map, one React keypath per
|
|
197
|
-
// visible row; transitions are a single atomic reconciliation.
|
|
198
|
-
const rows: ReactNode[] = []
|
|
199
|
-
for (const [visibleIdx, session] of visibleSessions.entries()) {
|
|
200
|
-
const sessionIndex = baselineOrder.indexOf(session.id) + 1
|
|
201
|
-
const isCurrentSession = session.id === currentSessionId
|
|
202
|
-
const worktrees = session.worktrees ?? []
|
|
203
|
-
const primaryWorktree = worktrees.find((w) => w.source === 'primary') ?? worktrees[0]
|
|
204
|
-
const extraWorktrees = worktrees.filter((w) => w.id !== primaryWorktree?.id)
|
|
205
|
-
const workspaceIsActiveItem =
|
|
206
|
-
isCurrentSession &&
|
|
207
|
-
(session.activeWorktreeId == null ||
|
|
208
|
-
session.activeWorktreeId === '' ||
|
|
209
|
-
session.activeWorktreeId === primaryWorktree?.id)
|
|
210
|
-
rows.push(
|
|
211
|
-
<WorkspaceRow
|
|
212
|
-
key={`ws:${session.id}`}
|
|
213
|
-
session={session}
|
|
214
|
-
isActiveItem={workspaceIsActiveItem}
|
|
215
|
-
inCurrentGroup={isCurrentSession}
|
|
216
|
-
primaryWorktree={primaryWorktree}
|
|
217
|
-
hasExtraWorktrees={extraWorktrees.length > 0}
|
|
218
|
-
status={statusMap[session.id] ?? IDLE_SESSION_STATUS}
|
|
219
|
-
dragging={draggingId === session.id}
|
|
220
|
-
contentWidth={contentWidth}
|
|
221
|
-
marginTop={visibleIdx > 0 ? 1 : 0}
|
|
222
|
-
setRowRef={setRowRef}
|
|
223
|
-
onDragStart={handleRowDragStart}
|
|
224
|
-
onDrag={handleRowDrag}
|
|
225
|
-
onDrop={commitDrop}
|
|
226
|
-
onDragCancel={cancelDrag}
|
|
227
|
-
/>
|
|
228
|
-
)
|
|
229
|
-
for (const [wtIdx, worktree] of extraWorktrees.entries()) {
|
|
230
|
-
rows.push(
|
|
231
|
-
<WorktreeRow
|
|
232
|
-
key={`wt:${worktree.id}`}
|
|
233
|
-
session={session}
|
|
234
|
-
worktree={worktree}
|
|
235
|
-
sessionIndex={sessionIndex}
|
|
236
|
-
isActiveItem={isCurrentSession && worktree.id === session.activeWorktreeId}
|
|
237
|
-
inCurrentGroup={isCurrentSession}
|
|
238
|
-
isLast={wtIdx === extraWorktrees.length - 1}
|
|
239
|
-
/>
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return rows
|
|
244
|
-
})()}
|
|
245
|
-
</scrollbox>
|
|
246
|
-
<box
|
|
247
|
-
flexDirection="row"
|
|
248
|
-
flexShrink={0}
|
|
249
|
-
marginTop={1}
|
|
250
|
-
backgroundColor={t.backgroundPanel}
|
|
251
|
-
justifyContent="center"
|
|
252
|
-
onMouseDown={handleNewSession}
|
|
253
|
-
>
|
|
254
|
-
<text fg={t.text} selectable={false}>
|
|
255
|
-
+ New workspace
|
|
256
|
-
</text>
|
|
257
|
-
</box>
|
|
258
|
-
</box>
|
|
259
|
-
)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
interface WorkspaceRowProps {
|
|
263
|
-
session: SessionRecord
|
|
264
|
-
/** True when this row is the active cursor item (workspace's primary active). */
|
|
265
|
-
isActiveItem: boolean
|
|
266
|
-
/** True when this row belongs to the current workspace (selection scope). */
|
|
267
|
-
inCurrentGroup: boolean
|
|
268
|
-
/** The session's primary worktree — its git branch is shown as the workspace's anchor identity. */
|
|
269
|
-
primaryWorktree: WorktreeRecord | undefined
|
|
270
|
-
/** True when at least one non-primary worktree follows — used to draw the tree continuator. */
|
|
271
|
-
hasExtraWorktrees: boolean
|
|
272
|
-
status: SessionStatus
|
|
273
|
-
dragging: boolean
|
|
274
|
-
contentWidth: number
|
|
275
|
-
/** Vertical spacing above this row — used to separate workspace blocks. */
|
|
276
|
-
marginTop: number
|
|
277
|
-
setRowRef: (id: string, ref: BoxRenderable | null) => void
|
|
278
|
-
onDragStart: (id: string) => void
|
|
279
|
-
onDrag: (event: OtuiMouseEvent) => void
|
|
280
|
-
onDrop: () => void
|
|
281
|
-
onDragCancel: () => void
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const WorkspaceRow = memo(function WorkspaceRow({
|
|
285
|
-
contentWidth,
|
|
286
|
-
dragging,
|
|
287
|
-
hasExtraWorktrees,
|
|
288
|
-
inCurrentGroup,
|
|
289
|
-
isActiveItem,
|
|
290
|
-
marginTop,
|
|
291
|
-
onDrag,
|
|
292
|
-
onDragCancel,
|
|
293
|
-
onDragStart,
|
|
294
|
-
onDrop,
|
|
295
|
-
primaryWorktree,
|
|
296
|
-
session,
|
|
297
|
-
setRowRef,
|
|
298
|
-
status,
|
|
299
|
-
}: WorkspaceRowProps) {
|
|
300
|
-
const t = useTheme()
|
|
301
|
-
// Selection highlight must stay opaque in transparent mode — otherwise the
|
|
302
|
-
// cursor row visually disappears against the see-through chrome.
|
|
303
|
-
const base = useBaseTheme()
|
|
304
|
-
const showSpinner = status.working
|
|
305
|
-
const showWaiting = status.waiting
|
|
306
|
-
const spinner = useBusySpinner(showSpinner)
|
|
307
|
-
let bgColor: string | undefined
|
|
308
|
-
if (dragging || isActiveItem) {
|
|
309
|
-
bgColor = base.backgroundElement
|
|
310
|
-
} else if (inCurrentGroup) {
|
|
311
|
-
bgColor = base.backgroundPanel
|
|
312
|
-
}
|
|
313
|
-
const workingColor = t.primary
|
|
314
|
-
const waitingColor = t.warning
|
|
315
|
-
const divergence = useAppStore((s) =>
|
|
316
|
-
primaryWorktree ? s.worktreeDivergence[primaryWorktree.id] : undefined
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
const handleRef = useCallback(
|
|
320
|
-
(r: BoxRenderable | null) => setRowRef(session.id, r),
|
|
321
|
-
[setRowRef, session.id]
|
|
322
|
-
)
|
|
323
|
-
const handleMouseDown = useCallback(
|
|
324
|
-
(e: OtuiMouseEvent) => {
|
|
325
|
-
e.preventDefault()
|
|
326
|
-
e.stopPropagation()
|
|
327
|
-
onDragStart(session.id)
|
|
328
|
-
},
|
|
329
|
-
[onDragStart, session.id]
|
|
330
|
-
)
|
|
331
|
-
const handleMouseUp = useCallback(
|
|
332
|
-
(e: OtuiMouseEvent) => {
|
|
333
|
-
e.preventDefault()
|
|
334
|
-
onDrop()
|
|
335
|
-
},
|
|
336
|
-
[onDrop]
|
|
337
|
-
)
|
|
338
|
-
const rightClickMenu = useMemo<[string, () => void][]>(
|
|
339
|
-
() => [
|
|
340
|
-
[
|
|
341
|
-
'Rename workspace',
|
|
342
|
-
() =>
|
|
343
|
-
dispatchGlobal({
|
|
344
|
-
initialName: session.name,
|
|
345
|
-
returnToSessionPicker: false,
|
|
346
|
-
sessionTargetId: session.id,
|
|
347
|
-
type: 'open-session-name-modal',
|
|
348
|
-
}),
|
|
349
|
-
],
|
|
350
|
-
[
|
|
351
|
-
'Delete workspace',
|
|
352
|
-
() => runSideEffectGlobal({ sessionId: session.id, type: 'delete-session' }),
|
|
353
|
-
],
|
|
354
|
-
],
|
|
355
|
-
[session.id, session.name]
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
// Neutral muted marker; working/waiting overrides it. Keeping a glyph
|
|
359
|
-
// in the slot avoids name shifts when state indicators come and go.
|
|
360
|
-
let leadingGlyph = '•'
|
|
361
|
-
let leadingColor = t.textMuted
|
|
362
|
-
if (showWaiting) {
|
|
363
|
-
leadingGlyph = '?'
|
|
364
|
-
leadingColor = waitingColor
|
|
365
|
-
} else if (showSpinner) {
|
|
366
|
-
leadingGlyph = spinner
|
|
367
|
-
leadingColor = workingColor
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const branchText = primaryWorktree?.branch ?? ''
|
|
371
|
-
const divergenceText = formatDivergence(divergence)
|
|
372
|
-
const showBranch = branchText !== ''
|
|
373
|
-
const nameLabel = truncate(session.name, Math.max(0, contentWidth - 4))
|
|
374
|
-
const branchLabel = truncate(
|
|
375
|
-
branchText,
|
|
376
|
-
Math.max(0, contentWidth - 5 - (divergenceText.length + 1))
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
return (
|
|
380
|
-
<ContextMenuBox
|
|
381
|
-
ref={handleRef}
|
|
382
|
-
id={`sidebar-ws-${session.id}`}
|
|
383
|
-
flexDirection="column"
|
|
384
|
-
flexShrink={0}
|
|
385
|
-
marginTop={marginTop}
|
|
386
|
-
paddingLeft={1}
|
|
387
|
-
paddingRight={1}
|
|
388
|
-
backgroundColor={bgColor}
|
|
389
|
-
rightClickMenu={rightClickMenu}
|
|
390
|
-
onMouseDown={handleMouseDown}
|
|
391
|
-
onMouseDrag={onDrag}
|
|
392
|
-
onMouseUp={handleMouseUp}
|
|
393
|
-
onMouseDragEnd={onDragCancel}
|
|
394
|
-
>
|
|
395
|
-
<box flexDirection="row" alignItems="center">
|
|
396
|
-
<text fg={leadingColor} selectable={false} wrapMode="none">
|
|
397
|
-
{leadingGlyph}
|
|
398
|
-
</text>
|
|
399
|
-
<text fg={t.text} selectable={false} wrapMode="none">
|
|
400
|
-
{' '}
|
|
401
|
-
</text>
|
|
402
|
-
<FlashLabelBadge rowKey={`ws:${session.id}`} />
|
|
403
|
-
<text fg={t.text} selectable={false} wrapMode="none">
|
|
404
|
-
{nameLabel}
|
|
405
|
-
</text>
|
|
406
|
-
</box>
|
|
407
|
-
{showBranch ? (
|
|
408
|
-
<box flexDirection="row">
|
|
409
|
-
<text fg={t.textMuted} selectable={false} wrapMode="none">
|
|
410
|
-
{hasExtraWorktrees ? '│ ' : ' '}
|
|
411
|
-
{'\u{e702}'} {branchLabel}
|
|
412
|
-
{divergenceText !== '' ? ` ${divergenceText}` : ''}
|
|
413
|
-
</text>
|
|
414
|
-
</box>
|
|
415
|
-
) : null}
|
|
416
|
-
</ContextMenuBox>
|
|
417
|
-
)
|
|
418
|
-
})
|