@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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-workspace activity, and the notification sound that goes with it.
|
|
3
|
+
*
|
|
4
|
+
* The store only holds tabs for the project the client is attached to, but the
|
|
5
|
+
* daemon broadcasts every tab's status with its `workspaceId` (v18). So the
|
|
6
|
+
* table of "which tab is doing what" lives here, module-owned, and only the
|
|
7
|
+
* aggregate per workspace is dispatched into `AppState.workspaceActivity` —
|
|
8
|
+
* which is what the sidebar draws for every project, current or not.
|
|
9
|
+
*
|
|
10
|
+
* Both notification sounds are triggered from here so the glyph and the sound
|
|
11
|
+
* can never disagree about what happened:
|
|
12
|
+
* - a tab entering `waiting-input` (the agent is asking something),
|
|
13
|
+
* - a tab completing a turn (the daemon's settled end-of-turn signal).
|
|
14
|
+
* Neither fires for the tab you are looking at.
|
|
15
|
+
*/
|
|
16
|
+
import type { TabActivity } from '../state/types'
|
|
17
|
+
|
|
18
|
+
import { playNotificationSound } from '../settings/sections/notifications'
|
|
19
|
+
import { appStore } from '../state/app-store'
|
|
20
|
+
import { dispatchGlobal } from '../state/dispatch-ref'
|
|
21
|
+
|
|
22
|
+
interface Entry {
|
|
23
|
+
status: TabActivity
|
|
24
|
+
workspaceId: string | undefined
|
|
25
|
+
/**
|
|
26
|
+
* Whether this client has seen the tab do something since the last turn it
|
|
27
|
+
* announced. The daemon's end-of-turn timer is per-process: restart it (an
|
|
28
|
+
* update, `restart-daemon`) and every surviving idle PTY produces a
|
|
29
|
+
* turn-complete a settle window later. Requiring an observed `working` /
|
|
30
|
+
* `waiting-input` first is what keeps that restart from ticking every
|
|
31
|
+
* workspace and ringing.
|
|
32
|
+
*/
|
|
33
|
+
armed: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A tab in another project never sends this client its `tabExit`, so an entry
|
|
37
|
+
// here can outlive its PTY. The daemon emits a final `idle` for a tab it stops
|
|
38
|
+
// seeing (the tick cleanup in `assistant-status-detection-loop.ts`), which is
|
|
39
|
+
// what keeps a tab that died mid-turn from pinning a spinner to a workspace
|
|
40
|
+
// row forever; the entry itself is left behind, contributing nothing.
|
|
41
|
+
const entries = new Map<string, Entry>()
|
|
42
|
+
|
|
43
|
+
/** The two flags a set of tabs adds up to. Pure, and the reason it is exported. */
|
|
44
|
+
export function aggregate(statuses: readonly TabActivity[]): {
|
|
45
|
+
working: boolean
|
|
46
|
+
waiting: boolean
|
|
47
|
+
} {
|
|
48
|
+
return {
|
|
49
|
+
waiting: statuses.includes('waiting-input'),
|
|
50
|
+
working: statuses.includes('working'),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function publish(workspaceId: string | undefined): void {
|
|
55
|
+
if (workspaceId == null || workspaceId === '') return
|
|
56
|
+
const statuses: TabActivity[] = []
|
|
57
|
+
for (const entry of entries.values()) {
|
|
58
|
+
if (entry.workspaceId === workspaceId) statuses.push(entry.status)
|
|
59
|
+
}
|
|
60
|
+
const { waiting, working } = aggregate(statuses)
|
|
61
|
+
dispatchGlobal({ type: 'set-workspace-activity', waiting, working, workspaceId })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** True when this tab is the one on screen — no sound, no unseen-tick, for it. */
|
|
65
|
+
function isBeingWatched(tabId: string): boolean {
|
|
66
|
+
return appStore.getState().activeTabId === tabId
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function recordTabStatus(
|
|
70
|
+
tabId: string,
|
|
71
|
+
status: TabActivity,
|
|
72
|
+
workspaceId: string | undefined,
|
|
73
|
+
options?: { silent?: boolean }
|
|
74
|
+
): void {
|
|
75
|
+
const previous = entries.get(tabId)
|
|
76
|
+
const armed = status === 'working' || status === 'waiting-input' || (previous?.armed ?? false)
|
|
77
|
+
// An event that carries no workspace does not mean the tab lost one. A tab
|
|
78
|
+
// created before the daemon recorded workspaces has none in its registry,
|
|
79
|
+
// but the client's own snapshot does — and the attach seeded us with it, so
|
|
80
|
+
// keep it rather than blanking the row on the next status change.
|
|
81
|
+
const known = workspaceId ?? previous?.workspaceId
|
|
82
|
+
entries.set(tabId, { armed, status, workspaceId: known })
|
|
83
|
+
// A tab can be reassigned to another workspace (workspace move); the one it
|
|
84
|
+
// left has one tab fewer and has to be recomputed too.
|
|
85
|
+
if (previous && previous.workspaceId !== known) publish(previous.workspaceId)
|
|
86
|
+
publish(known)
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
options?.silent !== true &&
|
|
90
|
+
status === 'waiting-input' &&
|
|
91
|
+
previous?.status !== 'waiting-input' &&
|
|
92
|
+
!isBeingWatched(tabId)
|
|
93
|
+
) {
|
|
94
|
+
playNotificationSound()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Seed from the tabs an attach handed us, so a workspace whose agent has been
|
|
100
|
+
* waiting since before this client started says so on the first paint instead
|
|
101
|
+
* of waiting for a transition that already happened. Silent by definition:
|
|
102
|
+
* nothing here is news.
|
|
103
|
+
*/
|
|
104
|
+
export function seedTabActivity(
|
|
105
|
+
tabs: readonly { id: string; activity?: TabActivity; workspaceId?: string }[]
|
|
106
|
+
): void {
|
|
107
|
+
for (const tab of tabs) {
|
|
108
|
+
recordTabStatus(tab.id, tab.activity ?? 'idle', tab.workspaceId, { silent: true })
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function recordTurnComplete(tabId: string, workspaceId: string | undefined): void {
|
|
113
|
+
const entry = entries.get(tabId)
|
|
114
|
+
// Nothing observed since the last turn — this is a restarted daemon
|
|
115
|
+
// re-announcing the end of a turn that ended before we were listening.
|
|
116
|
+
if (entry?.armed !== true) return
|
|
117
|
+
entry.armed = false
|
|
118
|
+
if (isBeingWatched(tabId)) return
|
|
119
|
+
if (workspaceId != null && workspaceId !== '') {
|
|
120
|
+
dispatchGlobal({ type: 'mark-workspace-done', workspaceId })
|
|
121
|
+
}
|
|
122
|
+
// The workspace tick says where to look; this says which tab rang. A no-op
|
|
123
|
+
// for a tab this client doesn't hold — its workspace row is the answer there.
|
|
124
|
+
dispatchGlobal({ tabId, type: 'mark-tab-unseen' })
|
|
125
|
+
playNotificationSound()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function forgetTabActivity(tabId: string): void {
|
|
129
|
+
const entry = entries.get(tabId)
|
|
130
|
+
if (!entry) return
|
|
131
|
+
entries.delete(tabId)
|
|
132
|
+
publish(entry.workspaceId)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Test seam: drop every remembered tab. */
|
|
136
|
+
export function resetWorkspaceActivity(): void {
|
|
137
|
+
entries.clear()
|
|
138
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Names a workspace after the prompt that created it.
|
|
2
|
+
//
|
|
3
|
+
// Two stages, because a model call takes seconds and workspace creation must
|
|
4
|
+
// not wait on the network: a local heuristic name is applied at creation, then
|
|
5
|
+
// the generated one replaces it in place when it arrives. The heuristic result
|
|
6
|
+
// is not a stopgap to be embarrassed about — when no headless CLI is installed
|
|
7
|
+
// it is the final name, and it is still derived from what the user asked for.
|
|
8
|
+
|
|
9
|
+
import type { AssistantId, WorkspaceRecord } from '../state/types'
|
|
10
|
+
|
|
11
|
+
import { heuristicTitle } from '../auto-rename/heuristic-title'
|
|
12
|
+
import { generateTabTitle, type TitleSpawnFn } from '../auto-rename/title-runner'
|
|
13
|
+
import { renameGitBranch } from '../git/worktree'
|
|
14
|
+
import { sanitizePathSegment } from '../platform/worktree-paths'
|
|
15
|
+
|
|
16
|
+
/** Model naming is best-effort background work; it must never outlive the app. */
|
|
17
|
+
const NAMING_TIMEOUT_MS = 30_000
|
|
18
|
+
|
|
19
|
+
export const BRANCH_PREFIX = 'aimux/'
|
|
20
|
+
const BRANCH_SLUG_MAX = 40
|
|
21
|
+
|
|
22
|
+
export function branchNameFor(name: string): string {
|
|
23
|
+
return `${BRANCH_PREFIX}${sanitizePathSegment(name, BRANCH_SLUG_MAX).toLowerCase()}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The name a workspace carries until the model answers. Undefined when the
|
|
28
|
+
* prompt yields nothing usable, which lets `createAimuxTempWorkspace` fall back
|
|
29
|
+
* to its own `wt-<project>` default.
|
|
30
|
+
*/
|
|
31
|
+
export function placeholderWorkspaceName(prompt: string): string | undefined {
|
|
32
|
+
return heuristicTitle(prompt) ?? undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface WorkspaceNamingTarget {
|
|
36
|
+
projectId: string
|
|
37
|
+
workspace: WorkspaceRecord
|
|
38
|
+
prompt: string
|
|
39
|
+
/** Provider used for the naming call — the assistant the user just picked. */
|
|
40
|
+
provider: AssistantId
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface WorkspaceNamingDeps {
|
|
44
|
+
applyName: (projectId: string, workspaceId: string, patch: Partial<WorkspaceRecord>) => void
|
|
45
|
+
renameBranch?: (repoRoot: string, from: string, to: string) => Promise<boolean>
|
|
46
|
+
spawn?: TitleSpawnFn
|
|
47
|
+
signal?: AbortSignal
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Replace a workspace's placeholder name and branch with ones generated from
|
|
52
|
+
* the prompt. Silent on every failure: a missing CLI, a timeout or a branch
|
|
53
|
+
* collision all leave the placeholder standing, which is a perfectly usable
|
|
54
|
+
* outcome and not worth a toast.
|
|
55
|
+
*
|
|
56
|
+
* ponytail: fixed timeout, no model override. Read `autoRename.models` here if
|
|
57
|
+
* anyone wants naming on a cheaper model than the tab's assistant.
|
|
58
|
+
*/
|
|
59
|
+
export async function renameWorkspaceFromPrompt(
|
|
60
|
+
target: WorkspaceNamingTarget,
|
|
61
|
+
deps: WorkspaceNamingDeps
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
const result = await generateTabTitle({
|
|
64
|
+
firstPrompt: target.prompt,
|
|
65
|
+
provider: target.provider,
|
|
66
|
+
signal: deps.signal ?? new AbortController().signal,
|
|
67
|
+
spawn: deps.spawn,
|
|
68
|
+
timeoutMs: NAMING_TIMEOUT_MS,
|
|
69
|
+
})
|
|
70
|
+
if (result.status !== 'ok') return
|
|
71
|
+
|
|
72
|
+
const { workspace } = target
|
|
73
|
+
if (result.title === workspace.name) return
|
|
74
|
+
|
|
75
|
+
const branch = branchNameFor(result.title)
|
|
76
|
+
const rename = deps.renameBranch ?? renameGitBranch
|
|
77
|
+
// Renamed from inside the workspace, not the main checkout: the branch is
|
|
78
|
+
// that worktree's current branch, which is the form git always accepts.
|
|
79
|
+
// Renaming first means a refusal (the name is taken) leaves the record
|
|
80
|
+
// pointing at the branch that actually exists.
|
|
81
|
+
const branchRenamed =
|
|
82
|
+
workspace.branch != null && workspace.branch !== '' && branch !== workspace.branch
|
|
83
|
+
? await rename(workspace.path, workspace.branch, branch)
|
|
84
|
+
: false
|
|
85
|
+
|
|
86
|
+
deps.applyName(target.projectId, workspace.id, {
|
|
87
|
+
name: result.title,
|
|
88
|
+
...(branchRenamed ? { branch } : null),
|
|
89
|
+
})
|
|
90
|
+
}
|
package/src/app.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AimuxUserConfig,
|
|
2
3
|
type ResolvedConfig,
|
|
3
4
|
setAutoCommitEnabled,
|
|
4
5
|
setExternalEditorConfig,
|
|
@@ -8,20 +9,23 @@ import {
|
|
|
8
9
|
import { useKeyboard, useRenderer, useTerminalDimensions } from '@opentui/react'
|
|
9
10
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
10
11
|
|
|
12
|
+
import type { SideEffectContext } from './app-runtime/side-effect-context'
|
|
11
13
|
import type { KeyChord } from './input/keymap/key-chord'
|
|
12
14
|
import type { TrieBinding } from './input/keymap/trie'
|
|
13
15
|
import type { KeyResult, ModeContext, ModeId } from './input/modes/types'
|
|
14
16
|
import type { TerminalContentOrigin } from './input/raw-input-handler'
|
|
15
17
|
import type { SessionBackend } from './session-backend/types'
|
|
16
18
|
|
|
17
|
-
import {
|
|
19
|
+
import { setActiveMeasure } from './app-runtime/measure-ref'
|
|
20
|
+
import { executeSideEffect } from './app-runtime/side-effects'
|
|
18
21
|
import { useAutoCommitDriver } from './app-runtime/use-auto-commit-driver'
|
|
19
22
|
import { useBackendRuntime } from './app-runtime/use-backend-runtime'
|
|
20
23
|
import { useDirectorySearch } from './app-runtime/use-directory-search'
|
|
21
24
|
import { useMouseHandlers } from './app-runtime/use-mouse-handlers'
|
|
25
|
+
import { useProjectAutosave } from './app-runtime/use-project-autosave'
|
|
22
26
|
import { useRendererBindings } from './app-runtime/use-renderer-bindings'
|
|
27
|
+
import { useSetupRunner } from './app-runtime/use-setup-runner'
|
|
23
28
|
import { useTerminalResize } from './app-runtime/use-terminal-resize'
|
|
24
|
-
import { useWorkspaceAutosave } from './app-runtime/use-workspace-autosave'
|
|
25
29
|
import { loadConfig, saveConfig } from './config'
|
|
26
30
|
import { enqueueGitOp } from './git/command-queue'
|
|
27
31
|
import { pruneOrphanAimuxBranches } from './git/worktree'
|
|
@@ -34,11 +38,19 @@ import { highlightSnapshot, warmClaudeSyntaxOverlay } from './integrations/claud
|
|
|
34
38
|
import { ensureClaudeSettingsThemePref, syncClaudeTheme } from './integrations/claude-theme-sync'
|
|
35
39
|
import { getProfileConfigDir, getProfileName } from './profile-paths'
|
|
36
40
|
import { startAIUsageService } from './services/ai-usage/provider'
|
|
41
|
+
import {
|
|
42
|
+
useAIUsageConfig,
|
|
43
|
+
useAutoCommitConfig,
|
|
44
|
+
useHarmonizeClaudeTheme,
|
|
45
|
+
useSnippetTriggerChar,
|
|
46
|
+
} from './settings/live'
|
|
47
|
+
import { ALL_SETTING_ROWS } from './settings/sections'
|
|
48
|
+
import { hydrateSettings } from './settings/settings-store'
|
|
37
49
|
import { aiUsageStore } from './state/ai-usage-store'
|
|
38
50
|
import { appStore, useAppStore } from './state/app-store'
|
|
39
51
|
import { setActiveDispatch, setActiveSideEffectRunner } from './state/dispatch-ref'
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
52
|
+
import { findMostRecentProject, loadProjectCatalog } from './state/project-catalog'
|
|
53
|
+
import { getActiveWorkspacePath } from './state/project-workspaces'
|
|
42
54
|
import { loadSnippetCatalog, mergeConfigSnippets } from './state/snippet-catalog'
|
|
43
55
|
import { createInitialState } from './state/store'
|
|
44
56
|
import { toast } from './state/toast-store'
|
|
@@ -59,14 +71,16 @@ import {
|
|
|
59
71
|
isNewerVersion,
|
|
60
72
|
} from './update/version-check'
|
|
61
73
|
|
|
62
|
-
const
|
|
74
|
+
const PROJECT_SAVE_DEBOUNCE_MS = 250
|
|
63
75
|
|
|
64
76
|
export function App({
|
|
65
77
|
backend,
|
|
66
78
|
resolvedConfig,
|
|
79
|
+
userConfig,
|
|
67
80
|
}: {
|
|
68
81
|
backend: SessionBackend
|
|
69
82
|
resolvedConfig: ResolvedConfig
|
|
83
|
+
userConfig: AimuxUserConfig
|
|
70
84
|
}) {
|
|
71
85
|
// Publish the auto-commit enabled flag before any children render so
|
|
72
86
|
// actions (which live outside React) can read it synchronously.
|
|
@@ -104,72 +118,42 @@ export function App({
|
|
|
104
118
|
// selector reads. We discard the value — the store is the source of truth.
|
|
105
119
|
useState(() => {
|
|
106
120
|
const json = loadConfig()
|
|
107
|
-
const
|
|
108
|
-
resolvedConfig.
|
|
109
|
-
const sidebarOverrides = json.sidebar
|
|
110
|
-
|
|
121
|
+
const projectBarVisible =
|
|
122
|
+
resolvedConfig.projectBar?.initialVisible ?? json.projectBarVisible ?? true
|
|
111
123
|
// Merge config-file gitPane (persisted prefs) with user's resolved gitPane
|
|
112
124
|
// (programmatic config). User config wins; file provides persisted prior state.
|
|
113
125
|
const userGitPane = resolvedConfig.gitPane
|
|
114
|
-
const fileListMode = userGitPane?.initialFileListMode ?? json.gitPane?.fileListMode ?? 'tree'
|
|
115
|
-
const diffModeRatio = userGitPane?.initialDiffModeRatio ?? json.gitPane?.diffModeRatio ?? 0.35
|
|
116
|
-
const treeCompaction =
|
|
117
|
-
userGitPane?.initialTreeCompaction ?? json.gitPane?.treeCompaction ?? true
|
|
118
|
-
const prefetchRadius = userGitPane?.prefetchRadius ?? json.gitPane?.prefetchRadius ?? 5
|
|
119
|
-
const persistedPaneRatio = json.gitPane?.paneRatio ?? json.gitPane?.ratio ?? 0.5
|
|
120
|
-
const persistedEmbeddedRatio = json.gitPane?.embeddedRatio ?? json.gitPane?.ratio ?? 0.5
|
|
121
126
|
const gitPaneOverrides = {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
? userGitPane.initialRatio
|
|
127
|
-
: persistedEmbeddedRatio,
|
|
128
|
-
fileListMode,
|
|
129
|
-
paneRatio:
|
|
130
|
-
userGitPane?.initialMode === 'pane' && userGitPane?.initialRatio !== undefined
|
|
131
|
-
? userGitPane.initialRatio
|
|
132
|
-
: persistedPaneRatio,
|
|
133
|
-
prefetchRadius,
|
|
134
|
-
treeCompaction,
|
|
135
|
-
...(userGitPane?.initialVisible !== undefined ? { visible: userGitPane.initialVisible } : {}),
|
|
136
|
-
...(userGitPane?.initialMode !== undefined ? { mode: userGitPane.initialMode } : {}),
|
|
137
|
-
...(userGitPane?.initialPosition !== undefined
|
|
138
|
-
? { position: userGitPane.initialPosition }
|
|
139
|
-
: {}),
|
|
140
|
-
...(userGitPane?.initialDiffModeRatio !== undefined
|
|
141
|
-
? { diffModeRatio: userGitPane.initialDiffModeRatio }
|
|
142
|
-
: {}),
|
|
127
|
+
diffModeRatio: userGitPane?.initialDiffModeRatio ?? json.gitPane?.diffModeRatio ?? 0.35,
|
|
128
|
+
fileListMode: userGitPane?.initialFileListMode ?? json.gitPane?.fileListMode ?? 'tree',
|
|
129
|
+
prefetchRadius: userGitPane?.prefetchRadius ?? json.gitPane?.prefetchRadius ?? 5,
|
|
130
|
+
treeCompaction: userGitPane?.initialTreeCompaction ?? json.gitPane?.treeCompaction ?? true,
|
|
143
131
|
...(userGitPane?.path !== undefined ? { path: userGitPane.path } : {}),
|
|
144
132
|
...(userGitPane?.diffCount !== undefined ? { diffCount: userGitPane.diffCount } : {}),
|
|
145
133
|
}
|
|
146
134
|
|
|
147
|
-
const
|
|
135
|
+
const projectCatalog = loadProjectCatalog()
|
|
148
136
|
const mergedSnippets = mergeConfigSnippets(loadSnippetCatalog(), resolvedConfig.snippets)
|
|
149
137
|
const initial = createInitialState(
|
|
150
138
|
json.customCommands,
|
|
151
|
-
|
|
139
|
+
projectCatalog,
|
|
152
140
|
mergedSnippets,
|
|
153
|
-
|
|
141
|
+
projectCatalog.length === 0,
|
|
154
142
|
{
|
|
143
|
+
bars: json.bars,
|
|
155
144
|
gitPane: gitPaneOverrides,
|
|
156
|
-
|
|
157
|
-
sidebar: sidebarOverrides,
|
|
158
|
-
worktreeTemplates:
|
|
159
|
-
resolvedConfig.worktreeTemplates.length > 0
|
|
160
|
-
? resolvedConfig.worktreeTemplates
|
|
161
|
-
: json.worktreeTemplates,
|
|
145
|
+
projectBarVisible,
|
|
162
146
|
}
|
|
163
147
|
)
|
|
164
148
|
// Replace the module-level default with the fully-resolved initial state.
|
|
165
149
|
// Preserves the dispatch baked into the store by app-store.ts.
|
|
166
150
|
appStore.setState(initial)
|
|
167
|
-
const mostRecent =
|
|
151
|
+
const mostRecent = findMostRecentProject(projectCatalog)
|
|
168
152
|
if (mostRecent) {
|
|
169
153
|
appStore.getState().dispatch({
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
154
|
+
projectId: mostRecent.id,
|
|
155
|
+
projectSnapshot: mostRecent.projectSnapshot,
|
|
156
|
+
type: 'load-project',
|
|
173
157
|
})
|
|
174
158
|
}
|
|
175
159
|
return null
|
|
@@ -182,6 +166,15 @@ export function App({
|
|
|
182
166
|
const state = useAppStore((s) => s)
|
|
183
167
|
const dispatch = state.dispatch
|
|
184
168
|
|
|
169
|
+
// Config the settings screen can change under us. The resolved config file is
|
|
170
|
+
// the baseline each of these falls back to.
|
|
171
|
+
const autoCommitConfig = useAutoCommitConfig(resolvedConfig.autoCommit)
|
|
172
|
+
const aiUsageConfig = useAIUsageConfig(resolvedConfig.statusBar?.aiUsage)
|
|
173
|
+
const harmonizeClaudeTheme = useHarmonizeClaudeTheme(
|
|
174
|
+
resolvedConfig.theme?.beta?.harmonizeClaudeTheme
|
|
175
|
+
)
|
|
176
|
+
const snippetTriggerChar = useSnippetTriggerChar(resolvedConfig.snippetTriggerChar)
|
|
177
|
+
|
|
185
178
|
useLayoutEffect(() => {
|
|
186
179
|
setActiveDispatch(dispatch)
|
|
187
180
|
return () => {
|
|
@@ -190,14 +183,26 @@ export function App({
|
|
|
190
183
|
}
|
|
191
184
|
}, [dispatch])
|
|
192
185
|
|
|
186
|
+
// Replays what the settings screen has written on top of the config file (the
|
|
187
|
+
// setters at the top of this component are its baseline). Deliberately after
|
|
188
|
+
// the effect above and never during render: a row applies its value through
|
|
189
|
+
// whatever owns it, and some of those owners are reached by dispatching.
|
|
190
|
+
// Layout effect, so it lands before the first paint.
|
|
191
|
+
useLayoutEffect(() => {
|
|
192
|
+
hydrateSettings(ALL_SETTING_ROWS, userConfig)
|
|
193
|
+
// Once per launch: the config file is read at startup and does not change
|
|
194
|
+
// under us, and every later write goes through `writeRow`.
|
|
195
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
196
|
+
}, [])
|
|
197
|
+
|
|
193
198
|
useEffect(() => {
|
|
194
|
-
if (!
|
|
199
|
+
if (!harmonizeClaudeTheme) return
|
|
195
200
|
ensureClaudeSettingsThemePref()
|
|
196
201
|
syncClaudeTheme(getCurrentTheme(), getCurrentMode())
|
|
197
202
|
return subscribeThemeChanges((resolved, mode) => {
|
|
198
203
|
syncClaudeTheme(resolved, mode)
|
|
199
204
|
})
|
|
200
|
-
}, [
|
|
205
|
+
}, [harmonizeClaudeTheme])
|
|
201
206
|
|
|
202
207
|
useEffect(() => {
|
|
203
208
|
// Opt-in via `integrations.claudeHooks` in aimux.config.ts. When enabled,
|
|
@@ -210,13 +215,12 @@ export function App({
|
|
|
210
215
|
}, [resolvedConfig.integrations.claudeHooks])
|
|
211
216
|
|
|
212
217
|
useEffect(() => {
|
|
213
|
-
|
|
214
|
-
if (!(aiUsage?.enabled === true)) {
|
|
218
|
+
if (!(aiUsageConfig.enabled === true)) {
|
|
215
219
|
aiUsageStore.getState().setEnabled(false)
|
|
216
220
|
return
|
|
217
221
|
}
|
|
218
222
|
aiUsageStore.getState().setEnabled(true)
|
|
219
|
-
const handle = startAIUsageService(
|
|
223
|
+
const handle = startAIUsageService(aiUsageConfig, (snap) => {
|
|
220
224
|
aiUsageStore.getState().setSnapshot(snap)
|
|
221
225
|
})
|
|
222
226
|
return () => {
|
|
@@ -224,7 +228,9 @@ export function App({
|
|
|
224
228
|
aiUsageStore.getState().clear()
|
|
225
229
|
aiUsageStore.getState().setEnabled(false)
|
|
226
230
|
}
|
|
227
|
-
|
|
231
|
+
// Memoized by `useAIUsageConfig`, so toggling the indicator restarts the
|
|
232
|
+
// service and nothing else does.
|
|
233
|
+
}, [aiUsageConfig])
|
|
228
234
|
|
|
229
235
|
useEffect(() => {
|
|
230
236
|
if (process.env.AIMUX_DISABLE_UPDATE_CHECK === '1') return
|
|
@@ -254,16 +260,16 @@ export function App({
|
|
|
254
260
|
}, [])
|
|
255
261
|
|
|
256
262
|
useEffect(() => {
|
|
257
|
-
// One-shot cleanup: prune `aimux/` branches orphaned by temp
|
|
263
|
+
// One-shot cleanup: prune `aimux/` branches orphaned by temp workspaces that
|
|
258
264
|
// were deleted before delete-time branch cleanup existed. Gated by a config
|
|
259
265
|
// flag so it runs once per machine. All git calls are best-effort (nothrow)
|
|
260
|
-
// and git protects branches still checked out in a live
|
|
266
|
+
// and git protects branches still checked out in a live workspace.
|
|
261
267
|
if (loadConfig().prunedOrphanAimuxBranches === true) return
|
|
262
268
|
void (async () => {
|
|
263
269
|
const repoRoots = new Set<string>()
|
|
264
|
-
for (const
|
|
265
|
-
for (const
|
|
266
|
-
if (
|
|
270
|
+
for (const project of loadProjectCatalog()) {
|
|
271
|
+
for (const workspace of project.workspaces ?? []) {
|
|
272
|
+
if (workspace.repoRoot !== '') repoRoots.add(workspace.repoRoot)
|
|
267
273
|
}
|
|
268
274
|
}
|
|
269
275
|
let removed = 0
|
|
@@ -282,15 +288,15 @@ export function App({
|
|
|
282
288
|
// still the bootstrap default, and useBackendRuntime's attach effect would
|
|
283
289
|
// then hand 80x24 to the backend, resizing the daemon-persisted PTYs (or
|
|
284
290
|
// newly-spawned ones) to the wrong size and stranding the assistant's
|
|
285
|
-
// already-drawn content in the top-left until a
|
|
291
|
+
// already-drawn content in the top-left until a project switch.
|
|
286
292
|
const layoutRef = useRef(state.layout)
|
|
287
293
|
const activeTab = useMemo(
|
|
288
294
|
() => state.tabs.find((tab) => tab.id === state.activeTabId),
|
|
289
295
|
[state.activeTabId, state.tabs]
|
|
290
296
|
)
|
|
291
|
-
const
|
|
292
|
-
() => state.
|
|
293
|
-
[state.
|
|
297
|
+
const currentProject = useMemo(
|
|
298
|
+
() => state.projects.find((project) => project.id === state.currentProjectId),
|
|
299
|
+
[state.currentProjectId, state.projects]
|
|
294
300
|
)
|
|
295
301
|
const activeMouseForwardingEnabled = activeTab?.terminalModes.mouseTrackingMode !== 'none'
|
|
296
302
|
const activeLocalScrollbackEnabled =
|
|
@@ -311,11 +317,11 @@ export function App({
|
|
|
311
317
|
snippetsRef.current = state.snippets
|
|
312
318
|
const branchRef = useRef(state.gitPanel.branch)
|
|
313
319
|
branchRef.current = state.gitPanel.branch
|
|
314
|
-
const triggerCharRef = useRef(
|
|
315
|
-
triggerCharRef.current =
|
|
320
|
+
const triggerCharRef = useRef(snippetTriggerChar)
|
|
321
|
+
triggerCharRef.current = snippetTriggerChar
|
|
316
322
|
|
|
317
323
|
const contentOriginRef = useRef<TerminalContentOrigin>({ cols: 0, rows: 0, x: 0, y: 0 })
|
|
318
|
-
const
|
|
324
|
+
const currentProjectProjectSnapshot = currentProject?.projectSnapshot
|
|
319
325
|
|
|
320
326
|
// Must run before useBackendRuntime so its open-loop terminalSize seeds
|
|
321
327
|
// layoutRef before the attach effect reads it. See the comment on layoutRef.
|
|
@@ -329,6 +335,15 @@ export function App({
|
|
|
329
335
|
})
|
|
330
336
|
layoutRef.current = { terminalCols: terminalSize.cols, terminalRows: terminalSize.rows }
|
|
331
337
|
|
|
338
|
+
// Reaches the setup widget, which hosts its own PTY outside root.tsx's pane
|
|
339
|
+
// tree and so never sees the onMeasure prop chain.
|
|
340
|
+
useLayoutEffect(() => {
|
|
341
|
+
setActiveMeasure(terminalSize.onMeasure)
|
|
342
|
+
return () => {
|
|
343
|
+
setActiveMeasure(null)
|
|
344
|
+
}
|
|
345
|
+
}, [terminalSize.onMeasure])
|
|
346
|
+
|
|
332
347
|
const syntaxOverlayFlag = resolvedConfig.theme?.beta?.experimentalSyntaxHighlight === true
|
|
333
348
|
const syntaxOverlayFlagRef = useRef(syntaxOverlayFlag)
|
|
334
349
|
syntaxOverlayFlagRef.current = syntaxOverlayFlag
|
|
@@ -363,18 +378,20 @@ export function App({
|
|
|
363
378
|
const { clearIdleTimer, clearStartupGrace, startStartupGrace } = useBackendRuntime({
|
|
364
379
|
activeTabId: state.activeTabId,
|
|
365
380
|
backend,
|
|
366
|
-
|
|
367
|
-
|
|
381
|
+
currentProjectId: state.currentProjectId,
|
|
382
|
+
currentProjectProjectSnapshot,
|
|
368
383
|
dispatch,
|
|
369
384
|
layoutRef,
|
|
370
385
|
resizingRef,
|
|
371
386
|
syntaxOverlayEnabled,
|
|
372
387
|
})
|
|
373
388
|
|
|
374
|
-
|
|
389
|
+
useProjectAutosave(state, PROJECT_SAVE_DEBOUNCE_MS)
|
|
375
390
|
useDirectorySearch(state.modal, dispatch)
|
|
376
391
|
useAutoCommitDriver({
|
|
377
|
-
|
|
392
|
+
// Through the settings screen, so a change lands without a restart. The
|
|
393
|
+
// resolved config is the baseline it falls back to.
|
|
394
|
+
config: autoCommitConfig,
|
|
378
395
|
dispatch,
|
|
379
396
|
getProfileConfigRoot: getProfileConfigDir,
|
|
380
397
|
state,
|
|
@@ -382,13 +399,12 @@ export function App({
|
|
|
382
399
|
})
|
|
383
400
|
|
|
384
401
|
const {
|
|
385
|
-
|
|
386
|
-
|
|
402
|
+
handleBarBoundaryResizeStart,
|
|
403
|
+
handleBarResizeStart,
|
|
387
404
|
handlePaneActivate,
|
|
388
405
|
handleSeparatorDrag,
|
|
389
406
|
handleSeparatorDragEnd,
|
|
390
407
|
handleSeparatorDragStart,
|
|
391
|
-
handleSidebarResizeStart,
|
|
392
408
|
handleSplitResize,
|
|
393
409
|
handleTerminalClick,
|
|
394
410
|
handleTerminalDrag,
|
|
@@ -443,9 +459,9 @@ export function App({
|
|
|
443
459
|
clearIdleTimer,
|
|
444
460
|
clearStartupGrace,
|
|
445
461
|
dispatch,
|
|
446
|
-
|
|
447
|
-
if (!(state.
|
|
448
|
-
return
|
|
462
|
+
getCurrentProjectProjectPath: () => {
|
|
463
|
+
if (!(state.currentProjectId != null && state.currentProjectId !== '')) return
|
|
464
|
+
return getActiveWorkspacePath(state.projects.find((s) => s.id === state.currentProjectId))
|
|
449
465
|
},
|
|
450
466
|
// Read straight from the store, not stateRef. stateRef is only refreshed
|
|
451
467
|
// on render, so within a single JS turn (a click handler that dispatches
|
|
@@ -459,6 +475,8 @@ export function App({
|
|
|
459
475
|
themeId,
|
|
460
476
|
}
|
|
461
477
|
|
|
478
|
+
useSetupRunner(state, sideEffectCtx)
|
|
479
|
+
|
|
462
480
|
function processKeyResult(result: KeyResult, modeId: ModeId): void {
|
|
463
481
|
for (const action of result.actions) {
|
|
464
482
|
dispatch(action)
|
|
@@ -515,12 +533,12 @@ export function App({
|
|
|
515
533
|
dispatch({ tabId: target.tabId, type: 'set-active-tab' })
|
|
516
534
|
return
|
|
517
535
|
}
|
|
518
|
-
if (target.kind === '
|
|
536
|
+
if (target.kind === 'project' || target.kind === 'workspace') {
|
|
519
537
|
executeSideEffect(
|
|
520
538
|
{
|
|
521
|
-
index: target.
|
|
522
|
-
type: 'switch-
|
|
523
|
-
|
|
539
|
+
index: target.projectIndex,
|
|
540
|
+
type: 'switch-project-by-index',
|
|
541
|
+
workspaceId: target.workspaceId,
|
|
524
542
|
},
|
|
525
543
|
sideEffectCtx
|
|
526
544
|
)
|
|
@@ -563,12 +581,11 @@ export function App({
|
|
|
563
581
|
onTerminalMouseUp={handleTerminalMouseUp}
|
|
564
582
|
onPaneActivate={handlePaneActivate}
|
|
565
583
|
onSplitResize={handleSplitResize}
|
|
566
|
-
|
|
567
|
-
|
|
584
|
+
onBarBoundaryResizeStart={handleBarBoundaryResizeStart}
|
|
585
|
+
onBarResizeStart={handleBarResizeStart}
|
|
568
586
|
onSeparatorDragStart={handleSeparatorDragStart}
|
|
569
587
|
onSeparatorDrag={handleSeparatorDrag}
|
|
570
588
|
onSeparatorDragEnd={handleSeparatorDragEnd}
|
|
571
|
-
onSidebarResizeStart={handleSidebarResizeStart}
|
|
572
589
|
onMeasure={terminalSize.onMeasure}
|
|
573
590
|
terminalCols={terminalSize.cols}
|
|
574
591
|
terminalRows={terminalSize.rows}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -205,10 +205,10 @@ export class DaemonClient {
|
|
|
205
205
|
return response.payload
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
async listTabs(
|
|
208
|
+
async listTabs(projectId: string): Promise<ListTabsResult> {
|
|
209
209
|
const response = await this.send({
|
|
210
210
|
id: crypto.randomUUID(),
|
|
211
|
-
payload: {
|
|
211
|
+
payload: { projectId },
|
|
212
212
|
type: 'listTabs',
|
|
213
213
|
})
|
|
214
214
|
if (response.type !== 'listTabsResult') {
|