@brimveyn/aimux 1.21.0 → 1.22.2
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 +302 -1541
- package/src/app-runtime/snippet-actions.ts +3 -2
- 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 +2 -1
- 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 +18 -14
- 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 +93 -48
- 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 +70 -112
- 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-status.ts +1 -1
- 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 +4 -4
- 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/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 +19 -6
- 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 +1 -1
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/layout-resize.ts +2 -2
- 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} +74 -68
- 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 -6
- 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 +4 -3
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +57 -24
- package/src/state/types.ts +244 -405
- 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/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-widget.tsx +7 -7
- 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 +46 -36
- 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 +139 -121
- 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 +6 -3
- package/src/ui/widgets/widget-context-menu.ts +26 -10
- 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 -43
- 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,5 +1,6 @@
|
|
|
1
1
|
import type { SessionBackend } from '../session-backend/types'
|
|
2
|
-
import type { AppAction
|
|
2
|
+
import type { AppAction } from '../state/actions'
|
|
3
|
+
import type { AppState, SnippetRecord, TabSession } from '../state/types'
|
|
3
4
|
|
|
4
5
|
import { createPrefixedId } from '../platform/id'
|
|
5
6
|
import { isConfigSnippetId, saveSnippetCatalog } from '../state/snippet-catalog'
|
|
@@ -40,7 +41,7 @@ export function saveSnippetEditorState(state: AppState): SnippetRecord[] | null
|
|
|
40
41
|
return null
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const snippetId = state.modal.
|
|
44
|
+
const snippetId = state.modal.projectTargetId
|
|
44
45
|
// Config-pinned snippets are sticky and read-only in the UI.
|
|
45
46
|
if (snippetId != null && snippetId !== '' && isConfigSnippetId(snippetId)) {
|
|
46
47
|
return null
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { AssistantId, TabSession } from '../state/types'
|
|
2
|
+
import type { SideEffectContext } from './side-effect-context'
|
|
3
|
+
|
|
4
|
+
import { logInputDebug } from '../debug/input-log'
|
|
5
|
+
import { createPrefixedId } from '../platform/id'
|
|
6
|
+
import {
|
|
7
|
+
getAllAssistantOptions,
|
|
8
|
+
getAssistantOption,
|
|
9
|
+
isCommandAvailable,
|
|
10
|
+
parseCommand,
|
|
11
|
+
} from '../pty/command-registry'
|
|
12
|
+
import { createTerminalBounds } from '../state/layout-resize'
|
|
13
|
+
import {
|
|
14
|
+
computePaneRects,
|
|
15
|
+
createLeaf,
|
|
16
|
+
getTreeForTab,
|
|
17
|
+
PANE_BORDER,
|
|
18
|
+
type SplitDirection,
|
|
19
|
+
splitNode,
|
|
20
|
+
} from '../state/layout-tree'
|
|
21
|
+
import { getActiveWorkspace, getCurrentProject } from '../state/project-workspaces'
|
|
22
|
+
import { createDefaultTerminalModes } from '../state/terminal-modes'
|
|
23
|
+
import { getSelectedAssistantOption } from './selection'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How long a freshly spawned assistant is given to draw something before the
|
|
27
|
+
* tab stops being treated as "still starting up".
|
|
28
|
+
*/
|
|
29
|
+
const STARTUP_GRACE_MS = 5_000
|
|
30
|
+
|
|
31
|
+
export function confirmSplitSelection(ctx: SideEffectContext): void {
|
|
32
|
+
const { dispatch, state } = ctx
|
|
33
|
+
const option = getSelectedAssistantOption(state)
|
|
34
|
+
const direction = state.modal.type === 'split-picker' ? state.modal.splitDirection : 'vertical'
|
|
35
|
+
const customCommand = state.customCommands[option.id]
|
|
36
|
+
const tab = createTabSession(
|
|
37
|
+
option.id,
|
|
38
|
+
customCommand,
|
|
39
|
+
state.customCommands,
|
|
40
|
+
getActiveWorkspace(getCurrentProject(state))?.id
|
|
41
|
+
)
|
|
42
|
+
dispatch({ type: 'close-modal' })
|
|
43
|
+
executeSplitPane(ctx, direction, tab)
|
|
44
|
+
dispatch({ focusMode: 'terminal-input', type: 'set-focus-mode' })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createTabId(): string {
|
|
48
|
+
return createPrefixedId('tab')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createTabSession(
|
|
52
|
+
assistant: AssistantId,
|
|
53
|
+
customCommand?: string,
|
|
54
|
+
customCommands?: Record<string, string>,
|
|
55
|
+
workspaceId?: string
|
|
56
|
+
): TabSession {
|
|
57
|
+
const allOptions = getAllAssistantOptions(customCommands ?? {})
|
|
58
|
+
const option = allOptions.find((o) => o.id === assistant) ?? getAssistantOption(0)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
activity: 'idle',
|
|
62
|
+
assistant,
|
|
63
|
+
buffer: '',
|
|
64
|
+
command: customCommand ?? option.command,
|
|
65
|
+
id: createTabId(),
|
|
66
|
+
status: 'starting',
|
|
67
|
+
terminalModes: createDefaultTerminalModes(),
|
|
68
|
+
title: option.label,
|
|
69
|
+
workspaceId,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Everything `startTabSession` needs from the side-effect context. */
|
|
74
|
+
type StartTabSessionContext = Pick<
|
|
75
|
+
SideEffectContext,
|
|
76
|
+
'backend' | 'clearStartupGrace' | 'dispatch' | 'startStartupGrace'
|
|
77
|
+
>
|
|
78
|
+
|
|
79
|
+
export interface StartTabSessionOptions {
|
|
80
|
+
cols: number
|
|
81
|
+
rows: number
|
|
82
|
+
cwd?: string
|
|
83
|
+
/** Default true: only a tab the user did not just ask for opts out. */
|
|
84
|
+
autoRenameCandidate?: boolean
|
|
85
|
+
/**
|
|
86
|
+
* Appended after the command's own args. Kept out of `tab.command` on purpose:
|
|
87
|
+
* an initial prompt is up to a few thousand characters, and `command` is what
|
|
88
|
+
* the UI shows, what the snapshot persists, and what the custom-command editor
|
|
89
|
+
* round-trips.
|
|
90
|
+
*/
|
|
91
|
+
extraArgs?: string[]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function startTabSession(
|
|
95
|
+
ctx: StartTabSessionContext,
|
|
96
|
+
tab: Pick<TabSession, 'id' | 'assistant' | 'title' | 'command' | 'workspaceId'>,
|
|
97
|
+
{ autoRenameCandidate = true, cols, cwd, extraArgs, rows }: StartTabSessionOptions
|
|
98
|
+
): void {
|
|
99
|
+
const { backend, clearStartupGrace, dispatch } = ctx
|
|
100
|
+
logInputDebug('app.tab.start.request', {
|
|
101
|
+
cols,
|
|
102
|
+
command: tab.command,
|
|
103
|
+
cwd: cwd ?? null,
|
|
104
|
+
rows,
|
|
105
|
+
tabId: tab.id,
|
|
106
|
+
title: tab.title,
|
|
107
|
+
workspaceId: tab.workspaceId ?? null,
|
|
108
|
+
})
|
|
109
|
+
ctx.startStartupGrace(tab.id, STARTUP_GRACE_MS)
|
|
110
|
+
|
|
111
|
+
const { args, executable } = parseCommand(tab.command)
|
|
112
|
+
|
|
113
|
+
if (!isCommandAvailable(executable)) {
|
|
114
|
+
clearStartupGrace(tab.id)
|
|
115
|
+
dispatch({
|
|
116
|
+
message: `[command not found] ${executable} is not available in PATH.`,
|
|
117
|
+
tabId: tab.id,
|
|
118
|
+
type: 'set-tab-error',
|
|
119
|
+
})
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
backend.createSession({
|
|
124
|
+
args: extraArgs ? [...args, ...extraArgs] : args,
|
|
125
|
+
assistant: tab.assistant,
|
|
126
|
+
autoRenameCandidate,
|
|
127
|
+
cols,
|
|
128
|
+
command: executable,
|
|
129
|
+
cwd,
|
|
130
|
+
rows,
|
|
131
|
+
tabId: tab.id,
|
|
132
|
+
title: tab.title,
|
|
133
|
+
workspaceId: tab.workspaceId,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns the id of the tab it created, so a caller can write into it.
|
|
139
|
+
*
|
|
140
|
+
* `initialPromptArgs` hands the prompt to the CLI at spawn — see
|
|
141
|
+
* `assistantAcceptsPromptArg`. Callers pass it only for assistants that support
|
|
142
|
+
* it, and fall back to `injectPromptWhenReady` otherwise.
|
|
143
|
+
*/
|
|
144
|
+
export function launchAssistant(
|
|
145
|
+
ctx: SideEffectContext,
|
|
146
|
+
assistant: AssistantId,
|
|
147
|
+
workspaceId?: string,
|
|
148
|
+
initialPromptArgs?: string[]
|
|
149
|
+
): string {
|
|
150
|
+
const { dispatch, state } = ctx
|
|
151
|
+
const customCommand = state.customCommands[assistant]
|
|
152
|
+
const tab = createTabSession(
|
|
153
|
+
assistant,
|
|
154
|
+
customCommand,
|
|
155
|
+
state.customCommands,
|
|
156
|
+
workspaceId ?? getActiveWorkspace(getCurrentProject(state))?.id
|
|
157
|
+
)
|
|
158
|
+
logInputDebug('app.launchAssistant', {
|
|
159
|
+
assistant,
|
|
160
|
+
command: tab.command,
|
|
161
|
+
tabId: tab.id,
|
|
162
|
+
})
|
|
163
|
+
dispatch({ tab, type: 'add-tab' })
|
|
164
|
+
dispatch({ focusMode: 'terminal-input', type: 'set-focus-mode' })
|
|
165
|
+
startTabSession(ctx, tab, {
|
|
166
|
+
cols: state.layout.terminalCols,
|
|
167
|
+
cwd: getTabProjectPath(ctx, tab),
|
|
168
|
+
extraArgs: initialPromptArgs,
|
|
169
|
+
rows: state.layout.terminalRows,
|
|
170
|
+
})
|
|
171
|
+
return tab.id
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getTabProjectPath(
|
|
175
|
+
ctx: SideEffectContext,
|
|
176
|
+
tab: Pick<TabSession, 'workspaceId'>
|
|
177
|
+
): string | undefined {
|
|
178
|
+
// Scoped to the current project on purpose: a tab pinned to a workspace of
|
|
179
|
+
// some other project gets the current project's path, not that project's.
|
|
180
|
+
const project = getCurrentProject(ctx.state)
|
|
181
|
+
const workspace = project?.workspaces?.find((entry) => entry.id === tab.workspaceId)
|
|
182
|
+
if (workspace) return workspace.path
|
|
183
|
+
return ctx.getCurrentProjectProjectPath()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function startExistingTab(ctx: SideEffectContext, tab: TabSession): void {
|
|
187
|
+
startTabSession(ctx, tab, {
|
|
188
|
+
autoRenameCandidate: false,
|
|
189
|
+
cols: ctx.state.layout.terminalCols,
|
|
190
|
+
cwd: getTabProjectPath(ctx, tab),
|
|
191
|
+
rows: ctx.state.layout.terminalRows,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function executeSplitPane(
|
|
196
|
+
ctx: SideEffectContext,
|
|
197
|
+
direction: SplitDirection,
|
|
198
|
+
tab: TabSession
|
|
199
|
+
): void {
|
|
200
|
+
const { dispatch, state } = ctx
|
|
201
|
+
const activeTabId = state.activeTabId
|
|
202
|
+
if (!(activeTabId != null && activeTabId !== '')) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const existingTree = getTreeForTab(state.layoutTrees, state.tabGroupMap, activeTabId)
|
|
207
|
+
const baseTree = existingTree ?? createLeaf(activeTabId)
|
|
208
|
+
const newTree = splitNode(baseTree, activeTabId, direction, tab.id)
|
|
209
|
+
const bounds = createTerminalBounds(state.layout.terminalCols, state.layout.terminalRows)
|
|
210
|
+
const paneRect = computePaneRects(newTree, bounds).get(tab.id)
|
|
211
|
+
|
|
212
|
+
dispatch({ direction, newTab: tab, type: 'split-pane' })
|
|
213
|
+
startTabSession(ctx, tab, {
|
|
214
|
+
cols: Math.max(1, (paneRect?.cols ?? state.layout.terminalCols) - PANE_BORDER * 2),
|
|
215
|
+
cwd: getTabProjectPath(ctx, tab),
|
|
216
|
+
rows: Math.max(1, (paneRect?.rows ?? state.layout.terminalRows) - PANE_BORDER * 2),
|
|
217
|
+
})
|
|
218
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type MutableRefObject, useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
|
-
import type { AppAction
|
|
3
|
+
import type { AppAction } from '../state/actions'
|
|
4
|
+
import type { AppState, GitRefreshPayload, TabActivity } from '../state/types'
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
type AutoCommitConfigSnapshot,
|
|
@@ -61,7 +62,7 @@ export function useAutoCommitDriver({
|
|
|
61
62
|
const prevActivityRef = useRef<Map<string, TabActivity | undefined>>(new Map())
|
|
62
63
|
|
|
63
64
|
useEffect(() => {
|
|
64
|
-
// Always refresh the activity map so re-enabling mid-
|
|
65
|
+
// Always refresh the activity map so re-enabling mid-project doesn't fire
|
|
65
66
|
// a stale "became idle" transition. Skip only the dispatch work.
|
|
66
67
|
const prev = prevActivityRef.current
|
|
67
68
|
const next = new Map<string, TabActivity | undefined>()
|
|
@@ -73,15 +74,15 @@ export function useAutoCommitDriver({
|
|
|
73
74
|
const becameIdle =
|
|
74
75
|
(before === 'working' || before === 'waiting-input') && tab.activity === 'idle'
|
|
75
76
|
if (becameIdle) {
|
|
76
|
-
const
|
|
77
|
-
if (!(
|
|
78
|
-
const
|
|
77
|
+
const projectId = state.currentProjectId
|
|
78
|
+
if (!(projectId != null && projectId !== '')) continue
|
|
79
|
+
const project = state.projects.find((s) => s.id === projectId)
|
|
79
80
|
const git = gitPayloadFromState(state)
|
|
80
81
|
void onActivityTransition(depsRef.current, {
|
|
81
82
|
assistant: tab.assistant,
|
|
82
83
|
git,
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
projectId,
|
|
85
|
+
projectPath: project?.projectPath,
|
|
85
86
|
tabId: tab.id,
|
|
86
87
|
})
|
|
87
88
|
}
|
|
@@ -94,14 +95,14 @@ export function useAutoCommitDriver({
|
|
|
94
95
|
|
|
95
96
|
useEffect(() => {
|
|
96
97
|
if (!configRef.current.enabled) return
|
|
97
|
-
const
|
|
98
|
-
if (!(
|
|
98
|
+
const projectId = state.currentProjectId
|
|
99
|
+
if (!(projectId != null && projectId !== '')) return
|
|
99
100
|
const payload = gitPayloadFromState(state)
|
|
100
101
|
if (!payload) return
|
|
101
102
|
const cacheKey = JSON.stringify(payload)
|
|
102
103
|
if (lastGitHashRef.current === cacheKey) return
|
|
103
104
|
lastGitHashRef.current = cacheKey
|
|
104
|
-
onGitRefresh(depsRef.current,
|
|
105
|
+
onGitRefresh(depsRef.current, projectId, payload)
|
|
105
106
|
|
|
106
107
|
// Debounced trigger: when the working tree stays stable for
|
|
107
108
|
// GIT_STABILIZATION_DEBOUNCE_MS, try to start generation. Covers cases
|
|
@@ -114,14 +115,14 @@ export function useAutoCommitDriver({
|
|
|
114
115
|
? state.tabs.find((tab) => tab.id === activeTabId)
|
|
115
116
|
: undefined
|
|
116
117
|
if (!activeTab) return
|
|
117
|
-
const
|
|
118
|
+
const project = state.projects.find((s) => s.id === projectId)
|
|
118
119
|
gitStabilizeTimerRef.current = setTimeout(() => {
|
|
119
120
|
gitStabilizeTimerRef.current = null
|
|
120
121
|
void onActivityTransition(depsRef.current, {
|
|
121
122
|
assistant: activeTab.assistant,
|
|
122
123
|
git: payload,
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
projectId,
|
|
125
|
+
projectPath: project?.projectPath,
|
|
125
126
|
tabId: activeTab.id,
|
|
126
127
|
})
|
|
127
128
|
}, GIT_STABILIZATION_DEBOUNCE_MS)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type MutableRefObject, useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
import type { SessionBackend } from '../session-backend/types'
|
|
4
|
-
import type { AppAction
|
|
4
|
+
import type { AppAction } from '../state/actions'
|
|
5
|
+
import type { LayoutState } from '../state/types'
|
|
5
6
|
|
|
6
7
|
import { attachCurrentSession } from './backend-attach-runtime'
|
|
7
8
|
import { bindBackendRuntimeEvents } from './backend-runtime-events'
|
|
@@ -11,10 +12,10 @@ interface BackendRuntimeOptions {
|
|
|
11
12
|
backend: SessionBackend
|
|
12
13
|
dispatch: (action: AppAction) => void
|
|
13
14
|
activeTabId: string | null
|
|
14
|
-
|
|
15
|
+
currentProjectId: string | null
|
|
15
16
|
layoutRef: MutableRefObject<LayoutState>
|
|
16
17
|
resizingRef: MutableRefObject<boolean>
|
|
17
|
-
|
|
18
|
+
currentProjectProjectSnapshot: Parameters<SessionBackend['attach']>[0]['projectSnapshot']
|
|
18
19
|
syntaxOverlayEnabled: () => boolean
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -27,8 +28,8 @@ export interface TabRuntimeControls {
|
|
|
27
28
|
export function useBackendRuntime({
|
|
28
29
|
activeTabId,
|
|
29
30
|
backend,
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
currentProjectId,
|
|
32
|
+
currentProjectProjectSnapshot,
|
|
32
33
|
dispatch,
|
|
33
34
|
layoutRef,
|
|
34
35
|
resizingRef,
|
|
@@ -39,7 +40,7 @@ export function useBackendRuntime({
|
|
|
39
40
|
const { clearAllTimers, clearIdleTimer, clearStartupGrace, startStartupGrace } = timeouts
|
|
40
41
|
|
|
41
42
|
useEffect(() => {
|
|
42
|
-
if (!(
|
|
43
|
+
if (!(currentProjectId != null && currentProjectId !== '')) {
|
|
43
44
|
attachRequestIdRef.current += 1
|
|
44
45
|
return
|
|
45
46
|
}
|
|
@@ -47,22 +48,22 @@ export function useBackendRuntime({
|
|
|
47
48
|
return attachCurrentSession({
|
|
48
49
|
attachRequestIdRef,
|
|
49
50
|
backend,
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
currentProjectId,
|
|
52
|
+
currentProjectProjectSnapshot,
|
|
52
53
|
dispatch,
|
|
53
54
|
layoutRef,
|
|
54
55
|
})
|
|
55
|
-
}, [backend,
|
|
56
|
+
}, [backend, currentProjectId, currentProjectProjectSnapshot, dispatch, layoutRef])
|
|
56
57
|
|
|
57
58
|
useEffect(() => {
|
|
58
|
-
if (!(
|
|
59
|
+
if (!(currentProjectId != null && currentProjectId !== '')) {
|
|
59
60
|
return
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// The backend owns each tab's scroll position; a backgrounded tab keeps it
|
|
63
64
|
// in its own emulator, so switching back needs no re-anchor from here.
|
|
64
65
|
backend.setActiveTab(activeTabId)
|
|
65
|
-
}, [activeTabId, backend,
|
|
66
|
+
}, [activeTabId, backend, currentProjectId])
|
|
66
67
|
|
|
67
68
|
useEffect(() => {
|
|
68
69
|
return bindBackendRuntimeEvents({
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
|
|
3
|
-
import type { AppAction
|
|
3
|
+
import type { AppAction } from '../state/actions'
|
|
4
|
+
import type { ModalState } from '../state/types'
|
|
4
5
|
|
|
5
6
|
import { searchProjectDirectories, warmDirectoryCache } from '../platform/project-search'
|
|
6
7
|
|
|
7
8
|
const DEFAULT_DIRECTORY_SEARCH_DEBOUNCE_MS = 200
|
|
8
9
|
|
|
9
10
|
function getDirectoryQuery(modal: ModalState): string {
|
|
10
|
-
if (modal.type !== 'create-
|
|
11
|
+
if (modal.type !== 'create-project') {
|
|
11
12
|
return ''
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -26,12 +27,12 @@ export function useDirectorySearch(
|
|
|
26
27
|
const directoryQuery = getDirectoryQuery(modal)
|
|
27
28
|
|
|
28
29
|
useEffect(() => {
|
|
29
|
-
if (modal.type !== 'create-
|
|
30
|
+
if (modal.type !== 'create-project') return
|
|
30
31
|
void warmDirectoryCache()
|
|
31
32
|
}, [modal.type])
|
|
32
33
|
|
|
33
34
|
useEffect(() => {
|
|
34
|
-
if (modal.type !== 'create-
|
|
35
|
+
if (modal.type !== 'create-project') {
|
|
35
36
|
return
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -4,8 +4,9 @@ import { useEffect, useRef } from 'react'
|
|
|
4
4
|
|
|
5
5
|
import type { TerminalContentOrigin } from '../input/raw-input-handler'
|
|
6
6
|
import type { SessionBackend } from '../session-backend/types'
|
|
7
|
+
import type { AppAction } from '../state/actions'
|
|
7
8
|
import type { SplitDirection } from '../state/layout-tree'
|
|
8
|
-
import type {
|
|
9
|
+
import type { AppState, BarSide, TabSession, TerminalLine } from '../state/types'
|
|
9
10
|
|
|
10
11
|
import { logInputDebug } from '../debug/input-log'
|
|
11
12
|
import { MultiClickDetector } from '../input/multi-click-detector'
|
|
@@ -2,9 +2,9 @@ import { useEffect, useRef } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import type { AppState } from '../state/types'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { saveCurrentProject } from '../state/project-save'
|
|
6
6
|
|
|
7
|
-
export function
|
|
7
|
+
export function useProjectAutosave(state: AppState, debounceMs: number): void {
|
|
8
8
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
9
9
|
const latestStateRef = useRef(state)
|
|
10
10
|
latestStateRef.current = state
|
|
@@ -15,7 +15,7 @@ export function useWorkspaceAutosave(state: AppState, debounceMs: number): void
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
timeoutRef.current = setTimeout(() => {
|
|
18
|
-
|
|
18
|
+
saveCurrentProject(latestStateRef.current)
|
|
19
19
|
timeoutRef.current = null
|
|
20
20
|
}, debounceMs)
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@ export function useWorkspaceAutosave(state: AppState, debounceMs: number): void
|
|
|
33
33
|
clearTimeout(timeoutRef.current)
|
|
34
34
|
timeoutRef.current = null
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
saveCurrentProject(latestStateRef.current)
|
|
37
37
|
}
|
|
38
38
|
}, [])
|
|
39
39
|
}
|
|
@@ -4,7 +4,8 @@ import { type MutableRefObject, useEffect, useRef } from 'react'
|
|
|
4
4
|
|
|
5
5
|
import type { KeyChord } from '../input/keymap/key-chord'
|
|
6
6
|
import type { SessionBackend } from '../session-backend/types'
|
|
7
|
-
import type { AppAction
|
|
7
|
+
import type { AppAction } from '../state/actions'
|
|
8
|
+
import type { FocusMode, SnippetRecord, TabSession } from '../state/types'
|
|
8
9
|
|
|
9
10
|
import { INPUT_DEBUG_LOG_PATH, logInputDebug } from '../debug/input-log'
|
|
10
11
|
import { createRawInputHandler } from '../input/raw-input-handler'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { AppState, ProjectRecord, TabSession, WorkspaceRecord } from '../state/types'
|
|
4
|
+
import type { SideEffectContext } from './side-effect-context'
|
|
5
|
+
|
|
6
|
+
import { hasSetupScript } from '../state/project-data'
|
|
7
|
+
import { findSetupTab, runSetup } from './setup-actions'
|
|
8
|
+
|
|
9
|
+
export interface SetupCandidate {
|
|
10
|
+
projectId: string
|
|
11
|
+
workspace: WorkspaceRecord
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Which workspaces should have their setup script run right now.
|
|
16
|
+
*
|
|
17
|
+
* The trigger is "a workspace id this session has not seen before", not "a
|
|
18
|
+
* workspace became active". Activation is the wrong signal: `j`/`k` cycles
|
|
19
|
+
* workspaces, so an activation-based rule would fire the script in every
|
|
20
|
+
* workspace the user scrolls past. Watching the id set also means one rule
|
|
21
|
+
* covers both creation paths — the TUI's `<C-p>` and `aimux workspace create`
|
|
22
|
+
* arriving over the daemon — instead of a hook per call site, which is how the
|
|
23
|
+
* CLI path ends up silently skipping setup.
|
|
24
|
+
*
|
|
25
|
+
* Mutates `seenByProject`: every id examined is marked seen, so a workspace is
|
|
26
|
+
* only ever a candidate once.
|
|
27
|
+
*/
|
|
28
|
+
export function collectSetupCandidates(
|
|
29
|
+
projects: ProjectRecord[],
|
|
30
|
+
tabs: TabSession[],
|
|
31
|
+
seenByProject: Map<string, Set<string>>,
|
|
32
|
+
hasScript: (projectId: string) => boolean
|
|
33
|
+
): SetupCandidate[] {
|
|
34
|
+
const candidates: SetupCandidate[] = []
|
|
35
|
+
|
|
36
|
+
for (const project of projects) {
|
|
37
|
+
const seen = seenByProject.get(project.id)
|
|
38
|
+
const workspaces = project.workspaces ?? []
|
|
39
|
+
|
|
40
|
+
// First sight of a project: record what it already has and run nothing.
|
|
41
|
+
// Without this, switching to a project surfaces N pre-existing workspaces
|
|
42
|
+
// at once and would fire N setups.
|
|
43
|
+
if (!seen) {
|
|
44
|
+
seenByProject.set(project.id, new Set(workspaces.map((w) => w.id)))
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const workspace of workspaces) {
|
|
49
|
+
if (seen.has(workspace.id)) continue
|
|
50
|
+
seen.add(workspace.id)
|
|
51
|
+
|
|
52
|
+
// The primary workspace *is* the user's real repo checkout. Running a
|
|
53
|
+
// script that does `cp .env.example .env` over it would clobber their
|
|
54
|
+
// actual .env.
|
|
55
|
+
if (workspace.source === 'primary') continue
|
|
56
|
+
if (!hasScript(project.id)) continue
|
|
57
|
+
if (findSetupTab(tabs, workspace.id)) continue
|
|
58
|
+
|
|
59
|
+
candidates.push({ projectId: project.id, workspace })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return candidates
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Runs a project's setup script once per newly created workspace.
|
|
68
|
+
*
|
|
69
|
+
* ponytail: a workspace created by the CLI while the app is closed is seeded as
|
|
70
|
+
* pre-existing on the next boot and never auto-runs. The widget's Run button
|
|
71
|
+
* covers it; compare createdAt against setupRanAt if that turns out to matter.
|
|
72
|
+
*
|
|
73
|
+
* The outcome is recorded by `recordSetupExitIfHidden`, called from the
|
|
74
|
+
* backend's exit event, which already carries the exit code.
|
|
75
|
+
*/
|
|
76
|
+
export function useSetupRunner(state: AppState, ctx: SideEffectContext): void {
|
|
77
|
+
const seenByProject = useRef(new Map<string, Set<string>>())
|
|
78
|
+
const ctxRef = useRef(ctx)
|
|
79
|
+
ctxRef.current = ctx
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
// Tabs are read fresh rather than taken as a dependency: their array
|
|
83
|
+
// identity changes on every viewport frame, which would re-run this at the
|
|
84
|
+
// rate the PTYs print. The store is also the more correct source — the
|
|
85
|
+
// render snapshot can lag a setup tab that was just added.
|
|
86
|
+
const candidates = collectSetupCandidates(
|
|
87
|
+
state.projects,
|
|
88
|
+
ctxRef.current.getState().tabs,
|
|
89
|
+
seenByProject.current,
|
|
90
|
+
hasSetupScript
|
|
91
|
+
)
|
|
92
|
+
for (const { projectId, workspace } of candidates) {
|
|
93
|
+
runSetup(ctxRef.current, projectId, workspace)
|
|
94
|
+
}
|
|
95
|
+
}, [state.projects])
|
|
96
|
+
}
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
|
|
11
11
|
import type { TerminalContentOrigin } from '../input/raw-input-handler'
|
|
12
12
|
import type { SessionBackend } from '../session-backend/types'
|
|
13
|
-
import type { AppAction
|
|
13
|
+
import type { AppAction } from '../state/actions'
|
|
14
|
+
import type { AppState } from '../state/types'
|
|
14
15
|
import type { MeasuredPaneRect } from './use-pane-size-report'
|
|
15
16
|
|
|
16
17
|
import { getBarWidth } from '../state/bars'
|
|
@@ -151,7 +152,10 @@ export function useTerminalResize({
|
|
|
151
152
|
const handledBySyncRef = useRef(false)
|
|
152
153
|
const sidebarMountedRef = useRef(false)
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
// Hidden tabs are excluded from the open-loop cascade: their host (the setup
|
|
156
|
+
// widget) measures its own box, and the cascade would overwrite that with the
|
|
157
|
+
// main terminal area's size on every terminal or bar resize.
|
|
158
|
+
const currentTabIds = state.tabs.filter((t) => t.hidden !== true).map((t) => t.id)
|
|
155
159
|
const tabIdsChanged =
|
|
156
160
|
currentTabIds.length !== tabIdsRef.current.length ||
|
|
157
161
|
currentTabIds.some((id, i) => id !== tabIdsRef.current[i])
|
|
@@ -179,15 +183,15 @@ export function useTerminalResize({
|
|
|
179
183
|
// loop so an unchanged box never re-triggers a resize.
|
|
180
184
|
const measuredRef = useRef(new Map<string, { cols: number; rows: number }>())
|
|
181
185
|
|
|
182
|
-
// Drop the dedupe cache when the
|
|
186
|
+
// Drop the dedupe cache when the project changes. attach() re-gates every
|
|
183
187
|
// restored tab's paneReady to false; if a tabId carries the same dimensions
|
|
184
|
-
// across
|
|
188
|
+
// across projects, handleMeasure's prev-match would short-circuit and
|
|
185
189
|
// never call resizeTab({confirmedFromMeasurement:true}), leaving the gate
|
|
186
190
|
// closed forever — the new pane then paints a stale buffered snapshot from
|
|
187
|
-
// the old
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
+
// the old project, which is the 2–3 row offset users see.
|
|
192
|
+
const lastProjectIdRef = useRef(state.currentProjectId)
|
|
193
|
+
if (lastProjectIdRef.current !== state.currentProjectId) {
|
|
194
|
+
lastProjectIdRef.current = state.currentProjectId
|
|
191
195
|
measuredRef.current.clear()
|
|
192
196
|
}
|
|
193
197
|
|
|
@@ -229,13 +233,13 @@ export function useTerminalResize({
|
|
|
229
233
|
const leftBarWidth = getBarWidth(state.bars.left)
|
|
230
234
|
const rightBarWidth = getBarWidth(state.bars.right)
|
|
231
235
|
const terminalSize = useMemo(() => {
|
|
232
|
-
const
|
|
233
|
-
const
|
|
236
|
+
const projectBarRows = state.projectBar.visible ? 1 : 0
|
|
237
|
+
const projectBarTopOffset = projectBarRows
|
|
234
238
|
const reservedRows =
|
|
235
239
|
MAIN_AREA_VERTICAL_PADDING +
|
|
236
240
|
STATUS_BAR_HEIGHT +
|
|
237
241
|
TERMINAL_PANE_VERTICAL_CHROME +
|
|
238
|
-
|
|
242
|
+
projectBarRows
|
|
239
243
|
const cols = Math.max(
|
|
240
244
|
MIN_TERMINAL_COLS,
|
|
241
245
|
Math.floor(dimensions.width - leftBarWidth - rightBarWidth - MAIN_AREA_HORIZONTAL_CHROME)
|
|
@@ -246,7 +250,7 @@ export function useTerminalResize({
|
|
|
246
250
|
cols,
|
|
247
251
|
rows,
|
|
248
252
|
x: leftBarWidth + 1,
|
|
249
|
-
y: 1 +
|
|
253
|
+
y: 1 + projectBarTopOffset,
|
|
250
254
|
}
|
|
251
255
|
|
|
252
256
|
return { cols, rows }
|
|
@@ -256,7 +260,7 @@ export function useTerminalResize({
|
|
|
256
260
|
dimensions.width,
|
|
257
261
|
leftBarWidth,
|
|
258
262
|
rightBarWidth,
|
|
259
|
-
state.
|
|
263
|
+
state.projectBar.visible,
|
|
260
264
|
])
|
|
261
265
|
|
|
262
266
|
useLayoutEffect(() => {
|
|
@@ -278,7 +282,7 @@ export function useTerminalResize({
|
|
|
278
282
|
})
|
|
279
283
|
handledBySyncRef.current = true
|
|
280
284
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
281
|
-
}, [leftBarWidth, rightBarWidth, state.
|
|
285
|
+
}, [leftBarWidth, rightBarWidth, state.projectBar.visible])
|
|
282
286
|
|
|
283
287
|
useEffect(() => {
|
|
284
288
|
if (handledBySyncRef.current) {
|