@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useRenderer } from '@opentui/react'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { useActivitySprites } from '../../settings/live'
|
|
5
|
+
import { detectGraphicsProtocol, isInsideTmux } from '../terminal-graphics/capabilities'
|
|
6
|
+
import {
|
|
7
|
+
loadSprites,
|
|
8
|
+
SPRITE_PLACEHOLDERS,
|
|
9
|
+
type SpriteSet,
|
|
10
|
+
type SpriteState,
|
|
11
|
+
} from '../terminal-graphics/sprites'
|
|
12
|
+
|
|
13
|
+
export interface ActivitySprite {
|
|
14
|
+
/** The colour is the frame's image id — see `Sprite.colors`. */
|
|
15
|
+
fg: string
|
|
16
|
+
/** The cells to draw, one entry per line of the row, top line first. */
|
|
17
|
+
glyphs: readonly string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const NO_SPRITES: SpriteSet = {}
|
|
21
|
+
|
|
22
|
+
function useFrame(intervalMs: number): number {
|
|
23
|
+
const [frame, setFrame] = useState(0)
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
// A tick count, not an index: consumers cycle over lists of different
|
|
27
|
+
// lengths and take their own modulo, so wrapping here would make whichever
|
|
28
|
+
// length does not divide the wrap jump backwards once per cycle.
|
|
29
|
+
if (intervalMs <= 0) return
|
|
30
|
+
const interval = setInterval(() => setFrame((prev) => prev + 1), intervalMs)
|
|
31
|
+
return () => clearInterval(interval)
|
|
32
|
+
}, [intervalMs])
|
|
33
|
+
|
|
34
|
+
return frame
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The sprite standing in for a row's status marker, or null when the caller
|
|
39
|
+
* should draw its own glyph — the toggle is off, the terminal cannot composite
|
|
40
|
+
* images, or nothing in the sprite folder covers this state.
|
|
41
|
+
*
|
|
42
|
+
* tmux owns the grid and does not understand placeholder cells, so sprites are
|
|
43
|
+
* off there even when the terminal underneath could draw them.
|
|
44
|
+
*/
|
|
45
|
+
export function useActivitySprite(state: SpriteState | null): ActivitySprite | null {
|
|
46
|
+
const [sprites, setSprites] = useState<SpriteSet>(NO_SPRITES)
|
|
47
|
+
const renderer = useRenderer()
|
|
48
|
+
const enabled =
|
|
49
|
+
useActivitySprites() && detectGraphicsProtocol(renderer) === 'kitty' && !isInsideTmux()
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!enabled) return
|
|
53
|
+
let cancelled = false
|
|
54
|
+
void (async () => {
|
|
55
|
+
const loaded = await loadSprites()
|
|
56
|
+
if (!cancelled) setSprites(loaded)
|
|
57
|
+
})()
|
|
58
|
+
return () => {
|
|
59
|
+
cancelled = true
|
|
60
|
+
}
|
|
61
|
+
}, [enabled])
|
|
62
|
+
|
|
63
|
+
// Gated on `enabled` here, not only at load time: the loaded set stays in
|
|
64
|
+
// state when the toggle is switched off, and the toggle is a live setting.
|
|
65
|
+
const sprite = !enabled || state === null ? undefined : sprites[state]
|
|
66
|
+
const colors = sprite?.colors
|
|
67
|
+
// A still sprite needs no timer, and neither does a row whose state has none.
|
|
68
|
+
const frame = useFrame(colors === undefined || colors.length < 2 ? 0 : (sprite?.frameMs ?? 0))
|
|
69
|
+
|
|
70
|
+
const fg = colors?.[frame % colors.length]
|
|
71
|
+
if (fg === undefined) return null
|
|
72
|
+
return { fg, glyphs: SPRITE_PLACEHOLDERS }
|
|
73
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
/** Id of the active child, without the prefix. Null when there is none. */
|
|
9
|
+
activeId: string | null
|
|
10
|
+
idPrefix: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Scroll the active child into view whenever it changes (and on first reveal).
|
|
15
|
+
* A thin wrapper around `scrollChildIntoView` — no wrap-around/edge heuristics,
|
|
16
|
+
* which is all a single-axis list of equal rows needs.
|
|
17
|
+
*/
|
|
18
|
+
export function useScrollActiveIntoView({ activeId, idPrefix, scrollRef, visible }: Options): void {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!visible) return
|
|
21
|
+
if (activeId == null || activeId === '') return
|
|
22
|
+
const scrollbox = scrollRef.current
|
|
23
|
+
if (!scrollbox) return
|
|
24
|
+
scrollbox.scrollChildIntoView(`${idPrefix}${activeId}`)
|
|
25
|
+
}, [activeId, idPrefix, scrollRef, visible])
|
|
26
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ProjectRecord } from '../state/types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Return
|
|
4
|
+
* Return projects in user-facing display order: persisted `order` ascending,
|
|
5
5
|
* with any missing `order` falling back to `createdAt` ascending.
|
|
6
6
|
*/
|
|
7
|
-
export function
|
|
8
|
-
return [...
|
|
7
|
+
export function orderProjectsForDisplay(projects: ProjectRecord[]): ProjectRecord[] {
|
|
8
|
+
return [...projects].sort((a, b) => {
|
|
9
9
|
const ao = a.order ?? Number.MAX_SAFE_INTEGER
|
|
10
10
|
const bo = b.order ?? Number.MAX_SAFE_INTEGER
|
|
11
11
|
if (ao !== bo) return ao - bo
|
package/src/ui/root.tsx
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import type { MouseEvent } from '@opentui/core'
|
|
2
2
|
|
|
3
|
-
import { useCallback, useMemo } from 'react'
|
|
3
|
+
import { type ReactNode, useCallback, useMemo } from 'react'
|
|
4
4
|
|
|
5
5
|
import type { MeasuredPaneRect } from '../app-runtime/use-pane-size-report'
|
|
6
|
-
import type { WorktreeTemplate } from '../config'
|
|
7
6
|
import type { TerminalContentOrigin } from '../input/raw-input-handler'
|
|
8
7
|
import type {
|
|
9
8
|
BarSide,
|
|
10
9
|
FocusMode,
|
|
11
10
|
ModalState,
|
|
12
|
-
|
|
11
|
+
ProjectRecord,
|
|
13
12
|
SnippetRecord,
|
|
14
|
-
|
|
13
|
+
WorkspaceRecord,
|
|
15
14
|
} from '../state/types'
|
|
16
15
|
import type { ThemeId } from './themes'
|
|
17
16
|
|
|
18
|
-
import {
|
|
17
|
+
import { useWorkspaceBranchPolling } from '../git/workspace-branch-poller'
|
|
18
|
+
import { useAutoCommitModel } from '../settings/live'
|
|
19
19
|
import { useAppStore } from '../state/app-store'
|
|
20
20
|
import { getBarWidth } from '../state/bars'
|
|
21
21
|
import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
|
|
@@ -29,39 +29,41 @@ import { AIUsageModal } from './components/modals/app/ai-usage-modal'
|
|
|
29
29
|
import { HelpModal } from './components/modals/app/help-modal'
|
|
30
30
|
import { UpdateAvailableModal } from './components/modals/app/update-available-modal'
|
|
31
31
|
import { GitCommitModal } from './components/modals/git/git-commit-modal'
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
32
|
+
import { CreateProjectModal } from './components/modals/projects/create-project-modal'
|
|
33
|
+
import { ProjectNameModal } from './components/modals/projects/project-name-modal'
|
|
34
|
+
import { ProjectPickerModal } from './components/modals/projects/project-picker-modal'
|
|
35
|
+
import { SettingsSearchModal } from './components/modals/settings/settings-search-modal'
|
|
36
|
+
import { WorkspaceDeleteConfirm } from './components/modals/shared/workspace-delete-confirm'
|
|
36
37
|
import { SnippetEditorModal } from './components/modals/snippets/snippet-editor-modal'
|
|
37
38
|
import { SnippetPickerModal } from './components/modals/snippets/snippet-picker-modal'
|
|
38
39
|
import { NewTabModal } from './components/modals/tabs/new-tab-modal'
|
|
39
40
|
import { ThemePickerModal } from './components/modals/themes/theme-picker-modal'
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
41
|
+
import { CreateWorkspaceModal } from './components/modals/workspace/create-workspace-modal'
|
|
42
|
+
import { WorkspaceMoveConfirmModal } from './components/modals/workspace/workspace-move-confirm-modal'
|
|
43
|
+
import { WorkspaceMoveModal } from './components/modals/workspace/workspace-move-modal'
|
|
42
44
|
import { ContextMenuOverlay } from './components/overlays/context-menu/context-menu-overlay'
|
|
43
45
|
import { PendingChordOverlay } from './components/overlays/pending-chord-overlay'
|
|
44
46
|
import { ToastViewport } from './components/overlays/toast/toast-viewport'
|
|
47
|
+
import { SettingsView } from './components/settings/settings-view'
|
|
45
48
|
import { useTheme } from './theme'
|
|
46
49
|
|
|
47
|
-
const
|
|
48
|
-
const EMPTY_BASE_BRANCHES: string[] = []
|
|
50
|
+
const EMPTY_WORKSPACES: WorkspaceRecord[] = []
|
|
49
51
|
|
|
50
|
-
function
|
|
51
|
-
if (modal.type !== 'create-
|
|
52
|
-
return { directoryQuery: '',
|
|
52
|
+
function getCreateProjectFields(modal: ModalState) {
|
|
53
|
+
if (modal.type !== 'create-project') {
|
|
54
|
+
return { directoryQuery: '', projectName: '' }
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
if (modal.activeField === 'directory') {
|
|
56
58
|
return {
|
|
57
59
|
directoryQuery: modal.editBuffer ?? '',
|
|
58
|
-
|
|
60
|
+
projectName: modal.nameBuffer,
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
return {
|
|
63
65
|
directoryQuery: modal.nameBuffer,
|
|
64
|
-
|
|
66
|
+
projectName: modal.editBuffer ?? '',
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -82,18 +84,17 @@ function renderModal(
|
|
|
82
84
|
modal: ModalState,
|
|
83
85
|
options: {
|
|
84
86
|
customCommands: Record<string, string>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
projects: ProjectRecord[]
|
|
88
|
+
currentProjectId: string | null
|
|
87
89
|
currentTabCount: number
|
|
88
90
|
snippets: SnippetRecord[]
|
|
89
91
|
themeId: ThemeId
|
|
90
|
-
|
|
92
|
+
createProjectFields: { directoryQuery: string; projectName: string }
|
|
91
93
|
snippetEditorFields: { snippetName: string; snippetTrigger: string; snippetContent: string }
|
|
92
94
|
focusMode: FocusMode
|
|
93
95
|
activeAssistant?: string
|
|
94
96
|
autoCommitModel?: string
|
|
95
|
-
|
|
96
|
-
worktreeTemplates: WorktreeTemplate[]
|
|
97
|
+
workspaceDivergence: Record<string, { ahead: number; behind: number }>
|
|
97
98
|
}
|
|
98
99
|
) {
|
|
99
100
|
switch (modal.type) {
|
|
@@ -105,62 +106,72 @@ function renderModal(
|
|
|
105
106
|
customCommands={options.customCommands}
|
|
106
107
|
filter={modal.editBuffer}
|
|
107
108
|
cursorPos={modal.cursorPos}
|
|
108
|
-
currentSessionId={options.currentSessionId}
|
|
109
109
|
editingCommand={modal.type === 'new-tab' ? modal.editingCommand : null}
|
|
110
110
|
editBuffer={modal.editBuffer ?? ''}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
111
|
+
excludeTerminal={modal.type === 'new-tab' && modal.pendingWorkspace != null}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
case 'create-workspace':
|
|
115
|
+
return (
|
|
116
|
+
<CreateWorkspaceModal
|
|
117
|
+
activeField={modal.activeField}
|
|
118
|
+
prompt={modal.prompt}
|
|
119
|
+
branchError={modal.branchError}
|
|
120
|
+
baseQuery={modal.baseQuery}
|
|
121
|
+
baseRef={modal.baseRef}
|
|
122
|
+
baseBranches={modal.baseBranches}
|
|
123
|
+
cursorPos={modal.cursorPos}
|
|
124
|
+
selectedIndex={modal.selectedIndex}
|
|
125
|
+
workspaces={
|
|
126
|
+
options.currentProjectId != null && options.currentProjectId !== ''
|
|
127
|
+
? (options.projects.find((project) => project.id === options.currentProjectId)
|
|
128
|
+
?.workspaces ?? EMPTY_WORKSPACES)
|
|
129
|
+
: EMPTY_WORKSPACES
|
|
126
130
|
}
|
|
127
|
-
worktreeName={modal.type === 'new-tab' ? modal.worktreeName : ''}
|
|
128
|
-
worktreeTemplates={options.worktreeTemplates}
|
|
129
|
-
allowTemplateShortcut={modal.type === 'new-tab'}
|
|
130
131
|
/>
|
|
131
132
|
)
|
|
132
|
-
case '
|
|
133
|
+
case 'project-picker':
|
|
133
134
|
return (
|
|
134
|
-
<
|
|
135
|
-
|
|
135
|
+
<ProjectPickerModal
|
|
136
|
+
projects={options.projects}
|
|
136
137
|
selectedIndex={modal.selectedIndex}
|
|
137
|
-
|
|
138
|
+
currentProjectId={options.currentProjectId}
|
|
138
139
|
currentTabCount={options.currentTabCount}
|
|
139
140
|
filter={modal.editBuffer}
|
|
140
141
|
cursorPos={modal.cursorPos}
|
|
141
142
|
/>
|
|
142
143
|
)
|
|
143
|
-
case '
|
|
144
|
+
case 'project-name':
|
|
144
145
|
return (
|
|
145
|
-
<
|
|
146
|
+
<ProjectNameModal
|
|
146
147
|
title={
|
|
147
|
-
modal.
|
|
148
|
-
? 'Rename
|
|
149
|
-
: 'Create
|
|
148
|
+
modal.projectTargetId != null && modal.projectTargetId !== ''
|
|
149
|
+
? 'Rename project'
|
|
150
|
+
: 'Create project'
|
|
150
151
|
}
|
|
151
152
|
value={modal.editBuffer ?? ''}
|
|
152
153
|
/>
|
|
153
154
|
)
|
|
154
155
|
case 'rename-tab':
|
|
155
|
-
return <
|
|
156
|
-
case '
|
|
157
|
-
return <
|
|
158
|
-
case '
|
|
156
|
+
return <ProjectNameModal title="Rename tab" value={modal.editBuffer ?? ''} />
|
|
157
|
+
case 'setting-text':
|
|
158
|
+
return <ProjectNameModal title={modal.settingLabel} value={modal.editBuffer ?? ''} />
|
|
159
|
+
case 'settings-search':
|
|
160
|
+
return (
|
|
161
|
+
<SettingsSearchModal
|
|
162
|
+
filter={modal.editBuffer}
|
|
163
|
+
selectedIndex={modal.selectedIndex}
|
|
164
|
+
cursorPos={modal.cursorPos}
|
|
165
|
+
/>
|
|
166
|
+
)
|
|
167
|
+
case 'rename-workspace':
|
|
168
|
+
return <ProjectNameModal title="Rename workspace" value={modal.editBuffer ?? ''} />
|
|
169
|
+
case 'create-project':
|
|
159
170
|
return (
|
|
160
|
-
<
|
|
171
|
+
<CreateProjectModal
|
|
161
172
|
activeField={modal.activeField}
|
|
162
|
-
directoryQuery={options.
|
|
163
|
-
|
|
173
|
+
directoryQuery={options.createProjectFields.directoryQuery}
|
|
174
|
+
projectName={options.createProjectFields.projectName}
|
|
164
175
|
results={modal.directoryResults}
|
|
165
176
|
selectedIndex={modal.selectedIndex}
|
|
166
177
|
pendingProjectPath={modal.pendingProjectPath}
|
|
@@ -183,7 +194,7 @@ function renderModal(
|
|
|
183
194
|
snippetName={options.snippetEditorFields.snippetName}
|
|
184
195
|
snippetTrigger={options.snippetEditorFields.snippetTrigger}
|
|
185
196
|
snippetContent={options.snippetEditorFields.snippetContent}
|
|
186
|
-
isEditing={modal.
|
|
197
|
+
isEditing={modal.projectTargetId !== null}
|
|
187
198
|
/>
|
|
188
199
|
)
|
|
189
200
|
case 'theme-picker':
|
|
@@ -203,25 +214,25 @@ function renderModal(
|
|
|
203
214
|
latestVersion={modal.latestVersion}
|
|
204
215
|
/>
|
|
205
216
|
)
|
|
206
|
-
case '
|
|
207
|
-
const
|
|
208
|
-
options.
|
|
209
|
-
? options.
|
|
217
|
+
case 'workspace-move': {
|
|
218
|
+
const project =
|
|
219
|
+
options.currentProjectId != null && options.currentProjectId !== ''
|
|
220
|
+
? options.projects.find((entry) => entry.id === options.currentProjectId)
|
|
210
221
|
: undefined
|
|
211
222
|
return (
|
|
212
|
-
<
|
|
213
|
-
deleteSource={modal.type === '
|
|
214
|
-
divergence={options.
|
|
223
|
+
<WorkspaceMoveModal
|
|
224
|
+
deleteSource={modal.type === 'workspace-move' ? modal.deleteSource : false}
|
|
225
|
+
divergence={options.workspaceDivergence}
|
|
215
226
|
selectedIndex={modal.selectedIndex}
|
|
216
|
-
|
|
227
|
+
sourceWorkspaceId={modal.sourceWorkspaceId}
|
|
217
228
|
stats={modal.stats}
|
|
218
|
-
|
|
229
|
+
workspaces={project?.workspaces ?? EMPTY_WORKSPACES}
|
|
219
230
|
/>
|
|
220
231
|
)
|
|
221
232
|
}
|
|
222
|
-
case '
|
|
233
|
+
case 'workspace-move-confirm':
|
|
223
234
|
return (
|
|
224
|
-
<
|
|
235
|
+
<WorkspaceMoveConfirmModal
|
|
225
236
|
variant={modal.variant}
|
|
226
237
|
files={modal.files}
|
|
227
238
|
sourceLabel={modal.sourceLabel}
|
|
@@ -239,12 +250,12 @@ function renderModal(
|
|
|
239
250
|
)
|
|
240
251
|
case 'ai-usage':
|
|
241
252
|
return <AIUsageModal />
|
|
242
|
-
case '
|
|
253
|
+
case 'workspace-delete-confirm':
|
|
243
254
|
return (
|
|
244
|
-
<
|
|
245
|
-
keybindsModeId="modal.
|
|
255
|
+
<WorkspaceDeleteConfirm
|
|
256
|
+
keybindsModeId="modal.workspace-delete-confirm"
|
|
246
257
|
reason={modal.reason}
|
|
247
|
-
|
|
258
|
+
workspaceLabel={modal.workspaceLabel}
|
|
248
259
|
/>
|
|
249
260
|
)
|
|
250
261
|
case 'git-commit': {
|
|
@@ -332,17 +343,16 @@ export function RootView({
|
|
|
332
343
|
const modal = useAppStore((s) => s.modal)
|
|
333
344
|
const snippets = useAppStore((s) => s.snippets)
|
|
334
345
|
const customCommands = useAppStore((s) => s.customCommands)
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
const worktreeTemplates = useAppStore((s) => s.worktreeTemplates)
|
|
346
|
+
const projects = useAppStore((s) => s.projects)
|
|
347
|
+
const currentProjectId = useAppStore((s) => s.currentProjectId)
|
|
348
|
+
const workspaceDivergence = useAppStore((s) => s.workspaceDivergence)
|
|
339
349
|
const leftBarWidth = useAppStore((s) => getBarWidth(s.bars.left))
|
|
340
350
|
|
|
341
351
|
const splitChrome = PANE_BORDER * 2
|
|
342
352
|
|
|
343
|
-
// Keep every
|
|
353
|
+
// Keep every workspace's `branch` in state synchronized with the on-disk
|
|
344
354
|
// HEAD so the sidebar (and divergence calc) never reads a stale value.
|
|
345
|
-
|
|
355
|
+
useWorkspaceBranchPolling(true)
|
|
346
356
|
|
|
347
357
|
// The terminal's own left edge stays a drag target for the left bar, so the
|
|
348
358
|
// resize gesture works from either side of the seam.
|
|
@@ -396,50 +406,42 @@ export function RootView({
|
|
|
396
406
|
)
|
|
397
407
|
|
|
398
408
|
const activeTab = tabs.find((tab) => tab.id === activeTabId)
|
|
409
|
+
// The generating overlay has always been able to name the model, and nothing
|
|
410
|
+
// ever passed it one.
|
|
411
|
+
const autoCommitModel = useAutoCommitModel(activeTab?.assistant)
|
|
399
412
|
const activeTree =
|
|
400
413
|
activeTabId != null && activeTabId !== ''
|
|
401
414
|
? getTreeForTab(layoutTrees, tabGroupMap, activeTabId)
|
|
402
415
|
: null
|
|
403
|
-
const
|
|
416
|
+
const createProjectFields = getCreateProjectFields(modal)
|
|
404
417
|
const snippetEditorFields = getSnippetEditorFields(modal)
|
|
405
418
|
|
|
419
|
+
// Git mode and settings each swap the pane tree for one full-width view.
|
|
420
|
+
// Everything else on screen — status bar, overlays, modal, toasts — is the
|
|
421
|
+
// same either way, so only the center is a branch: a return per view would
|
|
422
|
+
// mean maintaining the chrome three times.
|
|
406
423
|
const inGitMode = focusMode === 'git' || modal.type === 'git-commit'
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
customCommands,
|
|
421
|
-
focusMode,
|
|
422
|
-
sessions,
|
|
423
|
-
snippetEditorFields,
|
|
424
|
-
snippets,
|
|
425
|
-
themeId,
|
|
426
|
-
worktreeDivergence,
|
|
427
|
-
worktreeTemplates,
|
|
428
|
-
})}
|
|
429
|
-
<ToastViewport />
|
|
430
|
-
</box>
|
|
431
|
-
)
|
|
432
|
-
}
|
|
424
|
+
// A settings row's text field flips focusMode to command-edit, the same way the
|
|
425
|
+
// commit modal does in git mode, so the screen behind it has to stay mounted:
|
|
426
|
+
// those two modals belong to the screen and are read against it.
|
|
427
|
+
//
|
|
428
|
+
// Not `returnTo === 'settings'`, which is where focus goes and not what is
|
|
429
|
+
// behind: the theme picker also returns here, and it previews each theme as you
|
|
430
|
+
// move. Previewing it on the settings screen is not the answer to "how does this
|
|
431
|
+
// theme look" — so it gets the panes, and closing still comes back here.
|
|
432
|
+
const inSettings =
|
|
433
|
+
focusMode === 'settings' || modal.type === 'setting-text' || modal.type === 'settings-search'
|
|
434
|
+
let replacesPanes: ReactNode = null
|
|
435
|
+
if (inGitMode) replacesPanes = <GitView themeId={themeId} />
|
|
436
|
+
else if (inSettings) replacesPanes = <SettingsView />
|
|
433
437
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
onMouseUp={handleRootMouseUp}
|
|
442
|
-
>
|
|
438
|
+
const center =
|
|
439
|
+
replacesPanes !== null ? (
|
|
440
|
+
<>
|
|
441
|
+
<TopTabBar panesReplaced />
|
|
442
|
+
{replacesPanes}
|
|
443
|
+
</>
|
|
444
|
+
) : (
|
|
443
445
|
<box flexDirection="row" gap={0} padding={0} flexGrow={1}>
|
|
444
446
|
<Bar
|
|
445
447
|
side="left"
|
|
@@ -503,21 +505,37 @@ export function RootView({
|
|
|
503
505
|
onResizeDragEnd={onSeparatorDragEnd}
|
|
504
506
|
/>
|
|
505
507
|
</box>
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
return (
|
|
511
|
+
<box
|
|
512
|
+
flexDirection="column"
|
|
513
|
+
width="100%"
|
|
514
|
+
height="100%"
|
|
515
|
+
backgroundColor={editorBg}
|
|
516
|
+
// Both drag handlers no-op unless a gesture is in flight, so the full-width
|
|
517
|
+
// views inherit them harmlessly — and a drag that was in flight when one
|
|
518
|
+
// opened gets finalised instead of stranded.
|
|
519
|
+
onMouseDrag={handleRootMouseDrag}
|
|
520
|
+
onMouseUp={handleRootMouseUp}
|
|
521
|
+
>
|
|
522
|
+
{center}
|
|
506
523
|
<StatusBar />
|
|
507
524
|
<PendingChordOverlay />
|
|
508
525
|
<ContextMenuOverlay />
|
|
509
526
|
{renderModal(modal, {
|
|
510
|
-
|
|
511
|
-
|
|
527
|
+
activeAssistant: activeTab?.assistant,
|
|
528
|
+
autoCommitModel,
|
|
529
|
+
createProjectFields,
|
|
530
|
+
currentProjectId,
|
|
512
531
|
currentTabCount: tabs.length,
|
|
513
532
|
customCommands,
|
|
514
533
|
focusMode,
|
|
515
|
-
|
|
534
|
+
projects,
|
|
516
535
|
snippetEditorFields,
|
|
517
536
|
snippets,
|
|
518
537
|
themeId,
|
|
519
|
-
|
|
520
|
-
worktreeTemplates,
|
|
538
|
+
workspaceDivergence,
|
|
521
539
|
})}
|
|
522
540
|
<ToastViewport />
|
|
523
541
|
</box>
|